Compare commits

..

358 Commits

Author SHA1 Message Date
Fabian Affolter 1f046972d9 Merge pull request #6756 from home-assistant/release-0-41
0.41
2017-03-26 00:01:49 +01:00
Paulus Schoutsen 53f8828181 Merge branch 'master' into release-0-41 2017-03-23 23:32:50 -07:00
Paulus Schoutsen b6b40286ef Version bump to 0.41 2017-03-23 08:42:01 -07:00
Andrey 8a86ec5b74 Add zwave per-node entity. (#6690)
* Add zwave per-node entity.

* Disable lint import error

* Add tests for base class

* More tests

* More tests

* Sort .coveragerc

* more tests

* Move location, battery and wakeup to node entity

* More tests

* Cleanup

* Make zwave node entity visible by default

* Remove battery sensor

* Fix tests
2017-03-23 08:37:20 -07:00
Dan Ports 20c5f9de4b Add sensor for Lyft time and price (based on Uber sensor) (#6711)
* Add sensor for Lyft time and price (based on Uber sensor)

* Minor fixes to lyft sensor
 - use add_devices(...,True) instead of explicitly calling update
 - move sensor name check into constructor

* lyft sensor: disable sandbox mode
2017-03-23 08:15:52 -07:00
Mitesh Patel 61730012d8 Adds Support for Lutron Caseta devices. (#6631)
* Adds support for the Lutron Caseta family of devices

* Added external requirement

* Removes unuse import

* Adds requirement to requirements_all.txt

* Removes unuse import

* fixes requirement_all.txt. by regenerating

* Cleans up syantax

* Cleans up syantax

* Cleans up syantax

* Cleans up syantax

* Shortens long line

* adds lutron_caseta component to .coveragerc

* Merges into light, removes component

* Fixes long lines

* Fixes requirement.txt

* Removes 'ERROR' log statements. Adds missing dependency

* savig work to pick up on other machine

* Enables Lutron Caseta component with Light and Switch platforms

* Add missing file to .coveragerc

* Changes based on PR review

* fixes requirements file

* Fixes install of paramiko dependency.

* Moves callback registration to

* comment changes

* Platform have no return value

* Change style for guard

* fix logic

* fix lint
2017-03-23 01:18:14 +01:00
Paulus Schoutsen 25d2df5689 Merge pull request #6740 from home-assistant/release-0-40-2
0.40.2
2017-03-22 09:31:21 -07:00
Paulus Schoutsen 672b83db8a Update constraints 2017-03-22 09:30:36 -07:00
Paulus Schoutsen b37438ebb7 Constrain core dependencies to core versions (#6738)
* Require at least pip 7.1

* Write and use constraint files for packages

* Update gen_requirements_all.py
2017-03-22 08:51:18 -07:00
Paulus Schoutsen 902b72ba1a Constrain core dependencies to core versions (#6738)
* Require at least pip 7.1

* Write and use constraint files for packages

* Update gen_requirements_all.py
2017-03-22 08:50:54 -07:00
Paulus Schoutsen f10fede17f Bump PyChromecast to 0.8.1 (#6702) 2017-03-22 08:50:42 -07:00
Paulus Schoutsen c9548b11b1 Version bump to 0.40.2 2017-03-22 08:50:15 -07:00
Wolfgang Malgadey f4aec3ac88 Tado climate device (#6572)
* Added tado climate component

named the component v1 because of the unsupported state of the api I
used (mytado.com)

* sensor component
* climate component which uses sensors
* main component initiating sensor and climate devices
* order of imports
* consts for username and password
* removed redundant code
* changed wrong calls and properties

* remove pylint overrides

* merged update() and push_update()

* changed wrong calls
* removed pylint overrides
* moved try..except

* renamed MyTado hass-data object

* added TadoDataStore

* moved update methods from sensor to TadoDataStore

* reorganised climate component

* use new TadoDataStore

* small change to overlay handling

* code refactoring

* removed unnessesary comments
* changed throttle to attribute
* changed suggestions from PR

* Added constant variable for string literal

* remove wrong fget() call

* changed dependencies

* Changed operation mode list

* added human readable list of operations
* removed unnecessary const
* activated update on add_devices

* droped unit

* removed unnused property

* changed temperature conversion

* removed defaults from config
changed naming of tado data const

* switched operation_list key/values

* changed the value returned as state

* added one extra line

* dropped state to use base impl.

* renamed component

* had to inplement temperature_unit

* because it is not implemented in base class

* create a copy of the sensors list

* because it can be changed by other components

* had to check for empty data object

* hass is too fast now
2017-03-22 08:18:13 -04:00
Adam Mills e7425e9808 ZWave Lock Tests (#6730)
* ZWave Lock Tests

* Linting fixes

* Missed coveragerc
2017-03-21 08:55:21 -07:00
Matt N 978b539111 camera.zoneminder: Show recording state (#6686) 2017-03-21 07:25:10 +01:00
Paulus Schoutsen 9f4cd5fafe Do not log warning on rest_command if no error (#6713) 2017-03-21 07:18:33 +01:00
Anders Melchiorsen ba3c9f9765 Fix LIFX unregister races (#6723)
* Fix LIFX unregister races

If the initial state request never got a response, we tried to unregister
a device that was not yet registered.

Also, aiolifx 0.4.2 has an "unregister" race fix.

* Update requirements
2017-03-21 07:17:58 +01:00
Paulus Schoutsen 4ee8be52fe Update frontend 2017-03-20 21:31:58 -07:00
Adam Mills 866bf887d3 ZWave switch tests (#6722) 2017-03-20 13:17:42 -04:00
Adam Mills dddbce82f5 ZWave Sensor tests (#6721)
* ZWave Sensor tests

* Add missed coverage
2017-03-20 13:17:17 -04:00
Jeff Wilson be15ca3f23 Don't warn if octoprint completion is null (#6719) 2017-03-20 09:00:45 -07:00
Finbarr Brady 9a305c9742 Fix for issue: luci returns 403 invalid token when rebooted #6715 (#6717) 2017-03-20 08:55:59 -07:00
Paulus Schoutsen de231cf9ab restore_state: do not crash if domain not defined (#6714) 2017-03-20 08:54:51 -07:00
Adam Mills 8325f9db8a Add zwave light tests (#6710)
* Add zwave light tests

* Test turn_off
2017-03-19 22:22:13 -07:00
Paulus Schoutsen 3f38b9e52f Update frontend 2017-03-19 21:59:13 -07:00
printzlau 1cb2a6add0 automatically use bundled certificate it set to auto (#6707)
* automatically use bundled certificate if certificate-parameter is set to 'auto' and seperate this from which port is specified

* Fix travis error and long lines

* Update __init__.py
2017-03-19 14:51:53 -07:00
Greg Dowling acf75b5253 Revise power and energy units and property names. (#6212)
* Revise power and energy units and property names.

* Add change for new wemo parameter.
2017-03-19 22:02:11 +01:00
Adam Mills 970bde9e99 Fix Kodi when websocket is disabled (#6706) 2017-03-19 21:38:12 +01:00
Adam Mills 796143a6c6 Kodi use websocket loop task created by library (#6703) 2017-03-19 12:46:14 -07:00
Paulus Schoutsen 5569ae38f1 Bump PyChromecast to 0.8.1 (#6702) 2017-03-19 11:53:58 -07:00
John Mihalic 7eaad4fb3a Fix longitude (#6697) 2017-03-19 11:00:13 -07:00
Fabian Affolter 35c679a956 Upgrade distro to 1.0.3 (#6693) 2017-03-19 15:59:07 +01:00
martinfrancois 678f273002 Rflink: added support for lights with toggle type (#6521)
* added support for lights with toggle type

* fixed style errors

* introduced tests for the toggle type

it's not passing yet because of an assertionerror at line 407

* updated to reflect tristate of "state"

* Format code according to pep8

Added line break for import that was too long.

* fixed lint, replaced if statement with 'var = bool(test)'

* changed implementation of state check according to bug on https://github.com/home-assistant/home-assistant/pull/6521/files/6bceb04ca15666d2a7e7f03548af69e15f5965a6#r106758784
2017-03-18 21:34:17 +01:00
Tyler Crumpton 4e91c65d6e Update Torque component to match recent API. (#6671) 2017-03-18 11:25:38 +01:00
Robbie Trencheny f6106706e5 Merge pull request #6672 from robbiet480/new-plex-fix
Fixed Show All Controls feature
2017-03-17 17:34:26 -07:00
JesseWebDotCom ecf337b123 Fixed Show All Controls feature 2017-03-17 17:22:38 -07:00
Martin Nöhrer f5d8327d9a Fix hass script execution on Windows (#4977). (#6601)
* Fix hass script execution on Windows (#4977).

hass.exe returned ERRNO2 on a windows machine and must be started using
package loading. This fix adapts the command line options for
`setup_and_run_hass()` to start
either a script with `python homeassistant/__main__.py` or with
`Scripts/hass.exe`

* Fix code style
2017-03-17 17:07:36 -07:00
John Mihalic 30d4c54187 Update Emby component to async (#6664)
* Update Emby component to async

* Address comments

* Make SSL default

* Bump library

* Port based on SSL, use available property
2017-03-17 15:55:07 +01:00
David Straub edf20f542a Phone book lookup support for Fritz!Box call monitor (#6474)
* Update fritzconnection dependency in fritz and fritzbox_netmonitor components

* Add phone book name lookup support to FritzBox call monitor

* Updated requirements_all.txt

* Requested changes to FritzBox call monitor

* Listen to HOME_ASSISTANT_STOP and close thread

* Safe CPU time
2017-03-17 14:40:12 +01:00
Jay Love 9778000e9a Add new media_player platform: Volumio Media Player (#6556)
* Add new media_player platform: Volumio Media Player

Volumio media player is a rpi music player, this platfor adds http based control of the player.

* Modify mute command to accept boolean

* Adjust mute call to reset volume after unmute
Remove references to volimi"a"

* Use yield from calls in mute and volume calls

Trying to speed up the indication of mute and volume level changes in UI, but doesn't seem to do much.

* Adjust async_add_devices call
2017-03-16 23:32:52 -07:00
miniconfig b5149dfba6 Added support for multiple efergy sensors in the same household. (#6630)
* Added support for multiple efergy sensors in the same household.
Also added inital tests for the efergy platform.

* Fixed current_values units.
Changed name to include efergy_ prefix.
2017-03-16 23:22:10 -07:00
John Mihalic ced3cd2616 Refactor Neurio to add Daily Power Sensor (#6662)
* Refactor Neurio to add Daily Power Sensor
2017-03-16 23:20:14 -07:00
RickyTaterSalad 7050236a61 add latitude and longitude configuration to darksky sensor (#6191)
* Optional latitude and longitude to darksky sensor

allow configuration of latitude and longitude in the darksky sensor.
falls back to home assistant coordinates if latitude/longitude not
specified.

* lat/long as inclusive on schema. removed None check for lat/long in setup_platorm

altered schema to require latitude and longitude configured as a pair.
removed None check on latitude and longitude since values will fall back
to hass config if not present

* adhere to line limit of 79 characters

adhere to line limit of 79 characters
2017-03-16 23:04:01 -07:00
Pascal Vizeli c8e1ffad89 Pump Android ip webcam to 0.6 (#6665)
* Pump Android ip webcam to 0.5

* update to 0.6
2017-03-16 22:19:48 +01:00
hawk259 e3edff8a72 Add configurable timeout option to camera.synology (#6655) 2017-03-16 21:26:35 +01:00
Sebastian d6fd0f405e Corrected help text for refresh_node (#6659)
Changed entity_id to node_id.
2017-03-16 21:50:01 +02:00
Fabian Affolter 1ab47b5d2b Check if droplet exists (#6663)
* Check if droplet exists

* Add droplet to message and remove else
2017-03-16 19:59:34 +01:00
Fabian Affolter a2365eccf6 Upgrade aiohttp to 1.3.5 (#6660) 2017-03-16 18:04:00 +01:00
Fabian Affolter c46ba3446d Upgrade astral to 1.4 (#6332)
* Upgrade astral to 1.4

* Update test for norway
2017-03-16 17:38:46 +01:00
JesseWebDotCom 5714f156c3 Support for non-clients, NVidia shield, dynamic grouping, extra metad… (#6054)
* Support for non-clients, NVidia shield, dynamic grouping, extra metadata, and more

* Fixed import orderdering, line lengths, and comment violations

* Local player check and season fixes

* Honor entity_namespace when using custom entity ids

* Semi compatibility with channels, force controls option added

* media_position_updated_at fix - only update when media is playing

* Fix: controls now work as expected on 1) casted sessions and 2) client sessions when client and PMS reside on the same sever

* Made PEP8 compliant

* Made PEP8 compliant

* Made PEP8 compliant, hopefully

* Better Tivo compatibility

* Added frozen detection and remediation

* PlayMedia EPISODE now supports season number and episode number (instead of episode index)

* Fix: Dynamic groups now exclude hidden devices

* Fix: Dynamic groups now exclude hidden devices

* Implemented arsaboo suggested formatting

* Implemented pylint command line suggested fixes

* Implemented Travis CI build suggested fixes

* Sorted Imports using Importanize

* Grouped request imports

* Removed dynamic groups, network calls from properties, and other cleanup

* Balloob recommendations and Plex Protocol Capabilities checks

* Remove deprecated disable-msg in favor of disable

* Removed position related items (seek, frozen detection, etc)

* Removed unused datetime
2017-03-16 09:09:46 -07:00
joe248 959dd29c90 round output values (#6657) 2017-03-16 15:36:44 +01:00
John Arild Berentsen e75a66ed20 Add Zwave sensors test (#6640)
* Test for zwave sensor

* Add test for ZWave sensors

* Hound...

* Hound...

* Review changes
2017-03-16 08:25:37 -04:00
Fabian Affolter 3e72aa8643 Upgrade python-digitalocean to 1.11 (#6653) 2017-03-16 11:14:36 +01:00
John Arild Berentsen 5aaa1f8404 Add test for Z-wave switch (#6619)
* Add test for Z-wave switch

* Changes for new tests
2017-03-16 11:05:51 +01:00
Paulus Schoutsen 7292e564f8 Merge pull request #6652 from home-assistant/release-0-40-1
0.40.1
2017-03-15 23:47:10 -07:00
Wolf-Bastian Pöttner 509cfb6433 Added workday sensor (#6599)
* Added workday sensor

* Added unit tests
2017-03-15 23:46:13 -07:00
Pascal Vizeli acdab67c1b Bugfix RFLINK remove group (#6580)
* Bugfix RFLINK remove group

* Remove group hack from lutron too

* fix tests

* fix lint

* fix lint
2017-03-15 23:19:33 -07:00
deisi d7addf59cd Fix #6534 (#6598)
* Fix #6534

Makes sure 0 is not passes to `color_temperature_kelvin_to_mired`.

* Update osramlightify.py

* Update osramlightify.py
2017-03-15 23:19:24 -07:00
Yum ccf9edf815 since knx_2_float can't handle 0, bypass converting 0 value from knx to float (#6626) 2017-03-15 23:18:55 -07:00
Johann Kellerman 2fd3c186e2 Update SMA solar sensor to work with the new add_devices callback (#6602) 2017-03-15 23:18:11 -07:00
Dale Higgs 719199da45 Update pyecobee version to 0.0.7 (#6593) 2017-03-15 23:17:44 -07:00
Thibault Cohen a3cd7d653d Fix hydroquebec (#6574) 2017-03-15 23:17:17 -07:00
Andrey aeeb927e19 Fix for the case of zwave value used in several devices. (#6577) 2017-03-15 23:16:50 -07:00
Jesse Newland a3a14f9ea4 Don't start the push updater if the Apple TV is 'off' (#6552)
Add an optional extended description…
2017-03-15 23:16:32 -07:00
Tyler Page f4e7b231bc Fix wake_on_lan ping with None as host (#6532)
* Update configuration validation

With the new update, wake_on_lan requires a host key in the configuration

* cast self._host to str as requested

* Changed host key back to optional
2017-03-15 23:15:56 -07:00
Paulus Schoutsen 5f68735375 Version bump to 0.40.1 2017-03-15 23:10:50 -07:00
Pascal Vizeli 774fd19638 Bugfix RFLINK remove group (#6580)
* Bugfix RFLINK remove group

* Remove group hack from lutron too

* fix tests

* fix lint

* fix lint
2017-03-15 23:08:47 -07:00
Paulus Schoutsen e265401cd0 self.loop.create_task -> self.add_job (#6632)
* self.loop.create_task -> self.add_job

* Core to use create task
2017-03-16 06:58:54 +01:00
deisi 5b3dc7f2a5 Fix #6534 (#6598)
* Fix #6534

Makes sure 0 is not passes to `color_temperature_kelvin_to_mired`.

* Update osramlightify.py

* Update osramlightify.py
2017-03-15 22:58:06 -07:00
Anders Melchiorsen 9ef084d903 Move LIFX to aiolifx for driving the bulbs (#6584)
* Move LIFX to aiolifx for driving the bulbs

* Fix whitespace

* Fix more whitespace

* Fix lint

* Define _available in init

* Add @callback decorators

* Use hass.async_add_job

* Rename class
2017-03-15 22:50:33 -07:00
Robbie Trencheny 95b1e257bb Merge pull request #6633 from home-assistant/deprecate-event-forwarding
Deprecate event forwarding
2017-03-15 22:27:49 -07:00
Adam Mills b06cf87c74 Kodi: Fix episode media type classification (#6645) 2017-03-15 22:07:30 -07:00
Adam Mills 326337777a Add ZWave cover tests (#6648) 2017-03-15 22:06:37 -07:00
mvillarejo 2c8a06bfbe media_player.kodi extra attributes for tvshow and music media (#6622)
* media_player.kodi extra attributes for tvshow and music media

* removed extra whitespaces/CR

* Kodi - add extra attributes #6250 (removed music attributes)

* Restored music attributes, this is ready for merge

* linting amended

* Fix Kodi artist support

* Copy-paste error

* Fix for non-music artist lookup

Kodi returns an emtpy list on videos, so we need to be able to
handle that as well.
2017-03-15 19:51:31 -04:00
Pascal Vizeli 198a234468 aioHttp 1.3.4 (#6643) 2017-03-15 22:30:46 +01:00
Paulus Schoutsen e94aa3afe9 Deprecate event forwarding 2017-03-15 08:38:26 -07:00
Paulus Schoutsen 96e22c7b41 Remove event decorators (#6634) 2017-03-15 14:46:57 +01:00
Nathan Henrie 33450c726d Use sqlite's WAL mode to avoid database is locked errors (#6519)
* Use sqlite's WAL mode to avoid `database is locked` errors

- Relevant issue: https://github.com/home-assistant/home-assistant/issues/4780

Code:

- http://stackoverflow.com/a/23661501/1588795
- http://docs.sqlalchemy.org/en/rel_0_9/dialects/sqlite.html#foreign-key-support
- https://github.com/g2p/bedup/pull/86/files

* Only set WAL if using sqlite

* Reorder imports

* Fix pylint warnings
2017-03-15 00:14:12 -07:00
Yum 97b9d3bd21 since knx_2_float can't handle 0, bypass converting 0 value from knx to float (#6626) 2017-03-14 20:25:52 -07:00
Adam Mills fff589eeab Correctly flag Kodi media types (#6628) 2017-03-14 20:25:15 -07:00
Adam Mills 5e5d2e8ab8 Tests for ZWave climate (#6629)
But wait, there's more! Also a few fixes discovered writing the tests.
2017-03-14 20:24:35 -07:00
Paulus Schoutsen 1a7ffdca52 Add "Refactor zwave discovery to entity schema" (#6565)
* Revert "Revert "Refactor zwave discovery to entity schema (#6445)" (#6564)"

This reverts commit 58826b264a.

* Update zwave tests for enitity schema

* Fix merge error

* Switch dict_id to id(self)
2017-03-14 19:55:33 -04:00
Erik Eriksson cada74df22 Merge pull request #6583 from molobrakos/voc-fix
Bump VOC version (fixes heater bug)
2017-03-14 23:37:32 +01:00
Johan Bloemberg bd3fbe8363 Upgrade to dsmr_parser 0.8, supporting protocol 3 and 5. (#6600)
* Upgrade to dsmr_parser 0.8, supporting protocol 3 and 5.

* Update tests for new import.
2017-03-14 20:16:43 +01:00
Daniyar Yeralin 4d9c7d9684 Update mpd.py (#6553)
* Update mpd.py

Introducing new parameter "Name"

* Update mpd.py

Change `CONF_LOCATION` to `CONF_NAME`
2017-03-14 20:10:35 +01:00
ArrayLabs 0bf66384ed Cover myq fix update pymyq (#6595)
* update pymyq to 0.0.8

update to correct wrong version in setup.py

* update pymyq to 0.0.8

update to correct wrong version in setup.py
2017-03-14 20:06:30 +01:00
tflack c7798ef43c Define db for SHOW DIAGNOSTICS query since some users will not have a… (#6566)
* Define db for SHOW DIAGNOSTICS query since some users will not have admin perms

* fix white space error from CI
2017-03-14 11:46:46 -07:00
hawk259 f4d8095e54 Add configurable timeout option to notify/smtp (#6609)
* Add configurable timeout option to notify/smtp

* Updated smtp test to include timeout param

* fixed 80 column style issue
2017-03-14 19:00:16 +01:00
Pascal Vizeli 5529d77c62 Prevent entities running multiple updates simultaneously (#6511)
* Protect entity for multible updates on same time.

* Address all comments / make update more robust

* fix unittest

* fix lint

* address comments
2017-03-14 09:26:55 -07:00
Erik Eriksson c4e151f621 Error handling when connection refused (#6614)
Add an optional extended description…
2017-03-14 10:08:40 +01:00
Fabian Affolter f58941a0d4 Upgrade googlemaps to 2.4.6 (#6611) 2017-03-14 07:54:19 +01:00
Fabian Affolter bca673f039 Upgrade py-cpuinfo to 0.2.7 (#6610) 2017-03-14 07:54:10 +01:00
Fabian Affolter 2687f2f623 Fix link (#6612) 2017-03-14 07:53:48 +01:00
Johann Kellerman 134b3d2f3b Update SMA solar sensor to work with the new add_devices callback (#6602) 2017-03-14 06:39:30 +02:00
Dale Higgs f450c1351c Update pyecobee version to 0.0.7 (#6593) 2017-03-13 21:19:51 +01:00
Fabian Affolter c6b10f3703 Upgrade Sphinx to 1.5.3 (#6587) 2017-03-13 21:05:27 +01:00
Fabian Affolter 4a08067b9c Upgrade psutil to 5.2.0 (#6585) 2017-03-13 21:05:07 +01:00
Fabian Affolter 9c37437a59 Upgrade sqlalchemy to 1.1.6 (#6591) 2017-03-13 21:02:28 +01:00
Fabian Affolter 9330142987 Upgrade pyasn1 to 0.2.3 (#6588) 2017-03-13 21:01:45 +01:00
Fabian Affolter 2bbaac44d4 Upgrade async_timeout to 1.2.0 (#6590) 2017-03-13 21:01:25 +01:00
Thibault Cohen 253dee8e4d Fix hydroquebec (#6574) 2017-03-13 18:54:23 +01:00
Andrey 5722cf53bf Fix for the case of zwave value used in several devices. (#6577) 2017-03-13 12:19:45 -05:00
Pascal Vizeli 5d301590c3 Remove dispatcher camera (#6579) 2017-03-13 17:54:28 +01:00
Erik 353f5d6b49 bump voc version (fixes heater bug) 2017-03-13 17:28:27 +01:00
Jesse Newland 11da7bed12 Don't start the push updater if the Apple TV is 'off' (#6552)
Add an optional extended description…
2017-03-13 14:23:15 +01:00
Kevin Fronczak a358c8e10d Upgraded blinkpy version, increased Throttle time for camera (#6561) 2017-03-13 00:12:48 -07:00
Paulus Schoutsen 58826b264a Revert "Refactor zwave discovery to entity schema (#6445)" (#6564)
This reverts commit 56abc7f9b4.
2017-03-12 23:35:10 -07:00
Adam Mills 56abc7f9b4 Refactor zwave discovery to entity schema (#6445)
* Refactor zwave discovery to entity schema

* Address PR concerns

* Split DISCOVERY_SCHEMAS into separate file

* Only check cover reverse workaround once
2017-03-12 23:13:34 -07:00
Adam Mills 55d60a6a13 ZWave binary sensor tests (#6555)
* ZWave binary sensor tests

* Test fixes

* Improve coverage of features
2017-03-12 22:08:53 -07:00
Dennis de Greef 5183cb5903 Be able to select mqtt:tls_version for Python < 3.6 (#6442)
* Be able to select tls_version

* This test should always assert this value, not only in 3.6

* Disable linting on future property (py36)

* Only allow TLS 1.0, 1.1 and 1.2

* Fix line length issue

* Fix check config tests

* Allow auto as a TLS version
2017-03-12 22:02:59 -07:00
Tyler Page 0aa8933df6 Fix wake_on_lan ping with None as host (#6532)
* Update configuration validation

With the new update, wake_on_lan requires a host key in the configuration

* cast self._host to str as requested

* Changed host key back to optional
2017-03-12 20:46:58 +01:00
siebert fc46a24996 Fix gen_requirements_all.py script for Windows. (#6547) 2017-03-12 21:08:49 +02:00
Paulus Schoutsen 5be58bd056 Simplify Android IP webcam discovery (#6528) 2017-03-12 18:56:48 +01:00
Lewis Juggins 4a423e63f3 Version bump to 0.41.0.dev0 2017-03-12 08:34:35 +00:00
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
Adam Mills 157ab77232 Update Kodi notifier to async (#6497)
* Update Kodi notifier to async

* Change Kodi CONF_SSL to CONF_PROXY_SSL
2017-03-11 10:41:05 -08:00
Anders Melchiorsen 9a86ccaaea 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 10:40:16 -08:00
Greg Dowling 32dd815852 Discovery is a dict rather than an array. (#6525) 2017-03-11 10:39:26 -08:00
Boris K 9ac3928600 Add type configuration in history_stats (#6430) 2017-03-11 10:38:18 -08:00
William Scanlon 62e57456e1 Wink scene(shortcut) support (#6147)
* Wink scene(shortcut) support

* Scenes to Scene

* Moved wink scenes from switches to scenes

* Updated python-wink version
2017-03-11 10:18:29 -08:00
Róbert Nagy 11f11481b2 Force update support for MQTT sensor (#6492) 2017-03-11 10:07:52 -08:00
Greg Dowling 10f5e9744b 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 10:06:46 -08:00
Paulus Schoutsen b2a2193ba3 Remove mint finance sensor (#6522) 2017-03-11 01:02:32 -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
Paulus Schoutsen 493c0bbb4c Update frontend 2017-03-10 22:51:44 -08:00
Martin Hjelmare 13dd17b2ab Fix mysensors gateway windows setup (#6500) 2017-03-11 03:30:23 +01:00
Pascal Vizeli 0ef1c3af34 Android webcam better error handling / pump library 0.4 (#6518) 2017-03-10 23:11:16 +01:00
Pascal Vizeli 44da43065f Android webcam better error handling / pump library 0.4 (#6518) 2017-03-10 23:10:35 +01:00
Pascal Bach ffb1613d55 [packaging] Include LICENSE.md in tarball (#6514) 2017-03-10 19:59:38 +00:00
Pascal Vizeli b0a2909835 Bugfix rpi_rf cleanup (#6513)
Add an optional extended description…
2017-03-10 14:57:03 +01:00
Pascal Vizeli b5fb558c62 Bugfix rpi_rf cleanup (#6513)
Add an optional extended description…
2017-03-10 14:56:13 +01:00
Robbie Trencheny 846a0513c7 Don't allow sending to invalid iOS targets (#6115)
* Don't allow sending to invalid targets

* Fix valid target check
2017-03-10 12:32:43 +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
Caleb b705b3ddb9 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:15:21 +01:00
Craig J. Ward 330d352d3a 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:14:31 +01:00
Pascal Vizeli bdaae6f844 Bugfix android camera autodiscovery settings (#6510)
Add an optional extended description…
2017-03-10 10:14:05 +01:00
Pascal Vizeli 49308bec13 Bugfix android camera autodiscovery settings (#6510)
Add an optional extended description…
2017-03-10 10:10:35 +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
539 changed files with 16321 additions and 6051 deletions
+32 -7
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
@@ -53,6 +59,9 @@ omit =
homeassistant/components/lutron.py
homeassistant/components/*/lutron.py
homeassistant/components/lutron_caseta.py
homeassistant/components/*/lutron_caseta.py
homeassistant/components/modbus.py
homeassistant/components/*/modbus.py
@@ -85,6 +94,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
@@ -105,9 +118,6 @@ omit =
homeassistant/components/zigbee.py
homeassistant/components/*/zigbee.py
homeassistant/components/zwave/*
homeassistant/components/*/zwave.py
homeassistant/components/enocean.py
homeassistant/components/*/enocean.py
@@ -132,6 +142,12 @@ omit =
homeassistant/components/zabbix.py
homeassistant/components/*/zabbix.py
homeassistant/components/maxcube.py
homeassistant/components/*/maxcube.py
homeassistant/components/tado.py
homeassistant/components/*/tado.py
homeassistant/components/alarm_control_panel/alarmdotcom.py
homeassistant/components/alarm_control_panel/concord232.py
homeassistant/components/alarm_control_panel/nx584.py
@@ -158,6 +174,7 @@ omit =
homeassistant/components/climate/oem.py
homeassistant/components/climate/proliphix.py
homeassistant/components/climate/radiotherm.py
homeassistant/components/config/zwave.py
homeassistant/components/cover/garadget.py
homeassistant/components/cover/homematic.py
homeassistant/components/cover/myq.py
@@ -231,6 +248,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
@@ -255,10 +273,12 @@ omit =
homeassistant/components/media_player/sonos.py
homeassistant/components/media_player/squeezebox.py
homeassistant/components/media_player/vlc.py
homeassistant/components/media_player/volumio.py
homeassistant/components/media_player/yamaha.py
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 +306,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,16 +321,17 @@ 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
homeassistant/components/sensor/efergy.py
homeassistant/components/sensor/eliqonline.py
homeassistant/components/sensor/emoncms.py
homeassistant/components/sensor/fastdotcom.py
@@ -333,11 +352,13 @@ 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/lyft.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,7 +432,11 @@ omit =
homeassistant/components/upnp.py
homeassistant/components/weather/bom.py
homeassistant/components/weather/openweathermap.py
homeassistant/components/weather/zamg.py
homeassistant/components/zeroconf.py
homeassistant/components/zwave/__init__.py
homeassistant/components/zwave/util.py
homeassistant/components/zwave/workaround.py
[report]
+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
+1 -1
View File
@@ -1,5 +1,5 @@
include README.rst
include LICENSE
include LICENSE.md
graft homeassistant
prune homeassistant/components/frontend/www_static/home-assistant-polymer
recursive-exclude * *.py[co]
+5 -2
View File
@@ -255,10 +255,13 @@ def closefds_osx(min_fd: int, max_fd: int) -> None:
def cmdline() -> List[str]:
"""Collect path and arguments to re-execute the current hass instance."""
if sys.argv[0].endswith('/__main__.py'):
if sys.argv[0].endswith(os.path.sep + '__main__.py'):
modulepath = os.path.dirname(sys.argv[0])
os.environ['PYTHONPATH'] = os.path.dirname(modulepath)
return [sys.executable] + [arg for arg in sys.argv if arg != '--daemon']
return [sys.executable] + [arg for arg in sys.argv if
arg != '--daemon']
else:
return [arg for arg in sys.argv if arg != '--daemon']
def setup_and_run_hass(config_dir: str,
+33 -394
View File
@@ -4,320 +4,30 @@ 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.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 +49,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 +73,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(
@@ -429,24 +126,25 @@ def async_from_config_dict(config: Dict[str, Any],
_LOGGER.info('Home Assistant core initialized')
# Give event decorators access to HASS
event_decorators.HASS = hass
service.HASS = hass
# stage 1
for component in components:
if component not in FIRST_INIT_COMPONENT:
continue
hass.async_add_job(async_setup_component(hass, component, config))
# Setup the components
dependency_blacklist = loader.DEPENDENCY_BLACKLIST - set(components)
yield from hass.async_block_till_done()
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 _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 +162,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 +193,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 +278,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.
@@ -113,7 +113,7 @@ def async_setup(hass, config):
if not alarm.should_poll:
continue
update_coro = hass.loop.create_task(
update_coro = hass.async_add_job(
alarm.async_update_ha_state(True))
if hasattr(alarm, 'async_update'):
update_tasks.append(update_coro)
@@ -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,295 @@
"""
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.6"]
_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_MOTION_SENSOR = 'motion_sensor'
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_SWITCHES, default=None):
vol.All(cv.ensure_list, [vol.In(SWITCHES)]),
vol.Optional(CONF_SENSORS, default=None):
vol.All(cv.ensure_list, [vol.In(SENSORS)]),
vol.Optional(CONF_MOTION_SENSOR, default=None): 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]
)
if switches is None:
switches = [setting for setting in cam.enabled_settings
if setting in SWITCHES]
if sensors is None:
sensors = [sensor for sensor in cam.enabled_sensors
if sensor in SENSORS]
sensors.extend(['audio_connections', 'video_connections'])
if motion is None:
motion = 'motion_active' in cam.enabled_sensors
@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)
# 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
+2
View File
@@ -327,6 +327,8 @@ class APIEventForwardingView(HomeAssistantView):
@asyncio.coroutine
def post(self, request):
"""Setup an event forwarder."""
_LOGGER.warning('Event forwarding is deprecated. '
'Will be removed by 0.43')
hass = request.app['hass']
try:
data = yield from request.json()
+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
@@ -36,6 +36,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
dev = []
for droplet in droplets:
droplet_id = digital_ocean.DIGITAL_OCEAN.get_droplet_id(droplet)
if droplet_id is None:
_LOGGER.error("Droplet %s is not available", droplet)
return False
dev.append(DigitalOceanBinarySensor(
digital_ocean.DIGITAL_OCEAN, droplet_id))
@@ -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):
@@ -0,0 +1,157 @@
"""Sensor to indicate whether the current day is a workday."""
import asyncio
import logging
import datetime
import voluptuous as vol
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (STATE_ON, STATE_OFF, STATE_UNKNOWN,
CONF_NAME, WEEKDAYS)
import homeassistant.util.dt as dt_util
from homeassistant.helpers.entity import Entity
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['holidays==0.8.1']
# List of all countries currently supported by holidays
# There seems to be no way to get the list out at runtime
ALL_COUNTRIES = ['Australia', 'AU', 'Austria', 'AT', 'Canada', 'CA',
'Colombia', 'CO', 'Czech', 'CZ', 'Denmark', 'DK', 'England',
'EuropeanCentralBank', 'ECB', 'TAR', 'Germany', 'DE',
'Ireland', 'Isle of Man', 'Mexico', 'MX', 'Netherlands', 'NL',
'NewZealand', 'NZ', 'Northern Ireland', 'Norway', 'NO',
'Portugal', 'PT', 'PortugalExt', 'PTE', 'Scotland', 'Spain',
'ES', 'UnitedKingdom', 'UK', 'UnitedStates', 'US', 'Wales']
CONF_COUNTRY = 'country'
CONF_PROVINCE = 'province'
CONF_WORKDAYS = 'workdays'
# By default, Monday - Friday are workdays
DEFAULT_WORKDAYS = ['mon', 'tue', 'wed', 'thu', 'fri']
CONF_EXCLUDES = 'excludes'
# By default, public holidays, Saturdays and Sundays are excluded from workdays
DEFAULT_EXCLUDES = ['sat', 'sun', 'holiday']
DEFAULT_NAME = 'Workday Sensor'
ALLOWED_DAYS = WEEKDAYS + ['holiday']
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_COUNTRY): vol.In(ALL_COUNTRIES),
vol.Optional(CONF_PROVINCE, default=None): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_WORKDAYS, default=DEFAULT_WORKDAYS):
vol.All(cv.ensure_list, [vol.In(ALLOWED_DAYS)]),
vol.Optional(CONF_EXCLUDES, default=DEFAULT_EXCLUDES):
vol.All(cv.ensure_list, [vol.In(ALLOWED_DAYS)]),
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Workday sensor."""
import holidays
# Get the Sensor name from the config
sensor_name = config.get(CONF_NAME)
# Get the country code from the config
country = config.get(CONF_COUNTRY)
# Get the province from the config
province = config.get(CONF_PROVINCE)
# Get the list of workdays from the config
workdays = config.get(CONF_WORKDAYS)
# Get the list of excludes from the config
excludes = config.get(CONF_EXCLUDES)
# Instantiate the holidays module for the current year
year = datetime.datetime.now().year
obj_holidays = getattr(holidays, country)(years=year)
# Also apply the provience, if available for the configured country
if province:
if province not in obj_holidays.PROVINCES:
_LOGGER.error('There is no province/state %s in country %s',
province, country)
return False
else:
year = datetime.datetime.now().year
obj_holidays = getattr(holidays, country)(prov=province,
years=year)
# Output found public holidays via the debug channel
_LOGGER.debug("Found the following holidays for your configuration:")
for date, name in sorted(obj_holidays.items()):
_LOGGER.debug("%s %s", date, name)
# Add ourselves as device
add_devices([IsWorkdaySensor(obj_holidays, workdays,
excludes, sensor_name)], True)
def day_to_string(day):
"""Convert day index 0 - 7 to string."""
try:
return ALLOWED_DAYS[day]
except IndexError:
return None
class IsWorkdaySensor(Entity):
"""Implementation of a Workday sensor."""
def __init__(self, obj_holidays, workdays, excludes, name):
"""Initialize the Workday sensor."""
self._name = name
self._obj_holidays = obj_holidays
self._workdays = workdays
self._excludes = excludes
self._state = STATE_UNKNOWN
@property
def name(self):
"""Return the name of the sensor."""
return self._name
@property
def state(self):
"""Return the state of the device."""
return self._state
def is_include(self, day, now):
"""Check if given day is in the includes list."""
# Check includes
if day in self._workdays:
return True
elif 'holiday' in self._workdays and now in self._obj_holidays:
return True
return False
def is_exclude(self, day, now):
"""Check if given day is in the excludes list."""
# Check excludes
if day in self._excludes:
return True
elif 'holiday' in self._excludes and now in self._obj_holidays:
return True
return False
@asyncio.coroutine
def async_update(self):
"""Get date and look whether it is a holiday."""
# Default is no workday
self._state = STATE_OFF
# Get iso day of the week (1 = Monday, 7 = Sunday)
day = datetime.datetime.today().isoweekday() - 1
day_of_week = day_to_string(day)
if self.is_include(day_of_week, dt_util.now()):
self._state = STATE_ON
if self.is_exclude(day_of_week, dt_util.now()):
self._state = STATE_OFF
@@ -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):
+23 -41
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,45 +19,34 @@ _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)
device_mapping = workaround.get_device_mapping(value)
def get_device(values, **kwargs):
"""Create zwave entity device."""
device_mapping = workaround.get_device_mapping(values.primary)
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
re_arm_multiplier = zwave.get_config_value(values.primary.node, 9) or 4
return ZWaveTriggerSensor(values, "motion", re_arm_multiplier * 8)
if workaround.get_device_component_mapping(value) == DOMAIN:
add_devices([ZWaveBinarySensor(value, None)])
return
if workaround.get_device_component_mapping(values.primary) == DOMAIN:
return ZWaveBinarySensor(values, None)
if value.command_class == zwave.const.COMMAND_CLASS_SENSOR_BINARY:
add_devices([ZWaveBinarySensor(value, None)])
if values.primary.command_class == zwave.const.COMMAND_CLASS_SENSOR_BINARY:
return ZWaveBinarySensor(values, None)
return None
class ZWaveBinarySensor(BinarySensorDevice, zwave.ZWaveDeviceEntity):
"""Representation of a binary sensor within Z-Wave."""
def __init__(self, value, device_class):
def __init__(self, values, device_class):
"""Initialize the sensor."""
zwave.ZWaveDeviceEntity.__init__(self, value, DOMAIN)
zwave.ZWaveDeviceEntity.__init__(self, values, DOMAIN)
self._sensor_type = device_class
self._state = self._value.data
self._state = self.values.primary.data
def update_properties(self):
"""Callback on data changes for node values."""
self._state = self._value.data
self._state = self.values.primary.data
@property
def is_on(self):
@@ -68,35 +58,27 @@ class ZWaveBinarySensor(BinarySensorDevice, zwave.ZWaveDeviceEntity):
"""Return the class of this sensor, from DEVICE_CLASSES."""
return self._sensor_type
@property
def should_poll(self):
"""No polling needed."""
return False
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, values, device_class, re_arm_sec=60):
"""Initialize the sensor."""
super(ZWaveTriggerSensor, self).__init__(value, device_class)
self._hass = hass
super(ZWaveTriggerSensor, self).__init__(values, device_class)
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
self._state = self.values.primary.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.5.2']
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=90)
_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
+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):
+16 -11
View File
@@ -14,7 +14,7 @@ import async_timeout
from homeassistant.const import (
CONF_NAME, CONF_USERNAME, CONF_PASSWORD,
CONF_URL, CONF_WHITELIST, CONF_VERIFY_SSL)
CONF_URL, CONF_WHITELIST, CONF_VERIFY_SSL, CONF_TIMEOUT)
from homeassistant.components.camera import (
Camera, PLATFORM_SCHEMA)
from homeassistant.helpers.aiohttp_client import (
@@ -27,7 +27,7 @@ _LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'Synology Camera'
DEFAULT_STREAM_ID = '0'
TIMEOUT = 5
DEFAULT_TIMEOUT = 5
CONF_CAMERA_NAME = 'camera_name'
CONF_STREAM_ID = 'stream_id'
@@ -51,6 +51,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_URL): cv.string,
vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
vol.Optional(CONF_WHITELIST, default=[]): cv.ensure_list,
vol.Optional(CONF_VERIFY_SSL, default=True): cv.boolean,
})
@@ -60,6 +61,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Setup a Synology IP Camera."""
verify_ssl = config.get(CONF_VERIFY_SSL)
timeout = config.get(CONF_TIMEOUT)
websession_init = async_get_clientsession(hass, verify_ssl)
# Determine API to use for authentication
@@ -74,7 +76,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
}
query_req = None
try:
with async_timeout.timeout(TIMEOUT, loop=hass.loop):
with async_timeout.timeout(timeout, loop=hass.loop):
query_req = yield from websession_init.get(
syno_api_url,
params=query_payload
@@ -103,7 +105,8 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
websession_init,
config.get(CONF_USERNAME),
config.get(CONF_PASSWORD),
syno_auth_url
syno_auth_url,
timeout
)
# init websession
@@ -120,7 +123,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
'version': '1'
}
try:
with async_timeout.timeout(TIMEOUT, loop=hass.loop):
with async_timeout.timeout(timeout, loop=hass.loop):
camera_req = yield from websession.get(
syno_camera_url,
params=camera_payload
@@ -149,15 +152,16 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
snapshot_path,
streaming_path,
camera_path,
auth_path
auth_path,
timeout
)
devices.append(device)
yield from async_add_devices(devices)
async_add_devices(devices)
@asyncio.coroutine
def get_session_id(hass, websession, username, password, login_url):
def get_session_id(hass, websession, username, password, login_url, timeout):
"""Get a session id."""
auth_payload = {
'api': AUTH_API,
@@ -170,7 +174,7 @@ def get_session_id(hass, websession, username, password, login_url):
}
auth_req = None
try:
with async_timeout.timeout(TIMEOUT, loop=hass.loop):
with async_timeout.timeout(timeout, loop=hass.loop):
auth_req = yield from websession.get(
login_url,
params=auth_payload
@@ -192,7 +196,7 @@ class SynologyCamera(Camera):
def __init__(self, hass, websession, config, camera_id,
camera_name, snapshot_path, streaming_path, camera_path,
auth_path):
auth_path, timeout):
"""Initialize a Synology Surveillance Station camera."""
super().__init__()
self.hass = hass
@@ -206,6 +210,7 @@ class SynologyCamera(Camera):
self._streaming_path = streaming_path
self._camera_path = camera_path
self._auth_path = auth_path
self._timeout = timeout
def camera_image(self):
"""Return bytes of camera image."""
@@ -225,7 +230,7 @@ class SynologyCamera(Camera):
'cameraId': self._camera_id
}
try:
with async_timeout.timeout(TIMEOUT, loop=self.hass.loop):
with async_timeout.timeout(self._timeout, loop=self.hass.loop):
response = yield from self._websession.get(
image_url,
params=image_payload
+38 -2
View File
@@ -19,6 +19,9 @@ _LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['zoneminder']
DOMAIN = 'zoneminder'
# From ZoneMinder's web/includes/config.php.in
ZM_STATE_ALARM = "2"
def _get_image_url(hass, monitor, mode):
zm_data = hass.data[DOMAIN]
@@ -69,10 +72,43 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
CONF_MJPEG_URL: _get_image_url(hass, monitor, 'jpeg'),
CONF_STILL_IMAGE_URL: _get_image_url(hass, monitor, 'single')
}
cameras.append(MjpegCamera(hass, device_info))
cameras.append(ZoneMinderCamera(hass, device_info, monitor))
if not cameras:
_LOGGER.warning('No active cameras found')
return
yield from async_add_devices(cameras)
async_add_devices(cameras)
class ZoneMinderCamera(MjpegCamera):
"""Representation of a ZoneMinder Monitor Stream."""
def __init__(self, hass, device_info, monitor):
"""Initialize as a subclass of MjpegCamera."""
super().__init__(hass, device_info)
self._monitor_id = int(monitor['Id'])
self._is_recording = None
@property
def should_poll(self):
"""Update the recording state periodically."""
return True
def update(self):
"""Update our recording state from the ZM API."""
_LOGGER.debug('Updating camera state for monitor %i', self._monitor_id)
status_response = zoneminder.get_state(
'api/monitors/alarm/id:%i/command:status.json' % self._monitor_id
)
if not status_response:
_LOGGER.warning('Could not get status for monitor %i',
self._monitor_id)
return
self._is_recording = status_response['status'] == ZM_STATE_ALARM
@property
def is_recording(self):
"""Return whether the monitor is in alarm mode."""
return self._is_recording
+1 -1
View File
@@ -224,7 +224,7 @@ def async_setup(hass, config):
if not climate.should_poll:
continue
update_coro = hass.loop.create_task(
update_coro = hass.async_add_job(
climate.async_update_ha_state(True))
if hasattr(climate, 'async_update'):
update_tasks.append(update_coro)
+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
+296
View File
@@ -0,0 +1,296 @@
"""tado component to create a climate device for each zone."""
import logging
from homeassistant.const import TEMP_CELSIUS
from homeassistant.components.climate import (
ClimateDevice)
from homeassistant.const import (
ATTR_TEMPERATURE)
from homeassistant.components.tado import (
DATA_TADO)
CONST_MODE_SMART_SCHEDULE = "SMART_SCHEDULE" # Default mytado mode
CONST_MODE_OFF = "OFF" # Switch off heating in a zone
# When we change the temperature setting, we need an overlay mode
# wait until tado changes the mode automatic
CONST_OVERLAY_TADO_MODE = "TADO_MODE"
# the user has change the temperature or mode manually
CONST_OVERLAY_MANUAL = "MANUAL"
# the temperature will be reset after a timespan
CONST_OVERLAY_TIMER = "TIMER"
OPERATION_LIST = {
CONST_OVERLAY_MANUAL: "Manual",
CONST_OVERLAY_TIMER: "Timer",
CONST_OVERLAY_TADO_MODE: "Tado mode",
CONST_MODE_SMART_SCHEDULE: "Smart schedule",
CONST_MODE_OFF: "Off"}
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the climate platform."""
# get the PyTado object from the hub component
tado = hass.data[DATA_TADO]
try:
zones = tado.get_zones()
except RuntimeError:
_LOGGER.error("Unable to get zone info from mytado")
return False
climate_devices = []
for zone in zones:
climate_devices.append(create_climate_device(tado, hass,
zone,
zone['name'],
zone['id']))
if len(climate_devices) > 0:
add_devices(climate_devices, True)
return True
else:
return False
def create_climate_device(tado, hass, zone, name, zone_id):
"""Create a climate device."""
capabilities = tado.get_capabilities(zone_id)
unit = TEMP_CELSIUS
min_temp = float(capabilities["temperatures"]["celsius"]["min"])
max_temp = float(capabilities["temperatures"]["celsius"]["max"])
ac_mode = capabilities["type"] != "HEATING"
data_id = 'zone {} {}'.format(name, zone_id)
device = TadoClimate(tado,
name, zone_id, data_id,
hass.config.units.temperature(min_temp, unit),
hass.config.units.temperature(max_temp, unit),
ac_mode)
tado.add_sensor(data_id, {
"id": zone_id,
"zone": zone,
"name": name,
"climate": device
})
return device
class TadoClimate(ClimateDevice):
"""Representation of a tado climate device."""
def __init__(self, store, zone_name, zone_id, data_id,
min_temp, max_temp, ac_mode,
tolerance=0.3):
"""Initialization of TadoClimate device."""
self._store = store
self._data_id = data_id
self.zone_name = zone_name
self.zone_id = zone_id
self.ac_mode = ac_mode
self._active = False
self._device_is_active = False
self._unit = TEMP_CELSIUS
self._cur_temp = None
self._cur_humidity = None
self._is_away = False
self._min_temp = min_temp
self._max_temp = max_temp
self._target_temp = None
self._tolerance = tolerance
self._current_operation = CONST_MODE_SMART_SCHEDULE
self._overlay_mode = CONST_MODE_SMART_SCHEDULE
@property
def name(self):
"""Return the name of the sensor."""
return self.zone_name
@property
def current_humidity(self):
"""Return the current humidity."""
return self._cur_humidity
@property
def current_temperature(self):
"""Return the sensor temperature."""
return self._cur_temp
@property
def current_operation(self):
"""Return current readable operation mode."""
return OPERATION_LIST.get(self._current_operation)
@property
def operation_list(self):
"""List of available operation modes (readable)."""
return list(OPERATION_LIST.values())
@property
def temperature_unit(self):
"""The unit of measurement used by the platform."""
return self._unit
@property
def is_away_mode_on(self):
"""Return true if away mode is on."""
return self._is_away
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
return self._target_temp
def set_temperature(self, **kwargs):
"""Set new target temperature."""
temperature = kwargs.get(ATTR_TEMPERATURE)
if temperature is None:
return
self._current_operation = CONST_OVERLAY_TADO_MODE
self._overlay_mode = None
self._target_temp = temperature
self._control_heating()
def set_operation_mode(self, readable_operation_mode):
"""Set new operation mode."""
operation_mode = CONST_MODE_SMART_SCHEDULE
for mode, readable in OPERATION_LIST.items():
if readable == readable_operation_mode:
operation_mode = mode
break
self._current_operation = operation_mode
self._overlay_mode = None
self._control_heating()
@property
def min_temp(self):
"""Return the minimum temperature."""
if self._min_temp:
return self._min_temp
else:
# get default temp from super class
return super().min_temp
@property
def max_temp(self):
"""Return the maximum temperature."""
if self._max_temp:
return self._max_temp
else:
# Get default temp from super class
return super().max_temp
def update(self):
"""Update the state of this climate device."""
self._store.update()
data = self._store.get_data(self._data_id)
if data is None:
_LOGGER.debug('Recieved no data for zone %s',
self.zone_name)
return
if 'sensorDataPoints' in data:
sensor_data = data['sensorDataPoints']
temperature = float(
sensor_data['insideTemperature']['celsius'])
humidity = float(
sensor_data['humidity']['percentage'])
setting = 0
# temperature setting will not exist when device is off
if 'temperature' in data['setting'] and \
data['setting']['temperature'] is not None:
setting = float(
data['setting']['temperature']['celsius'])
unit = TEMP_CELSIUS
self._cur_temp = self.hass.config.units.temperature(
temperature, unit)
self._target_temp = self.hass.config.units.temperature(
setting, unit)
self._cur_humidity = humidity
if 'tadoMode' in data:
mode = data['tadoMode']
self._is_away = mode == "AWAY"
if 'setting' in data:
power = data['setting']['power']
if power == "OFF":
self._current_operation = CONST_MODE_OFF
self._device_is_active = False
else:
self._device_is_active = True
if 'overlay' in data and data['overlay'] is not None:
overlay = True
termination = data['overlay']['termination']['type']
else:
overlay = False
termination = ""
# if you set mode manualy to off, there will be an overlay
# and a termination, but we want to see the mode "OFF"
if overlay and self._device_is_active:
# there is an overlay the device is on
self._overlay_mode = termination
self._current_operation = termination
else:
# there is no overlay, the mode will always be
# "SMART_SCHEDULE"
self._overlay_mode = CONST_MODE_SMART_SCHEDULE
self._current_operation = CONST_MODE_SMART_SCHEDULE
def _control_heating(self):
"""Send new target temperature to mytado."""
if not self._active and None not in (self._cur_temp,
self._target_temp):
self._active = True
_LOGGER.info('Obtained current and target temperature. '
'tado thermostat active.')
if not self._active or self._current_operation == self._overlay_mode:
return
if self._current_operation == CONST_MODE_SMART_SCHEDULE:
_LOGGER.info('Switching mytado.com to SCHEDULE (default) '
'for zone %s', self.zone_name)
self._store.reset_zone_overlay(self.zone_id)
self._overlay_mode = self._current_operation
return
if self._current_operation == CONST_MODE_OFF:
_LOGGER.info('Switching mytado.com to OFF for zone %s',
self.zone_name)
self._store.set_zone_overlay(self.zone_id, CONST_OVERLAY_MANUAL)
self._overlay_mode = self._current_operation
return
_LOGGER.info('Switching mytado.com to %s mode for zone %s',
self._current_operation, self.zone_name)
self._store.set_zone_overlay(self.zone_id,
self._current_operation,
self._target_temp)
self._overlay_mode = self._current_operation
+6 -5
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):
@@ -84,11 +85,11 @@ class VeraThermostat(VeraDevice, ClimateDevice):
return self.vera_device.fan_cycle()
@property
def current_power_mwh(self):
"""Current power usage in mWh."""
def current_power_w(self):
"""Current power usage in W."""
power = self.vera_device.power
if power:
return convert(power, float, 0.0) * 1000
return convert(power, float, 0.0)
def update(self):
"""Called by the vera device callback to update state."""
+53 -96
View File
@@ -10,7 +10,7 @@ import logging
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,29 +32,18 @@ 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, values, **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(values, temp_unit)
class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
"""Representation of a Z-Wave Climate device."""
def __init__(self, value, temp_unit):
def __init__(self, values, temp_unit):
"""Initialize the Z-Wave climate device."""
ZWaveDeviceEntity.__init__(self, value, DOMAIN)
self._index = value.index
self._node = value.node
ZWaveDeviceEntity.__init__(self, values, DOMAIN)
self._target_temperature = None
self._current_temperature = None
self._current_operation = None
@@ -69,10 +58,11 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
_LOGGER.debug("temp_unit is %s", self._unit)
self._zxt_120 = None
# Make sure that we have values for the key before converting to int
if (value.node.manufacturer_id.strip() and
value.node.product_id.strip()):
specific_sensor_key = (int(value.node.manufacturer_id, 16),
int(value.node.product_id, 16))
if (self.node.manufacturer_id.strip() and
self.node.product_id.strip()):
specific_sensor_key = (
int(self.node.manufacturer_id, 16),
int(self.node.product_id, 16))
if specific_sensor_key in DEVICE_MAPPINGS:
if DEVICE_MAPPINGS[specific_sensor_key] == WORKAROUND_ZXT_120:
_LOGGER.debug("Remotec ZXT-120 Zwave Thermostat"
@@ -83,86 +73,58 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
def update_properties(self):
"""Callback on data changes for node values."""
# Operation Mode
self._current_operation = self.get_value(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_MODE, member='data')
operation_list = self.get_value(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_MODE,
member='data_items')
if operation_list:
self._operation_list = list(operation_list)
if self.values.mode:
self._current_operation = self.values.mode.data
operation_list = self.values.mode.data_items
if operation_list:
self._operation_list = list(operation_list)
_LOGGER.debug("self._operation_list=%s", self._operation_list)
_LOGGER.debug("self._current_operation=%s", self._current_operation)
# Current Temp
self._current_temperature = self.get_value(
class_id=zwave.const.COMMAND_CLASS_SENSOR_MULTILEVEL,
label=['Temperature'], member='data')
device_unit = self.get_value(
class_id=zwave.const.COMMAND_CLASS_SENSOR_MULTILEVEL,
label=['Temperature'], member='units')
if device_unit is not None:
self._unit = device_unit
if self.values.temperature:
self._current_temperature = self.values.temperature.data
device_unit = self.values.temperature.units
if device_unit is not None:
self._unit = device_unit
# Fan Mode
self._current_fan_mode = self.get_value(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_FAN_MODE,
member='data')
fan_list = self.get_value(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_FAN_MODE,
member='data_items')
if fan_list:
self._fan_list = list(fan_list)
if self.values.fan_mode:
self._current_fan_mode = self.values.fan_mode.data
fan_list = self.values.fan_mode.data_items
if fan_list:
self._fan_list = list(fan_list)
_LOGGER.debug("self._fan_list=%s", self._fan_list)
_LOGGER.debug("self._current_fan_mode=%s",
self._current_fan_mode)
# Swing mode
if self._zxt_120 == 1:
self._current_swing_mode = (
self.get_value(
class_id=zwave.const.COMMAND_CLASS_CONFIGURATION,
index=33,
member='data'))
swing_list = self.get_value(class_id=zwave.const
.COMMAND_CLASS_CONFIGURATION,
index=33,
member='data_items')
if swing_list:
self._swing_list = list(swing_list)
if self.values.zxt_120_swing_mode:
self._current_swing_mode = self.values.zxt_120_swing_mode.data
swing_list = self.values.zxt_120_swing_mode.data_items
if swing_list:
self._swing_list = list(swing_list)
_LOGGER.debug("self._swing_list=%s", self._swing_list)
_LOGGER.debug("self._current_swing_mode=%s",
self._current_swing_mode)
# Set point
temps = []
for value in (
self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_SETPOINT)
.values()):
temps.append((round(float(value.data)), 1))
if value.index == self._index:
if value.data == 0:
_LOGGER.debug("Setpoint is 0, setting default to "
"current_temperature=%s",
self._current_temperature)
self._target_temperature = (
round((float(self._current_temperature)), 1))
break
else:
self._target_temperature = round((float(value.data)), 1)
if self.values.primary.data == 0:
_LOGGER.debug("Setpoint is 0, setting default to "
"current_temperature=%s",
self._current_temperature)
self._target_temperature = (
round((float(self._current_temperature)), 1))
else:
self._target_temperature = round(
(float(self.values.primary.data)), 1)
# Operating state
self._operating_state = self.get_value(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_OPERATING_STATE,
member='data')
if self.values.operating_state:
self._operating_state = self.values.operating_state.data
# Fan operating state
self._fan_state = self.get_value(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_FAN_STATE,
member='data')
@property
def should_poll(self):
"""No polling on Z-Wave."""
return False
if self.values.fan_state:
self._fan_state = self.values.fan_state.data
@property
def current_fan_mode(self):
@@ -221,36 +183,31 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
else:
return
self.set_value(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_SETPOINT,
index=self._index, data=temperature)
self.update_ha_state()
self.values.primary.data = temperature
def set_fan_mode(self, fan):
"""Set new target fan mode."""
self.set_value(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_FAN_MODE,
index=0, data=bytes(fan, 'utf-8'))
if self.values.fan_mode:
self.values.fan_mode.data = bytes(fan, 'utf-8')
def set_operation_mode(self, operation_mode):
"""Set new target operation mode."""
self.set_value(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_MODE,
index=0, data=bytes(operation_mode, 'utf-8'))
if self.values.mode:
self.values.mode.data = bytes(operation_mode, 'utf-8')
def set_swing_mode(self, swing_mode):
"""Set new target swing mode."""
if self._zxt_120 == 1:
self.set_value(
class_id=zwave.const.COMMAND_CLASS_CONFIGURATION,
index=33, data=bytes(swing_mode, 'utf-8'))
if self.values.zxt_120_swing_mode:
self.values.zxt_120_swing_mode.data = bytes(
swing_mode, 'utf-8')
@property
def device_state_attributes(self):
"""Return the device specific state attributes."""
data = super().device_state_attributes
if self._operating_state:
data[ATTR_OPERATING_STATE] = self._operating_state,
data[ATTR_OPERATING_STATE] = self._operating_state
if self._fan_state:
data[ATTR_FAN_STATE] = self._fan_state
return data
+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
@@ -166,7 +166,7 @@ def async_setup(hass, config):
if not cover.should_poll:
continue
update_coro = hass.loop.create_task(
update_coro = hass.async_add_job(
cover.async_update_ha_state(True))
if hasattr(cover, 'async_update'):
update_tasks.append(update_coro)
+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.8.zip'
'#pymyq==0.0.8']
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 -42
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,42 +20,31 @@ _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]]
if (value.command_class == zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL
and value.index == 0):
value.set_change_verified(False)
add_devices([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
def get_device(values, **kwargs):
"""Create zwave entity device."""
if (values.primary.command_class ==
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL
and values.primary.index == 0):
return ZwaveRollershutter(values)
elif (values.primary.command_class in [
zwave.const.COMMAND_CLASS_SWITCH_BINARY,
zwave.const.COMMAND_CLASS_BARRIER_OPERATOR]):
return ZwaveGarageDoor(values)
return None
class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice):
"""Representation of an Zwave roller shutter."""
def __init__(self, value):
def __init__(self, values):
"""Initialize the zwave rollershutter."""
ZWaveDeviceEntity.__init__(self, value, DOMAIN)
ZWaveDeviceEntity.__init__(self, values, DOMAIN)
# pylint: disable=no-member
self._node = value.node
self._open_id = None
self._close_id = None
self._current_position = None
self._workaround = workaround.get_device_mapping(value)
self._workaround = workaround.get_device_mapping(values.primary)
if self._workaround:
_LOGGER.debug("Using workaround %s", self._workaround)
self.update_properties()
@@ -62,17 +52,17 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice):
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:
self._open_id, self._close_id = self._close_id, self._open_id
self._current_position = self.values.primary.data
if self.values.open and self.values.close and \
self._open_id is None and self._close_id is None:
if self._workaround == workaround.WORKAROUND_REVERSE_OPEN_CLOSE:
self._open_id = self.values.close.value_id
self._close_id = self.values.open.value_id
self._workaround = None
else:
self._open_id = self.values.open.value_id
self._close_id = self.values.close.value_id
@property
def is_closed(self):
@@ -107,7 +97,7 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice):
def set_cover_position(self, position, **kwargs):
"""Move the roller shutter to a specific position."""
self._node.set_dimmer(self._value.value_id, position)
self.node.set_dimmer(self.values.primary.value_id, position)
def stop_cover(self, **kwargs):
"""Stop the roller shutter."""
@@ -117,14 +107,14 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice):
class ZwaveGarageDoor(zwave.ZWaveDeviceEntity, CoverDevice):
"""Representation of an Zwave garage door device."""
def __init__(self, value):
def __init__(self, values):
"""Initialize the zwave garage door."""
ZWaveDeviceEntity.__init__(self, value, DOMAIN)
ZWaveDeviceEntity.__init__(self, values, DOMAIN)
self.update_properties()
def update_properties(self):
"""Callback on data changes for node values."""
self._state = self._value.data
self._state = self.values.primary.data
@property
def is_closed(self):
@@ -133,11 +123,11 @@ class ZwaveGarageDoor(zwave.ZWaveDeviceEntity, CoverDevice):
def close_cover(self):
"""Close the garage door."""
self._value.data = False
self.values.primary.data = False
def open_cover(self):
"""Open the garage door."""
self._value.data = True
self.values.primary.data = True
@property
def device_class(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())
@@ -15,7 +15,7 @@ from homeassistant.components.device_tracker import (
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.util import Throttle
REQUIREMENTS = ['fritzconnection==0.6']
REQUIREMENTS = ['fritzconnection==0.6.3']
# Return cached results if last scan was less then this time ago.
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
@@ -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!')
@@ -14,6 +14,7 @@ import requests
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.exceptions import HomeAssistantError
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
@@ -31,6 +32,12 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
class InvalidLuciTokenError(HomeAssistantError):
"""When an invalid token is detected."""
pass
def get_scanner(hass, config):
"""Validate the configuration and return a Luci scanner."""
scanner = LuciDeviceScanner(config[DOMAIN])
@@ -46,8 +53,9 @@ class LuciDeviceScanner(DeviceScanner):
def __init__(self, config):
"""Initialize the scanner."""
host = config[CONF_HOST]
username, password = config[CONF_USERNAME], config[CONF_PASSWORD]
self.host = config[CONF_HOST]
self.username = config[CONF_USERNAME]
self.password = config[CONF_PASSWORD]
self.parse_api_pattern = re.compile(r"(?P<param>\w*) = (?P<value>.*);")
@@ -55,12 +63,15 @@ class LuciDeviceScanner(DeviceScanner):
self.last_results = {}
self.token = _get_token(host, username, password)
self.host = host
self.refresh_token()
self.mac2name = None
self.success_init = self.token is not None
def refresh_token(self):
"""Get a new token."""
self.token = _get_token(self.host, self.username, self.password)
def scan_devices(self):
"""Scan for new devices and return a list with found device IDs."""
self._update_info()
@@ -98,8 +109,15 @@ class LuciDeviceScanner(DeviceScanner):
_LOGGER.info('Checking ARP')
url = 'http://{}/cgi-bin/luci/rpc/sys'.format(self.host)
result = _req_json_rpc(url, 'net.arptable',
params={'auth': self.token})
try:
result = _req_json_rpc(url, 'net.arptable',
params={'auth': self.token})
except InvalidLuciTokenError:
_LOGGER.info('Refreshing token')
self.refresh_token()
return False
if result:
self.last_results = []
for device_entry in result:
@@ -116,6 +134,7 @@ class LuciDeviceScanner(DeviceScanner):
def _req_json_rpc(url, method, *args, **kwargs):
"""Perform one JSON RPC operation."""
data = json.dumps({'method': method, 'params': args})
try:
res = requests.post(url, data=data, timeout=5, **kwargs)
except requests.exceptions.Timeout:
@@ -139,6 +158,10 @@ def _req_json_rpc(url, method, *args, **kwargs):
"Failed to authenticate, "
"please check your username and password")
return
elif res.status_code == 403:
_LOGGER.error('Luci responded with a 403 Invalid token')
raise InvalidLuciTokenError
else:
_LOGGER.error('Invalid response from luci: %s', res)
@@ -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(
+1 -1
View File
@@ -13,7 +13,7 @@ from homeassistant.const import CONF_ACCESS_TOKEN
from homeassistant.util import Throttle
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['python-digitalocean==1.10.1']
REQUIREMENTS = ['python-digitalocean==1.11']
_LOGGER = logging.getLogger(__name__)
+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
@@ -18,7 +18,7 @@ from homeassistant.util import Throttle
REQUIREMENTS = [
'https://github.com/nkgilley/python-ecobee-api/archive/'
'4856a704670c53afe1882178a89c209b5f98533d.zip#python-ecobee==0.0.6']
'a4496b293956b2eac285305136a62ac78bef510d.zip#python-ecobee==0.0.7']
_CONFIGURING = {}
_LOGGER = logging.getLogger(__name__)
+1 -1
View File
@@ -218,7 +218,7 @@ def async_setup(hass, config: dict):
if not fan.should_poll:
continue
update_coro = hass.loop.create_task(
update_coro = hass.async_add_job(
fan.async_update_ha_state(True))
if hasattr(fan, 'async_update'):
update_tasks.append(update_coro)
+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)
@@ -63,7 +63,7 @@
window.Polymer = {
lazyRegister: true,
useNativeCSSProperties: true,
dom: 'shady',
dom: 'shadow',
suppressTemplateNotifications: true,
suppressBindingNotifications: true,
};
+11 -11
View File
@@ -2,19 +2,19 @@
FINGERPRINTS = {
"compatibility.js": "83d9c77748dafa9db49ae77d7f3d8fb0",
"core.js": "1f7f88d8f5dada08bce1d935cfa5f33e",
"frontend.html": "ca9efa7e4506aa6b1a668703c8d0f800",
"mdi.html": "c1dde43ccf5667f687c418fc8daf9668",
"core.js": "5d08475f03adb5969bd31855d5ca0cfd",
"frontend.html": "53c45b837a3bcae7cfb9ef4a5919844f",
"mdi.html": "4921d26f29dc148c3e8bd5bcd8ce5822",
"micromarkdown-js.html": "93b5ec4016f0bba585521cf4d18dec1a",
"panels/ha-panel-config.html": "412b3e24515ffa1ee8074ce974cf4057",
"panels/ha-panel-dev-event.html": "91347dedf3b4fa9b49ccf4c0a28a03c4",
"panels/ha-panel-config.html": "6dcb246cd356307a638f81c4f89bf9b3",
"panels/ha-panel-dev-event.html": "1f169700c2345785855b1d7919d12326",
"panels/ha-panel-dev-info.html": "61610e015a411cfc84edd2c4d489e71d",
"panels/ha-panel-dev-service.html": "a9247f255174b084fad2c04bdb9ec7a9",
"panels/ha-panel-dev-state.html": "90f3bede9602241552ef7bb7958198c6",
"panels/ha-panel-dev-template.html": "c249a4fc18a3a6994de3d6330cfe6cbb",
"panels/ha-panel-history.html": "fdaa4d2402d49d4c8bd64a1708ab7a50",
"panels/ha-panel-dev-service.html": "0fe8e6acdccf2dc3d1ae657b2c7f2df0",
"panels/ha-panel-dev-state.html": "48d37db4a1d6708314ded1d624d0f4d4",
"panels/ha-panel-dev-template.html": "6f353392d68574fbc5af188bca44d0ae",
"panels/ha-panel-history.html": "bfd5f929d5aa9cefdd799ec37624efa1",
"panels/ha-panel-iframe.html": "d920f0aa3c903680f2f8795e2255daab",
"panels/ha-panel-logbook.html": "2af1feb30b37427f481d5437a438a3f2",
"panels/ha-panel-map.html": "e10704a3469e44d1714eac9ed8e4b6a0",
"panels/ha-panel-logbook.html": "a1fc2b5d739bedb9d87e4da4cd929a71",
"panels/ha-panel-map.html": "9aa065b1908089f3bb5af7fdf9485be5",
"websocket_test.html": "575de64b431fe11c3785bf96d7813450"
}
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
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
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

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