Compare commits

..

152 Commits

Author SHA1 Message Date
Paulus Schoutsen 4239a2b844 Merge pull request #3715 from home-assistant/dev
0.30
2016-10-08 09:58:08 -07:00
Paulus Schoutsen 408b68bfac Version bump to 0.30.0 2016-10-08 09:57:51 -07:00
Paulus Schoutsen fe317b806f Close event loop to avoid error on exiting HASS (#3762) 2016-10-08 18:56:36 +02:00
Paulus Schoutsen 09cbf68637 Update frontend 2016-10-08 09:23:45 -07:00
Johann Kellerman fb94aaa5a1 Load yaml using validator and include consider_home (#3743)
* Load yaml using validator, consider_home

* timedelta, track_if_away

* improve voluptuous

* Add default back

* Change time_period validation order
2016-10-07 18:08:33 -07:00
Fabian Affolter b09b13f552 Catch exception (fixes #3699) (#3727) 2016-10-07 17:30:59 -07:00
Erik Eriksson 2d4df42a65 improved error handling (#3725) 2016-10-07 17:25:51 -07:00
Erik Eriksson fccc7e69d0 Improved exception handling. Don't stop updating from server because of exception. (#3724) 2016-10-07 17:24:02 -07:00
Pascal Vizeli f1e5d32ef5 Async exception handling (#3731)
* remove unused exception

* add logging

* disable pylint broad-except

* add exception handler

* fix lint

* update log output

* change log message in async with exc_info

* Add exc_info to asyncio exception handler
2016-10-07 17:20:39 -07:00
Erik Eriksson 1c24018fbb Improved exception handling (#3746) 2016-10-07 17:16:35 -07:00
Robbie Trencheny f37038921f Fix E501 line too long error 2016-10-07 11:48:38 -07:00
Robbie Trencheny f030ff67ad Set anel_pwrctrl switch to specific commit instead of master 2016-10-07 11:38:39 -07:00
Robbie Trencheny 3cfec2b5e2 Set TP-Link switch to specific commit instead of master 2016-10-07 11:36:43 -07:00
Robbie Trencheny 58b3dd7cc0 Set specific commit for Hunter Douglas API package due to upstream rename 2016-10-07 11:34:41 -07:00
Fabian Affolter 5e16dc6307 Fix typo (#3744) 2016-10-07 00:00:12 -07:00
Fabian Affolter 8b059e9aad Migrate to voluptuous (#3687) 2016-10-06 16:11:08 +02:00
Fabian Affolter 12f1be9b1c Fix PEP257 issues and ordering (#3720) 2016-10-05 17:32:29 -07:00
Robbie Trencheny 519f400175 Merge branch 'master' into dev 2016-10-05 15:35:01 -07:00
Fabian Affolter a94571fd10 Change name of Forecast.io platform to Dark Sky (#3698)
* Rename Forecast.io platform to Dark Sky

* Upgrade to python-forecastio to 1.3.5

* Update to reflect name change (Forecast.io -> Dark Sky)

* Rename forecast to darksky
2016-10-05 21:42:58 +02:00
Fabian Affolter cb3a78b330 Adjust vol to accept filenames (fixes #3701) (#3707) 2016-10-05 18:02:45 +02:00
John 1bc6366051 Increase allowable polling intensity values (#3711) 2016-10-05 16:58:06 +02:00
Fabian Affolter 5d339fb141 Fix sentence (#3709) 2016-10-05 16:28:38 +02:00
Fabian Affolter 0c68f381b0 Migrate to voluptuous (#3679) 2016-10-05 14:40:08 +02:00
Daniel Høyer Iversen 3e40b24293 Merge pull request #3708 from home-assistant/flux_led
Update flux led library
2016-10-05 11:12:56 +02:00
Daniel Høyer Iversen 4b828d225e Merge pull request #3656 from home-assistant/pushetta
customize if pushetta should send test message on start up
2016-10-05 11:03:35 +02:00
Daniel 201294e481 Update flux led library 2016-10-05 10:49:33 +02:00
Daniel 350d23f7eb customize if pushetta should send test message on start up 2016-10-05 10:45:41 +02:00
Daniel Høyer Iversen b8beae9c6c Merge pull request #3651 from home-assistant/automation
Customize initial state of automation
2016-10-05 09:48:55 +02:00
Daniel 46f3337b07 Customize initial state of automation 2016-10-05 09:20:51 +02:00
John Arild Berentsen b2354f45be Add possibility to set temperature to a given operation_mode (#3646)
* Add possibility to set temperature to a given operation_mode

* Correctly break

* Review changes
2016-10-04 23:48:25 -07:00
Paulus Schoutsen e4e13b59cf Update frontend 2016-10-04 23:15:10 -07:00
Paulus Schoutsen 8c694eb279 Speed up MQTT server test (#3703) 2016-10-04 22:49:43 -07:00
Paulus Schoutsen 9d085a023c Merge pull request #3706 from home-assistant/hotfix-0-29-7b
Fix broken unit tests
2016-10-04 22:47:46 -07:00
Martin Hjelmare c06313a897 Set force_update to true for mysensors sensors (#3648) 2016-10-04 22:45:38 -07:00
Per Sandström ed490b1c11 Add västtrafik sensor (#3640) 2016-10-04 22:37:45 -07:00
Marcelo Moreira de Mello 70fc94003d Fixed issue #3624 and bumped python-nest to 2.10.0 version (#3665)
* Fixed issue #3624 and bumped python-nest to 2.10.0 version

*    Fixed return state when accessing attribute operation_mode

* Fixed typo
2016-10-04 22:29:21 -07:00
Paulus Schoutsen 114fae76e1 Fix broken unit tests 2016-10-04 22:23:58 -07:00
Paulus Schoutsen 4f0064b00e Fix broken tests (#3704)
* Fix broken tests

* Lint
2016-10-04 22:19:12 -07:00
Fabian Affolter dc53c21548 Check that no configuration is provided (#3675) 2016-10-04 22:10:31 -07:00
Pascal Vizeli b9b41d3855 Update ha-alpr / change default interval (#3691) 2016-10-04 22:04:50 -07:00
John Arild Berentsen 17a8dd3f70 Add set_config_parameter service (#3696) 2016-10-04 22:04:19 -07:00
gwendalg 03d6a7c42a Add Ted5000 Sensor (#3559)
* Add Ted5000 Sensor

Signed-off-by: Gwendal Grignou <gwendal@gmail.com>

* sensors: ted5000: use requests instead of urllib.

Signed-off-by: Gwendal Grignou <gwendal@gmail.com>

* Update ted5000.py

Add timeout to requests call.
2016-10-04 21:57:40 -07:00
Daniel Høyer Iversen d2d393feb5 Add automations and scripts to group.all_automations and group.all_scripts (#3664)
* Add automations to group.all_automations

* Add scripts to group.all_scripts
2016-10-04 21:20:48 -07:00
Paulus Schoutsen e49651cdeb Merge pull request #3702 from home-assistant/hotfix-0-29-7
0.29.7
2016-10-04 21:03:47 -07:00
Paulus Schoutsen a60e845203 Version bump to 0.29.7 2016-10-04 21:01:28 -07:00
Pascal Vizeli f23eb9336f Service & signal (stop/restart) fix (#3690)
* Bugfix signhandling/services

* change from coroutine to callback

* add error handling

* fix bug with endless running

* fix unit test

* Revert "fix unit test"

This reverts commit 31135c7709.

* Disable sigterm/sighup test
2016-10-04 21:01:08 -07:00
Pascal Vizeli 0bf8bb62ad Service & signal (stop/restart) fix (#3690)
* Bugfix signhandling/services

* change from coroutine to callback

* add error handling

* fix bug with endless running

* fix unit test

* Revert "fix unit test"

This reverts commit 31135c7709.

* Disable sigterm/sighup test
2016-10-04 21:00:36 -07:00
Paulus Schoutsen 5085cdb0f7 Add async_safe annotation (#3688)
* Add async_safe annotation

* More async_run_job

* coroutine -> async_save

* Lint

* Rename async_safe -> callback

* Add tests to core for different job types

* Add one more test with different type of callbacks

* Fix typing signature for callback methods

* Fix callback service executed method

* Fix method signatures for callback
2016-10-04 20:44:32 -07:00
Robbie Trencheny 8358f938b5 Now no one wants to be blacklisted, so lets remove the configuration entirely 2016-10-04 13:39:48 -07:00
Robbie Trencheny be7401f4a2 Now no one wants to be blacklisted, so lets remove the configuration entirely 2016-10-04 13:38:56 -07:00
Robbie Trencheny 760117167d Update .mention-bot to add more users to blacklist and remove skipAlreadyMentionedPR 2016-10-04 13:25:57 -07:00
Robbie Trencheny 2fd83b439c Update .mention-bot to add more users to blacklist and remove skipAlreadyMentionedPR 2016-10-04 13:25:16 -07:00
Robbie Trencheny e523c3d196 Add @mention-bot configuration 2016-10-04 13:22:35 -07:00
Per Sandström d9e73d1d71 Merge pull request #3697 from persandstrom/verisure_0.10.3
vsure 0.10.3
2016-10-04 22:20:12 +02:00
Per Sandström 5d3e93b4ef vsure 0.10.3 2016-10-04 22:01:36 +02:00
sam-io 85782f9c29 fix for date formatting issue (#3693) 2016-10-04 08:41:39 -07:00
William Scanlon 2cf49f3de6 Added support for Wink Smoke and CO detectors (#3645) 2016-10-04 01:07:50 -07:00
Fabian Affolter a072047d9d Auth and headers support for REST sensor (#3592)
* Add auth and header support

* Update header part
2016-10-04 01:07:17 -07:00
Fabian Affolter 74b0e4cb45 Statistics sensor (#3513)
* Initial stats sensor

* Add total and tests

* Use deque, rename var, set default, and only update sensor
2016-10-04 01:04:00 -07:00
Rob Capellini 694983379f test: mocking IO in HTML5 notify tests (#3685)
Replacing temporary file creation in tests with mock's mock_open for faster IO.
2016-10-04 01:01:41 -07:00
Erik Eriksson 5900d8a2c1 only query vehicle attributes once (#3668)
use registration number as dev id
2016-10-04 00:58:25 -07:00
Erik Eriksson 287a7e2720 Support for encrypted payload (#3587) 2016-10-04 00:57:37 -07:00
William Scanlon 8592ba3cb9 Report availability of arest (#3614) 2016-10-04 00:51:45 -07:00
deisi c93b63963b Fix 3621 (#3642)
Happens when the scanner searches for a mac address, that has been forgotten
by the fritzbox.
2016-10-04 00:47:58 -07:00
Fabian Affolter 194402f7e7 Upgrade Sphinx to 1.4.8 and set missing variables (#3650) 2016-10-04 00:47:28 -07:00
Paulus Schoutsen d58548dd1c Address asyncio comments (#3663)
* Template platforms: create_task instead of yield from

* Automation: less yielding, more create_tasking

* Helpers.script: less yielding, more create_tasking

* Deflake logbook test

* Deflake automation reload config test

* MQTT: Use async_add_job and threaded_listener_factory

* Deflake other logbook test

* lint

* Add test for automation trigger service

* MQTT client can be called from within async
2016-10-03 22:39:27 -07:00
Robbie Trencheny f2a12b7ac2 Add @mention-bot configuration 2016-10-03 17:36:00 -07:00
Pascal Vizeli 625319846c Big Homematic update (#3677)
* Update homeassistant with new pyhomematic layout

* fix linter
2016-10-03 23:21:53 +02:00
Daniel Høyer Iversen c3ea04f2dd revert update of input_boolean 2016-10-03 22:05:06 +02:00
Daniel Høyer Iversen 4a5a1e1e96 Ensure unique entity id for input_boolean 2016-10-03 22:03:16 +02:00
Pascal Vizeli 41aff96375 Bugfix temp convert on none temp attribute / unittest for that (#3654) 2016-10-03 10:46:31 +02:00
Justin Weberg 0219df17f5 Expose Configuration path to frontend (#3660)
* Expose config path to frontend

* Fix typo

* Add deleted code.
2016-10-03 00:04:43 -07:00
Robbie Trencheny b586e80977 Squashed commit of the following:
commit 220331260e9748ac8e17b3ce776330c1dfb7725b
Merge: 73d93e5 c891820
Author: Robbie Trencheny <me@robbiet.us>
Date:   Sun Oct 2 17:57:24 2016 -0700

    Merge branch 'color_temp_for_mqtt_light' of https://github.com/alterscape/home-assistant into alterscape-color_temp_for_mqtt_light

commit c89182008a
Author: Ryan Spicer <ryanspicer@gmail.com>
Date:   Sun Sep 18 23:06:34 2016 -0700

    fix missing docstring.

commit e61dda4dd3
Author: Ryan Spicer <ryanspicer@gmail.com>
Date:   Sun Sep 18 22:43:04 2016 -0700

    fix pep8 errors and typos in tests.

commit 559d1752d2
Author: Ryan Spicer <ryanspicer@gmail.com>
Date:   Sun Sep 18 21:41:07 2016 -0700

    add tests for mqtt color temp support

commit 702defb932
Author: Ryan Spicer <ryanspicer@gmail.com>
Date:   Sun Sep 18 20:55:07 2016 -0700

    Add color temp support to mqtt lights.
2016-10-02 18:04:00 -07:00
hexa- 73d93e526e Add anel_pwrctrl platform to control switches on ANEL PwrCtrl devices (#3644)
* Add pwrctrl platform to control switches on ANEL PwrCtrl devices

* make requested changes
2016-10-02 16:51:15 -07:00
Paulus Schoutsen 148ea82513 Update frontend 2016-10-02 15:38:18 -07:00
Paulus Schoutsen abb8bcb6d9 Protect waiting for event loop from within event loop (#3658)
* Protect waiting for event loop from within event loop

* Faster fetching of loop attribute for ident check
2016-10-02 15:07:23 -07:00
John Arild Berentsen e455daa61d Make sure temperature slider is shown if reported temp is 0 (#3653) 2016-10-02 17:47:04 +02:00
Martin Hjelmare b6b0bad0c7 Add new mysensors types (#3637)
* Add S_INFO, S_GAS, S_GPS, S_WATER_QUALITY.
* Extend S_CUSTOM, S_POWER.
* Add more units.
2016-10-01 23:23:31 -07:00
Paulus Schoutsen d5ab292ff3 Sensor.emoncms: Never disable SSL verification. 2016-10-01 23:13:07 -07:00
Erik Eriksson 9f8acbec95 Add support for tracking position and status for a Volvo car (#3617) 2016-10-01 23:00:01 -07:00
Jeff Wilson d55ed7a3a2 Flux switch improvements (#3615)
* Make flux always adjust brightness of light (even when not in XY mode)

* Remove kelvin mode from flux switch

The light/turn_on service only works with mired values, kelvin values
are out of range per the schema.

* Use already defined min/max values for light/turn_on schema

* Clamp temp value to light/turn_on allowed values
2016-10-01 22:57:15 -07:00
Greg Dowling fcbfcf0aa7 Bump pywemo version - changes port order for faster startup. (#3612) 2016-10-01 22:53:24 -07:00
Simon Szustkowski 183936375f Add a Timeout for InfluxDB connections (#3563)
* Add a Timeout for InfluxDB connections

* Removed trailing whitespace
2016-10-01 22:29:06 -07:00
Paulus Schoutsen 1f1ac02457 Merge branch 'pr/3552' into dev 2016-10-01 22:20:58 -07:00
Robbie Trencheny 646eaccd4d Update notify to expect a list of string targets instead of a single … (#3548)
* Update notify to expect a list of string targets instead of a single string

* Actually do the thing I set out to do

* Fix notify.group test to expect an array of targets

* REST platform will only use the first target in the list

* Update notify platforms to expect a list of targets

* Update notify services.yaml
2016-10-01 22:19:17 -07:00
Pascal Vizeli c189c05676 Add temp convert to climate base object (#3611)
* Add temp convert to climate base object

* fix nest

* fix lint
2016-10-01 22:15:19 -07:00
Lewis Juggins 9683e3e3ad Correctly define requirements for emulated hue (#3535) 2016-10-01 21:56:45 -07:00
Fabian Affolter a61e08680a Add persistent notification for failed login attempts (#3528) 2016-10-01 21:20:16 -07:00
Klaas Hoekema 9da2d6edd0 Make forecast.io update interval configurable (#3520)
Adds a config parameter (`update_interval`) to the `forecast` sensor to
set the minimum update interval. The default remains 120 seconds.
2016-10-01 21:18:10 -07:00
William Scanlon 1f38e9fa57 Support for wink oath2 and relay sensors (#3496) 2016-10-01 20:45:39 -07:00
Paulus Schoutsen 996d7cf1cd Merge pull request #3627 from home-assistant/async-entity-update
Add async updates to entities
2016-10-01 16:20:48 -07:00
Paulus Schoutsen c36d30f4fe Typo 2016-10-01 15:43:33 -07:00
Paulus Schoutsen 8f3e12c9b8 Make Automation.reload_service_handler async 2016-10-01 15:42:17 -07:00
Paulus Schoutsen 56fdc2a625 Automation: call prepare_setup_platform in executor 2016-10-01 14:11:16 -07:00
Paulus Schoutsen e18825ba20 Automation: only call executor once when processing config 2016-10-01 14:11:16 -07:00
Paulus Schoutsen 7ab7edd81c Make automation async 2016-10-01 14:11:16 -07:00
Paulus Schoutsen 16ff68ca84 Add mqtt.async_subscribe 2016-10-01 14:11:16 -07:00
Paulus Schoutsen b8504f8fc8 Make helpers.script async 2016-10-01 14:11:16 -07:00
Paulus Schoutsen 185bd6c28a Make helpers.condition.* async 2016-10-01 14:11:16 -07:00
Paulus Schoutsen 33a51623f8 Make Service.call_from_config async 2016-10-01 14:11:16 -07:00
Paulus Schoutsen 4198c42736 Have template platforms never leave the event loop 2016-10-01 14:11:16 -07:00
Paulus Schoutsen 3e24a35c1e Skip RFXtrx tests unless RFXTRX=RUN (#3625)
* Skip RFXtrx tests unless RFXTRX=RUN

* Remove my previous hacks to slightly speed up rfxtrx

* Exclude RFXTRX tests from coverage

* Remove unused import in rfxtrx tstt

* Add close connection back to RFXtrx tests

* Typo
2016-10-01 13:57:10 -07:00
Paulus Schoutsen 651f3ab55c Merge pull request #3641 from home-assistant/hotfix-0-29-6
0.29.6
2016-10-01 12:12:56 -07:00
Paulus Schoutsen 756f23f0b4 Version bump to 0.29.6 2016-10-01 12:10:00 -07:00
Paulus Schoutsen ef0e018cbb Service config calls will no longer mutate original config (#3628) 2016-10-01 12:09:25 -07:00
Ben Bangert 9cf2ad0b55 Monkey-patch a weakref set in Task to be a no-op. (#3639)
* Monkey-patch a weakref set in Task to be a no-op.

* Fix linting issues
2016-10-01 12:08:56 -07:00
Ben Bangert 892bbdc2dd Monkey-patch a weakref set in Task to be a no-op. (#3639)
* Monkey-patch a weakref set in Task to be a no-op.

* Fix linting issues
2016-10-01 12:08:25 -07:00
Paulus Schoutsen bb03960ba5 Voluptuous arest (#3558)
* Migrate to voluptuous

* Adjust sensor.arest for new template system

* Use items() to align the var section with the pins
2016-10-01 18:45:43 +02:00
Fabian Affolter 15ed8c6332 Add units (fixes #3619) (#3633) 2016-10-01 17:35:32 +02:00
Paulus Schoutsen 412b5350ce Service config calls will no longer mutate original config (#3628) 2016-09-30 23:26:15 -07:00
Otto Winter d5f8aa52c4 Add support for MySensors cover (#3512)
* Added support for MySensors cover device

* Fixed set_req not defined

* Fixed V_PERCENTAGE is str

* Removed set_cover_position

The MySensors documentation doesn’t specify when sending a V_PERCENTAGE
is allowed.

* Fixed homeassistant/components/mysensors.py line too long

* Fixed lint ATTR_POSITION imported but unused

* Use V_PERCENTAGE for MySensors cover

* Revert "Removed set_cover_position"

This reverts commit d78cb3a04d.

* Fix set_req, ATTR_POSITION not defined

* Added support for non-exactly positionable covers

* Fixed V_PERCENTAGE cast to bool

* Ported MySensors cover back to v1.4

`V_PERCENTAGE` and `V_DIMMER` are aliases just like `V_STATUS` and
`V_LIGHT`, so the code inside `MySensorsCover` doesn’t need to be
updated.

* Fixed v1.5 V_TYPES not in in v1.4 gateway SetReq
2016-10-01 01:36:04 +02:00
Paulus Schoutsen b650b2b0db Spread async love (#3575)
* Convert Entity.update_ha_state to be async

* Make Service.call async

* Update entity.py

* Add Entity.async_update

* Make automation zone trigger async

* Fix linting

* Reduce flakiness in hass.block_till_done

* Make automation.numeric_state async

* Make mqtt.subscribe async

* Make automation.mqtt async

* Make automation.time async

* Make automation.sun async

* Add async_track_point_in_utc_time

* Make helpers.track_sunrise/set async

* Add async_track_state_change

* Make automation.state async

* Clean up helpers/entity.py tests

* Lint

* Lint

* Core.is_state and Core.is_state_attr are async friendly

* Lint

* Lint
2016-09-30 12:57:24 -07:00
Fabian Affolter 7e50ccd32a Component for Digital Ocean (#3322)
* Add Digital Ocean implementation

* Remove kernel
2016-09-30 18:30:44 +02:00
John Arild Berentsen 521080d1b0 Zwave: Update commandclasses and deviceclasses according to sigma SDK (#3495)
* Update Command classes and device types to Sigma SDK

* Fix some pylint

* Seperate constants to file

* Flake8

* coverage and flake8 pylint

* Add services.yaml

* Service descriptions was missing

* Spelling :)

* grammar

* Remove zwave service descriptions from main
2016-09-30 08:43:18 -07:00
Paulus Schoutsen d76cf092c3 Merge pull request #3610 from home-assistant/hotfix-0-29-5
0.29.5
2016-09-30 00:24:59 -07:00
Paulus Schoutsen dfb92fa836 Version bump to 0.29.5 2016-09-30 00:15:14 -07:00
Paulus Schoutsen 5cb8ce71ef Lint 2016-09-30 00:14:59 -07:00
Jeff Wilson 099e983ca0 Nest operation modes (#3609)
* Add ability to change Nest mode to all available options

* Make Nest state reflect current operation not current operation mode

* Update Nest sensor to use operation mode

* Fix linting

* Revert "Make Nest state reflect current operation not current operation mode"

This reverts commit 573ba028d8.

Conflicts:
	homeassistant/components/climate/nest.py
2016-09-30 00:14:59 -07:00
Marcelo Moreira de Mello 39514be1f9 Fixed issue #3574 - Temperature slider fixed on frontend on heat/cool mode (#3606)
* Fixed issue #3574 - Temperature slider fixed on frontend on heat/cool mode

* Fixed target_temperature to return None when self.is_away_mode_on is True
2016-09-30 00:14:59 -07:00
Paulus Schoutsen 234f4449b0 Lint 2016-09-30 00:14:08 -07:00
Jeff Wilson a89d036e26 Nest operation modes (#3609)
* Add ability to change Nest mode to all available options

* Make Nest state reflect current operation not current operation mode

* Update Nest sensor to use operation mode

* Fix linting

* Revert "Make Nest state reflect current operation not current operation mode"

This reverts commit 573ba028d8.

Conflicts:
	homeassistant/components/climate/nest.py
2016-09-29 23:44:14 -07:00
Marcelo Moreira de Mello 9ea030f42e Fixed issue #3574 - Temperature slider fixed on frontend on heat/cool mode (#3606)
* Fixed issue #3574 - Temperature slider fixed on frontend on heat/cool mode

* Fixed target_temperature to return None when self.is_away_mode_on is True
2016-09-29 22:17:13 -07:00
Paulus Schoutsen 7ac8425099 Fix tests 2016-09-29 21:42:36 -07:00
Fabian Affolter c000e74d0a Migrate to voluptuous (#3374) 2016-09-29 19:07:35 -07:00
Fabian Affolter 6632747543 Migrate to voluptuous (#3341) 2016-09-29 19:06:28 -07:00
Fabian Affolter a7266ae6cf Check that no configuration is provided (#3553) 2016-09-29 19:02:22 -07:00
Paulus Schoutsen 4031f70665 Merge pull request #3603 from home-assistant/hotfix-0-29-4
Hotfix 0 29 4
2016-09-29 18:59:09 -07:00
Paulus Schoutsen 2b59409e52 Version 0.29.4 2016-09-29 18:57:33 -07:00
Dan Cinnamon b41c795d34 Passing original config reference to load_platform. (#3602) 2016-09-29 18:56:09 -07:00
Pascal Vizeli db56ed400d Bugfix voluptuous acer_projector (#3598) 2016-09-29 18:56:09 -07:00
Paulus Schoutsen c603ffbe26 Fix voluptuous alexa config (#3596) 2016-09-29 18:56:09 -07:00
Dan Cinnamon 68028afb98 Passing original config reference to load_platform. (#3602) 2016-09-29 18:55:43 -07:00
Paulus Schoutsen 733120c577 Fix voluptuous alexa config (#3596) 2016-09-29 18:45:55 -07:00
Pascal Vizeli 01435f7f42 Bugfix voluptuous acer_projector (#3598) 2016-09-29 18:36:20 -07:00
Paulus Schoutsen 77c91c8a5e Merge pull request #3590 from home-assistant/hotfix-0-29-3
Hotfix 0 29 3
2016-09-29 09:09:51 -07:00
Paulus Schoutsen a321c2f0d8 Version bump to 0.29.3 2016-09-29 09:07:19 -07:00
Pascal Vizeli 807daf8f5d Bugfix voluptuous for hue (#3589) 2016-09-29 09:07:00 -07:00
Pascal Vizeli 08bacd8e31 Bugfix voluptuous for hue (#3589) 2016-09-29 09:06:41 -07:00
Paulus Schoutsen f79d762e66 Merge pull request #3585 from home-assistant/hotfix-29-2
Hotfix 29 2
2016-09-29 08:18:51 -07:00
Paulus Schoutsen 30aa67b789 Version bump to 0.29.2 2016-09-29 07:50:34 -07:00
Hugo Dupras 883e45c476 Netatmo: Hotfix for hass 0.29 (#3576)
Signed-off-by: Hugo D. (jabesq) <jabesq@gmail.com>
2016-09-29 07:46:04 -07:00
Hugo Dupras dd551cf056 Netatmo: Hotfix for hass 0.29 (#3576)
Signed-off-by: Hugo D. (jabesq) <jabesq@gmail.com>
2016-09-29 07:45:54 -07:00
Fabian Affolter 47a3adb6b3 Remove duplicate port entry (fixes #3583) (#3584) 2016-09-29 07:45:39 -07:00
Fabian Affolter 7d86fb8c72 Remove duplicate port entry (fixes #3583) (#3584) 2016-09-29 07:45:25 -07:00
Greg Dowling e87467cb20 Merge pull request #3577 from home-assistant/fix_loopenergy_exceptions
Loopenergy library - bump to catch more exceptions in poll thread.
2016-09-29 10:49:14 +01:00
pavoni abd1213cc4 Bump library to catch more exceptions in poll thread. 2016-09-29 09:55:05 +01:00
Paulus Schoutsen f3d838c448 Version bump to 0.29.1 2016-09-28 22:08:02 -07:00
Marcelo Moreira de Mello 574e3231d0 Fixed typo (#3569)
Sep 29 00:59:22 pi hass[21333]: if self.device.measurment_scale == 'F':
Sep 29 00:59:22 pi hass[21333]: AttributeError: 'Device' object has no attribute 'measurment_scale'
2016-09-28 22:07:40 -07:00
Marcelo Moreira de Mello 48ffdea31f Fixed typo (#3569)
Sep 29 00:59:22 pi hass[21333]: if self.device.measurment_scale == 'F':
Sep 29 00:59:22 pi hass[21333]: AttributeError: 'Device' object has no attribute 'measurment_scale'
2016-09-28 22:07:23 -07:00
Paulus Schoutsen 6e80581b30 Version bump to 0.30.0.dev0 2016-09-28 21:21:03 -07:00
Paulus Schoutsen 9780ea5c52 Version bump to 0.29 2016-09-28 21:20:47 -07:00
happyleaves 559f63bfc3 bump limitlessled version 2016-09-27 16:30:26 -04:00
189 changed files with 4715 additions and 1970 deletions
+12 -2
View File
@@ -16,6 +16,9 @@ omit =
homeassistant/components/bloomsky.py
homeassistant/components/*/bloomsky.py
homeassistant/components/digital_ocean.py
homeassistant/components/*/digital_ocean.py
homeassistant/components/dweet.py
homeassistant/components/*/dweet.py
@@ -46,6 +49,9 @@ omit =
homeassistant/components/qwikswitch.py
homeassistant/components/*/qwikswitch.py
homeassistant/components/rfxtrx.py
homeassistant/components/*/rfxtrx.py
homeassistant/components/rpi_gpio.py
homeassistant/components/*/rpi_gpio.py
@@ -77,7 +83,7 @@ omit =
homeassistant/components/zigbee.py
homeassistant/components/*/zigbee.py
homeassistant/components/zwave.py
homeassistant/components/zwave/*
homeassistant/components/*/zwave.py
homeassistant/components/enocean.py
@@ -135,6 +141,7 @@ omit =
homeassistant/components/device_tracker/tomato.py
homeassistant/components/device_tracker/tplink.py
homeassistant/components/device_tracker/ubus.py
homeassistant/components/device_tracker/volvooncall.py
homeassistant/components/discovery.py
homeassistant/components/downloader.py
homeassistant/components/fan/mqtt.py
@@ -213,6 +220,7 @@ omit =
homeassistant/components/sensor/bom.py
homeassistant/components/sensor/coinmarketcap.py
homeassistant/components/sensor/cpuspeed.py
homeassistant/components/sensor/darksky.py
homeassistant/components/sensor/deutsche_bahn.py
homeassistant/components/sensor/dht.py
homeassistant/components/sensor/dte_energy_bridge.py
@@ -222,7 +230,6 @@ omit =
homeassistant/components/sensor/fastdotcom.py
homeassistant/components/sensor/fitbit.py
homeassistant/components/sensor/fixer.py
homeassistant/components/sensor/forecast.py
homeassistant/components/sensor/fritzbox_callmonitor.py
homeassistant/components/sensor/glances.py
homeassistant/components/sensor/google_travel_time.py
@@ -255,17 +262,20 @@ omit =
homeassistant/components/sensor/swiss_hydrological_data.py
homeassistant/components/sensor/swiss_public_transport.py
homeassistant/components/sensor/systemmonitor.py
homeassistant/components/sensor/ted5000.py
homeassistant/components/sensor/temper.py
homeassistant/components/sensor/time_date.py
homeassistant/components/sensor/torque.py
homeassistant/components/sensor/transmission.py
homeassistant/components/sensor/twitch.py
homeassistant/components/sensor/uber.py
homeassistant/components/sensor/vasttrafik.py
homeassistant/components/sensor/worldclock.py
homeassistant/components/sensor/xbox_live.py
homeassistant/components/sensor/yahoo_finance.py
homeassistant/components/sensor/yweather.py
homeassistant/components/switch/acer_projector.py
homeassistant/components/switch/anel_pwrctrl.py
homeassistant/components/switch/arest.py
homeassistant/components/switch/dlink.py
homeassistant/components/switch/edimax.py
+1 -1
View File
@@ -15,7 +15,7 @@
If user exposed functionality or configuration variables are added/changed:
- [ ] Documentation added/updated in [home-assistant.io](https://github.com/home-assistant/home-assistant.io)
If code communicates with devices, web services, or a:
If the code communicates with devices, web services, or third-party tools:
- [ ] Local tests with `tox` run successfully. **Your PR cannot be merged unless tests pass**
- [ ] New dependencies have been added to the `REQUIREMENTS` variable ([example][ex-requir]).
- [ ] New dependencies are only imported inside functions that use them ([example][ex-import]).
+5 -5
View File
@@ -340,8 +340,8 @@ latex_elements = {
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, 'Home-Assistant.tex', 'Home-Assistant Documentation',
'Home-Assistant Team', 'manual'),
(master_doc, 'home-assistant.tex', 'Home Assistant Documentation',
'Home Assistant Team', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
@@ -382,7 +382,7 @@ latex_documents = [
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(master_doc, 'home-assistant', 'Home-Assistant Documentation',
(master_doc, 'home-assistant', 'Home Assistant Documentation',
[author], 1)
]
@@ -397,8 +397,8 @@ man_pages = [
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, 'Home-Assistant', 'Home-Assistant Documentation',
author, 'Home-Assistant', 'One line description of project.',
(master_doc, 'Home-Assistant', 'Home Assistant Documentation',
author, 'Home Assistant', 'Open-source home automation platform.',
'Miscellaneous'),
]
+45
View File
@@ -19,6 +19,49 @@ from homeassistant.const import (
from homeassistant.util.async import run_callback_threadsafe
def monkey_patch_asyncio():
"""Replace weakref.WeakSet to address Python 3 bug.
Under heavy threading operations that schedule calls into
the asyncio event loop, Task objects are created. Due to
a bug in Python, GC may have an issue when switching between
the threads and objects with __del__ (which various components
in HASS have).
This monkey-patch removes the weakref.Weakset, and replaces it
with an object that ignores the only call utilizing it (the
Task.__init__ which calls _all_tasks.add(self)). It also removes
the __del__ which could trigger the future objects __del__ at
unpredictable times.
The side-effect of this manipulation of the Task is that
Task.all_tasks() is no longer accurate, and there will be no
warning emitted if a Task is GC'd while in use.
On Python 3.6, after the bug is fixed, this monkey-patch can be
disabled.
See https://bugs.python.org/issue26617 for details of the Python
bug.
"""
# pylint: disable=no-self-use, too-few-public-methods, protected-access
# pylint: disable=bare-except
import asyncio.tasks
class IgnoreCalls:
"""Ignore add calls."""
def add(self, other):
"""No-op add."""
return
asyncio.tasks.Task._all_tasks = IgnoreCalls()
try:
del asyncio.tasks.Task.__del__
except:
pass
def validate_python() -> None:
"""Validate we're running the right Python version."""
if sys.version_info[:3] < REQUIRED_PYTHON_VER:
@@ -308,6 +351,8 @@ def try_to_restart() -> None:
def main() -> int:
"""Start Home Assistant."""
monkey_patch_asyncio()
validate_python()
args = get_arguments()
+1 -1
View File
@@ -63,7 +63,7 @@ CONFIG_SCHEMA = vol.Schema({
}
}
}
})
}, extra=vol.ALLOW_EXTRA)
def setup(hass, config):
+138 -145
View File
@@ -4,6 +4,7 @@ Allow to setup simple automation rules via the config file.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/automation/
"""
import asyncio
from functools import partial
import logging
import os
@@ -23,12 +24,15 @@ from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.loader import get_platform
from homeassistant.util.dt import utcnow
import homeassistant.helpers.config_validation as cv
from homeassistant.util.async import run_coroutine_threadsafe
DOMAIN = 'automation'
ENTITY_ID_FORMAT = DOMAIN + '.{}'
DEPENDENCIES = ['group']
GROUP_NAME_ALL_AUTOMATIONS = 'all automations'
CONF_ALIAS = 'alias'
CONF_HIDE_ENTITY = 'hide_entity'
@@ -36,6 +40,7 @@ CONF_CONDITION = 'condition'
CONF_ACTION = 'action'
CONF_TRIGGER = 'trigger'
CONF_CONDITION_TYPE = 'condition_type'
CONF_INITIAL_STATE = 'initial_state'
CONDITION_USE_TRIGGER_VALUES = 'use_trigger_values'
CONDITION_TYPE_AND = 'and'
@@ -43,9 +48,7 @@ CONDITION_TYPE_OR = 'or'
DEFAULT_CONDITION_TYPE = CONDITION_TYPE_AND
DEFAULT_HIDE_ENTITY = False
METHOD_TRIGGER = 'trigger'
METHOD_IF_ACTION = 'if_action'
DEFAULT_INITIAL_STATE = True
ATTR_LAST_TRIGGERED = 'last_triggered'
ATTR_VARIABLES = 'variables'
@@ -55,21 +58,14 @@ SERVICE_RELOAD = 'reload'
_LOGGER = logging.getLogger(__name__)
def _platform_validator(method, schema):
"""Generate platform validator for different steps."""
def validator(config):
"""Validate it is a valid platform."""
platform = get_platform(DOMAIN, config[CONF_PLATFORM])
def _platform_validator(config):
"""Validate it is a valid platform."""
platform = get_platform(DOMAIN, config[CONF_PLATFORM])
if not hasattr(platform, method):
raise vol.Invalid('invalid method platform')
if not hasattr(platform, 'TRIGGER_SCHEMA'):
return config
if not hasattr(platform, schema):
return config
return getattr(platform, schema)(config)
return validator
return getattr(platform, 'TRIGGER_SCHEMA')(config)
_TRIGGER_SCHEMA = vol.All(
cv.ensure_list,
@@ -78,33 +74,19 @@ _TRIGGER_SCHEMA = vol.All(
vol.Schema({
vol.Required(CONF_PLATFORM): cv.platform_validator(DOMAIN)
}, extra=vol.ALLOW_EXTRA),
_platform_validator(METHOD_TRIGGER, 'TRIGGER_SCHEMA')
_platform_validator
),
]
)
_CONDITION_SCHEMA = vol.Any(
CONDITION_USE_TRIGGER_VALUES,
vol.All(
cv.ensure_list,
[
vol.All(
vol.Schema({
CONF_PLATFORM: str,
CONF_CONDITION: str,
}, extra=vol.ALLOW_EXTRA),
cv.has_at_least_one_key(CONF_PLATFORM, CONF_CONDITION),
),
]
)
)
_CONDITION_SCHEMA = vol.All(cv.ensure_list, [cv.CONDITION_SCHEMA])
PLATFORM_SCHEMA = vol.Schema({
CONF_ALIAS: cv.string,
vol.Optional(CONF_INITIAL_STATE,
default=DEFAULT_INITIAL_STATE): cv.boolean,
vol.Optional(CONF_HIDE_ENTITY, default=DEFAULT_HIDE_ENTITY): cv.boolean,
vol.Required(CONF_TRIGGER): _TRIGGER_SCHEMA,
vol.Required(CONF_CONDITION_TYPE, default=DEFAULT_CONDITION_TYPE):
vol.All(vol.Lower, vol.Any(CONDITION_TYPE_AND, CONDITION_TYPE_OR)),
vol.Optional(CONF_CONDITION): _CONDITION_SCHEMA,
vol.Required(CONF_ACTION): cv.SCRIPT_SCHEMA,
})
@@ -163,9 +145,11 @@ def reload(hass):
def setup(hass, config):
"""Setup the automation."""
component = EntityComponent(_LOGGER, DOMAIN, hass)
component = EntityComponent(_LOGGER, DOMAIN, hass,
group_name=GROUP_NAME_ALL_AUTOMATIONS)
success = _process_config(hass, config, component)
success = run_coroutine_threadsafe(
_async_process_config(hass, config, component), hass.loop).result()
if not success:
return False
@@ -173,22 +157,37 @@ def setup(hass, config):
descriptions = conf_util.load_yaml_config_file(
os.path.join(os.path.dirname(__file__), 'services.yaml'))
@asyncio.coroutine
def trigger_service_handler(service_call):
"""Handle automation triggers."""
for entity in component.extract_from_service(service_call):
entity.trigger(service_call.data.get(ATTR_VARIABLES))
hass.loop.create_task(entity.async_trigger(
service_call.data.get(ATTR_VARIABLES), True))
def service_handler(service_call):
"""Handle automation service calls."""
@asyncio.coroutine
def turn_onoff_service_handler(service_call):
"""Handle automation turn on/off service calls."""
method = 'async_{}'.format(service_call.service)
for entity in component.extract_from_service(service_call):
getattr(entity, service_call.service)()
hass.loop.create_task(getattr(entity, method)())
@asyncio.coroutine
def toggle_service_handler(service_call):
"""Handle automation toggle service calls."""
for entity in component.extract_from_service(service_call):
if entity.is_on:
hass.loop.create_task(entity.async_turn_off())
else:
hass.loop.create_task(entity.async_turn_on())
@asyncio.coroutine
def reload_service_handler(service_call):
"""Remove all automations and load new ones from config."""
conf = component.prepare_reload()
conf = yield from hass.loop.run_in_executor(
None, component.prepare_reload)
if conf is None:
return
_process_config(hass, conf, component)
hass.loop.create_task(_async_process_config(hass, conf, component))
hass.services.register(DOMAIN, SERVICE_TRIGGER, trigger_service_handler,
descriptions.get(SERVICE_TRIGGER),
@@ -198,8 +197,12 @@ def setup(hass, config):
descriptions.get(SERVICE_RELOAD),
schema=RELOAD_SERVICE_SCHEMA)
for service in (SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE):
hass.services.register(DOMAIN, service, service_handler,
hass.services.register(DOMAIN, SERVICE_TOGGLE, toggle_service_handler,
descriptions.get(SERVICE_TOGGLE),
schema=SERVICE_SCHEMA)
for service in (SERVICE_TURN_ON, SERVICE_TURN_OFF):
hass.services.register(DOMAIN, service, turn_onoff_service_handler,
descriptions.get(service),
schema=SERVICE_SCHEMA)
@@ -209,15 +212,17 @@ def setup(hass, config):
class AutomationEntity(ToggleEntity):
"""Entity to show status of entity."""
# pylint: disable=abstract-method
# pylint: disable=too-many-arguments, too-many-instance-attributes
def __init__(self, name, attach_triggers, cond_func, action, hidden):
def __init__(self, name, async_attach_triggers, cond_func, async_action,
hidden):
"""Initialize an automation entity."""
self._name = name
self._attach_triggers = attach_triggers
self._detach_triggers = attach_triggers(self.trigger)
self._async_attach_triggers = async_attach_triggers
self._async_detach_triggers = None
self._cond_func = cond_func
self._action = action
self._enabled = True
self._async_action = async_action
self._enabled = False
self._last_triggered = None
self._hidden = hidden
@@ -248,41 +253,65 @@ class AutomationEntity(ToggleEntity):
"""Return True if entity is on."""
return self._enabled
def turn_on(self, **kwargs) -> None:
"""Turn the entity on."""
@asyncio.coroutine
def async_turn_on(self, **kwargs) -> None:
"""Turn the entity on and update the state."""
if self._enabled:
return
self._detach_triggers = self._attach_triggers(self.trigger)
self._enabled = True
self.update_ha_state()
yield from self.async_enable()
self.hass.loop.create_task(self.async_update_ha_state())
def turn_off(self, **kwargs) -> None:
@asyncio.coroutine
def async_turn_off(self, **kwargs) -> None:
"""Turn the entity off."""
if not self._enabled:
return
self._detach_triggers()
self._detach_triggers = None
self._async_detach_triggers()
self._async_detach_triggers = None
self._enabled = False
self.update_ha_state()
self.hass.loop.create_task(self.async_update_ha_state())
def trigger(self, variables):
"""Trigger automation."""
if self._cond_func(variables):
self._action(variables)
@asyncio.coroutine
def async_trigger(self, variables, skip_condition=False):
"""Trigger automation.
This method is a coroutine.
"""
if skip_condition or self._cond_func(variables):
yield from self._async_action(variables)
self._last_triggered = utcnow()
self.update_ha_state()
self.hass.loop.create_task(self.async_update_ha_state())
def remove(self):
"""Remove automation from HASS."""
self.turn_off()
run_coroutine_threadsafe(self.async_turn_off(),
self.hass.loop).result()
super().remove()
@asyncio.coroutine
def async_enable(self):
"""Enable this automation entity.
def _process_config(hass, config, component):
"""Process config and add automations."""
success = False
This method is a coroutine.
"""
if self._enabled:
return
self._async_detach_triggers = yield from self._async_attach_triggers(
self.async_trigger)
self._enabled = True
@asyncio.coroutine
def _async_process_config(hass, config, component):
"""Process config and add automations.
This method is a coroutine.
"""
entities = []
tasks = []
for config_key in extract_domain_configs(config, DOMAIN):
conf = config[config_key]
@@ -293,10 +322,11 @@ def _process_config(hass, config, component):
hidden = config_block[CONF_HIDE_ENTITY]
action = _get_action(hass, config_block.get(CONF_ACTION, {}), name)
action = _async_get_action(hass, config_block.get(CONF_ACTION, {}),
name)
if CONF_CONDITION in config_block:
cond_func = _process_if(hass, config, config_block)
cond_func = _async_process_if(hass, config, config_block)
if cond_func is None:
continue
@@ -305,101 +335,78 @@ def _process_config(hass, config, component):
"""Condition will always pass."""
return True
attach_triggers = partial(_process_trigger, hass, config,
config_block.get(CONF_TRIGGER, []), name)
entity = AutomationEntity(name, attach_triggers, cond_func, action,
hidden)
component.add_entities((entity,))
success = True
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(hass.loop.create_task(entity.async_enable()))
entities.append(entity)
return success
yield from asyncio.gather(*tasks, loop=hass.loop)
yield from hass.loop.run_in_executor(
None, component.add_entities, entities)
return len(entities) > 0
def _get_action(hass, config, name):
def _async_get_action(hass, config, name):
"""Return an action based on a configuration."""
script_obj = script.Script(hass, config, name)
@asyncio.coroutine
def action(variables=None):
"""Action to be executed."""
_LOGGER.info('Executing %s', name)
logbook.log_entry(hass, name, 'has been triggered', DOMAIN)
script_obj.run(variables)
logbook.async_log_entry(hass, name, 'has been triggered', DOMAIN)
hass.loop.create_task(script_obj.async_run(variables))
return action
def _process_if(hass, config, p_config):
def _async_process_if(hass, config, p_config):
"""Process if checks."""
cond_type = p_config.get(CONF_CONDITION_TYPE,
DEFAULT_CONDITION_TYPE).lower()
# Deprecated since 0.19 - 5/5/2016
if cond_type != DEFAULT_CONDITION_TYPE:
_LOGGER.warning('Using condition_type: "or" is deprecated. Please use '
'"condition: or" instead.')
if_configs = p_config.get(CONF_CONDITION)
use_trigger = if_configs == CONDITION_USE_TRIGGER_VALUES
if use_trigger:
if_configs = p_config[CONF_TRIGGER]
checks = []
for if_config in if_configs:
# Deprecated except for used by use_trigger_values
# since 0.19 - 5/5/2016
if CONF_PLATFORM in if_config:
if not use_trigger:
_LOGGER.warning("Please switch your condition configuration "
"to use 'condition' instead of 'platform'.")
if_config = dict(if_config)
if_config[CONF_CONDITION] = if_config.pop(CONF_PLATFORM)
# To support use_trigger_values with state trigger accepting
# multiple entity_ids to monitor.
if_entity_id = if_config.get(ATTR_ENTITY_ID)
if isinstance(if_entity_id, list) and len(if_entity_id) == 1:
if_config[ATTR_ENTITY_ID] = if_entity_id[0]
try:
checks.append(condition.from_config(if_config))
checks.append(condition.async_from_config(if_config, False))
except HomeAssistantError as ex:
# Invalid conditions are allowed if we base it on trigger
if use_trigger:
_LOGGER.warning('Ignoring invalid condition: %s', ex)
else:
_LOGGER.warning('Invalid condition: %s', ex)
return None
_LOGGER.warning('Invalid condition: %s', ex)
return None
if cond_type == CONDITION_TYPE_AND:
def if_action(variables=None):
"""AND all conditions."""
return all(check(hass, variables) for check in checks)
else:
def if_action(variables=None):
"""OR all conditions."""
return any(check(hass, variables) for check in checks)
def if_action(variables=None):
"""AND all conditions."""
return all(check(hass, variables) for check in checks)
return if_action
def _process_trigger(hass, config, trigger_configs, name, action):
"""Setup the triggers."""
@asyncio.coroutine
def _async_process_trigger(hass, config, trigger_configs, name, action):
"""Setup the triggers.
This method is a coroutine.
"""
removes = []
for conf in trigger_configs:
platform = _resolve_platform(METHOD_TRIGGER, hass, config,
conf.get(CONF_PLATFORM))
if platform is None:
continue
platform = yield from hass.loop.run_in_executor(
None, prepare_setup_platform, hass, config, DOMAIN,
conf.get(CONF_PLATFORM))
remove = platform.trigger(hass, conf, action)
if platform is None:
return None
remove = platform.async_trigger(hass, conf, action)
if not remove:
_LOGGER.error("Error setting up rule %s", name)
_LOGGER.error("Error setting up trigger %s", name)
continue
_LOGGER.info("Initialized rule %s", name)
_LOGGER.info("Initialized trigger %s", name)
removes.append(remove)
if not removes:
@@ -411,17 +418,3 @@ def _process_trigger(hass, config, trigger_configs, name, action):
remove()
return remove_triggers
def _resolve_platform(method, hass, config, platform):
"""Find the automation platform."""
if platform is None:
return None
platform = prepare_setup_platform(hass, config, DOMAIN, platform)
if platform is None or not hasattr(platform, method):
_LOGGER.error("Unknown automation platform specified for %s: %s",
method, platform)
return None
return platform
+5 -5
View File
@@ -4,11 +4,11 @@ Offer event listening automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#event-trigger
"""
import asyncio
import logging
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.const import CONF_PLATFORM
from homeassistant.helpers import config_validation as cv
@@ -24,21 +24,21 @@ TRIGGER_SCHEMA = vol.Schema({
})
def trigger(hass, config, action):
def async_trigger(hass, config, action):
"""Listen for events based on configuration."""
event_type = config.get(CONF_EVENT_TYPE)
event_data = config.get(CONF_EVENT_DATA)
@asyncio.coroutine
@callback
def handle_event(event):
"""Listen for events and calls the action when data matches."""
if not event_data or all(val == event.data.get(key) for key, val
in event_data.items()):
hass.async_add_job(action, {
hass.async_run_job(action, {
'trigger': {
'platform': 'event',
'event': event,
},
})
return hass.bus.listen(event_type, handle_event)
return hass.bus.async_listen(event_type, handle_event)
+5 -3
View File
@@ -6,6 +6,7 @@ at https://home-assistant.io/components/automation/#mqtt-trigger
"""
import voluptuous as vol
from homeassistant.core import callback
import homeassistant.components.mqtt as mqtt
from homeassistant.const import (CONF_PLATFORM, CONF_PAYLOAD)
import homeassistant.helpers.config_validation as cv
@@ -21,15 +22,16 @@ TRIGGER_SCHEMA = vol.Schema({
})
def trigger(hass, config, action):
def async_trigger(hass, config, action):
"""Listen for state changes based on configuration."""
topic = config.get(CONF_TOPIC)
payload = config.get(CONF_PAYLOAD)
@callback
def mqtt_automation_listener(msg_topic, msg_payload, qos):
"""Listen for MQTT messages."""
if payload is None or payload == msg_payload:
action({
hass.async_run_job(action, {
'trigger': {
'platform': 'mqtt',
'topic': msg_topic,
@@ -38,4 +40,4 @@ def trigger(hass, config, action):
}
})
return mqtt.subscribe(hass, topic, mqtt_automation_listener)
return mqtt.async_subscribe(hass, topic, mqtt_automation_listener)
@@ -8,10 +8,11 @@ import logging
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.const import (
CONF_VALUE_TEMPLATE, CONF_PLATFORM, CONF_ENTITY_ID,
CONF_BELOW, CONF_ABOVE)
from homeassistant.helpers.event import track_state_change
from homeassistant.helpers.event import async_track_state_change
from homeassistant.helpers import condition, config_validation as cv
TRIGGER_SCHEMA = vol.All(vol.Schema({
@@ -25,7 +26,7 @@ TRIGGER_SCHEMA = vol.All(vol.Schema({
_LOGGER = logging.getLogger(__name__)
def trigger(hass, config, action):
def async_trigger(hass, config, action):
"""Listen for state changes based on configuration."""
entity_id = config.get(CONF_ENTITY_ID)
below = config.get(CONF_BELOW)
@@ -34,7 +35,7 @@ def trigger(hass, config, action):
if value_template is not None:
value_template.hass = hass
# pylint: disable=unused-argument
@callback
def state_automation_listener(entity, from_s, to_s):
"""Listen for state changes and calls action."""
if to_s is None:
@@ -50,19 +51,19 @@ def trigger(hass, config, action):
}
# If new one doesn't match, nothing to do
if not condition.numeric_state(
if not condition.async_numeric_state(
hass, to_s, below, above, value_template, variables):
return
# Only match if old didn't exist or existed but didn't match
# Written as: skip if old one did exist and matched
if from_s is not None and condition.numeric_state(
if from_s is not None and condition.async_numeric_state(
hass, from_s, below, above, value_template, variables):
return
variables['trigger']['from_state'] = from_s
variables['trigger']['to_state'] = to_s
action(variables)
hass.async_run_job(action, variables)
return track_state_change(hass, entity_id, state_automation_listener)
return async_track_state_change(hass, entity_id, state_automation_listener)
+25 -20
View File
@@ -6,9 +6,11 @@ at https://home-assistant.io/components/automation/#state-trigger
"""
import voluptuous as vol
from homeassistant.core import callback
import homeassistant.util.dt as dt_util
from homeassistant.const import MATCH_ALL, CONF_PLATFORM
from homeassistant.helpers.event import track_state_change, track_point_in_time
from homeassistant.helpers.event import (
async_track_state_change, async_track_point_in_utc_time)
import homeassistant.helpers.config_validation as cv
CONF_ENTITY_ID = "entity_id"
@@ -32,22 +34,23 @@ TRIGGER_SCHEMA = vol.All(
)
def trigger(hass, config, action):
def async_trigger(hass, config, action):
"""Listen for state changes based on configuration."""
entity_id = config.get(CONF_ENTITY_ID)
from_state = config.get(CONF_FROM, MATCH_ALL)
to_state = config.get(CONF_TO) or config.get(CONF_STATE) or MATCH_ALL
time_delta = config.get(CONF_FOR)
remove_state_for_cancel = None
remove_state_for_listener = None
async_remove_state_for_cancel = None
async_remove_state_for_listener = None
@callback
def state_automation_listener(entity, from_s, to_s):
"""Listen for state changes and calls action."""
nonlocal remove_state_for_cancel, remove_state_for_listener
nonlocal async_remove_state_for_cancel, async_remove_state_for_listener
def call_action():
"""Call action with right context."""
action({
hass.async_run_job(action, {
'trigger': {
'platform': 'state',
'entity_id': entity,
@@ -61,35 +64,37 @@ def trigger(hass, config, action):
call_action()
return
@callback
def state_for_listener(now):
"""Fire on state changes after a delay and calls action."""
remove_state_for_cancel()
async_remove_state_for_cancel()
call_action()
@callback
def state_for_cancel_listener(entity, inner_from_s, inner_to_s):
"""Fire on changes and cancel for listener if changed."""
if inner_to_s.state == to_s.state:
return
remove_state_for_listener()
remove_state_for_cancel()
async_remove_state_for_listener()
async_remove_state_for_cancel()
remove_state_for_listener = track_point_in_time(
async_remove_state_for_listener = async_track_point_in_utc_time(
hass, state_for_listener, dt_util.utcnow() + time_delta)
remove_state_for_cancel = track_state_change(
async_remove_state_for_cancel = async_track_state_change(
hass, entity, state_for_cancel_listener)
unsub = track_state_change(hass, entity_id, state_automation_listener,
from_state, to_state)
unsub = async_track_state_change(
hass, entity_id, state_automation_listener, from_state, to_state)
def remove():
"""Remove state listeners."""
def async_remove():
"""Remove state listeners async."""
unsub()
# pylint: disable=not-callable
if remove_state_for_cancel is not None:
remove_state_for_cancel()
if async_remove_state_for_cancel is not None:
async_remove_state_for_cancel()
if remove_state_for_listener is not None:
remove_state_for_listener()
if async_remove_state_for_listener is not None:
async_remove_state_for_listener()
return remove
return async_remove
+7 -5
View File
@@ -9,9 +9,10 @@ import logging
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.const import (
CONF_EVENT, CONF_OFFSET, CONF_PLATFORM, SUN_EVENT_SUNRISE)
from homeassistant.helpers.event import track_sunrise, track_sunset
from homeassistant.helpers.event import async_track_sunrise, async_track_sunset
import homeassistant.helpers.config_validation as cv
DEPENDENCIES = ['sun']
@@ -25,14 +26,15 @@ TRIGGER_SCHEMA = vol.Schema({
})
def trigger(hass, config, action):
def async_trigger(hass, config, action):
"""Listen for events based on configuration."""
event = config.get(CONF_EVENT)
offset = config.get(CONF_OFFSET)
@callback
def call_action():
"""Call action with right context."""
action({
hass.async_run_job(action, {
'trigger': {
'platform': 'sun',
'event': event,
@@ -42,6 +44,6 @@ def trigger(hass, config, action):
# Do something to call action
if event == SUN_EVENT_SUNRISE:
return track_sunrise(hass, call_action, offset)
return async_track_sunrise(hass, call_action, offset)
else:
return track_sunset(hass, call_action, offset)
return async_track_sunset(hass, call_action, offset)
@@ -4,14 +4,14 @@ Offer template automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#template-trigger
"""
import asyncio
import logging
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.const import CONF_VALUE_TEMPLATE, CONF_PLATFORM
from homeassistant.helpers import condition
from homeassistant.helpers.event import track_state_change
from homeassistant.helpers.event import async_track_state_change
import homeassistant.helpers.config_validation as cv
@@ -23,7 +23,7 @@ TRIGGER_SCHEMA = IF_ACTION_SCHEMA = vol.Schema({
})
def trigger(hass, config, action):
def async_trigger(hass, config, action):
"""Listen for state changes based on configuration."""
value_template = config.get(CONF_VALUE_TEMPLATE)
value_template.hass = hass
@@ -31,7 +31,7 @@ def trigger(hass, config, action):
# Local variable to keep track of if the action has already been triggered
already_triggered = False
@asyncio.coroutine
@callback
def state_changed_listener(entity_id, from_s, to_s):
"""Listen for state changes and calls action."""
nonlocal already_triggered
@@ -40,7 +40,7 @@ def trigger(hass, config, action):
# Check to see if template returns true
if template_result and not already_triggered:
already_triggered = True
hass.async_add_job(action, {
hass.async_run_job(action, {
'trigger': {
'platform': 'template',
'entity_id': entity_id,
@@ -51,5 +51,5 @@ def trigger(hass, config, action):
elif not template_result:
already_triggered = False
return track_state_change(hass, value_template.extract_entities(),
state_changed_listener)
return async_track_state_change(hass, value_template.extract_entities(),
state_changed_listener)
+7 -5
View File
@@ -8,9 +8,10 @@ import logging
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.const import CONF_AFTER, CONF_PLATFORM
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.event import track_time_change
from homeassistant.helpers.event import async_track_time_change
CONF_HOURS = "hours"
CONF_MINUTES = "minutes"
@@ -28,7 +29,7 @@ TRIGGER_SCHEMA = vol.All(vol.Schema({
CONF_SECONDS, CONF_AFTER))
def trigger(hass, config, action):
def async_trigger(hass, config, action):
"""Listen for state changes based on configuration."""
if CONF_AFTER in config:
after = config.get(CONF_AFTER)
@@ -38,14 +39,15 @@ def trigger(hass, config, action):
minutes = config.get(CONF_MINUTES)
seconds = config.get(CONF_SECONDS)
@callback
def time_automation_listener(now):
"""Listen for time changes and calls action."""
action({
hass.async_run_job(action, {
'trigger': {
'platform': 'time',
'now': now,
},
})
return track_time_change(hass, time_automation_listener,
hour=hours, minute=minutes, second=seconds)
return async_track_time_change(hass, time_automation_listener,
hour=hours, minute=minutes, second=seconds)
+7 -5
View File
@@ -6,9 +6,10 @@ at https://home-assistant.io/components/automation/#zone-trigger
"""
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.const import (
CONF_EVENT, CONF_ENTITY_ID, CONF_ZONE, MATCH_ALL, CONF_PLATFORM)
from homeassistant.helpers.event import track_state_change
from homeassistant.helpers.event import async_track_state_change
from homeassistant.helpers import (
condition, config_validation as cv, location)
@@ -25,12 +26,13 @@ TRIGGER_SCHEMA = vol.Schema({
})
def trigger(hass, config, action):
def async_trigger(hass, config, action):
"""Listen for state changes based on configuration."""
entity_id = config.get(CONF_ENTITY_ID)
zone_entity_id = config.get(CONF_ZONE)
event = config.get(CONF_EVENT)
@callback
def zone_automation_listener(entity, from_s, to_s):
"""Listen for state changes and calls action."""
if from_s and not location.has_location(from_s) or \
@@ -47,7 +49,7 @@ def trigger(hass, config, action):
# pylint: disable=too-many-boolean-expressions
if event == EVENT_ENTER and not from_match and to_match or \
event == EVENT_LEAVE and from_match and not to_match:
action({
hass.async_run_job(action, {
'trigger': {
'platform': 'zone',
'entity_id': entity,
@@ -58,5 +60,5 @@ def trigger(hass, config, action):
},
})
return track_state_change(hass, entity_id, zone_automation_listener,
MATCH_ALL, MATCH_ALL)
return async_track_state_change(hass, entity_id, zone_automation_listener,
MATCH_ALL, MATCH_ALL)
+16 -18
View File
@@ -1,5 +1,5 @@
"""
Support for exposed aREST RESTful API of a device.
Support for an exposed aREST RESTful API of a device.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.arest/
@@ -8,31 +8,32 @@ import logging
from datetime import timedelta
import requests
import voluptuous as vol
from homeassistant.components.binary_sensor import (
BinarySensorDevice, SENSOR_CLASSES)
from homeassistant.const import CONF_RESOURCE, CONF_PIN
BinarySensorDevice, PLATFORM_SCHEMA, SENSOR_CLASSES_SCHEMA)
from homeassistant.const import (
CONF_RESOURCE, CONF_PIN, CONF_NAME, CONF_SENSOR_CLASS)
from homeassistant.util import Throttle
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_RESOURCE): cv.url,
vol.Optional(CONF_NAME): cv.string,
vol.Required(CONF_PIN): cv.string,
vol.Optional(CONF_SENSOR_CLASS): SENSOR_CLASSES_SCHEMA,
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the aREST binary sensor."""
resource = config.get(CONF_RESOURCE)
pin = config.get(CONF_PIN)
sensor_class = config.get('sensor_class')
if sensor_class not in SENSOR_CLASSES:
_LOGGER.warning('Unknown sensor class: %s', sensor_class)
sensor_class = None
if None in (resource, pin):
_LOGGER.error('Not all required config keys present: %s',
', '.join((CONF_RESOURCE, CONF_PIN)))
return False
sensor_class = config.get(CONF_SENSOR_CLASS)
try:
response = requests.get(resource, timeout=10).json()
@@ -49,11 +50,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
arest = ArestData(resource, pin)
add_devices([ArestBinarySensor(
arest,
resource,
config.get('name', response['name']),
sensor_class,
pin)])
arest, resource, config.get(CONF_NAME, response[CONF_NAME]),
sensor_class, pin)])
# pylint: disable=too-many-instance-attributes, too-many-arguments
@@ -0,0 +1,91 @@
"""
Support for monitoring the state of Digital Ocean droplets.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.digital_ocean/
"""
import logging
import voluptuous as vol
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA)
from homeassistant.components.digital_ocean import (
CONF_DROPLETS, ATTR_CREATED_AT, ATTR_DROPLET_ID, ATTR_DROPLET_NAME,
ATTR_FEATURES, ATTR_IPV4_ADDRESS, ATTR_IPV6_ADDRESS, ATTR_MEMORY,
ATTR_REGION, ATTR_VCPUS)
from homeassistant.loader import get_component
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'Droplet'
DEFAULT_SENSOR_CLASS = 'motion'
DEPENDENCIES = ['digital_ocean']
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_DROPLETS): vol.All(cv.ensure_list, [cv.string]),
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Digital Ocean droplet sensor."""
digital_ocean = get_component('digital_ocean')
droplets = config.get(CONF_DROPLETS)
dev = []
for droplet in droplets:
droplet_id = digital_ocean.DIGITAL_OCEAN.get_droplet_id(droplet)
dev.append(DigitalOceanBinarySensor(
digital_ocean.DIGITAL_OCEAN, droplet_id))
add_devices(dev)
class DigitalOceanBinarySensor(BinarySensorDevice):
"""Representation of a Digital Ocean droplet sensor."""
def __init__(self, do, droplet_id):
"""Initialize a new Digital Ocean sensor."""
self._digital_ocean = do
self._droplet_id = droplet_id
self._state = None
self.update()
@property
def name(self):
"""Return the name of the sensor."""
return self.data.name
@property
def is_on(self):
"""Return true if the binary sensor is on."""
return self.data.status == 'active'
@property
def sensor_class(self):
"""Return the class of this sensor."""
return DEFAULT_SENSOR_CLASS
@property
def state_attributes(self):
"""Return the state attributes of the Digital Ocean droplet."""
return {
ATTR_CREATED_AT: self.data.created_at,
ATTR_DROPLET_ID: self.data.id,
ATTR_DROPLET_NAME: self.data.name,
ATTR_FEATURES: self.data.features,
ATTR_IPV4_ADDRESS: self.data.ip_address,
ATTR_IPV6_ADDRESS: self.data.ip_v6_address,
ATTR_MEMORY: self.data.memory,
ATTR_REGION: self.data.region['name'],
ATTR_VCPUS: self.data.vcpus,
}
def update(self):
"""Update state of sensor."""
self._digital_ocean.update()
for droplet in self._digital_ocean.data:
if droplet.id == self._droplet_id:
self.data = droplet
@@ -16,6 +16,7 @@ DEPENDENCIES = ['homematic']
SENSOR_TYPES_CLASS = {
"Remote": None,
"ShutterContact": "opening",
"IPShutterContact": "opening",
"Smoke": "smoke",
"SmokeV2": "smoke",
"Motion": "motion",
+37 -23
View File
@@ -1,41 +1,56 @@
"""
Support for exposing nx584 elements as sensors.
Support for exposing NX584 elements as sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.nx584/
https://home-assistant.io/components/binary_sensor.nx584/
"""
import logging
import threading
import time
import requests
import voluptuous as vol
from homeassistant.components.binary_sensor import (
SENSOR_CLASSES, BinarySensorDevice)
SENSOR_CLASSES, BinarySensorDevice, PLATFORM_SCHEMA)
from homeassistant.const import (CONF_HOST, CONF_PORT)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pynx584==0.2']
_LOGGER = logging.getLogger(__name__)
CONF_EXCLUDE_ZONES = 'exclude_zones'
CONF_ZONE_TYPES = 'zone_types'
DEFAULT_HOST = 'localhost'
DEFAULT_PORT = '5007'
DEFAULT_SSL = False
ZONE_TYPES_SCHEMA = vol.Schema({
cv.positive_int: vol.In(SENSOR_CLASSES),
})
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_EXCLUDE_ZONES, default=[]):
vol.All(cv.ensure_list, [cv.positive_int]),
vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_ZONE_TYPES, default={}): ZONE_TYPES_SCHEMA,
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup nx584 binary sensor platform."""
"""Setup the NX584 binary sensor platform."""
from nx584 import client as nx584_client
host = config.get('host', 'localhost:5007')
exclude = config.get('exclude_zones', [])
zone_types = config.get('zone_types', {})
if not all(isinstance(zone, int) for zone in exclude):
_LOGGER.error('Invalid excluded zone specified (use zone number)')
return False
if not all(isinstance(zone, int) and ztype in SENSOR_CLASSES
for zone, ztype in zone_types.items()):
_LOGGER.error('Invalid zone_types entry')
return False
host = config.get(CONF_HOST)
port = config.get(CONF_PORT)
exclude = config.get(CONF_EXCLUDE_ZONES)
zone_types = config.get(CONF_ZONE_TYPES)
try:
client = nx584_client.Client('http://%s' % host)
client = nx584_client.Client('http://{}:{}'.format(host, port))
zones = client.list_zones()
except requests.exceptions.ConnectionError as ex:
_LOGGER.error('Unable to connect to NX584: %s', str(ex))
@@ -43,7 +58,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
version = [int(v) for v in client.get_version().split('.')]
if version < [1, 1]:
_LOGGER.error('NX584 is too old to use for sensors (>=0.2 required)')
_LOGGER.error("NX584 is too old to use for sensors (>=0.2 required)")
return False
zone_sensors = {
@@ -57,13 +72,12 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
watcher = NX584Watcher(client, zone_sensors)
watcher.start()
else:
_LOGGER.warning('No zones found on NX584')
_LOGGER.warning("No zones found on NX584")
return True
class NX584ZoneSensor(BinarySensorDevice):
"""Represents a NX584 zone as a sensor."""
"""Representation of a NX584 zone as a sensor."""
def __init__(self, zone, zone_type):
"""Initialize the nx594 binary sensor."""
@@ -96,7 +110,7 @@ class NX584Watcher(threading.Thread):
"""Event listener thread to process NX584 events."""
def __init__(self, client, zone_sensors):
"""Initialize nx584 watcher thread."""
"""Initialize NX584 watcher thread."""
super(NX584Watcher, self).__init__()
self.daemon = True
self._client = client
@@ -130,5 +144,5 @@ class NX584Watcher(threading.Thread):
try:
self._run()
except requests.exceptions.ConnectionError:
_LOGGER.error('Failed to reach NX584 server')
_LOGGER.error("Failed to reach NX584 server")
time.sleep(10)
+24 -3
View File
@@ -5,15 +5,19 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.rest/
"""
import logging
import json
import voluptuous as vol
from requests.auth import HTTPBasicAuth, HTTPDigestAuth
from homeassistant.components.binary_sensor import (
BinarySensorDevice, SENSOR_CLASSES_SCHEMA, PLATFORM_SCHEMA)
from homeassistant.components.sensor.rest import RestData
from homeassistant.const import (
CONF_PAYLOAD, CONF_NAME, CONF_VALUE_TEMPLATE, CONF_METHOD, CONF_RESOURCE,
CONF_SENSOR_CLASS, CONF_VERIFY_SSL)
CONF_SENSOR_CLASS, CONF_VERIFY_SSL, CONF_USERNAME, CONF_PASSWORD,
CONF_HEADERS, CONF_AUTHENTICATION, HTTP_BASIC_AUTHENTICATION,
HTTP_DIGEST_AUTHENTICATION)
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
@@ -24,16 +28,21 @@ DEFAULT_VERIFY_SSL = True
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_RESOURCE): cv.url,
vol.Optional(CONF_AUTHENTICATION):
vol.In([HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION]),
vol.Optional(CONF_HEADERS): cv.string,
vol.Optional(CONF_METHOD, default=DEFAULT_METHOD): vol.In(['POST', 'GET']),
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PASSWORD): cv.string,
vol.Optional(CONF_PAYLOAD): cv.string,
vol.Optional(CONF_SENSOR_CLASS): SENSOR_CLASSES_SCHEMA,
vol.Optional(CONF_USERNAME): cv.string,
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean,
})
# pylint: disable=unused-variable
# pylint: disable=unused-variable, too-many-locals
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the REST binary sensor."""
name = config.get(CONF_NAME)
@@ -41,11 +50,23 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
method = config.get(CONF_METHOD)
payload = config.get(CONF_PAYLOAD)
verify_ssl = config.get(CONF_VERIFY_SSL)
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
headers = json.loads(config.get(CONF_HEADERS, '{}'))
sensor_class = config.get(CONF_SENSOR_CLASS)
value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template.hass = hass
rest = RestData(method, resource, payload, verify_ssl)
if username and password:
if config.get(CONF_AUTHENTICATION) == HTTP_DIGEST_AUTHENTICATION:
auth = HTTPDigestAuth(username, password)
else:
auth = HTTPBasicAuth(username, password)
else:
auth = None
rest = RestData(method, resource, auth, headers, payload, verify_ssl)
rest.update()
if rest.data is None:
@@ -4,10 +4,12 @@ Support for exposing a templated binary sensor.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.template/
"""
import asyncio
import logging
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.components.binary_sensor import (
BinarySensorDevice, ENTITY_ID_FORMAT, PLATFORM_SCHEMA,
SENSOR_CLASSES_SCHEMA)
@@ -81,9 +83,10 @@ class BinarySensorTemplate(BinarySensorDevice):
self.update()
@callback
def template_bsensor_state_listener(entity, old_state, new_state):
"""Called when the target device changes state."""
self.update_ha_state(True)
hass.loop.create_task(self.async_update_ha_state(True))
track_state_change(hass, entity_ids, template_bsensor_state_listener)
@@ -107,10 +110,11 @@ class BinarySensorTemplate(BinarySensorDevice):
"""No polling needed."""
return False
def update(self):
@asyncio.coroutine
def async_update(self):
"""Get the latest data and update the state."""
try:
self._state = self._template.render().lower() == 'true'
self._state = self._template.async_render().lower() == 'true'
except TemplateError as ex:
if ex.args and ex.args[0].startswith(
"UndefinedError: 'None' has no attribute"):
+21 -7
View File
@@ -20,7 +20,10 @@ SENSOR_TYPES = {
"vibration": "vibration",
"loudness": "sound",
"liquid_detected": "moisture",
"motion": "motion"
"motion": "motion",
"presence": "occupancy",
"co_detected": "gas",
"smoke_detected": "smoke"
}
@@ -35,6 +38,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
for key in pywink.get_keys():
add_devices([WinkBinarySensorDevice(key)])
for sensor in pywink.get_smoke_and_co_detectors():
add_devices([WinkBinarySensorDevice(sensor)])
class WinkBinarySensorDevice(WinkDevice, BinarySensorDevice, Entity):
"""Representation of a Wink binary sensor."""
@@ -58,17 +64,25 @@ class WinkBinarySensorDevice(WinkDevice, BinarySensorDevice, Entity):
def is_on(self):
"""Return true if the binary sensor is on."""
if self.capability == "loudness":
return self.wink.loudness_boolean()
state = self.wink.loudness_boolean()
elif self.capability == "vibration":
return self.wink.vibration_boolean()
state = self.wink.vibration_boolean()
elif self.capability == "brightness":
return self.wink.brightness_boolean()
state = self.wink.brightness_boolean()
elif self.capability == "liquid_detected":
return self.wink.liquid_boolean()
state = self.wink.liquid_boolean()
elif self.capability == "motion":
return self.wink.motion_boolean()
state = self.wink.motion_boolean()
elif self.capability == "presence":
state = self.wink.presence_boolean()
elif self.capability == "co_detected":
state = self.wink.co_detected_boolean()
elif self.capability == "smoke_detected":
state = self.wink.smoke_detected_boolean()
else:
return self.wink.state()
state = self.wink.state()
return state
@property
def sensor_class(self):
@@ -36,8 +36,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
if discovery_info is None or zwave.NETWORK is None:
return
node = zwave.NETWORK.nodes[discovery_info[zwave.ATTR_NODE_ID]]
value = node.values[discovery_info[zwave.ATTR_VALUE_ID]]
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)
# Make sure that we have values for the key before converting to int
@@ -58,7 +58,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
])
return
if value.command_class == zwave.COMMAND_CLASS_SENSOR_BINARY:
if value.command_class == zwave.const.COMMAND_CLASS_SENSOR_BINARY:
add_devices([ZWaveBinarySensor(value, None)])
+7 -3
View File
@@ -36,10 +36,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup access to Netatmo Welcome cameras."""
netatmo = get_component('netatmo')
home = config.get(CONF_HOME)
data = WelcomeData(netatmo.NETATMO_AUTH, home)
import lnetatmo
try:
data = WelcomeData(netatmo.NETATMO_AUTH, home)
except lnetatmo.NoDevice:
return None
for camera_name in data.get_camera_names():
if CONF_CAMERAS in config:
if config[CONF_CAMERAS] != []:
if camera_name not in config[CONF_CAMERAS]:
continue
add_devices([WelcomeCamera(data, camera_name, home)])
@@ -49,7 +53,7 @@ class WelcomeCamera(Camera):
"""Representation of the images published from Welcome camera."""
def __init__(self, data, camera_name, home):
"""Setup for access to the BloomSky camera images."""
"""Setup for access to the Netatmo camera images."""
super(WelcomeCamera, self).__init__()
self._data = data
self._camera_name = camera_name
+24 -4
View File
@@ -58,6 +58,12 @@ ATTR_OPERATION_LIST = "operation_list"
ATTR_SWING_MODE = "swing_mode"
ATTR_SWING_LIST = "swing_list"
CONVERTIBLE_ATTRIBUTE = [
ATTR_TEMPERATURE,
ATTR_TARGET_TEMP_LOW,
ATTR_TARGET_TEMP_HIGH,
]
_LOGGER = logging.getLogger(__name__)
SET_AWAY_MODE_SCHEMA = vol.Schema({
@@ -73,6 +79,7 @@ SET_TEMPERATURE_SCHEMA = vol.Schema({
vol.Inclusive(ATTR_TARGET_TEMP_HIGH, 'temperature'): vol.Coerce(float),
vol.Inclusive(ATTR_TARGET_TEMP_LOW, 'temperature'): vol.Coerce(float),
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional(ATTR_OPERATION_MODE): cv.string,
})
SET_FAN_MODE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
@@ -116,8 +123,10 @@ def set_aux_heat(hass, aux_heat, entity_id=None):
hass.services.call(DOMAIN, SERVICE_SET_AUX_HEAT, data)
# pylint: disable=too-many-arguments
def set_temperature(hass, temperature=None, entity_id=None,
target_temp_high=None, target_temp_low=None):
target_temp_high=None, target_temp_low=None,
operation_mode=None):
"""Set new target temperature."""
kwargs = {
key: value for key, value in [
@@ -125,6 +134,7 @@ def set_temperature(hass, temperature=None, entity_id=None,
(ATTR_TARGET_TEMP_HIGH, target_temp_high),
(ATTR_TARGET_TEMP_LOW, target_temp_low),
(ATTR_ENTITY_ID, entity_id),
(ATTR_OPERATION_MODE, operation_mode)
] if value is not None
}
_LOGGER.debug("set_temperature start data=%s", kwargs)
@@ -235,10 +245,20 @@ def setup(hass, config):
def temperature_set_service(service):
"""Set temperature on the target climate devices."""
target_climate = component.extract_from_service(service)
kwargs = service.data
for climate in target_climate:
climate.set_temperature(**kwargs)
for climate in target_climate:
kwargs = {}
for value, temp in service.data.items():
if value in CONVERTIBLE_ATTRIBUTE:
kwargs[value] = convert_temperature(
temp,
hass.config.units.temperature_unit,
climate.unit_of_measurement
)
else:
kwargs[value] = temp
climate.set_temperature(**kwargs)
if climate.should_poll:
climate.update_ha_state(True)
+48 -24
View File
@@ -8,11 +8,11 @@ import logging
import voluptuous as vol
import homeassistant.components.nest as nest
from homeassistant.components.climate import (
STATE_AUTO, STATE_COOL, STATE_HEAT, STATE_IDLE, ClimateDevice,
PLATFORM_SCHEMA, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW)
STATE_AUTO, STATE_COOL, STATE_HEAT, ClimateDevice,
PLATFORM_SCHEMA, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
ATTR_TEMPERATURE)
from homeassistant.const import (
TEMP_CELSIUS, CONF_SCAN_INTERVAL, STATE_ON, TEMP_FAHRENHEIT)
from homeassistant.util.temperature import convert as convert_temperature
TEMP_CELSIUS, CONF_SCAN_INTERVAL, STATE_ON, STATE_OFF, STATE_UNKNOWN)
DEPENDENCIES = ['nest']
_LOGGER = logging.getLogger(__name__)
@@ -30,7 +30,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
for structure, device in nest.devices()])
# pylint: disable=abstract-method
# pylint: disable=abstract-method,too-many-public-methods
class NestThermostat(ClimateDevice):
"""Representation of a Nest thermostat."""
@@ -40,6 +40,8 @@ class NestThermostat(ClimateDevice):
self.structure = structure
self.device = device
self._fan_list = [STATE_ON, STATE_AUTO]
self._operation_list = [STATE_HEAT, STATE_COOL, STATE_AUTO,
STATE_OFF]
@property
def name(self):
@@ -57,10 +59,7 @@ class NestThermostat(ClimateDevice):
@property
def unit_of_measurement(self):
"""Return the unit of measurement."""
if self.device.measurment_scale == 'F':
return TEMP_FAHRENHEIT
elif self.device.measurement_scale == 'C':
return TEMP_CELSIUS
return TEMP_CELSIUS
@property
def device_state_attributes(self):
@@ -69,7 +68,6 @@ class NestThermostat(ClimateDevice):
return {
"humidity": self.device.humidity,
"target_humidity": self.device.target_humidity,
"mode": self.device.mode
}
@property
@@ -78,14 +76,26 @@ class NestThermostat(ClimateDevice):
return self.device.temperature
@property
def operation(self):
def current_operation(self):
"""Return current operation ie. heat, cool, idle."""
if self.device.hvac_ac_state is True:
if self.device.mode == 'cool':
return STATE_COOL
elif self.device.hvac_heater_state is True:
elif self.device.mode == 'heat':
return STATE_HEAT
elif self.device.mode == 'range':
return STATE_AUTO
elif self.device.mode == 'off':
return STATE_OFF
else:
return STATE_IDLE
return STATE_UNKNOWN
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
if self.device.mode != 'range' and not self.is_away_mode_on:
return self.device.target
else:
return None
@property
def target_temperature_low(self):
@@ -95,7 +105,8 @@ class NestThermostat(ClimateDevice):
return self.device.away_temperature[0]
if self.device.mode == 'range':
return self.device.target[0]
return self.target_temperature
else:
return None
@property
def target_temperature_high(self):
@@ -105,7 +116,8 @@ class NestThermostat(ClimateDevice):
return self.device.away_temperature[1]
if self.device.mode == 'range':
return self.device.target[1]
return self.target_temperature
else:
return None
@property
def is_away_mode_on(self):
@@ -114,20 +126,32 @@ class NestThermostat(ClimateDevice):
def set_temperature(self, **kwargs):
"""Set new target temperature."""
if kwargs.get(ATTR_TARGET_TEMP_LOW) is not None and \
kwargs.get(ATTR_TARGET_TEMP_HIGH) is not None:
target_temp_high = convert_temperature(kwargs.get(
ATTR_TARGET_TEMP_HIGH), self._unit, TEMP_CELSIUS)
target_temp_low = convert_temperature(kwargs.get(
ATTR_TARGET_TEMP_LOW), self._unit, TEMP_CELSIUS)
target_temp_low = kwargs.get(ATTR_TARGET_TEMP_LOW)
target_temp_high = kwargs.get(ATTR_TARGET_TEMP_HIGH)
if target_temp_low is not None and target_temp_high is not None:
temp = (target_temp_low, target_temp_high)
if self.device.mode == 'range':
temp = (target_temp_low, target_temp_high)
else:
temp = kwargs.get(ATTR_TEMPERATURE)
_LOGGER.debug("Nest set_temperature-output-value=%s", temp)
self.device.target = temp
def set_operation_mode(self, operation_mode):
"""Set operation mode."""
self.device.mode = operation_mode
if operation_mode == STATE_HEAT:
self.device.mode = 'heat'
elif operation_mode == STATE_COOL:
self.device.mode = 'cool'
elif operation_mode == STATE_AUTO:
self.device.mode = 'range'
elif operation_mode == STATE_OFF:
self.device.mode = 'off'
@property
def operation_list(self):
"""List of available operation modes."""
return self._operation_list
def turn_away_mode_on(self):
"""Turn away on."""
@@ -34,6 +34,18 @@ set_temperature:
description: New target temperature for hvac
example: 25
target_temp_high:
description: New target high tempereature for hvac
example: 26
target_temp_low:
description: New target low temperature for hvac
example: 20
operation_mode:
description: Operation mode to set temperature to. This defaults to current_operation mode if not set, or set incorrectly.
example: 'Heat'
set_humidity:
description: Set target humidity of climate device
+55 -33
View File
@@ -8,9 +8,9 @@ https://home-assistant.io/components/climate.zwave/
# pylint: disable=import-error
import logging
from homeassistant.components.climate import DOMAIN
from homeassistant.components.climate import ClimateDevice
from homeassistant.components.zwave import (
ATTR_NODE_ID, ATTR_VALUE_ID, ZWaveDeviceEntity)
from homeassistant.components.climate import (
ClimateDevice, ATTR_OPERATION_MODE)
from homeassistant.components.zwave import ZWaveDeviceEntity
from homeassistant.components import zwave
from homeassistant.const import (
TEMP_CELSIUS, TEMP_FAHRENHEIT, ATTR_TEMPERATURE)
@@ -28,12 +28,6 @@ HORSTMANN = 0x0059
HORSTMANN_HRT4_ZW = 0x3
HORSTMANN_HRT4_ZW_THERMOSTAT = (HORSTMANN, HORSTMANN_HRT4_ZW)
COMMAND_CLASS_SENSOR_MULTILEVEL = 0x31
COMMAND_CLASS_THERMOSTAT_MODE = 0x40
COMMAND_CLASS_THERMOSTAT_SETPOINT = 0x43
COMMAND_CLASS_THERMOSTAT_FAN_MODE = 0x44
COMMAND_CLASS_CONFIGURATION = 0x70
WORKAROUND_ZXT_120 = 'zxt_120'
WORKAROUND_HRT4_ZW = 'hrt4_zw'
@@ -67,19 +61,19 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
discovery_info, zwave.NETWORK)
return
temp_unit = hass.config.units.temperature_unit
node = zwave.NETWORK.nodes[discovery_info[ATTR_NODE_ID]]
value = node.values[discovery_info[ATTR_VALUE_ID]]
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)
# pylint: disable=too-many-arguments, abstract-method
# pylint: disable=abstract-method
class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
"""Represents a ZWave Climate device."""
# pylint: disable=too-many-public-methods, too-many-instance-attributes
# pylint: disable=too-many-instance-attributes
def __init__(self, value, temp_unit):
"""Initialize the zwave climate device."""
from openzwave.network import ZWaveNetwork
@@ -130,7 +124,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
"""Callback on data change for the registered node/value pair."""
# Operation Mode
for value in self._node.get_values(
class_id=COMMAND_CLASS_THERMOSTAT_MODE).values():
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_MODE).values():
self._current_operation = value.data
self._index_operation = SET_TEMP_TO_INDEX.get(
self._current_operation)
@@ -139,14 +133,16 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
_LOGGER.debug("self._current_operation=%s",
self._current_operation)
# Current Temp
for value in self._node.get_values(
class_id=COMMAND_CLASS_SENSOR_MULTILEVEL).values():
for value in (self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_SENSOR_MULTILEVEL)
.values()):
if value.label == 'Temperature':
self._current_temperature = int(value.data)
self._unit = value.units
# Fan Mode
for value in self._node.get_values(
class_id=COMMAND_CLASS_THERMOSTAT_FAN_MODE).values():
for value in (self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_FAN_MODE)
.values()):
self._current_fan_mode = value.data
self._fan_list = list(value.data_items)
_LOGGER.debug("self._fan_list=%s", self._fan_list)
@@ -154,17 +150,27 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
self._current_fan_mode)
# Swing mode
if self._zxt_120 == 1:
for value in self._node.get_values(
class_id=COMMAND_CLASS_CONFIGURATION).values():
if value.command_class == 112 and value.index == 33:
for value in (self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_CONFIGURATION)
.values()):
if value.command_class == \
zwave.const.COMMAND_CLASS_CONFIGURATION and \
value.index == 33:
self._current_swing_mode = value.data
self._swing_list = list(value.data_items)
_LOGGER.debug("self._swing_list=%s", self._swing_list)
_LOGGER.debug("self._current_swing_mode=%s",
self._current_swing_mode)
# Set point
for value in self._node.get_values(
class_id=COMMAND_CLASS_THERMOSTAT_SETPOINT).values():
for value in (self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_SETPOINT)
.values()):
if value.data == 0:
_LOGGER.debug("Setpoint is 0, setting default to "
"current_temperature=%s",
self._current_temperature)
self._target_temperature = int(self._current_temperature)
break
if self.current_operation is not None and \
self.current_operation != 'Off':
if self._index_operation != value.index:
@@ -232,16 +238,26 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
"""Return the temperature we try to reach."""
return self._target_temperature
# pylint: disable=too-many-branches, too-many-statements
def set_temperature(self, **kwargs):
"""Set new target temperature."""
if kwargs.get(ATTR_TEMPERATURE) is not None:
temperature = kwargs.get(ATTR_TEMPERATURE)
else:
return
operation_mode = kwargs.get(ATTR_OPERATION_MODE)
_LOGGER.debug("set_temperature operation_mode=%s", operation_mode)
for value in (self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_SETPOINT)
.values()):
if operation_mode is not None:
setpoint_mode = SET_TEMP_TO_INDEX.get(operation_mode)
if value.index != setpoint_mode:
continue
_LOGGER.debug("setpoint_mode=%s", setpoint_mode)
value.data = temperature
break
for value in self._node.get_values(
class_id=COMMAND_CLASS_THERMOSTAT_SETPOINT).values():
if self.current_operation is not None:
if self._hrt4_zw and self.current_operation == 'Off':
# HRT4-ZW can change setpoint when off.
@@ -279,17 +295,21 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
def set_fan_mode(self, fan):
"""Set new target fan mode."""
for value in self._node.get_values(
class_id=COMMAND_CLASS_THERMOSTAT_FAN_MODE).values():
if value.command_class == 68 and value.index == 0:
for value in (self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_FAN_MODE).
values()):
if value.command_class == \
zwave.const.COMMAND_CLASS_THERMOSTAT_FAN_MODE and \
value.index == 0:
value.data = bytes(fan, 'utf-8')
break
def set_operation_mode(self, operation_mode):
"""Set new target operation mode."""
for value in self._node.get_values(
class_id=COMMAND_CLASS_THERMOSTAT_MODE).values():
if value.command_class == 64 and value.index == 0:
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_MODE).values():
if value.command_class == \
zwave.const.COMMAND_CLASS_THERMOSTAT_MODE and value.index == 0:
value.data = bytes(operation_mode, 'utf-8')
break
@@ -297,7 +317,9 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
"""Set new target swing mode."""
if self._zxt_120 == 1:
for value in self._node.get_values(
class_id=COMMAND_CLASS_CONFIGURATION).values():
if value.command_class == 112 and value.index == 33:
class_id=zwave.const.COMMAND_CLASS_CONFIGURATION).values():
if value.command_class == \
zwave.const.COMMAND_CLASS_CONFIGURATION and \
value.index == 33:
value.data = bytes(swing_mode, 'utf-8')
break
+9 -4
View File
@@ -29,6 +29,10 @@ SERVICE_PROCESS_SCHEMA = vol.Schema({
vol.Required(ATTR_TEXT): vol.All(cv.string, vol.Lower),
})
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({}),
}, extra=vol.ALLOW_EXTRA)
def setup(hass, config):
"""Register the process service."""
@@ -48,8 +52,8 @@ def setup(hass, config):
name, command = match.groups()
entities = {state.entity_id: state.name for state in hass.states.all()}
entity_ids = fuzzyExtract.extractOne(name, entities,
score_cutoff=65)[2]
entity_ids = fuzzyExtract.extractOne(
name, entities, score_cutoff=65)[2]
if not entity_ids:
logger.error(
@@ -70,6 +74,7 @@ def setup(hass, config):
logger.error('Got unsupported command %s from text %s',
command, text)
hass.services.register(DOMAIN, SERVICE_PROCESS, process,
schema=SERVICE_PROCESS_SCHEMA)
hass.services.register(
DOMAIN, SERVICE_PROCESS, process, schema=SERVICE_PROCESS_SCHEMA)
return True
+103
View File
@@ -0,0 +1,103 @@
"""
Support for MySensors covers.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/cover.mysensors/
"""
import logging
from homeassistant.components import mysensors
from homeassistant.components.cover import CoverDevice, ATTR_POSITION
from homeassistant.const import STATE_ON, STATE_OFF
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = []
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the mysensors platform for covers."""
if discovery_info is None:
return
for gateway in mysensors.GATEWAYS.values():
pres = gateway.const.Presentation
set_req = gateway.const.SetReq
map_sv_types = {
pres.S_COVER: [set_req.V_DIMMER, set_req.V_LIGHT],
}
if float(gateway.protocol_version) >= 1.5:
map_sv_types.update({
pres.S_COVER: [set_req.V_PERCENTAGE, set_req.V_STATUS],
})
devices = {}
gateway.platform_callbacks.append(mysensors.pf_callback_factory(
map_sv_types, devices, add_devices, MySensorsCover))
class MySensorsCover(mysensors.MySensorsDeviceEntity, CoverDevice):
"""Representation of the value of a MySensors Cover child node."""
@property
def assumed_state(self):
"""Return True if unable to access real state of entity."""
return self.gateway.optimistic
@property
def is_closed(self):
"""Return True if cover is closed."""
set_req = self.gateway.const.SetReq
if set_req.V_DIMMER in self._values:
return self._values.get(set_req.V_DIMMER) == 0
else:
return self._values.get(set_req.V_LIGHT) == STATE_OFF
@property
def current_cover_position(self):
"""Return current position of cover.
None is unknown, 0 is closed, 100 is fully open.
"""
set_req = self.gateway.const.SetReq
return self._values.get(set_req.V_DIMMER)
def open_cover(self, **kwargs):
"""Move the cover up."""
set_req = self.gateway.const.SetReq
self.gateway.set_child_value(
self.node_id, self.child_id, set_req.V_UP, 1)
if self.gateway.optimistic:
# Optimistically assume that cover has changed state.
if set_req.V_DIMMER in self._values:
self._values[set_req.V_DIMMER] = 100
else:
self._values[set_req.V_LIGHT] = STATE_ON
self.update_ha_state()
def close_cover(self, **kwargs):
"""Move the cover down."""
set_req = self.gateway.const.SetReq
self.gateway.set_child_value(
self.node_id, self.child_id, set_req.V_DOWN, 1)
if self.gateway.optimistic:
# Optimistically assume that cover has changed state.
if set_req.V_DIMMER in self._values:
self._values[set_req.V_DIMMER] = 0
else:
self._values[set_req.V_LIGHT] = STATE_OFF
self.update_ha_state()
def set_cover_position(self, **kwargs):
"""Move the cover to a specific position."""
position = kwargs.get(ATTR_POSITION)
set_req = self.gateway.const.SetReq
self.gateway.set_child_value(
self.node_id, self.child_id, set_req.V_DIMMER, position)
if self.gateway.optimistic:
# Optimistically assume that cover has changed state.
self._values[set_req.V_DIMMER] = position
self.update_ha_state()
def stop_cover(self, **kwargs):
"""Stop the device."""
set_req = self.gateway.const.SetReq
self.gateway.set_child_value(
self.node_id, self.child_id, set_req.V_STOP, 1)
+31 -29
View File
@@ -12,9 +12,6 @@ from homeassistant.components.zwave import ZWaveDeviceEntity
from homeassistant.components import zwave
from homeassistant.components.cover import CoverDevice
COMMAND_CLASS_SWITCH_MULTILEVEL = 0x26 # 38
COMMAND_CLASS_SWITCH_BINARY = 0x25 # 37
SOMFY = 0x47
SOMFY_ZRTSI = 0x5a52
SOMFY_ZRTSI_CONTROLLER = (SOMFY, SOMFY_ZRTSI)
@@ -32,17 +29,17 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
if discovery_info is None or zwave.NETWORK is None:
return
node = zwave.NETWORK.nodes[discovery_info[zwave.ATTR_NODE_ID]]
value = node.values[discovery_info[zwave.ATTR_VALUE_ID]]
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.COMMAND_CLASS_SWITCH_MULTILEVEL and
value.index == 0):
if node.has_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.COMMAND_CLASS_SWITCH_BINARY or
value.command_class == zwave.COMMAND_CLASS_BARRIER_OPERATOR):
if value.type != zwave.TYPE_BOOL and \
value.genre != zwave.GENRE_USER:
elif node.has_command_class(zwave.const.COMMAND_CLASS_SWITCH_BINARY) or \
node.has_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)])
@@ -59,6 +56,7 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice):
from openzwave.network import ZWaveNetwork
from pydispatch import dispatcher
ZWaveDeviceEntity.__init__(self, value, DOMAIN)
# pylint: disable=no-member
self._lozwmgr = libopenzwave.PyManager()
self._lozwmgr.create()
self._node = value.node
@@ -88,9 +86,10 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice):
"""Callback on data change for the registered node/value pair."""
# Position value
for value in self._node.get_values(
class_id=COMMAND_CLASS_SWITCH_MULTILEVEL).values():
if value.command_class == zwave.COMMAND_CLASS_SWITCH_MULTILEVEL \
and value.label == 'Level':
class_id=zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL).values():
if value.command_class == \
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL and \
value.label == 'Level':
self._current_position = value.data
@property
@@ -118,22 +117,24 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice):
def open_cover(self, **kwargs):
"""Move the roller shutter up."""
for value in self._node.get_values(
class_id=COMMAND_CLASS_SWITCH_MULTILEVEL).values():
if value.command_class == zwave.COMMAND_CLASS_SWITCH_MULTILEVEL \
and value.label == 'Open' or \
value.command_class == zwave.COMMAND_CLASS_SWITCH_MULTILEVEL \
and value.label == 'Down':
class_id=zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL).values():
if value.command_class == \
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL and value.label == \
'Open' or value.command_class == \
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL and value.label == \
'Down':
self._lozwmgr.pressButton(value.value_id)
break
def close_cover(self, **kwargs):
"""Move the roller shutter down."""
for value in self._node.get_values(
class_id=COMMAND_CLASS_SWITCH_MULTILEVEL).values():
if value.command_class == zwave.COMMAND_CLASS_SWITCH_MULTILEVEL \
and value.label == 'Up' or \
value.command_class == zwave.COMMAND_CLASS_SWITCH_MULTILEVEL \
and value.label == 'Close':
class_id=zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL).values():
if value.command_class == \
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL and value.label == \
'Up' or value.command_class == \
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL and value.label == \
'Close':
self._lozwmgr.pressButton(value.value_id)
break
@@ -144,11 +145,12 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice):
def stop_cover(self, **kwargs):
"""Stop the roller shutter."""
for value in self._node.get_values(
class_id=COMMAND_CLASS_SWITCH_MULTILEVEL).values():
if value.command_class == zwave.COMMAND_CLASS_SWITCH_MULTILEVEL \
and value.label == 'Open' or \
value.command_class == zwave.COMMAND_CLASS_SWITCH_MULTILEVEL \
and value.label == 'Down':
class_id=zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL).values():
if value.command_class == \
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL and value.label == \
'Open' or value.command_class == \
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL and value.label == \
'Down':
self._lozwmgr.releaseButton(value.value_id)
break
@@ -46,7 +46,6 @@ CONF_TRACK_NEW = 'track_new_devices'
DEFAULT_TRACK_NEW = True
CONF_CONSIDER_HOME = 'consider_home'
DEFAULT_CONSIDER_HOME = 180 # seconds
CONF_SCAN_INTERVAL = 'interval_seconds'
DEFAULT_SCAN_INTERVAL = 12
@@ -70,8 +69,10 @@ PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({
_CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.All(cv.ensure_list, [
vol.Schema({
vol.Optional(CONF_TRACK_NEW): cv.boolean,
vol.Optional(CONF_CONSIDER_HOME): cv.positive_int # seconds
vol.Optional(CONF_TRACK_NEW, default=DEFAULT_TRACK_NEW): cv.boolean,
vol.Optional(
CONF_CONSIDER_HOME, default=timedelta(seconds=180)): vol.All(
cv.time_period, cv.positive_timedelta)
}, extra=vol.ALLOW_EXTRA)])}, extra=vol.ALLOW_EXTRA)
DISCOVERY_PLATFORMS = {
@@ -118,9 +119,8 @@ def setup(hass: HomeAssistantType, config: ConfigType):
return False
else:
conf = conf[0] if len(conf) > 0 else {}
consider_home = timedelta(
seconds=conf.get(CONF_CONSIDER_HOME, DEFAULT_CONSIDER_HOME))
track_new = conf.get(CONF_TRACK_NEW, DEFAULT_TRACK_NEW)
consider_home = conf[CONF_CONSIDER_HOME]
track_new = conf[CONF_TRACK_NEW]
devices = load_config(yaml_path, hass, consider_home)
@@ -282,7 +282,7 @@ class Device(Entity):
def __init__(self, hass: HomeAssistantType, consider_home: timedelta,
track: bool, dev_id: str, mac: str, name: str=None,
picture: str=None, gravatar: str=None,
away_hide: bool=False) -> None:
hide_if_away: bool=False) -> None:
"""Initialize a device."""
self.hass = hass
self.entity_id = ENTITY_ID_FORMAT.format(dev_id)
@@ -307,7 +307,7 @@ class Device(Entity):
else:
self.config_picture = picture
self.away_hide = away_hide
self.away_hide = hide_if_away
@property
def name(self):
@@ -398,15 +398,29 @@ class Device(Entity):
def load_config(path: str, hass: HomeAssistantType, consider_home: timedelta):
"""Load devices from YAML configuration file."""
dev_schema = vol.Schema({
vol.Required('name'): cv.string,
vol.Optional('track', default=False): cv.boolean,
vol.Optional('mac', default=None): vol.Any(None, vol.All(cv.string,
vol.Upper)),
vol.Optional(CONF_AWAY_HIDE, default=DEFAULT_AWAY_HIDE): cv.boolean,
vol.Optional('gravatar', default=None): vol.Any(None, cv.string),
vol.Optional('picture', default=None): vol.Any(None, cv.string),
vol.Optional(CONF_CONSIDER_HOME, default=consider_home): vol.All(
cv.time_period, cv.positive_timedelta)
})
try:
return [
Device(hass, consider_home, device.get('track', False),
str(dev_id).lower(), None if device.get('mac') is None
else str(device.get('mac')).upper(),
device.get('name'), device.get('picture'),
device.get('gravatar'),
device.get(CONF_AWAY_HIDE, DEFAULT_AWAY_HIDE))
for dev_id, device in load_yaml_config_file(path).items()]
result = []
devices = load_yaml_config_file(path)
for dev_id, device in devices.items():
try:
device = dev_schema(device)
device['dev_id'] = cv.slug(dev_id)
except vol.Invalid as exp:
log_exception(exp, dev_id, devices)
else:
result.append(Device(hass, **device))
return result
except (HomeAssistantError, FileNotFoundError):
# When YAML file could not be loaded/did not contain a dict
return []
@@ -85,7 +85,9 @@ class FritzBoxScanner(object):
def get_device_name(self, mac):
"""Return the name of the given device or None if is not known."""
ret = self.fritz_box.get_specific_host_entry(mac)['NewHostName']
ret = self.fritz_box.get_specific_host_entry(mac).get(
'NewHostName'
)
if ret == {}:
return None
return ret
@@ -7,6 +7,7 @@ https://home-assistant.io/components/device_tracker.owntracks/
import json
import logging
import threading
import base64
from collections import defaultdict
import voluptuous as vol
@@ -18,53 +19,121 @@ from homeassistant.util import convert, slugify
from homeassistant.components import zone as zone_comp
from homeassistant.components.device_tracker import PLATFORM_SCHEMA
DEPENDENCIES = ['mqtt']
REGIONS_ENTERED = defaultdict(list)
MOBILE_BEACONS_ACTIVE = defaultdict(list)
BEACON_DEV_ID = 'beacon'
LOCATION_TOPIC = 'owntracks/+/+'
EVENT_TOPIC = 'owntracks/+/+/event'
WAYPOINT_TOPIC = 'owntracks/{}/{}/waypoint'
REQUIREMENTS = ['libnacl==1.5.0']
_LOGGER = logging.getLogger(__name__)
LOCK = threading.Lock()
BEACON_DEV_ID = 'beacon'
CONF_MAX_GPS_ACCURACY = 'max_gps_accuracy'
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'
VALIDATE_WAYPOINTS = 'waypoints'
WAYPOINT_LAT_KEY = 'lat'
WAYPOINT_LON_KEY = 'lon'
WAYPOINT_TOPIC = 'owntracks/{}/{}/waypoint'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_MAX_GPS_ACCURACY): vol.Coerce(float),
vol.Optional(CONF_WAYPOINT_IMPORT, default=True): cv.boolean,
vol.Optional(CONF_WAYPOINT_WHITELIST): vol.All(cv.ensure_list, [cv.string])
vol.Optional(CONF_WAYPOINT_WHITELIST): vol.All(
cv.ensure_list, [cv.string]),
vol.Optional(CONF_SECRET): vol.Any(
vol.Schema({vol.Optional(cv.string): cv.string}),
cv.string)
})
def get_cipher():
"""Return decryption function and length of key."""
from libnacl import crypto_secretbox_KEYBYTES as KEYLEN
from libnacl.secret import SecretBox
def decrypt(ciphertext, key):
"""Decrypt ciphertext using key."""
return SecretBox(key).decrypt(ciphertext)
return (KEYLEN, decrypt)
def setup_scanner(hass, config, see):
"""Setup an OwnTracks tracker."""
"""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)
def decrypt_payload(topic, ciphertext):
"""Decrypt encrypted payload."""
try:
keylen, decrypt = get_cipher()
except OSError:
_LOGGER.warning('Ignoring encrypted payload '
'because libsodium not installed.')
return None
if isinstance(secret, dict):
key = secret.get(topic)
else:
key = secret
if key is None:
_LOGGER.warning('Ignoring encrypted payload '
'because no decryption key known '
'for topic %s.', topic)
return None
key = key.encode("utf-8")
key = key[:keylen]
key = key.ljust(keylen, b'\0')
try:
ciphertext = base64.b64decode(ciphertext)
message = decrypt(ciphertext, key)
message = message.decode("utf-8")
_LOGGER.debug("Decrypted payload: %s", message)
return message
except ValueError:
_LOGGER.warning('Ignoring encrypted payload '
'because unable to decrypt using key '
'for topic %s.', topic)
return None
def validate_payload(topic, payload, data_type):
"""Validate the OwnTracks payload."""
# pylint: disable=too-many-return-statements
def validate_payload(payload, data_type):
"""Validate OwnTracks payload."""
try:
data = json.loads(payload)
except ValueError:
# If invalid JSON
_LOGGER.error('Unable to parse payload as JSON: %s', payload)
return None
if isinstance(data, dict) and \
data.get('_type') == 'encrypted' and \
'data' in data:
plaintext_payload = decrypt_payload(topic, data['data'])
if plaintext_payload is None:
return None
else:
return validate_payload(topic, plaintext_payload, data_type)
if not isinstance(data, dict) or data.get('_type') != data_type:
_LOGGER.debug('Skipping %s update for following data '
'because of missing or malformatted data: %s',
@@ -90,7 +159,7 @@ def setup_scanner(hass, config, see):
"""MQTT message received."""
# Docs on available data:
# http://owntracks.org/booklet/tech/json/#_typelocation
data = validate_payload(payload, VALIDATE_LOCATION)
data = validate_payload(topic, payload, VALIDATE_LOCATION)
if not data:
return
@@ -111,7 +180,7 @@ def setup_scanner(hass, config, see):
"""MQTT event (geofences) received."""
# Docs on available data:
# http://owntracks.org/booklet/tech/json/#_typetransition
data = validate_payload(payload, VALIDATE_TRANSITION)
data = validate_payload(topic, payload, VALIDATE_TRANSITION)
if not data:
return
@@ -206,7 +275,7 @@ def setup_scanner(hass, config, see):
"""List of waypoints published by a user."""
# Docs on available data:
# http://owntracks.org/booklet/tech/json/#_typewaypoints
data = validate_payload(payload, VALIDATE_WAYPOINTS)
data = validate_payload(topic, payload, VALIDATE_WAYPOINTS)
if not data:
return
@@ -0,0 +1,100 @@
"""
Support for Volvo On Call.
http://www.volvocars.com/intl/own/owner-info/volvo-on-call
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.volvooncall/
"""
import logging
from datetime import timedelta
from urllib.parse import urljoin
import voluptuous as vol
import requests
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import track_point_in_utc_time
from homeassistant.util.dt import utcnow
from homeassistant.const import (
CONF_PASSWORD,
CONF_SCAN_INTERVAL,
CONF_USERNAME)
from homeassistant.components.device_tracker import (
DEFAULT_SCAN_INTERVAL,
PLATFORM_SCHEMA)
MIN_TIME_BETWEEN_SCANS = timedelta(minutes=1)
_LOGGER = logging.getLogger(__name__)
SERVICE_URL = 'https://vocapi.wirelesscar.net/customerapi/rest/v3.0/'
HEADERS = {"X-Device-Id": "Device",
"X-OS-Type": "Android",
"X-Originator-Type": "App"}
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
})
def setup_scanner(hass, config, see):
"""Validate the configuration and return a scanner."""
session = requests.Session()
session.headers.update(HEADERS)
session.auth = (config.get(CONF_USERNAME),
config.get(CONF_PASSWORD))
interval = max(MIN_TIME_BETWEEN_SCANS.seconds,
config.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL))
def query(ref, rel=SERVICE_URL):
"""Perform a query to the online service."""
url = urljoin(rel, ref)
_LOGGER.debug("Request for %s", url)
res = session.get(url, timeout=15)
res.raise_for_status()
_LOGGER.debug("Received %s", res.json())
return res.json()
def update(now):
"""Update status from the online service."""
try:
_LOGGER.debug("Updating")
status = query("status", vehicle_url)
position = query("position", vehicle_url)
see(dev_id=dev_id,
host_name=host_name,
gps=(position["position"]["latitude"],
position["position"]["longitude"]),
attributes=dict(
tank_volume=attributes["fuelTankVolume"],
washer_fluid=status["washerFluidLevel"],
brake_fluid=status["brakeFluid"],
service_warning=status["serviceWarningStatus"],
fuel=status["fuelAmount"],
odometer=status["odometer"],
range=status["distanceToEmpty"]))
except requests.exceptions.RequestException as error:
_LOGGER.error("Could not query server: %s", error)
finally:
track_point_in_utc_time(hass, update,
now + timedelta(seconds=interval))
try:
_LOGGER.info('Logging in to service')
user = query("customeraccounts")
rel = query(user["accountVehicleRelations"][0])
vehicle_url = rel["vehicle"] + '/'
attributes = query("attributes", vehicle_url)
dev_id = "volvo_" + attributes["registrationNumber"]
host_name = "%s %s/%s" % (attributes["registrationNumber"],
attributes["vehicleType"],
attributes["modelYear"])
update(utcnow())
return True
except requests.exceptions.RequestException as error:
_LOGGER.error("Could not log in to service. "
"Please check configuration: "
"%s", error)
return False
+86
View File
@@ -0,0 +1,86 @@
"""
Support for Digital Ocean.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/digital_ocean/
"""
import logging
from datetime import timedelta
import voluptuous as vol
from homeassistant.const import CONF_ACCESS_TOKEN
from homeassistant.util import Throttle
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['python-digitalocean==1.9.0']
_LOGGER = logging.getLogger(__name__)
ATTR_CREATED_AT = 'created_at'
ATTR_DROPLET_ID = 'droplet_id'
ATTR_DROPLET_NAME = 'droplet_name'
ATTR_FEATURES = 'features'
ATTR_IPV4_ADDRESS = 'ipv4_address'
ATTR_IPV6_ADDRESS = 'ipv6_address'
ATTR_MEMORY = 'memory'
ATTR_REGION = 'region'
ATTR_VCPUS = 'vcpus'
CONF_DROPLETS = 'droplets'
DIGITAL_OCEAN = None
DIGITAL_OCEAN_PLATFORMS = ['switch', 'binary_sensor']
DOMAIN = 'digital_ocean'
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60)
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_ACCESS_TOKEN): cv.string,
}),
}, extra=vol.ALLOW_EXTRA)
# pylint: disable=unused-argument,too-few-public-methods
def setup(hass, config):
"""Setup the Digital Ocean component."""
conf = config[DOMAIN]
access_token = conf.get(CONF_ACCESS_TOKEN)
global DIGITAL_OCEAN
DIGITAL_OCEAN = DigitalOcean(access_token)
if not DIGITAL_OCEAN.manager.get_account():
_LOGGER.error("No Digital Ocean account found for the given API Token")
return False
return True
class DigitalOcean(object):
"""Handle all communication with the Digital Ocean API."""
def __init__(self, access_token):
"""Initialize the Digital Ocean connection."""
import digitalocean
self._access_token = access_token
self.data = None
self.manager = digitalocean.Manager(token=self._access_token)
def get_droplet_id(self, droplet_name):
"""Get the status of a Digital Ocean droplet."""
droplet_id = None
all_droplets = self.manager.get_all_droplets()
for droplet in all_droplets:
if droplet_name == droplet.name:
droplet_id = droplet.id
return droplet_id
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Use the data from Digital Ocean API."""
self.data = self.manager.get_all_droplets()
+6
View File
@@ -9,6 +9,8 @@ loaded before the EVENT_PLATFORM_DISCOVERED is fired.
import logging
import threading
import voluptuous as vol
from homeassistant.const import EVENT_HOMEASSISTANT_START
from homeassistant.helpers.discovery import load_platform, discover
@@ -33,6 +35,10 @@ SERVICE_HANDLERS = {
'directv': ('media_player', 'directv'),
}
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({}),
}, extra=vol.ALLOW_EXTRA)
def setup(hass, config):
"""Start a discovery service."""
+2
View File
@@ -25,6 +25,8 @@ from homeassistant.components.light import (
from homeassistant.components.http import (
HomeAssistantView, HomeAssistantWSGI
)
# pylint: disable=unused-import
from homeassistant.components.http import REQUIREMENTS # noqa
import homeassistant.helpers.config_validation as cv
DOMAIN = 'emulated_hue'
+3 -3
View File
@@ -184,13 +184,13 @@ def setup(hass, base_config):
load_platform(hass, 'alarm_control_panel', 'envisalink',
{CONF_PARTITIONS: _partitions,
CONF_CODE: _code,
CONF_PANIC: _panic_type}, config)
CONF_PANIC: _panic_type}, base_config)
load_platform(hass, 'sensor', 'envisalink',
{CONF_PARTITIONS: _partitions,
CONF_CODE: _code}, config)
CONF_CODE: _code}, base_config)
if _zones:
load_platform(hass, 'binary_sensor', 'envisalink',
{CONF_ZONES: _zones}, config)
{CONF_ZONES: _zones}, base_config)
return True
+8 -8
View File
@@ -1,14 +1,14 @@
"""DO NOT MODIFY. Auto-generated by script/fingerprint_frontend."""
FINGERPRINTS = {
"core.js": "78862c0984279b6876f594ffde45177c",
"frontend.html": "c1753e1ce530f978036742477c96d2fd",
"mdi.html": "6bd013a8252e19b3c1f1de52994cfbe4",
"panels/ha-panel-dev-event.html": "c4a5f70eece9f92616a65e8d26be803e",
"panels/ha-panel-dev-info.html": "34e2df1af32e60fffcafe7e008a92169",
"panels/ha-panel-dev-service.html": "07e83c6b7f79d78a59258f6dba477b54",
"panels/ha-panel-dev-state.html": "fd8eb946856b1346a87a51d0c86854ff",
"panels/ha-panel-dev-template.html": "7cff8a2ef3f44fdaf357a0d41696bf6d",
"core.js": "9b3e5ab4eac7e3b074e0daf3f619a638",
"frontend.html": "5854807d361de26fe93ad474010f19d2",
"mdi.html": "46a76f877ac9848899b8ed382427c16f",
"panels/ha-panel-dev-event.html": "550bf85345c454274a40d15b2795a002",
"panels/ha-panel-dev-info.html": "ec613406ce7e20d93754233d55625c8a",
"panels/ha-panel-dev-service.html": "c7974458ebc33412d95497e99b785e12",
"panels/ha-panel-dev-state.html": "4be627b74e683af14ef779d8203ec674",
"panels/ha-panel-dev-template.html": "d23943fa0370f168714da407c90091a2",
"panels/ha-panel-history.html": "efe1bcdd7733b09e55f4f965d171c295",
"panels/ha-panel-iframe.html": "d920f0aa3c903680f2f8795e2255daab",
"panels/ha-panel-logbook.html": "66108d82763359a218c9695f0553de40",
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
@@ -1,2 +1,2 @@
<html><head><meta charset="UTF-8"></head><body><dom-module id="ha-panel-dev-info"><template><style include="iron-positioning ha-style">:host{background-color:#fff;-ms-user-select:initial;-webkit-user-select:initial;-moz-user-select:initial}.content{padding:24px}.about{text-align:center;line-height:2em}.version{@apply(--paper-font-headline)}.develop{@apply(--paper-font-subhead)}.about a{color:var(--dark-primary-color)}.error-log-intro{margin-top:16px;border-top:1px solid var(--light-primary-color);padding-top:16px}paper-icon-button{float:right}.error-log{@apply(--paper-font-code1)
clear: both;white-space:pre-wrap}</style><app-header-layout has-scrolling-region=""><app-header fixed=""><app-toolbar><ha-menu-button narrow="[[narrow]]" show-menu="[[showMenu]]"></ha-menu-button><div main-title="">About</div></app-toolbar></app-header><div class="content fit"><div class="about"><p class="version"><a href="https://home-assistant.io"><img src="/static/icons/favicon-192x192.png" height="192"></a><br>Home Assistant<br>[[hassVersion]]</p><p class="develop"><a href="https://home-assistant.io/developers/credits/" target="_blank">Developed by a bunch of awesome people.</a></p><p>Published under the MIT license<br>Source: <a href="https://github.com/balloob/home-assistant" target="_blank">server</a><a href="https://github.com/balloob/home-assistant-polymer" target="_blank">frontend-ui</a><a href="https://github.com/balloob/home-assistant-js" target="_blank">frontend-core</a></p><p>Built using <a href="https://www.python.org">Python 3</a>, <a href="https://www.polymer-project.org" target="_blank">Polymer [[polymerVersion]]</a>, <a href="https://optimizely.github.io/nuclear-js/" target="_blank">NuclearJS [[nuclearVersion]]</a><br>Icons by <a href="https://www.google.com/design/icons/" target="_blank">Google</a> and <a href="https://MaterialDesignIcons.com" target="_blank">MaterialDesignIcons.com</a>.</p></div><p class="error-log-intro">The following errors have been logged this session:<paper-icon-button icon="mdi:refresh" on-tap="refreshErrorLog"></paper-icon-button></p><div class="error-log">[[errorLog]]</div></div></app-header-layout></template></dom-module><script>Polymer({is:"ha-panel-dev-info",behaviors:[window.hassBehavior],properties:{hass:{type:Object},narrow:{type:Boolean,value:!1},showMenu:{type:Boolean,value:!1},hassVersion:{type:String,bindNuclear:function(r){return r.configGetters.serverVersion}},polymerVersion:{type:String,value:Polymer.version},nuclearVersion:{type:String,value:"1.3.0"},errorLog:{type:String,value:""}},attached:function(){this.refreshErrorLog()},refreshErrorLog:function(r){r&&r.preventDefault(),this.errorLog="Loading error log…",this.hass.errorLogActions.fetchErrorLog().then(function(r){this.errorLog=r||"No errors have been reported."}.bind(this))}})</script></body></html>
clear: both;white-space:pre-wrap}</style><app-header-layout has-scrolling-region=""><app-header fixed=""><app-toolbar><ha-menu-button narrow="[[narrow]]" show-menu="[[showMenu]]"></ha-menu-button><div main-title="">About</div></app-toolbar></app-header><div class="content fit"><div class="about"><p class="version"><a href="https://home-assistant.io"><img src="/static/icons/favicon-192x192.png" height="192"></a><br>Home Assistant<br>[[hassVersion]]</p><p>Path to configuration.yaml: [[hassConfigDir]]</p><p class="develop"><a href="https://home-assistant.io/developers/credits/" target="_blank">Developed by a bunch of awesome people.</a></p><p>Published under the MIT license<br>Source: <a href="https://github.com/home-assistant/home-assistant" target="_blank">server</a><a href="https://github.com/home-assistant/home-assistant-polymer" target="_blank">frontend-ui</a><a href="https://github.com/home-assistant/home-assistant-js" target="_blank">frontend-core</a></p><p>Built using <a href="https://www.python.org">Python 3</a>, <a href="https://www.polymer-project.org" target="_blank">Polymer [[polymerVersion]]</a>, <a href="https://optimizely.github.io/nuclear-js/" target="_blank">NuclearJS [[nuclearVersion]]</a><br>Icons by <a href="https://www.google.com/design/icons/" target="_blank">Google</a> and <a href="https://MaterialDesignIcons.com" target="_blank">MaterialDesignIcons.com</a>.</p></div><p class="error-log-intro">The following errors have been logged this session:<paper-icon-button icon="mdi:refresh" on-tap="refreshErrorLog"></paper-icon-button></p><div class="error-log">[[errorLog]]</div></div></app-header-layout></template></dom-module><script>Polymer({is:"ha-panel-dev-info",behaviors:[window.hassBehavior],properties:{hass:{type:Object},narrow:{type:Boolean,value:!1},showMenu:{type:Boolean,value:!1},hassVersion:{type:String,bindNuclear:function(r){return r.configGetters.serverVersion}},hassConfigDir:{type:String,bindNuclear:function(r){return r.configGetters.configDir}},polymerVersion:{type:String,value:Polymer.version},nuclearVersion:{type:String,value:"1.3.0"},errorLog:{type:String,value:""}},attached:function(){this.refreshErrorLog()},refreshErrorLog:function(r){r&&r.preventDefault(),this.errorLog="Loading error log…",this.hass.errorLogActions.fetchErrorLog().then(function(r){this.errorLog=r||"No errors have been reported."}.bind(this))}})</script></body></html>
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
@@ -22,15 +22,15 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
if discovery_info is None or zwave.NETWORK is None:
return
node = zwave.NETWORK.nodes[discovery_info[zwave.ATTR_NODE_ID]]
value = node.values[discovery_info[zwave.ATTR_VALUE_ID]]
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.COMMAND_CLASS_SWITCH_BINARY and \
value.command_class != zwave.COMMAND_CLASS_BARRIER_OPERATOR:
if value.command_class != zwave.const.COMMAND_CLASS_SWITCH_BINARY and \
value.command_class != zwave.const.COMMAND_CLASS_BARRIER_OPERATOR:
return
if value.type != zwave.TYPE_BOOL:
if value.type != zwave.const.TYPE_BOOL:
return
if value.genre != zwave.GENRE_USER:
if value.genre != zwave.const.GENRE_USER:
return
value.set_change_verified(False)
+76 -96
View File
@@ -23,7 +23,7 @@ from homeassistant.config import load_yaml_config_file
from homeassistant.util import Throttle
DOMAIN = 'homematic'
REQUIREMENTS = ["pyhomematic==0.1.14"]
REQUIREMENTS = ["pyhomematic==0.1.16"]
HOMEMATIC = None
HOMEMATIC_LINK_DELAY = 0.5
@@ -52,17 +52,22 @@ SERVICE_VIRTUALKEY = 'virtualkey'
SERVICE_SET_VALUE = 'set_value'
HM_DEVICE_TYPES = {
DISCOVER_SWITCHES: ['Switch', 'SwitchPowermeter'],
DISCOVER_LIGHTS: ['Dimmer'],
DISCOVER_SENSORS: ['SwitchPowermeter', 'Motion', 'MotionV2',
'RemoteMotion', 'ThermostatWall', 'AreaThermostat',
'RotaryHandleSensor', 'WaterSensor', 'PowermeterGas',
'LuxSensor', 'WeatherSensor', 'WeatherStation'],
DISCOVER_CLIMATE: ['Thermostat', 'ThermostatWall', 'MAXThermostat'],
DISCOVER_BINARY_SENSORS: ['ShutterContact', 'Smoke', 'SmokeV2', 'Motion',
'MotionV2', 'RemoteMotion', 'WeatherSensor',
'TiltSensor'],
DISCOVER_COVER: ['Blind']
DISCOVER_SWITCHES: [
'Switch', 'SwitchPowermeter', 'IOSwitch', 'IPSwitch',
'IPSwitchPowermeter', 'KeyMatic'],
DISCOVER_LIGHTS: ['Dimmer', 'KeyDimmer'],
DISCOVER_SENSORS: [
'SwitchPowermeter', 'Motion', 'MotionV2', 'RemoteMotion',
'ThermostatWall', 'AreaThermostat', 'RotaryHandleSensor',
'WaterSensor', 'PowermeterGas', 'LuxSensor', 'WeatherSensor',
'WeatherStation', 'ThermostatWall2', 'TemperatureDiffSensor',
'TemperatureSensor', 'CO2Sensor'],
DISCOVER_CLIMATE: [
'Thermostat', 'ThermostatWall', 'MAXThermostat', 'ThermostatWall2'],
DISCOVER_BINARY_SENSORS: [
'ShutterContact', 'Smoke', 'SmokeV2', 'Motion', 'MotionV2',
'RemoteMotion', 'WeatherSensor', 'TiltSensor', 'IPShutterContact'],
DISCOVER_COVER: ['Blind', 'KeyBlind']
}
HM_IGNORE_DISCOVERY_NODE = [
@@ -87,11 +92,12 @@ HM_PRESS_EVENTS = [
'PRESS_SHORT',
'PRESS_LONG',
'PRESS_CONT',
'PRESS_LONG_RELEASE'
'PRESS_LONG_RELEASE',
'PRESS',
]
HM_IMPULSE_EVENTS = [
'SEQUENCE_OK'
'SEQUENCE_OK',
]
_LOGGER = logging.getLogger(__name__)
@@ -111,6 +117,15 @@ CONF_RESOLVENAMES = 'resolvenames'
CONF_DELAY = 'delay'
CONF_VARIABLES = 'variables'
DEFAULT_LOCAL_IP = "0.0.0.0"
DEFAULT_LOCAL_PORT = 0
DEFAULT_RESOLVENAMES = False
DEFAULT_REMOTE_PORT = 2001
DEFAULT_USERNAME = "Admin"
DEFAULT_PASSWORD = ""
DEFAULT_VARIABLES = False
DEFAULT_DELAY = 0.5
DEVICE_SCHEMA = vol.Schema({
vol.Required(CONF_PLATFORM): "homematic",
@@ -122,16 +137,16 @@ DEVICE_SCHEMA = vol.Schema({
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_LOCAL_IP): cv.string,
vol.Optional(CONF_LOCAL_PORT, default=8943): cv.port,
vol.Required(CONF_REMOTE_IP): cv.string,
vol.Optional(CONF_REMOTE_PORT, default=2001): cv.port,
vol.Optional(CONF_RESOLVENAMES, default=False):
vol.Optional(CONF_LOCAL_IP, default=DEFAULT_LOCAL_IP): cv.string,
vol.Optional(CONF_LOCAL_PORT, default=DEFAULT_LOCAL_PORT): cv.port,
vol.Optional(CONF_REMOTE_PORT, default=DEFAULT_REMOTE_PORT): cv.port,
vol.Optional(CONF_RESOLVENAMES, default=DEFAULT_RESOLVENAMES):
vol.In(CONF_RESOLVENAMES_OPTIONS),
vol.Optional(CONF_USERNAME, default="Admin"): cv.string,
vol.Optional(CONF_PASSWORD, default=""): cv.string,
vol.Optional(CONF_DELAY, default=0.5): vol.Coerce(float),
vol.Optional(CONF_VARIABLES, default=False): cv.boolean,
vol.Optional(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string,
vol.Optional(CONF_PASSWORD, default=DEFAULT_PASSWORD): cv.string,
vol.Optional(CONF_DELAY, default=DEFAULT_DELAY): vol.Coerce(float),
vol.Optional(CONF_VARIABLES, default=DEFAULT_VARIABLES): cv.boolean,
}),
}, extra=vol.ALLOW_EXTRA)
@@ -323,83 +338,45 @@ def _get_devices(device_type, keys):
metadata.update(device.SENSORNODE)
elif device_type == DISCOVER_BINARY_SENSORS:
metadata.update(device.BINARYNODE)
else:
metadata.update({None: device.ELEMENT})
params = _create_params_list(device, metadata, device_type)
if params:
if metadata:
# Generate options for 1...n elements with 1...n params
for channel in range(1, device.ELEMENT + 1):
_LOGGER.debug("Handling %s:%i", key, channel)
if channel in params:
for param in params[channel]:
name = _create_ha_name(
name=device.NAME,
channel=channel,
param=param
)
device_dict = {
CONF_PLATFORM: "homematic",
ATTR_ADDRESS: key,
ATTR_NAME: name,
ATTR_CHANNEL: channel
}
if param is not None:
device_dict.update({ATTR_PARAM: param})
for param, channels in metadata.items():
if param in HM_IGNORE_DISCOVERY_NODE:
continue
# Add new device
try:
DEVICE_SCHEMA(device_dict)
device_arr.append(device_dict)
except vol.MultipleInvalid as err:
_LOGGER.error("Invalid device config: %s",
str(err))
else:
_LOGGER.debug("Channel %i not in params", channel)
# add devices
_LOGGER.debug("Handling %s: %s", param, channels)
for channel in channels:
name = _create_ha_name(
name=device.NAME,
channel=channel,
param=param
)
device_dict = {
CONF_PLATFORM: "homematic",
ATTR_ADDRESS: key,
ATTR_NAME: name,
ATTR_CHANNEL: channel
}
if param is not None:
device_dict[ATTR_PARAM] = param
# Add new device
try:
DEVICE_SCHEMA(device_dict)
device_arr.append(device_dict)
except vol.MultipleInvalid as err:
_LOGGER.error("Invalid device config: %s",
str(err))
else:
_LOGGER.debug("Got no params for %s", key)
_LOGGER.debug("%s autodiscovery: %s", device_type, str(device_arr))
return device_arr
def _create_params_list(hmdevice, metadata, device_type):
"""Create a list from HMDevice with all possible parameters in config."""
params = {}
merge = False
# use merge?
if device_type in (DISCOVER_SENSORS, DISCOVER_BINARY_SENSORS):
merge = True
# Search in sensor and binary metadata per elements
for channel in range(1, hmdevice.ELEMENT + 1):
param_chan = []
for node, meta_chan in metadata.items():
try:
# Is this attribute ignored?
if node in HM_IGNORE_DISCOVERY_NODE:
continue
if meta_chan == 'c' or meta_chan is None:
# Only channel linked data
param_chan.append(node)
elif channel == 1:
# First channel can have other data channel
param_chan.append(node)
except (TypeError, ValueError):
_LOGGER.error("Exception generating %s (%s)",
hmdevice.ADDRESS, str(metadata))
# default parameter is merge is off
if len(param_chan) == 0 and not merge:
param_chan.append(None)
# Add to channel
if len(param_chan) > 0:
params.update({channel: param_chan})
_LOGGER.debug("Create param list for %s with: %s", hmdevice.ADDRESS,
str(params))
return params
def _create_ha_name(name, channel, param):
"""Generate a unique object name."""
# HMDevice is a simple device
@@ -484,12 +461,12 @@ def _hm_service_virtualkey(call):
hmdevice = HOMEMATIC.devices.get(address)
# if param exists for this device
if param not in hmdevice.ACTIONNODE:
if hmdevice is None or param not in hmdevice.ACTIONNODE:
_LOGGER.error("%s not datapoint in hm device %s", param, address)
return
# channel exists?
if channel > hmdevice.ELEMENT:
if channel in hmdevice.ACTIONNODE[param]:
_LOGGER.error("%i is not a channel in hm device %s", channel, address)
return
@@ -743,19 +720,22 @@ class HMDevice(Entity):
self._hmdevice.ATTRIBUTENODE,
self._hmdevice.WRITENODE, self._hmdevice.EVENTNODE,
self._hmdevice.ACTIONNODE):
for node, channel in metadata.items():
for node, channels in metadata.items():
# Data is needed for this instance
if node in self._data:
# chan is current channel
if channel == 'c' or channel is None:
if len(channels) == 1:
channel = channels[0]
else:
channel = self._channel
# Prepare for subscription
try:
if int(channel) >= 0:
channels_to_sub.update({int(channel): True})
except (ValueError, TypeError):
_LOGGER("Invalid channel in metadata from %s",
self._name)
_LOGGER.error("Invalid channel in metadata from %s",
self._name)
# Set callbacks
for channel in channels_to_sub:
+20 -16
View File
@@ -11,18 +11,20 @@ import mimetypes
import threading
import re
import ssl
import voluptuous as vol
import homeassistant.remote as rem
from homeassistant import util
from homeassistant.const import (
SERVER_PORT, HTTP_HEADER_HA_AUTH, HTTP_HEADER_CACHE_CONTROL,
HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN, CONTENT_TYPE_JSON,
HTTP_HEADER_ACCESS_CONTROL_ALLOW_HEADERS, ALLOWED_CORS_HEADERS,
EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_START)
from homeassistant.core import split_entity_id
import homeassistant.util.dt as dt_util
import homeassistant.helpers.config_validation as cv
from homeassistant.components import persistent_notification
DOMAIN = 'http'
REQUIREMENTS = ('cherrypy==8.1.0', 'static3==0.7.0', 'Werkzeug==0.11.11')
@@ -37,6 +39,7 @@ CONF_SSL_KEY = 'ssl_key'
CONF_CORS_ORIGINS = 'cors_allowed_origins'
DATA_API_PASSWORD = 'api_password'
NOTIFICATION_ID_LOGIN = 'http-login'
# TLS configuation follows the best-practice guidelines specified here:
# https://wiki.mozilla.org/Security/Server_Side_TLS
@@ -106,7 +109,7 @@ def setup(hass, config):
api_password = util.convert(conf.get(CONF_API_PASSWORD), str)
server_host = conf.get(CONF_SERVER_HOST, '0.0.0.0')
server_port = conf.get(CONF_SERVER_PORT, SERVER_PORT)
development = str(conf.get(CONF_DEVELOPMENT, "")) == "1"
development = str(conf.get(CONF_DEVELOPMENT, '')) == '1'
ssl_certificate = conf.get(CONF_SSL_CERTIFICATE)
ssl_key = conf.get(CONF_SSL_KEY)
cors_origins = conf.get(CONF_CORS_ORIGINS, [])
@@ -365,8 +368,8 @@ class HomeAssistantWSGI(object):
context.load_cert_chain(self.ssl_certificate, self.ssl_key)
self.server.ssl_adapter = ContextSSLAdapter(context)
threading.Thread(target=self.server.start, daemon=True,
name='WSGI-server').start()
threading.Thread(
target=self.server.start, daemon=True, name='WSGI-server').start()
def stop(self):
"""Stop the wsgi server."""
@@ -391,10 +394,10 @@ class HomeAssistantWSGI(object):
resp = ex.get_response(request.environ)
if request.accept_mimetypes.accept_json:
resp.data = json.dumps({
"result": "error",
"message": str(ex),
'result': 'error',
'message': str(ex),
})
resp.mimetype = "application/json"
resp.mimetype = CONTENT_TYPE_JSON
return resp
def base_app(self, environ, start_response):
@@ -403,11 +406,11 @@ class HomeAssistantWSGI(object):
response = self.dispatch_request(request)
if self.cors_origins:
cors_check = (environ.get("HTTP_ORIGIN") in self.cors_origins)
cors_check = (environ.get('HTTP_ORIGIN') in self.cors_origins)
cors_headers = ", ".join(ALLOWED_CORS_HEADERS)
if cors_check:
response.headers[HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN] = \
environ.get("HTTP_ORIGIN")
environ.get('HTTP_ORIGIN')
response.headers[HTTP_HEADER_ACCESS_CONTROL_ALLOW_HEADERS] = \
cors_headers
@@ -425,7 +428,7 @@ class HomeAssistantWSGI(object):
# Strip out any cachebusting MD5 fingerprints
fingerprinted = _FINGERPRINT.match(environ.get('PATH_INFO', ''))
if fingerprinted:
environ['PATH_INFO'] = "{}.{}".format(*fingerprinted.groups())
environ['PATH_INFO'] = '{}.{}'.format(*fingerprinted.groups())
return app(environ, start_response)
@@ -489,6 +492,10 @@ class HomeAssistantView(object):
if self.requires_auth and not authenticated:
_LOGGER.warning('Login attempt or request with an invalid '
'password from %s', request.remote_addr)
persistent_notification.create(
self.hass,
'Invalid password used from {}'.format(request.remote_addr),
'Login attempt failed', NOTIFICATION_ID_LOGIN)
raise Unauthorized()
request.authenticated = authenticated
@@ -512,12 +519,9 @@ class HomeAssistantView(object):
def json(self, result, status_code=200):
"""Return a JSON response."""
msg = json.dumps(
result,
sort_keys=True,
cls=rem.JSONEncoder
).encode('UTF-8')
return self.Response(msg, mimetype="application/json",
status=status_code)
result, sort_keys=True, cls=rem.JSONEncoder).encode('UTF-8')
return self.Response(
msg, mimetype=CONTENT_TYPE_JSON, status=status_code)
def json_message(self, error, status_code=200):
"""Return a JSON message response."""
+26 -26
View File
@@ -9,8 +9,7 @@ https://home-assistant.io/components/hvac.zwave/
import logging
from homeassistant.components.hvac import DOMAIN
from homeassistant.components.hvac import HvacDevice
from homeassistant.components.zwave import (
ATTR_NODE_ID, ATTR_VALUE_ID, ZWaveDeviceEntity)
from homeassistant.components.zwave import ZWaveDeviceEntity
from homeassistant.components import zwave
from homeassistant.const import (TEMP_FAHRENHEIT, TEMP_CELSIUS)
@@ -23,12 +22,6 @@ REMOTEC = 0x5254
REMOTEC_ZXT_120 = 0x8377
REMOTEC_ZXT_120_THERMOSTAT = (REMOTEC, REMOTEC_ZXT_120, 0)
COMMAND_CLASS_SENSOR_MULTILEVEL = 0x31
COMMAND_CLASS_THERMOSTAT_MODE = 0x40
COMMAND_CLASS_THERMOSTAT_SETPOINT = 0x43
COMMAND_CLASS_THERMOSTAT_FAN_MODE = 0x44
COMMAND_CLASS_CONFIGURATION = 0x70
WORKAROUND_ZXT_120 = 'zxt_120'
DEVICE_MAPPINGS = {
@@ -50,8 +43,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
discovery_info, zwave.NETWORK)
return
node = zwave.NETWORK.nodes[discovery_info[ATTR_NODE_ID]]
value = node.values[discovery_info[ATTR_VALUE_ID]]
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([ZWaveHvac(value)])
_LOGGER.debug("discovery_info=%s and zwave.NETWORK=%s",
@@ -107,25 +100,29 @@ class ZWaveHvac(ZWaveDeviceEntity, HvacDevice):
def update_properties(self):
"""Callback on data change for the registered node/value pair."""
# Set point
for value in self._node.get_values(
class_id=COMMAND_CLASS_THERMOSTAT_SETPOINT).values():
for value in (self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_SETPOINT)
.values()):
if int(value.data) != 0:
self._target_temperature = int(value.data)
# Operation Mode
for value in self._node.get_values(
class_id=COMMAND_CLASS_THERMOSTAT_MODE).values():
for value in (self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_MODE)
.values()):
self._current_operation = value.data
self._operation_list = list(value.data_items)
_LOGGER.debug("self._operation_list=%s", self._operation_list)
# Current Temp
for value in self._node.get_values(
class_id=COMMAND_CLASS_SENSOR_MULTILEVEL).values():
for value in (self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_SENSOR_MULTILEVEL)
.values()):
if value.label == 'Temperature':
self._current_temperature = int(value.data)
self._unit = value.units
# Fan Mode
for value in self._node.get_values(
class_id=COMMAND_CLASS_THERMOSTAT_FAN_MODE).values():
for value in (self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_FAN_MODE)
.values()):
self._current_operation_state = value.data
self._fan_list = list(value.data_items)
_LOGGER.debug("self._fan_list=%s", self._fan_list)
@@ -133,8 +130,9 @@ class ZWaveHvac(ZWaveDeviceEntity, HvacDevice):
self._current_operation_state)
# Swing mode
if self._zxt_120 == 1:
for value in self._node.get_values(
class_id=COMMAND_CLASS_CONFIGURATION).values():
for value in (self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_CONFIGURATION)
.values()):
if value.command_class == 112 and value.index == 33:
self._current_swing_mode = value.data
self._swing_list = list(value.data_items)
@@ -199,8 +197,9 @@ class ZWaveHvac(ZWaveDeviceEntity, HvacDevice):
def set_temperature(self, temperature):
"""Set new target temperature."""
for value in self._node.get_values(
class_id=COMMAND_CLASS_THERMOSTAT_SETPOINT).values():
for value in (self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_SETPOINT)
.values()):
if value.command_class != 67:
continue
if self._zxt_120:
@@ -217,8 +216,9 @@ class ZWaveHvac(ZWaveDeviceEntity, HvacDevice):
def set_fan_mode(self, fan):
"""Set new target fan mode."""
for value in self._node.get_values(
class_id=COMMAND_CLASS_THERMOSTAT_FAN_MODE).values():
for value in (self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_FAN_MODE)
.values()):
if value.command_class == 68 and value.index == 0:
value.data = bytes(fan, 'utf-8')
break
@@ -226,7 +226,7 @@ class ZWaveHvac(ZWaveDeviceEntity, HvacDevice):
def set_operation_mode(self, operation_mode):
"""Set new target operation mode."""
for value in self._node.get_values(
class_id=COMMAND_CLASS_THERMOSTAT_MODE).values():
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_MODE).values():
if value.command_class == 64 and value.index == 0:
value.data = bytes(operation_mode, 'utf-8')
break
@@ -235,7 +235,7 @@ class ZWaveHvac(ZWaveDeviceEntity, HvacDevice):
"""Set new target swing mode."""
if self._zxt_120 == 1:
for value in self._node.get_values(
class_id=COMMAND_CLASS_CONFIGURATION).values():
class_id=zwave.const.COMMAND_CLASS_CONFIGURATION).values():
if value.command_class == 112 and value.index == 33:
value.data = bytes(swing_mode, 'utf-8')
break
+4 -3
View File
@@ -28,6 +28,7 @@ DEFAULT_PORT = 8086
DEFAULT_SSL = False
DEFAULT_VERIFY_SSL = False
DOMAIN = 'influxdb'
TIMEOUT = 5
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
@@ -38,8 +39,7 @@ CONFIG_SCHEMA = vol.Schema({
vol.All(cv.ensure_list, [cv.entity_id]),
vol.Optional(CONF_DB_NAME, default=DEFAULT_DATABASE): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_PORT, default=False): cv.boolean,
vol.Optional(CONF_SSL, default=False): cv.boolean,
vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean,
vol.Optional(CONF_TAGS, default={}):
vol.Schema({cv.string: cv.string}),
vol.Optional(CONF_WHITELIST, default=[]):
@@ -70,7 +70,8 @@ def setup(hass, config):
try:
influx = InfluxDBClient(
host=host, port=port, username=username, password=password,
database=database, ssl=ssl, verify_ssl=verify_ssl)
database=database, ssl=ssl, verify_ssl=verify_ssl,
timeout=TIMEOUT)
influx.query("select * from /.*/ LIMIT 1;")
except exceptions.InfluxDBClientError as exc:
_LOGGER.error("Database host is not accessible due to '%s', please "
+6
View File
@@ -6,8 +6,14 @@ https://home-assistant.io/components/introduction/
"""
import logging
import voluptuous as vol
DOMAIN = 'introduction'
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({}),
}, extra=vol.ALLOW_EXTRA)
def setup(hass, config=None):
"""Setup the introduction component."""
+2 -1
View File
@@ -93,7 +93,8 @@ LIGHT_TURN_ON_SCHEMA = vol.Schema({
vol.Coerce(tuple)),
ATTR_XY_COLOR: vol.All(vol.ExactSequence((cv.small_float, cv.small_float)),
vol.Coerce(tuple)),
ATTR_COLOR_TEMP: vol.All(int, vol.Range(min=154, max=500)),
ATTR_COLOR_TEMP: vol.All(int, vol.Range(min=color_util.HASS_COLOR_MIN,
max=color_util.HASS_COLOR_MAX)),
ATTR_WHITE_VALUE: vol.All(int, vol.Range(min=0, max=255)),
ATTR_FLASH: vol.In([FLASH_SHORT, FLASH_LONG]),
ATTR_EFFECT: vol.In([EFFECT_COLORLOOP, EFFECT_RANDOM, EFFECT_WHITE]),
+2 -2
View File
@@ -17,8 +17,8 @@ from homeassistant.components.light import (
PLATFORM_SCHEMA)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['https://github.com/Danielhiversen/flux_led/archive/0.6.zip'
'#flux_led==0.6']
REQUIREMENTS = ['https://github.com/Danielhiversen/flux_led/archive/0.7.zip'
'#flux_led==0.7']
_LOGGER = logging.getLogger(__name__)
+1 -1
View File
@@ -50,7 +50,7 @@ SUPPORT_HUE = (SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP | SUPPORT_EFFECT |
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_ALLOW_UNREACHABLE): cv.boolean,
vol.Optional(CONF_FILENAME): cv.isfile,
vol.Optional(CONF_FILENAME): cv.string,
})
+41 -6
View File
@@ -7,19 +7,35 @@ https://home-assistant.io/components/light.limitlessled/
# pylint: disable=abstract-method
import logging
import voluptuous as vol
from homeassistant.const import (CONF_NAME, CONF_HOST, CONF_PORT)
from homeassistant.components.light import (
ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_EFFECT, ATTR_FLASH, ATTR_RGB_COLOR,
ATTR_TRANSITION, EFFECT_COLORLOOP, EFFECT_WHITE, FLASH_LONG,
SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP, SUPPORT_EFFECT, SUPPORT_FLASH,
SUPPORT_RGB_COLOR, SUPPORT_TRANSITION, Light)
SUPPORT_RGB_COLOR, SUPPORT_TRANSITION, Light, PLATFORM_SCHEMA)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['limitlessled==1.0.2']
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['limitlessled==1.0.0']
RGB_BOUNDARY = 40
DEFAULT_TRANSITION = 0
DEFAULT_PORT = 8899
DEFAULT_VERSION = 5
CONF_BRIDGES = 'bridges'
CONF_GROUPS = 'groups'
CONF_NUMBER = 'number'
CONF_TYPE = 'type'
CONF_VERSION = 'version'
DEFAULT_LED_TYPE = 'rgbw'
DEFAULT_PORT = 8899
DEFAULT_TRANSITION = 0
DEFAULT_VERSION = 5
LED_TYPE = ['rgbw', 'white']
RGB_BOUNDARY = 40
WHITE = [255, 255, 255]
SUPPORT_LIMITLESSLED_WHITE = (SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP |
@@ -28,6 +44,25 @@ SUPPORT_LIMITLESSLED_RGB = (SUPPORT_BRIGHTNESS | SUPPORT_EFFECT |
SUPPORT_FLASH | SUPPORT_RGB_COLOR |
SUPPORT_TRANSITION)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_BRIDGES): vol.All(cv.ensure_list, [
{
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_VERSION,
default=DEFAULT_VERSION): cv.positive_int,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Required(CONF_GROUPS): vol.All(cv.ensure_list, [
{
vol.Required(CONF_NAME): cv.string,
vol.Optional(CONF_TYPE, default=DEFAULT_LED_TYPE):
vol.In(LED_TYPE),
vol.Required(CONF_NUMBER): cv.positive_int,
}
]),
},
]),
})
def rewrite_legacy(config):
"""Rewrite legacy configuration to new format."""
+50 -4
View File
@@ -10,11 +10,12 @@ import voluptuous as vol
import homeassistant.components.mqtt as mqtt
from homeassistant.components.light import (
ATTR_BRIGHTNESS, ATTR_RGB_COLOR, SUPPORT_BRIGHTNESS, SUPPORT_RGB_COLOR,
Light)
ATTR_BRIGHTNESS, ATTR_RGB_COLOR, ATTR_COLOR_TEMP, SUPPORT_BRIGHTNESS,
SUPPORT_RGB_COLOR, SUPPORT_COLOR_TEMP, Light)
from homeassistant.const import (
CONF_NAME, CONF_OPTIMISTIC, CONF_VALUE_TEMPLATE, CONF_PAYLOAD_OFF,
CONF_PAYLOAD_ON, CONF_STATE, CONF_BRIGHTNESS, CONF_RGB)
CONF_PAYLOAD_ON, CONF_STATE, CONF_BRIGHTNESS, CONF_RGB,
CONF_COLOR_TEMP)
from homeassistant.components.mqtt import (
CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN)
import homeassistant.helpers.config_validation as cv
@@ -31,6 +32,9 @@ CONF_RGB_STATE_TOPIC = 'rgb_state_topic'
CONF_RGB_COMMAND_TOPIC = 'rgb_command_topic'
CONF_RGB_VALUE_TEMPLATE = 'rgb_value_template'
CONF_BRIGHTNESS_SCALE = 'brightness_scale'
CONF_COLOR_TEMP_STATE_TOPIC = 'color_temp_state_topic'
CONF_COLOR_TEMP_COMMAND_TOPIC = 'color_temp_command_topic'
CONF_COLOR_TEMP_VALUE_TEMPLATE = 'color_temp_value_template'
DEFAULT_NAME = 'MQTT Light'
DEFAULT_PAYLOAD_ON = 'ON'
@@ -44,6 +48,9 @@ PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_BRIGHTNESS_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_BRIGHTNESS_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_BRIGHTNESS_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_COLOR_TEMP_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_COLOR_TEMP_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_COLOR_TEMP_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_RGB_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_RGB_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_RGB_VALUE_TEMPLATE): cv.template,
@@ -70,12 +77,15 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
CONF_BRIGHTNESS_COMMAND_TOPIC,
CONF_RGB_STATE_TOPIC,
CONF_RGB_COMMAND_TOPIC,
CONF_COLOR_TEMP_STATE_TOPIC,
CONF_COLOR_TEMP_COMMAND_TOPIC
)
},
{
CONF_STATE: config.get(CONF_STATE_VALUE_TEMPLATE),
CONF_BRIGHTNESS: config.get(CONF_BRIGHTNESS_VALUE_TEMPLATE),
CONF_RGB: config.get(CONF_RGB_VALUE_TEMPLATE)
CONF_RGB: config.get(CONF_RGB_VALUE_TEMPLATE),
CONF_COLOR_TEMP: config.get(CONF_COLOR_TEMP_VALUE_TEMPLATE)
},
config.get(CONF_QOS),
config.get(CONF_RETAIN),
@@ -92,6 +102,7 @@ class MqttLight(Light):
"""MQTT light."""
# pylint: disable=too-many-arguments,too-many-instance-attributes
# pylint: disable=too-many-locals,too-many-branches
def __init__(self, hass, name, topic, templates, qos, retain, payload,
optimistic, brightness_scale):
"""Initialize MQTT light."""
@@ -106,6 +117,8 @@ class MqttLight(Light):
optimistic or topic[CONF_RGB_STATE_TOPIC] is None
self._optimistic_brightness = (
optimistic or topic[CONF_BRIGHTNESS_STATE_TOPIC] is None)
self._optimistic_color_temp = (
optimistic or topic[CONF_COLOR_TEMP_STATE_TOPIC] is None)
self._brightness_scale = brightness_scale
self._state = False
self._supported_features = 0
@@ -114,6 +127,9 @@ class MqttLight(Light):
self._supported_features |= (
topic[CONF_BRIGHTNESS_STATE_TOPIC] is not None and
SUPPORT_BRIGHTNESS)
self._supported_features |= (
topic[CONF_COLOR_TEMP_STATE_TOPIC] is not None and
SUPPORT_COLOR_TEMP)
for key, tpl in list(templates.items()):
if tpl is None:
@@ -168,6 +184,21 @@ class MqttLight(Light):
else:
self._rgb = None
def color_temp_received(topic, payload, qos):
"""A new MQTT message for color temp has been received."""
self._color_temp = int(templates[CONF_COLOR_TEMP](payload))
self.update_ha_state()
if self._topic[CONF_COLOR_TEMP_STATE_TOPIC] is not None:
mqtt.subscribe(
self._hass, self._topic[CONF_COLOR_TEMP_STATE_TOPIC],
color_temp_received, self._qos)
self._color_temp = 150
if self._topic[CONF_COLOR_TEMP_COMMAND_TOPIC] is not None:
self._color_temp = 150
else:
self._color_temp = None
@property
def brightness(self):
"""Return the brightness of this light between 0..255."""
@@ -178,6 +209,11 @@ class MqttLight(Light):
"""Return the RGB color value."""
return self._rgb
@property
def color_temp(self):
"""Return the color temperature in mired."""
return self._color_temp
@property
def should_poll(self):
"""No polling needed for a MQTT light."""
@@ -230,6 +266,16 @@ class MqttLight(Light):
self._brightness = kwargs[ATTR_BRIGHTNESS]
should_update = True
if ATTR_COLOR_TEMP in kwargs and \
self._topic[CONF_COLOR_TEMP_COMMAND_TOPIC] is not None:
color_temp = int(kwargs[ATTR_COLOR_TEMP])
mqtt.publish(
self._hass, self._topic[CONF_COLOR_TEMP_COMMAND_TOPIC],
color_temp, self._qos, self._retain)
if self._optimistic_color_temp:
self._color_temp = kwargs[ATTR_COLOR_TEMP]
should_update = True
mqtt.publish(self._hass, self._topic[CONF_COMMAND_TOPIC],
self._payload['on'], self._qos, self._retain)
+8 -8
View File
@@ -50,19 +50,19 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
if discovery_info is None or zwave.NETWORK is None:
return
node = zwave.NETWORK.nodes[discovery_info[zwave.ATTR_NODE_ID]]
value = node.values[discovery_info[zwave.ATTR_VALUE_ID]]
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.COMMAND_CLASS_SWITCH_MULTILEVEL:
if value.command_class != zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL:
return
if value.type != zwave.TYPE_BYTE:
if value.type != zwave.const.TYPE_BYTE:
return
if value.genre != zwave.GENRE_USER:
if value.genre != zwave.const.GENRE_USER:
return
value.set_change_verified(False)
if node.has_command_class(zwave.COMMAND_CLASS_COLOR):
if node.has_command_class(zwave.const.COMMAND_CLASS_SWITCH_COLOR):
try:
add_devices([ZwaveColorLight(value)])
except ValueError as exception:
@@ -195,8 +195,8 @@ class ZwaveColorLight(ZwaveDimmer):
raise ValueError("No matching color command found.")
for value_color_channels in value.node.get_values(
class_id=zwave.COMMAND_CLASS_COLOR, genre='System',
type="Int").values():
class_id=zwave.const.COMMAND_CLASS_SWITCH_COLOR,
genre='System', type="Int").values():
self._value_color_channels = value_color_channels
if self._value_color_channels is None:
+23 -17
View File
@@ -1,28 +1,35 @@
"""
LIRC interface to receive signals from a infrared remote control.
This sensor will momentarily set state to various values as defined
in the .lintrc file which can be interpreted in home-assistant to
trigger various actions.
Sending signals to other IR receivers can be accomplished with the
shell_command component and the irsend command for now.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/lirc/
"""
# pylint: disable=import-error
import threading
import time
import logging
from homeassistant.const import (EVENT_HOMEASSISTANT_STOP,
EVENT_HOMEASSISTANT_START)
import voluptuous as vol
from homeassistant.const import (
EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_START)
DOMAIN = "lirc"
REQUIREMENTS = ['python-lirc==1.2.1']
_LOGGER = logging.getLogger(__name__)
ICON = 'mdi:remote'
EVENT_IR_COMMAND_RECEIVED = 'ir_command_received'
BUTTON_NAME = 'button_name'
DOMAIN = 'lirc'
EVENT_IR_COMMAND_RECEIVED = 'ir_command_received'
ICON = 'mdi:remote'
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({}),
}, extra=vol.ALLOW_EXTRA)
def setup(hass, config):
"""Setup LIRC capability."""
@@ -65,20 +72,19 @@ class LircInterface(threading.Thread):
def run(self):
"""Main loop of LIRC interface thread."""
import lirc
_LOGGER.debug('LIRC interface thread started')
_LOGGER.debug("LIRC interface thread started")
while not self.stopped.isSet():
try:
code = lirc.nextcode() # list; empty if no buttons pressed
except lirc.NextCodeError:
_LOGGER.warning('Encountered error reading '
'next code from LIRC')
_LOGGER.warning("Error reading next code from LIRC")
code = None
# interpret result from python-lirc
if code:
code = code[0]
_LOGGER.info('Got new LIRC code %s', code)
self.hass.bus.fire(EVENT_IR_COMMAND_RECEIVED,
{BUTTON_NAME: code})
_LOGGER.info("Got new LIRC code %s", code)
self.hass.bus.fire(
EVENT_IR_COMMAND_RECEIVED, {BUTTON_NAME: code})
else:
time.sleep(0.2)
lirc.deinit()
+5 -5
View File
@@ -16,14 +16,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
if discovery_info is None or zwave.NETWORK is None:
return
node = zwave.NETWORK.nodes[discovery_info[zwave.ATTR_NODE_ID]]
value = node.values[discovery_info[zwave.ATTR_VALUE_ID]]
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.COMMAND_CLASS_DOOR_LOCK:
if value.command_class != zwave.const.COMMAND_CLASS_DOOR_LOCK:
return
if value.type != zwave.TYPE_BOOL:
if value.type != zwave.const.TYPE_BOOL:
return
if value.genre != zwave.GENRE_USER:
if value.genre != zwave.const.GENRE_USER:
return
value.set_change_verified(False)
+13 -3
View File
@@ -4,6 +4,7 @@ Event parser and human readable log generator.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/logbook/
"""
import asyncio
import logging
from datetime import timedelta
from itertools import groupby
@@ -20,6 +21,7 @@ from homeassistant.const import (EVENT_HOMEASSISTANT_START,
STATE_NOT_HOME, STATE_OFF, STATE_ON,
ATTR_HIDDEN)
from homeassistant.core import State, split_entity_id, DOMAIN as HA_DOMAIN
from homeassistant.util.async import run_callback_threadsafe
DOMAIN = "logbook"
DEPENDENCIES = ['recorder', 'frontend']
@@ -57,6 +59,13 @@ LOG_MESSAGE_SCHEMA = vol.Schema({
def log_entry(hass, name, message, domain=None, entity_id=None):
"""Add an entry to the logbook."""
run_callback_threadsafe(
hass.loop, async_log_entry, hass, name, message, domain, entity_id
).result()
def async_log_entry(hass, name, message, domain=None, entity_id=None):
"""Add an entry to the logbook."""
data = {
ATTR_NAME: name,
@@ -67,11 +76,12 @@ def log_entry(hass, name, message, domain=None, entity_id=None):
data[ATTR_DOMAIN] = domain
if entity_id is not None:
data[ATTR_ENTITY_ID] = entity_id
hass.bus.fire(EVENT_LOGBOOK_ENTRY, data)
hass.bus.async_fire(EVENT_LOGBOOK_ENTRY, data)
def setup(hass, config):
"""Listen for download events to download files."""
@asyncio.coroutine
def log_message(service):
"""Handle sending notification message service calls."""
message = service.data[ATTR_MESSAGE]
@@ -80,8 +90,8 @@ def setup(hass, config):
entity_id = service.data.get(ATTR_ENTITY_ID)
message.hass = hass
message = message.render()
log_entry(hass, name, message, domain, entity_id)
message = message.async_render()
async_log_entry(hass, name, message, domain, entity_id)
hass.wsgi.register_view(LogbookView(hass, config))
@@ -79,18 +79,11 @@ class LogitechMediaServer(object):
def _get_http_port(self):
"""Get http port from media server, it is used to get cover art."""
http_port = None
try:
http_port = self.query('pref', 'httpport', '?')
if not http_port:
_LOGGER.error("Unable to read data from server %s:%s",
self.host, self.port)
return
return http_port
except ConnectionError as ex:
_LOGGER.error("Failed to connect to server %s:%s - %s",
self.host, self.port, ex)
return
http_port = self.query('pref', 'httpport', '?')
if not http_port:
_LOGGER.error("Failed to connect to server %s:%s",
self.host, self.port)
return http_port
def create_players(self):
"""Create a list of SqueezeBoxDevices connected to the LMS."""
@@ -104,20 +97,27 @@ class LogitechMediaServer(object):
def query(self, *parameters):
"""Send request and await response from server."""
telnet = telnetlib.Telnet(self.host, self.port)
if self._username and self._password:
telnet.write('login {username} {password}\n'.format(
username=self._username,
password=self._password).encode('UTF-8'))
telnet.read_until(b'\n', timeout=3)
message = '{}\n'.format(' '.join(parameters))
telnet.write(message.encode('UTF-8'))
response = telnet.read_until(b'\n', timeout=3)\
.decode('UTF-8')\
.split(' ')[-1]\
.strip()
telnet.write(b'exit\n')
return urllib.parse.unquote(response)
try:
telnet = telnetlib.Telnet(self.host, self.port)
if self._username and self._password:
telnet.write('login {username} {password}\n'.format(
username=self._username,
password=self._password).encode('UTF-8'))
telnet.read_until(b'\n', timeout=3)
message = '{}\n'.format(' '.join(parameters))
telnet.write(message.encode('UTF-8'))
response = telnet.read_until(b'\n', timeout=3)\
.decode('UTF-8')\
.split(' ')[-1]\
.strip()
telnet.write(b'exit\n')
return urllib.parse.unquote(response)
except (OSError, ConnectionError) as error:
_LOGGER.error("Could not communicate with %s:%d: %s",
self.host,
self.port,
error)
return None
def get_player_status(self, player):
"""Get ithe status of a player."""
@@ -128,18 +128,24 @@ class LogitechMediaServer(object):
# K (artwork_url): URL to remote artwork
tags = 'adK'
new_status = {}
telnet = telnetlib.Telnet(self.host, self.port)
telnet.write('{player} status - 1 tags:{tags}\n'.format(
player=player,
tags=tags
try:
telnet = telnetlib.Telnet(self.host, self.port)
telnet.write('{player} status - 1 tags:{tags}\n'.format(
player=player,
tags=tags
).encode('UTF-8'))
response = telnet.read_until(b'\n', timeout=3)\
.decode('UTF-8')\
.split(' ')
telnet.write(b'exit\n')
for item in response:
parts = urllib.parse.unquote(item).partition(':')
new_status[parts[0]] = parts[2]
response = telnet.read_until(b'\n', timeout=3)\
.decode('UTF-8')\
.split(' ')
telnet.write(b'exit\n')
for item in response:
parts = urllib.parse.unquote(item).partition(':')
new_status[parts[0]] = parts[2]
except (OSError, ConnectionError) as error:
_LOGGER.error("Could not communicate with %s:%d: %s",
self.host,
self.port,
error)
return new_status
@@ -8,24 +8,31 @@ import logging
from datetime import timedelta
from urllib.parse import urlparse
import voluptuous as vol
import homeassistant.util as util
from homeassistant.components.media_player import (
SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK,
SUPPORT_TURN_OFF, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP,
SUPPORT_SELECT_SOURCE, SUPPORT_PLAY_MEDIA, MEDIA_TYPE_CHANNEL,
MediaPlayerDevice)
MediaPlayerDevice, PLATFORM_SCHEMA)
from homeassistant.const import (
CONF_HOST, CONF_CUSTOMIZE, STATE_OFF, STATE_PLAYING, STATE_PAUSED,
STATE_UNKNOWN)
STATE_UNKNOWN, CONF_NAME)
from homeassistant.loader import get_component
_CONFIGURING = {}
_LOGGER = logging.getLogger(__name__)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['https://github.com/TheRealLink/pylgtv'
'/archive/v0.1.2.zip'
'#pylgtv==0.1.2']
_CONFIGURING = {}
_LOGGER = logging.getLogger(__name__)
CONF_SOURCES = 'sources'
DEFAULT_NAME = 'LG WebOS Smart TV'
SUPPORT_WEBOSTV = SUPPORT_PAUSE | SUPPORT_VOLUME_STEP | \
SUPPORT_VOLUME_MUTE | SUPPORT_PREVIOUS_TRACK | \
SUPPORT_NEXT_TRACK | SUPPORT_TURN_OFF | \
@@ -44,6 +51,17 @@ WEBOS_APPS_SHORT = {
'makotv': WEBOS_APP_MAKO
}
CUSTOMIZE_SCHEMA = vol.Schema({
vol.Optional(CONF_SOURCES):
vol.All(cv.ensure_list, [vol.In(WEBOS_APPS_SHORT)]),
})
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_HOST): cv.string,
vol.Optional(CONF_CUSTOMIZE, default={}): CUSTOMIZE_SCHEMA,
})
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
@@ -51,21 +69,22 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
if discovery_info is not None:
host = urlparse(discovery_info[1]).hostname
else:
host = config.get(CONF_HOST, None)
host = config.get(CONF_HOST)
if host is None:
_LOGGER.error('No host found in configuration')
_LOGGER.error("No host found in configuration")
return False
# Only act if we are not already configuring this host
if host in _CONFIGURING:
return
customize = config.get(CONF_CUSTOMIZE, {})
setup_tv(host, customize, hass, add_devices)
name = config.get(CONF_NAME)
customize = config.get(CONF_CUSTOMIZE)
setup_tv(host, name, customize, hass, add_devices)
def setup_tv(host, customize, hass, add_devices):
def setup_tv(host, name, customize, hass, add_devices):
"""Setup a phue bridge based on host parameter."""
from pylgtv import WebOsClient
from pylgtv import PyLGTVPairException
@@ -79,15 +98,15 @@ def setup_tv(host, customize, hass, add_devices):
client.register()
except PyLGTVPairException:
_LOGGER.warning(
'Connected to LG WebOS TV at %s but not paired.', host)
"Connected to LG WebOS TV at %s but not paired", host)
return
except OSError:
_LOGGER.error('Unable to connect to host %s.', host)
_LOGGER.error("Unable to connect to host %s", host)
return
else:
# Not registered, request configuration.
_LOGGER.warning('LG WebOS TV at %s needs to be paired.', host)
request_configuration(host, customize, hass, add_devices)
request_configuration(host, name, customize, hass, add_devices)
return
# If we came here and configuring this host, mark as done.
@@ -96,10 +115,10 @@ def setup_tv(host, customize, hass, add_devices):
configurator = get_component('configurator')
configurator.request_done(request_id)
add_devices([LgWebOSDevice(host, customize)])
add_devices([LgWebOSDevice(host, name, customize)])
def request_configuration(host, customize, hass, add_devices):
def request_configuration(host, name, customize, hass, add_devices):
"""Request configuration steps from the user."""
configurator = get_component('configurator')
@@ -112,7 +131,7 @@ def request_configuration(host, customize, hass, add_devices):
# pylint: disable=unused-argument
def lgtv_configuration_callback(data):
"""The actions to do when our configuration callback is called."""
setup_tv(host, customize, hass, add_devices)
setup_tv(host, name, customize, hass, add_devices)
_CONFIGURING[host] = configurator.request_config(
hass, 'LG WebOS TV', lgtv_configuration_callback,
@@ -128,13 +147,13 @@ class LgWebOSDevice(MediaPlayerDevice):
"""Representation of a LG WebOS TV."""
# pylint: disable=too-many-public-methods
def __init__(self, host, customize):
def __init__(self, host, name, customize):
"""Initialize the webos device."""
from pylgtv import WebOsClient
self._client = WebOsClient(host)
self._customize = customize
self._name = 'LG WebOS TV Remote'
self._name = name
# Assume that the TV is not muted
self._muted = False
# Assume that the TV is in Play mode
@@ -160,7 +179,7 @@ class LgWebOSDevice(MediaPlayerDevice):
self._app_list = {}
custom_sources = []
for source in self._customize.get('sources', []):
for source in self._customize.get(CONF_SOURCES, []):
app_id = WEBOS_APPS_SHORT.get(source, None)
if app_id:
custom_sources.append(app_id)
+17 -9
View File
@@ -4,6 +4,7 @@ Support for MQTT message handling.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/mqtt/
"""
import asyncio
import logging
import os
import socket
@@ -14,8 +15,8 @@ import voluptuous as vol
from homeassistant.bootstrap import prepare_setup_platform
from homeassistant.config import load_yaml_config_file
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import template
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers import template, config_validation as cv
from homeassistant.helpers.event import threaded_listener_factory
from homeassistant.const import (
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP,
CONF_PLATFORM, CONF_SCAN_INTERVAL, CONF_VALUE_TEMPLATE)
@@ -162,21 +163,28 @@ def publish_template(hass, topic, payload_template, qos=None, retain=None):
hass.services.call(DOMAIN, SERVICE_PUBLISH, data)
def subscribe(hass, topic, callback, qos=DEFAULT_QOS):
def async_subscribe(hass, topic, callback, qos=DEFAULT_QOS):
"""Subscribe to an MQTT topic."""
@asyncio.coroutine
def mqtt_topic_subscriber(event):
"""Match subscribed MQTT topic."""
if _match_topic(topic, event.data[ATTR_TOPIC]):
callback(event.data[ATTR_TOPIC], event.data[ATTR_PAYLOAD],
event.data[ATTR_QOS])
if not _match_topic(topic, event.data[ATTR_TOPIC]):
return
remove = hass.bus.listen(EVENT_MQTT_MESSAGE_RECEIVED,
mqtt_topic_subscriber)
hass.async_run_job(callback, event.data[ATTR_TOPIC],
event.data[ATTR_PAYLOAD], event.data[ATTR_QOS])
async_remove = hass.bus.async_listen(EVENT_MQTT_MESSAGE_RECEIVED,
mqtt_topic_subscriber)
# Future: track subscriber count and unsubscribe in remove
MQTT_CLIENT.subscribe(topic, qos)
return remove
return async_remove
# pylint: disable=invalid-name
subscribe = threaded_listener_factory(async_subscribe)
def _setup_server(hass, config):
+4 -1
View File
@@ -158,7 +158,8 @@ def setup(hass, config): # pylint: disable=too-many-locals
'No devices could be setup as gateways, check your configuration')
return False
for component in 'sensor', 'switch', 'light', 'binary_sensor', 'climate':
for component in ['sensor', 'switch', 'light', 'binary_sensor', 'climate',
'cover']:
discovery.load_platform(hass, component, DOMAIN, {}, config)
return True
@@ -340,5 +341,7 @@ class MySensorsDeviceEntity(object):
set_req.V_LOCK_STATUS, set_req.V_TRIPPED):
self._values[value_type] = (
STATE_ON if int(value) == 1 else STATE_OFF)
elif value_type == set_req.V_DIMMER:
self._values[value_type] = int(value)
else:
self._values[value_type] = value
+1 -1
View File
@@ -14,7 +14,7 @@ from homeassistant.const import (CONF_PASSWORD, CONF_USERNAME, CONF_STRUCTURE)
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['python-nest==2.9.2']
REQUIREMENTS = ['python-nest==2.10.0']
DOMAIN = 'nest'
+2 -2
View File
@@ -37,7 +37,7 @@ CONFIG_SCHEMA = vol.Schema({
def setup(hass, config):
"""Setup the Netatmo devices."""
"""Set up the Netatmo devices."""
import lnetatmo
global NETATMO_AUTH
@@ -47,7 +47,7 @@ def setup(hass, config):
config[DOMAIN][CONF_USERNAME], config[DOMAIN][CONF_PASSWORD],
'read_station read_camera access_camera')
except HTTPError:
_LOGGER.error("Unable to connect to NatAtmo API")
_LOGGER.error("Unable to connect to Netatmo API")
return False
for component in 'camera', 'sensor':
+1 -1
View File
@@ -42,7 +42,7 @@ PLATFORM_SCHEMA = vol.Schema({
NOTIFY_SERVICE_SCHEMA = vol.Schema({
vol.Required(ATTR_MESSAGE): cv.template,
vol.Optional(ATTR_TITLE): cv.template,
vol.Optional(ATTR_TARGET): cv.string,
vol.Optional(ATTR_TARGET): vol.All(cv.ensure_list, [cv.string]),
vol.Optional(ATTR_DATA): dict,
})
@@ -79,9 +79,6 @@ class AWSLambda(BaseNotificationService):
_LOGGER.info("At least 1 target is required")
return
if not isinstance(targets, list):
targets = [targets]
for target in targets:
cleaned_kwargs = dict((k, v) for k, v in kwargs.items() if v)
payload = {"message": message}
@@ -70,9 +70,6 @@ class AWSSNS(BaseNotificationService):
_LOGGER.info("At least 1 target is required")
return
if not isinstance(targets, list):
targets = [targets]
message_attributes = {k: {"StringValue": json.dumps(v),
"DataType": "String"}
for k, v in kwargs.items() if v}
@@ -69,9 +69,6 @@ class AWSSQS(BaseNotificationService):
_LOGGER.info("At least 1 target is required")
return
if not isinstance(targets, list):
targets = [targets]
for target in targets:
cleaned_kwargs = dict((k, v) for k, v in kwargs.items() if v)
message_body = {"message": message}
-2
View File
@@ -359,8 +359,6 @@ class HTML5NotificationService(BaseNotificationService):
if not targets:
targets = self.registrations.keys()
elif not isinstance(targets, list):
targets = [targets]
for target in targets:
info = self.registrations.get(target)
@@ -58,9 +58,6 @@ class MessageBirdNotificationService(BaseNotificationService):
_LOGGER.error('No target specified.')
return
if not isinstance(targets, list):
targets = [targets]
for target in targets:
try:
self.client.message_create(self.sender,
@@ -87,10 +87,6 @@ class PushBulletNotificationService(BaseNotificationService):
_LOGGER.info('Sent notification to self')
return
# Make list if not so
if not isinstance(targets, list):
targets = [targets]
# Main loop, Process all targets specified
for target in targets:
try:
+24 -18
View File
@@ -18,45 +18,51 @@ REQUIREMENTS = ['pushetta==1.0.15']
CONF_CHANNEL_NAME = 'channel_name'
CONF_SEND_TEST_MSG = 'send_test_msg'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_API_KEY): cv.string,
vol.Required(CONF_CHANNEL_NAME): cv.string,
vol.Optional(CONF_SEND_TEST_MSG, default=False): cv.boolean,
})
def get_service(hass, config):
"""Get the Pushetta notification service."""
from pushetta import Pushetta, exceptions
pushetta_service = PushettaNotificationService(config[CONF_API_KEY],
config[CONF_CHANNEL_NAME],
config[CONF_SEND_TEST_MSG])
try:
pushetta = Pushetta(config[CONF_API_KEY])
pushetta.pushMessage(config[CONF_CHANNEL_NAME],
"Home Assistant started")
except exceptions.TokenValidationError:
_LOGGER.error("Please check your access token")
return None
except exceptions.ChannelNotFoundError:
_LOGGER.error("Channel '%s' not found", config[CONF_CHANNEL_NAME])
return None
return PushettaNotificationService(config[CONF_API_KEY],
config[CONF_CHANNEL_NAME])
if pushetta_service.is_valid:
return pushetta_service
# pylint: disable=too-few-public-methods
class PushettaNotificationService(BaseNotificationService):
"""Implement the notification service for Pushetta."""
def __init__(self, api_key, channel_name):
def __init__(self, api_key, channel_name, send_test_msg):
"""Initialize the service."""
from pushetta import Pushetta
self._api_key = api_key
self._channel_name = channel_name
self.pushetta = Pushetta(self._api_key)
self.is_valid = True
self.pushetta = Pushetta(api_key)
if send_test_msg:
self.send_message("Home Assistant started")
def send_message(self, message="", **kwargs):
"""Send a message to a user."""
from pushetta import exceptions
title = kwargs.get(ATTR_TITLE, ATTR_TITLE_DEFAULT)
self.pushetta.pushMessage(self._channel_name,
"{} {}".format(title, message))
try:
self.pushetta.pushMessage(self._channel_name,
"{} {}".format(title, message))
except exceptions.TokenValidationError:
_LOGGER.error("Please check your access token")
self.is_valid = False
except exceptions.ChannelNotFoundError:
_LOGGER.error("Channel '%s' not found", self._channel_name)
self.is_valid = False
+11 -9
View File
@@ -61,13 +61,15 @@ class PushoverNotificationService(BaseNotificationService):
data['title'] = kwargs.get(ATTR_TITLE, ATTR_TITLE_DEFAULT)
target = kwargs.get(ATTR_TARGET)
if target is not None:
data['device'] = target
targets = kwargs.get(ATTR_TARGET)
try:
self.pushover.send_message(message, **data)
except ValueError as val_err:
_LOGGER.error(str(val_err))
except RequestError:
_LOGGER.exception('Could not send pushover notification')
for target in targets:
if target is not None:
data['device'] = target
try:
self.pushover.send_message(message, **data)
except ValueError as val_err:
_LOGGER.error(str(val_err))
except RequestError:
_LOGGER.exception('Could not send pushover notification')
+4 -2
View File
@@ -75,8 +75,10 @@ class RestNotificationService(BaseNotificationService):
data[self._title_param_name] = kwargs.get(ATTR_TITLE,
ATTR_TITLE_DEFAULT)
if self._target_param_name is not None:
data[self._target_param_name] = kwargs.get(ATTR_TARGET)
if self._target_param_name is not None and ATTR_TARGET in kwargs:
# Target is a list as of 0.29 and we don't want to break existing
# integrations, so just return the first target in the list.
data[self._target_param_name] = kwargs[ATTR_TARGET][0]
if self._method == 'POST':
response = requests.post(self._resource, data=data, timeout=10)
@@ -11,9 +11,9 @@ notify:
example: 'Your Garage Door Friend'
target:
description: Target of the notification. Optional depending on the platform
description: An array of targets to send the notification to. Optional depending on the platform.
example: platform specific
data:
description: Extended information for notification. Optional depending on the platform
description: Extended information for notification. Optional depending on the platform.
example: platform specific
+14 -11
View File
@@ -9,7 +9,7 @@ import logging
import voluptuous as vol
from homeassistant.components.notify import (
PLATFORM_SCHEMA, BaseNotificationService)
ATTR_TARGET, PLATFORM_SCHEMA, BaseNotificationService)
from homeassistant.const import (
CONF_API_KEY, CONF_USERNAME, CONF_ICON)
import homeassistant.helpers.config_validation as cv
@@ -68,16 +68,19 @@ class SlackNotificationService(BaseNotificationService):
"""Send a message to a user."""
import slacker
channel = kwargs.get('target') or self._default_channel
targets = kwargs.get(ATTR_TARGET, [self._default_channel])
data = kwargs.get('data')
attachments = data.get('attachments') if data else None
try:
self.slack.chat.post_message(channel, message,
as_user=self._as_user,
username=self._username,
icon_emoji=self._icon,
attachments=attachments,
link_names=True)
except slacker.Error as err:
_LOGGER.error("Could not send slack notification. Error: %s", err)
for target in targets:
try:
self.slack.chat.post_message(target, message,
as_user=self._as_user,
username=self._username,
icon_emoji=self._icon,
attachments=attachments,
link_names=True)
except slacker.Error as err:
_LOGGER.error("Could not send slack notification. Error: %s",
err)
@@ -57,9 +57,6 @@ class TwilioSMSNotificationService(BaseNotificationService):
_LOGGER.info("At least 1 target is required")
return
if not isinstance(targets, list):
targets = [targets]
for target in targets:
self.client.messages.create(to=target, body=message,
from_=self.from_number)
+2 -2
View File
@@ -27,7 +27,7 @@ DEPENDENCIES = ['ffmpeg']
REQUIREMENTS = [
'https://github.com/pvizeli/cloudapi/releases/download/1.0.2/'
'python-1.0.2.zip#cloud_api==1.0.2',
'ha-alpr==0.2']
'ha-alpr==0.3']
_LOGGER = logging.getLogger(__name__)
@@ -68,7 +68,7 @@ DEFAULT_NAME = 'OpenAlpr'
DEFAULT_ENGINE = ENGINE_LOCAL
DEFAULT_RENDER = RENDER_FFMPEG
DEFAULT_BINARY = 'alpr'
DEFAULT_INTERVAL = 2
DEFAULT_INTERVAL = 10
DEFAULT_CONFIDENCE = 80.0
DEVICE_SCHEMA = vol.Schema({
+27 -25
View File
@@ -12,9 +12,6 @@ from homeassistant.components.zwave import ZWaveDeviceEntity
from homeassistant.components import zwave
from homeassistant.components.rollershutter import RollershutterDevice
COMMAND_CLASS_SWITCH_MULTILEVEL = 0x26 # 38
COMMAND_CLASS_SWITCH_BINARY = 0x25 # 37
SOMFY = 0x47
SOMFY_ZRTSI = 0x5a52
SOMFY_ZRTSI_CONTROLLER = (SOMFY, SOMFY_ZRTSI)
@@ -32,10 +29,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
if discovery_info is None or zwave.NETWORK is None:
return
node = zwave.NETWORK.nodes[discovery_info[zwave.ATTR_NODE_ID]]
value = node.values[discovery_info[zwave.ATTR_VALUE_ID]]
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.COMMAND_CLASS_SWITCH_MULTILEVEL:
if value.command_class != zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL:
return
if value.index != 0:
return
@@ -82,9 +79,10 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, RollershutterDevice):
"""Callback on data change for the registered node/value pair."""
# Position value
for value in self._node.get_values(
class_id=COMMAND_CLASS_SWITCH_MULTILEVEL).values():
if value.command_class == zwave.COMMAND_CLASS_SWITCH_MULTILEVEL \
and value.label == 'Level':
class_id=zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL).values():
if value.command_class == \
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL and \
value.label == 'Level':
self._current_position = value.data
@property
@@ -101,23 +99,26 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, RollershutterDevice):
def move_up(self, **kwargs):
"""Move the roller shutter up."""
for value in self._node.get_values(
class_id=COMMAND_CLASS_SWITCH_MULTILEVEL).values():
if value.command_class == zwave.COMMAND_CLASS_SWITCH_MULTILEVEL \
and value.label == 'Open' or \
value.command_class == zwave.COMMAND_CLASS_SWITCH_MULTILEVEL \
and value.label == 'Down':
for value in (self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL)
.values()):
if value.command_class == \
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL and value.label == \
'Open' or value.command_class == \
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL and value.label == \
'Down':
self._lozwmgr.pressButton(value.value_id)
break
def move_down(self, **kwargs):
"""Move the roller shutter down."""
for value in self._node.get_values(
class_id=COMMAND_CLASS_SWITCH_MULTILEVEL).values():
if value.command_class == zwave.COMMAND_CLASS_SWITCH_MULTILEVEL \
and value.label == 'Up' or \
value.command_class == zwave.COMMAND_CLASS_SWITCH_MULTILEVEL \
and value.label == 'Close':
class_id=zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL).values():
if value.command_class == \
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL and value.label == \
'Up' or value.command_class == \
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL and value.label == \
'Close':
self._lozwmgr.pressButton(value.value_id)
break
@@ -128,10 +129,11 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, RollershutterDevice):
def stop(self, **kwargs):
"""Stop the roller shutter."""
for value in self._node.get_values(
class_id=COMMAND_CLASS_SWITCH_MULTILEVEL).values():
if value.command_class == zwave.COMMAND_CLASS_SWITCH_MULTILEVEL \
and value.label == 'Open' or \
value.command_class == zwave.COMMAND_CLASS_SWITCH_MULTILEVEL \
and value.label == 'Down':
class_id=zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL).values():
if value.command_class == \
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL and value.label == \
'Open' or value.command_class == \
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL and value.label == \
'Down':
self._lozwmgr.releaseButton(value.value_id)
break
@@ -12,7 +12,7 @@ from homeassistant.helpers.entity import generate_entity_id
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = [
'https://github.com/sander76/powerviewApi/'
'archive/master.zip#powerviewApi==0.2']
'archive/cc6f75dd39160d4aaf46cb2ed9220136b924bcb4.zip#powerviewApi==0.2']
HUB_ADDRESS = 'address'
+4 -2
View File
@@ -23,6 +23,7 @@ from homeassistant.helpers.script import Script
DOMAIN = "script"
ENTITY_ID_FORMAT = DOMAIN + '.{}'
GROUP_NAME_ALL_SCRIPTS = 'all scripts'
DEPENDENCIES = ["group"]
CONF_SEQUENCE = "sequence"
@@ -73,7 +74,8 @@ def toggle(hass, entity_id):
def setup(hass, config):
"""Load the scripts from the configuration."""
component = EntityComponent(_LOGGER, DOMAIN, hass)
component = EntityComponent(_LOGGER, DOMAIN, hass,
group_name=GROUP_NAME_ALL_SCRIPTS)
def service_handler(service):
"""Execute a service call to script.<script name>."""
@@ -124,7 +126,7 @@ class ScriptEntity(ToggleEntity):
def __init__(self, hass, object_id, name, sequence):
"""Initialize the script."""
self.entity_id = ENTITY_ID_FORMAT.format(object_id)
self.script = Script(hass, sequence, name, self.update_ha_state)
self.script = Script(hass, sequence, name, self.async_update_ha_state)
@property
def should_poll(self):
+58 -42
View File
@@ -1,5 +1,5 @@
"""
The arest sensor will consume an exposed aREST API of a device.
Support for an exposed aREST RESTful API of a device.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.arest/
@@ -8,30 +8,48 @@ import logging
from datetime import timedelta
import requests
import voluptuous as vol
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (
ATTR_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE, DEVICE_DEFAULT_NAME,
CONF_RESOURCE, CONF_MONITORED_VARIABLES)
CONF_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE, CONF_RESOURCE,
CONF_MONITORED_VARIABLES, CONF_NAME, STATE_UNKNOWN)
from homeassistant.exceptions import TemplateError
from homeassistant.helpers.entity import Entity
from homeassistant.helpers import template
from homeassistant.util import Throttle
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30)
CONF_FUNCTIONS = 'functions'
CONF_PINS = 'pins'
DEFAULT_NAME = 'aREST sensor'
PIN_VARIABLE_SCHEMA = vol.Schema({
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string,
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
})
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_RESOURCE): cv.url,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PINS, default={}):
vol.Schema({cv.string: PIN_VARIABLE_SCHEMA}),
vol.Optional(CONF_MONITORED_VARIABLES, default={}):
vol.Schema({cv.string: PIN_VARIABLE_SCHEMA}),
})
# pylint: disable=too-many-locals
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the aREST sensor."""
resource = config.get(CONF_RESOURCE)
var_conf = config.get(CONF_MONITORED_VARIABLES)
pins = config.get('pins', None)
if resource is None:
_LOGGER.error('Not all required config keys present: %s',
CONF_RESOURCE)
return False
pins = config.get(CONF_PINS)
try:
response = requests.get(resource, timeout=10).json()
@@ -52,11 +70,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
if value_template is None:
return lambda value: value
value_template = template.Template(value_template, hass)
value_template.hass = hass
def _render(value):
try:
return value_template.render({'value': value})
return value_template.async_render({'value': value})
except TemplateError:
_LOGGER.exception('Error parsing value')
return value
@@ -66,33 +84,26 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
dev = []
if var_conf is not None:
for variable in var_conf:
if variable['name'] not in response['variables']:
_LOGGER.error('Variable: "%s" does not exist',
variable['name'])
for variable, var_data in var_conf.items():
if variable not in response['variables']:
_LOGGER.error("Variable: '%s' does not exist", variable)
continue
renderer = make_renderer(variable.get(CONF_VALUE_TEMPLATE))
dev.append(ArestSensor(arest,
resource,
config.get('name', response['name']),
variable['name'],
variable=variable['name'],
unit_of_measurement=variable.get(
ATTR_UNIT_OF_MEASUREMENT),
renderer=renderer))
renderer = make_renderer(var_data.get(CONF_VALUE_TEMPLATE))
dev.append(ArestSensor(
arest, resource, config.get(CONF_NAME, response[CONF_NAME]),
variable, variable=variable,
unit_of_measurement=var_data.get(CONF_UNIT_OF_MEASUREMENT),
renderer=renderer))
if pins is not None:
for pinnum, pin in pins.items():
renderer = make_renderer(pin.get(CONF_VALUE_TEMPLATE))
dev.append(ArestSensor(ArestData(resource, pinnum),
resource,
config.get('name', response['name']),
pin.get('name'),
pin=pinnum,
unit_of_measurement=pin.get(
ATTR_UNIT_OF_MEASUREMENT),
renderer=renderer))
dev.append(ArestSensor(
ArestData(resource, pinnum), resource,
config.get(CONF_NAME, response[CONF_NAME]), pin.get(CONF_NAME),
pin=pinnum, unit_of_measurement=pin.get(
CONF_UNIT_OF_MEASUREMENT), renderer=renderer))
add_devices(dev)
@@ -106,18 +117,17 @@ class ArestSensor(Entity):
"""Initialize the sensor."""
self.arest = arest
self._resource = resource
self._name = '{} {}'.format(location.title(), name.title()) \
or DEVICE_DEFAULT_NAME
self._name = '{} {}'.format(location.title(), name.title())
self._variable = variable
self._pin = pin
self._state = 'n/a'
self._state = STATE_UNKNOWN
self._unit_of_measurement = unit_of_measurement
self._renderer = renderer
self.update()
if self._pin is not None:
request = requests.get('{}/mode/{}/i'.format
(self._resource, self._pin), timeout=10)
request = requests.get(
'{}/mode/{}/i'.format(self._resource, self._pin), timeout=10)
if request.status_code is not 200:
_LOGGER.error("Can't set mode. Is device offline?")
@@ -139,15 +149,19 @@ class ArestSensor(Entity):
if 'error' in values:
return values['error']
value = self._renderer(values.get('value',
values.get(self._variable,
'N/A')))
value = self._renderer(
values.get('value', values.get(self._variable, STATE_UNKNOWN)))
return value
def update(self):
"""Get the latest data from aREST API."""
self.arest.update()
@property
def available(self):
"""Could the device be accessed during the last update call."""
return self.arest.available
# pylint: disable=too-few-public-methods
class ArestData(object):
@@ -158,6 +172,7 @@ class ArestData(object):
self._resource = resource
self._pin = pin
self.data = {}
self.available = True
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
@@ -179,7 +194,8 @@ class ArestData(object):
response = requests.get('{}/digital/{}'.format(
self._resource, self._pin), timeout=10)
self.data = {'value': response.json()['return_value']}
self.available = True
except requests.exceptions.ConnectionError:
_LOGGER.error("No route to device %s. Is device offline?",
self._resource)
self.data = {'error': 'error fetching'}
self.available = False
@@ -1,8 +1,8 @@
"""
Support for Forecast.io weather service.
Support for Dark Sky weather service.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.forecast/
https://home-assistant.io/components/sensor.darksky/
"""
import logging
from datetime import timedelta
@@ -18,15 +18,14 @@ from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['python-forecastio==1.3.4']
REQUIREMENTS = ['python-forecastio==1.3.5']
_LOGGER = logging.getLogger(__name__)
CONF_UNITS = 'units'
CONF_UPDATE_INTERVAL = 'update_interval'
DEFAULT_NAME = 'Forecast.io'
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=120)
DEFAULT_NAME = 'Dark Sky'
# Sensor types are defined like so:
# Name, si unit, us unit, ca unit, uk unit, uk2 unit
@@ -84,12 +83,15 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]),
vol.Required(CONF_API_KEY): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_UNITS): vol.In(['auto', 'si', 'us', 'ca', 'uk', 'uk2'])
vol.Optional(CONF_UNITS): vol.In(['auto', 'si', 'us', 'ca', 'uk', 'uk2']),
vol.Optional(CONF_UPDATE_INTERVAL, default=timedelta(seconds=120)): (
vol.All(cv.time_period, cv.positive_timedelta)),
})
# pylint: disable=too-many-arguments
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Forecast.io sensor."""
"""Setup the Dark Sky sensor."""
# Validate the configuration
if None in (hass.config.latitude, hass.config.longitude):
_LOGGER.error("Latitude or longitude not set in Home Assistant config")
@@ -105,9 +107,12 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
# Create a data fetcher to support all of the configured sensors. Then make
# the first call to init the data and confirm we can connect.
try:
forecast_data = ForeCastData(
config.get(CONF_API_KEY, None), hass.config.latitude,
hass.config.longitude, units)
forecast_data = DarkSkyData(
api_key=config.get(CONF_API_KEY, None),
latitude=hass.config.latitude,
longitude=hass.config.longitude,
units=units,
interval=config.get(CONF_UPDATE_INTERVAL))
forecast_data.update_currently()
except ValueError as error:
_LOGGER.error(error)
@@ -117,14 +122,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
sensors = []
for variable in config[CONF_MONITORED_CONDITIONS]:
sensors.append(ForeCastSensor(forecast_data, variable, name))
sensors.append(DarkSkySensor(forecast_data, variable, name))
add_devices(sensors)
# pylint: disable=too-few-public-methods
class ForeCastSensor(Entity):
"""Implementation of a Forecast.io sensor."""
class DarkSkySensor(Entity):
"""Implementation of a Dark Sky sensor."""
def __init__(self, forecast_data, sensor_type, name):
"""Initialize the sensor."""
@@ -175,10 +180,10 @@ class ForeCastSensor(Entity):
# pylint: disable=too-many-branches,too-many-statements
def update(self):
"""Get the latest data from Forecast.io and updates the states."""
"""Get the latest data from Dark Sky and updates the states."""
# Call the API for new forecast data. Each sensor will re-trigger this
# same exact call, but thats fine. We cache results for a short period
# of time to prevent hitting API limits. Note that forecast.io will
# same exact call, but that's fine. We cache results for a short period
# of time to prevent hitting API limits. Note that Dark Sky will
# charge users for too many calls in 1 day, so take care when updating.
self.forecast_data.update()
self.update_unit_of_measurement()
@@ -192,7 +197,8 @@ class ForeCastSensor(Entity):
hourly = self.forecast_data.data_hourly
self._state = getattr(hourly, 'summary', '')
elif self.type in ['daily_summary',
'temperature_min', 'temperature_max',
'temperature_min',
'temperature_max',
'apparent_temperature_min',
'apparent_temperature_max',
'precip_intensity_max']:
@@ -242,12 +248,11 @@ def convert_to_camel(data):
return components[0] + "".join(x.title() for x in components[1:])
class ForeCastData(object):
"""Gets the latest data from Forecast.io."""
class DarkSkyData(object):
"""Get the latest data from Darksky."""
# pylint: disable=too-many-instance-attributes
def __init__(self, api_key, latitude, longitude, units):
def __init__(self, api_key, latitude, longitude, units, interval):
"""Initialize the data object."""
self._api_key = api_key
self.latitude = latitude
@@ -261,36 +266,38 @@ class ForeCastData(object):
self.data_hourly = None
self.data_daily = None
# Apply throttling to methods using configured interval
self.update = Throttle(interval)(self._update)
self.update_currently = Throttle(interval)(self._update_currently)
self.update_minutely = Throttle(interval)(self._update_minutely)
self.update_hourly = Throttle(interval)(self._update_hourly)
self.update_daily = Throttle(interval)(self._update_daily)
self.update()
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Get the latest data from Forecast.io."""
def _update(self):
"""Get the latest data from Dark Sky."""
import forecastio
try:
self.data = forecastio.load_forecast(
self._api_key, self.latitude, self.longitude, units=self.units)
except (ConnectError, HTTPError, Timeout, ValueError) as error:
raise ValueError("Unable to init Forecast.io. - %s", error)
raise ValueError("Unable to init Dark Sky. %s", error)
self.unit_system = self.data.json['flags']['units']
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update_currently(self):
def _update_currently(self):
"""Update currently data."""
self.data_currently = self.data.currently()
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update_minutely(self):
def _update_minutely(self):
"""Update minutely data."""
self.data_minutely = self.data.minutely()
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update_hourly(self):
def _update_hourly(self):
"""Update hourly data."""
self.data_hourly = self.data.hourly()
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update_daily(self):
def _update_daily(self):
"""Update daily data."""
self.data_daily = self.data.daily()
+1 -2
View File
@@ -202,8 +202,7 @@ class EmonCmsData(object):
"""Get the latest data."""
try:
req = requests.get(self._url, params={"apikey": self._apikey},
verify=False, allow_redirects=True,
timeout=5)
allow_redirects=True, timeout=5)
except requests.exceptions.RequestException as exception:
_LOGGER.error(exception)
return
+9 -11
View File
@@ -1,5 +1,5 @@
"""
Support gahtering system information of hosts which are running glances.
Support gathering system information of hosts which are running glances.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.glances/
@@ -24,6 +24,8 @@ DEFAULT_HOST = 'localhost'
DEFAULT_NAME = 'Glances'
DEFAULT_PORT = '61208'
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60)
SENSOR_TYPES = {
'disk_use_percent': ['Disk Use', '%'],
'disk_use': ['Disk Use', 'GiB'],
@@ -34,11 +36,11 @@ SENSOR_TYPES = {
'swap_use_percent': ['Swap Use', '%'],
'swap_use': ['Swap Use', 'GiB'],
'swap_free': ['Swap Free', 'GiB'],
'processor_load': ['CPU Load', None],
'process_running': ['Running', None],
'process_total': ['Total', None],
'process_thread': ['Thread', None],
'process_sleeping': ['Sleeping', None]
'processor_load': ['CPU Load', '15 min'],
'process_running': ['Running', 'Count'],
'process_total': ['Total', 'Count'],
'process_thread': ['Thread', 'Count'],
'process_sleeping': ['Sleeping', 'Count']
}
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@@ -50,10 +52,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
# Return cached results if last scan was less then this time ago.
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60)
# pylint: disable=unused-variable
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Glances sensor."""
@@ -66,7 +64,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
try:
response = requests.get(url, timeout=10)
if not response.ok:
_LOGGER.error('Response status is "%s"', response.status_code)
_LOGGER.error("Response status is '%s'", response.status_code)
return False
except requests.exceptions.ConnectionError:
_LOGGER.error("No route to resource/endpoint: %s", url)

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