Compare commits

..

170 Commits

Author SHA1 Message Date
Paulus Schoutsen 6bc504bfcc Merge pull request #2381 from home-assistant/dev
0.23
2016-07-01 00:58:11 -07:00
Paulus Schoutsen c44eefacb4 Version bump to 0.23.0 2016-07-01 00:57:55 -07:00
patkap 952b1a3e0c kodi platform: following jsonrpc-request version bump (0.3), let kodi file abstraction layer handle a collection item, url or file to play (#2398) 2016-06-30 16:35:20 -07:00
Pascal Vizeli a57cd58675 Merge pull request #2399 from pvizeli/Homematic_fix
Homematic fix
2016-07-01 00:17:04 +02:00
Pascal Vizeli d67f79e2eb remove unused pylint exeption 2016-07-01 00:01:16 +02:00
Pascal Vizeli d326d187d1 fix bug in event handling and add cast for watersensor 2016-06-30 23:54:04 +02:00
Pascal Vizeli d0b1619946 Merge remote-tracking branch 'refs/remotes/home-assistant/dev' into Homematic_fix 2016-06-30 23:44:27 +02:00
Lewis Juggins 21be4c1828 Add Sonos unjoin functionality (#2379) 2016-06-30 14:21:57 -07:00
Paulus Schoutsen d1f4901d53 Migrate to cherrypy wsgi from eventlet (#2387) 2016-06-30 09:02:12 -07:00
patkap 7582eb9f63 jsonrpc-request version bump (0.3) (#2397) 2016-06-30 08:40:01 -07:00
Fabian Affolter 419ff18afb Docstrings (#2395)
* Replace switch with lock

* Update docstrings

* Add link to docs

* Add link to docs and update docstrings

* Update docstring

* Update docstrings and fix typos

* Add link to docs

* Add link to docs

* Add link to docs and update docstrings

* Fix link to docs and update docstrings

* Remove blank line

* Add link to docs
2016-06-30 10:33:34 +02:00
Fabian Affolter 8dd7ebb08e Add the two next trains (#2390) 2016-06-29 17:44:35 -07:00
rhooper 5cce02ab62 vera lock support (#2391)
* vera lock support

* fix formatting
2016-06-29 17:28:20 -07:00
William Scanlon 6a816116ab Wink subscription support (#2324) 2016-06-29 14:16:53 -07:00
Pascal Vizeli bb0f484caf update pyhomematic and homematic use now events from HA for remotes 2016-06-29 22:42:35 +02:00
Brent 3c5c018e3e Fixed issue with roku timeouts throwing exceptions when roku losses n… (#2386)
* Fixed issue with roku timeouts throwing exceptions when roku losses networking

* Fixed pylint errors
2016-06-28 20:26:37 -07:00
Ardetus 78e7e17484 Support more types of 1wire sensors and bus masters (#2384)
* Support more types of 1wire sensors and bus masters

- Added support for DS18S20, DS1822, DS1825 and DS28EA00 temperature sensors
- Added support for bus masters which use fuse to mount device tree.
  Mount can be specified by 'mount_dir' configuration parameter.

* Correct the lint problem
2016-06-28 18:39:16 -07:00
AlucardZero 31d2a5d2d1 Reenable TLS1.1 and 1.2 while leaving SSLv3 disabled (#2385) 2016-06-28 16:48:25 -07:00
Pascal Vizeli baa9bdf6fc change homematic to autodetect only 2016-06-28 22:53:53 +02:00
Fabian Affolter 00179763ef Upgrade influxdb to 3.0.0 (#2383) 2016-06-28 07:56:14 -07:00
Fabian Affolter 7a73dc7d6a Upgrade websocket-client to 0.37.0 (#2382) 2016-06-27 23:47:35 -07:00
Paulus Schoutsen d0b9b588a9 Merge branch 'master' into dev
Conflicts:
	homeassistant/const.py
2016-06-27 23:26:46 -07:00
Fabian Affolter 592c599488 Upgrade Werkzeug to 0.11.10 (#2380) 2016-06-27 17:39:44 -07:00
Paulus Schoutsen 6714392e9c Move elevation to core config and clean up HTTP mocking in tests (#2378)
* Stick version numbers

* Move elevation to core config

* Migrate forecast test to requests-mock

* Migrate YR tests to requests-mock

* Add requests_mock to requirements_test.txt

* Move conf code from bootstrap to config

* More config fixes

* Fix some more issues

* Add test for set config and failing auto detect
2016-06-27 09:02:45 -07:00
Adam Mills dc75b28b90 Initial Support for Zwave color bulbs (#2376)
* Initial Support for Zwave color bulbs

* Revert name override for ZwaveColorLight
2016-06-27 09:01:41 -07:00
Pascal Vizeli d2509ce9e3 Merge remote-tracking branch 'refs/remotes/home-assistant/dev' into dev 2016-06-27 15:57:01 +02:00
Pascal Vizeli 3afc566be1 Fix timing bug while linking HM device to HA object
https://github.com/danielperna84/home-assistant/issues/14
2016-06-26 23:18:18 +02:00
Dan fb3e388f04 Depreciate ssl2/3 (#2375)
* Depreciate ssl2/3

Following the best practices as defind here:
https://mozilla.github.io/server-side-tls/ssl-config-generator/

* Updated comment with better decription

Links to the rational rather than the config generator; explains link.

* add comment mentioning intermediate
2016-06-26 11:49:46 -07:00
Fabian Affolter 254b1c46ac Remove lxml dependency (#2374) 2016-06-26 10:13:52 -07:00
Philip Lundrigan d13cc227cc Push State (#2365)
* Add ability to push state changes

* Add tests for push state changes

* Fix style issues

* Use better name to force an update
2016-06-26 00:33:23 -07:00
Paulus Schoutsen 446f998759 Merge pull request #2368 from pvizeli/Homematic
Homematic Support (clean)
2016-06-25 20:37:53 -07:00
Paulus Schoutsen 206e7d7a67 Extend persistent notification support (#2371) 2016-06-25 16:40:33 -07:00
Pascal Vizeli c3b25f2cd5 fix logging-not-lazy 2016-06-25 22:20:09 +02:00
Pascal Vizeli f3199e7dae fix wrong import 2016-06-25 22:13:29 +02:00
Pascal Vizeli 4ecd724578 fix linter errors 2016-06-25 22:10:47 +02:00
Pascal Vizeli e4d3b25f1e Merge remote-tracking branch 'refs/remotes/home-assistant/dev' into Homematic
# Conflicts:
#	homeassistant/components/thermostat/homematic.py
2016-06-25 22:02:14 +02:00
Pascal Vizeli 7e7f7b64e5 Merge remote-tracking branch 'refs/remotes/home-assistant/dev' into dev 2016-06-25 21:58:34 +02:00
Pascal Vizeli e0e9d3c57b change autodiscovery 2016-06-25 21:37:51 +02:00
Pascal Vizeli a687bdb388 Revert "Third batch of (minor) fixes as suggested by @balloob"
This reverts commit 87c138c559.
2016-06-25 21:03:41 +02:00
Pascal Vizeli 199fbc7a15 Revert "fix autodiscovery"
This reverts commit 86ccf26a1a.
2016-06-25 21:03:37 +02:00
Pascal Vizeli 57754cd2ff Revert "fix discovery function"
This reverts commit be72b04855.
2016-06-25 21:03:33 +02:00
John Arild Berentsen 21381a95d4 Zwave fixes. (#2373)
* Fix move_up and move_down

I managed to switch up the zwave move_up and move_down commands.
This PR fixes it.
Thank you @nunofgs for bringing this to my attention :)

* Fix for aeotec 6 multisensor
2016-06-25 20:35:36 +02:00
Pascal Vizeli be72b04855 fix discovery function 2016-06-25 20:30:02 +02:00
Pascal Vizeli 86ccf26a1a fix autodiscovery 2016-06-25 20:12:49 +02:00
Pascal Vizeli 87c138c559 Third batch of (minor) fixes as suggested by @balloob 2016-06-25 19:25:59 +02:00
Pascal Vizeli b3acd7d21d add resolvenames function support from pyhomematic (homegear only) 2016-06-25 18:54:14 +02:00
Pascal Vizeli a19f7bff28 fix false autodetect with HM GongSensor types 2016-06-25 18:36:52 +02:00
Pascal Vizeli 30b7c6b694 Second batch of (minor) fixes as suggested by @balloob 2016-06-25 18:34:35 +02:00
Daniel Perna 43faeff42a Moved trx/except, added debug messages, minor fixes 2016-06-25 18:19:05 +02:00
Daniel Perna 5ca26fc13f Moved try/except-block and moved delay to link_homematic 2016-06-25 16:25:33 +02:00
Daniel Perna 04748e3ad1 First batch of (minor) fixes as suggested by @balloob 2016-06-25 15:10:19 +02:00
Johann Kellerman 7b02dc434a Secrets support for configuration files (#2312)
* ! secret based on yaml.py

* Private Secrets Dict, removed cmdline, fixed log level

* Secrets limited to yaml only

* Add keyring & debug tests
2016-06-25 00:10:03 -07:00
Matthew Treinish 1c1d18053b Add cmus media device (#2321)
This commit adds support for the cmus console music player as a media
device.
2016-06-25 00:06:36 -07:00
arsaboo 2ac752d67a Add OpenExchangeRates sensor (#2356)
* Create openexchangerates.py

* Create OpenExchangeRates Sensor

* Add openexchangerate sensor

* Update openexchangerates.py

* Added params dict

* Update openexchangerates.py

* Update openexchangerates.py

* Update openexchangerates.py

* Update openexchangerates.py

* Added API key validation

* Update openexchangerates.py
2016-06-25 00:02:28 -07:00
John Arild Berentsen a1ef1c996c Fix physical manual update of state of device (#2372) 2016-06-24 23:22:14 -07:00
Paulus Schoutsen cbb897b2cf Update frontend 2016-06-24 22:34:55 -07:00
Fabian Affolter e4b67c9574 Add persistent notification component (#1844) 2016-06-24 21:43:44 -07:00
Daniel Høyer Iversen 7a8c5a0709 Add frontend to the example config (#2367) 2016-06-24 21:40:02 -07:00
Paulus Schoutsen aadd730ddd Merge branch 'pr/2348' into dev
Conflicts:
	.coveragerc
2016-06-24 21:30:08 -07:00
Paulus Schoutsen 68df3deee0 ABC consistent not implemented behavior (#2359) 2016-06-24 21:27:40 -07:00
Johann Kellerman c616115419 rpi_gpi garage_door controller (#2369) 2016-06-24 21:22:10 -07:00
Daniel Perna dfe1b8d934 Fixed minor feature-detection bug with incomplet configuration 2016-06-24 19:46:42 +02:00
John Arild Berentsen ec8dc25c9c Zwave garagedoor (#2361)
* First go at zwave Garage door

* Refactor of zwave discovery

* Allaround fixes for rollershutter and garage door
2016-06-24 11:44:24 -04:00
Pascal Vizeli 67a04c2a0e Initial clean import 2016-06-24 10:06:58 +02:00
Dale Higgs 600a3e3965 Allow service data to be passed to shell_command (#2362) 2016-06-23 08:47:56 -07:00
Fabian Affolter 3349bdc2bd Log successful and failed login attempts (#2347) 2016-06-23 12:34:13 +02:00
Dan Cinnamon 12e26d25a5 Bump to pyenvisalink 1.0 (#2358) 2016-06-22 22:48:16 -07:00
Matthew Treinish aa3d0e1047 Fix incorrect check on presence of password and pub_key (#2355)
This commit fixes an issue with the use of None in default values
for the config get() calls in __init__() of AsusWrtDeviceScanner.
These values are cast as strings and when a NoneType is cast it
returns the string "None" this broke the check for the existence
of these fields. This commit fixes the issue by changing the default
value to be an empty string '' which will conform with the behavior
expected by the ssh login code.

Closes #2343
2016-06-22 17:01:39 -07:00
happyleaves d0ee8abcb8 couple fixes 2016-06-22 17:29:22 -04:00
happyleaves 94b47d8bc3 addressed review 2016-06-22 17:07:46 -04:00
Fabian Affolter 7b942243ab Increase interval (#2353) 2016-06-22 20:12:36 +02:00
Paulus Schoutsen a70f922a71 ps - add reload core config service (#2350) 2016-06-22 09:13:18 -07:00
Jean-Philippe Bouillot 9ce9b8debb Add support for wind, battery, radio signals for Netatmo sensor (#2351)
* Add support for wind, battery, radio signals

* Fix indentation error

* second indentation fix

* Fix for pylint too many statements error

* Moving "pylint: disable=too-many-statements"
2016-06-22 09:01:53 -07:00
Dale Higgs d7b006600e [notify.pushover] Fix 'NoneType' error on data retrieval (#2352)
* Fix 'NoneType' error on data retrieval

* Reduce code for empty dict as the default
2016-06-22 08:54:44 -07:00
Paulus Schoutsen a564fe8286 Fix error log (#2349) 2016-06-21 22:26:40 -07:00
happyleaves 7fc9fa4b0c satisfy farcy 2016-06-21 19:31:40 -04:00
happyleavesaoc d87e969671 add cec platform 2016-06-21 18:36:34 -04:00
Fabian Affolter 278514b994 Add support for Fixer.io (#2336)
* Add support for Fixer.io

* Add unit of measurment and set throttle to one day
2016-06-21 07:43:02 -07:00
Fabian Affolter 38b0336694 Upgrade paho-mqtt to 1.2 (#2339) 2016-06-20 21:51:50 -07:00
Fabian Affolter caa096ebd5 Upgrade psutil to 4.3.0 (#2342)
* Upgrade psutil to 4.3.0

* Remove period
2016-06-20 21:51:07 -07:00
Fabian Affolter ba417a730b Upgrade slacker to 0.9.17 (#2340) 2016-06-20 08:55:57 -07:00
dale3h 6fa095f4a7 Add additional Pushover parameters (#2309)
* Add additional Pushover parameters

Add support for more Pushover parameters: target (device), sound, url, url_title, priority, timestamp

* Remove data dictionary reference

https://github.com/home-assistant/home-assistant/pull/2309#discussion_r67603127
2016-06-19 23:08:30 -07:00
John Arild Berentsen 5efa076080 Make sure we exit loop when value is set (#2326) 2016-06-19 22:42:23 -07:00
Antonio Párraga Navarro cbc0833360 Support for Sony Bravia TV (#2243)
* Added Sony Bravia support to HA

* Improvements to make it work on my poor raspberry 1

* Just a typo

* A few fixes in order to pass pylint

* - Remove noqa: was due to the 80 characters max per line restriction
- Move communication logic to a separate library at https://github.com/aparraga/braviarc.git
- Added dependency and adapt the code according to that

* A few improvements

* Just a typo in a comment

* Rebase from HM/dev

* Update requirements by executing the script/gen_requirements_all.py

* More isolation level for braviarc lib

* Remove unnecessary StringIO usage

* Revert submodule polymer commit

* Small refactorization and clean up of unused functions

* Executed script/gen_requirements_all.py

* Added a missing condition to ensure that a map is not null

* Fix missing parameter detected by pylint

* A few improvements, also added an empty line to avoid the lint error

* A typo
2016-06-19 22:35:26 -07:00
John Arild Berentsen 2e62053629 Basic implementation of Zwave Rollershutters (#2313)
* Basic implementation of Zwave Rollershutters

* Better filtering, by @wokar

* Fix typo

* Remove polling from component, and loop fix

* linter fix

* Filter to channel devices to correct component

* Remove overwriting of parent node name
2016-06-19 22:30:57 -07:00
Paulus Schoutsen 4f09279524 Merge pull request #2334 from home-assistant/hotfix-22-1
Hotfix 0.22.1
2016-06-19 21:16:22 -07:00
Paulus Schoutsen 57dfce1583 Version bump to 0.22.1 2016-06-19 20:55:21 -07:00
Paulus Schoutsen 33bafb8451 fix insteon hub discovery 2016-06-19 20:54:22 -07:00
Paulus Schoutsen f59e242c63 fix insteon hub discovery 2016-06-19 20:53:56 -07:00
Dan Cinnamon cb6f50b7ff Envisalink support (#2304)
* Created a new platform for envisalink-based alarm panels (Honeywell/DSC)

* Added a sensor component and cleanup

* Completed initial development.

* Fixing pylint issues.

* Fix more pylint issues

* Fixed more validation issues.

* Final pylint issues

* Final tweaks prior to PR.

* Fixed final pylint issue

* Resolved a few minor issues, and used volumptous for validation.

* Fixing final lint issues

* Fixes to validation schema and refactoring.
2016-06-19 10:45:07 -07:00
Paulus Schoutsen 44177a7fde Version bump to 0.23.0.dev0 2016-06-18 13:21:04 -07:00
Paulus Schoutsen 8c505e625b Merge pull request #2323 from home-assistant/dev
0.22
2016-06-18 13:20:51 -07:00
Paulus Schoutsen 314fa42298 Version bump to 0.22 2016-06-18 13:19:57 -07:00
Paulus Schoutsen a80a74b586 Add camera timeouts 2016-06-18 13:06:14 -07:00
Paulus Schoutsen 2508e9f9ff Add timeout to mjpeg streams 2016-06-18 12:34:39 -07:00
Paulus Schoutsen 71157dbec9 Merge branch 'master' into dev
Conflicts:
	homeassistant/components/frontend/version.py
	homeassistant/components/frontend/www_static/core.js.gz
	homeassistant/components/frontend/www_static/frontend.html
	homeassistant/components/frontend/www_static/frontend.html.gz
	homeassistant/components/frontend/www_static/service_worker.js
	homeassistant/components/frontend/www_static/service_worker.js.gz
	homeassistant/const.py
2016-06-18 12:00:38 -07:00
devdelay 1f7792678b Add service set_hvac_mode (#2303)
* set hvac_mode

* Update __init__.py

* Update __init__.py
2016-06-18 10:20:39 -07:00
Phil Kates 40840044ca Wink Rollershutter (#2294)
* Update python-wink to 0.7.7

* Add Wink Rollershutter component
2016-06-18 09:59:13 -07:00
Nick Touran 2882f05f2c Added template rendering to shell_command component (#2268)
* Added template rendering to `shell_command` component

* Security upgrades to template rendering in shell_command.

* Added new unit tests for shell_command templates.
Better failure when template is invalid in shell_command
2016-06-18 09:57:18 -07:00
Fabian Affolter b646accf87 Catch ValueError (#2296)
* Catch ValueError

* Less options and don't use state
2016-06-18 09:48:32 -07:00
Nick Touran e7ea6ecf5a Better handling for when user hasn't properly configured Pandora client (#2317) 2016-06-18 08:23:35 -07:00
Paulus Schoutsen 29343ad651 Fix pep257 bt home hub 5 test 2016-06-18 08:20:14 -07:00
Fabian Affolter 28d86207e1 Add support for hydrological data from FOEN (#2318) 2016-06-18 08:18:48 -07:00
Fabian Affolter 6a01227635 Upgrade python-telegram-bot to 4.2.1 (#2319) 2016-06-18 08:16:28 -07:00
Nolan Gilley b6fb21edaf Plex sensor (#2210)
add option to name in config

fix const import

use plexapi

add myplex support for remote access

use first server if server not specified

use list comprehension

use dictionary comprehension
2016-06-14 23:07:00 -07:00
Paulus Schoutsen a65a122464 Fix discovery (#2305) 2016-06-14 22:51:46 -07:00
Nick Touran 5c601f1d5f Stability improvement in Pandora and proper shutdown in LIRC (#2299)
* Pandora cleanups and enhancements

Added media_content_type
reduced debug messages
made more robust station list
Eliminated auto-pause detection issue

* Added proper de-init of LIRC

* Now won't re-spawn Pandora client if turn_on command is sent twice
2016-06-14 22:42:54 -07:00
Lewis Juggins 7b8b78ec0e BT Home Hub 5 device tracker support (#2250) 2016-06-14 22:41:49 -07:00
Per Sandström 38030fcfca ASUSWRT Autodetect protocol (#2300) 2016-06-14 22:17:32 -07:00
Paulus Schoutsen 39913075f4 Fix Locative view name 2016-06-14 22:12:44 -07:00
Paulus Schoutsen 2036c44364 Hotfix 21 2 (#2302)
* Update frontend

Conflicts:
	homeassistant/components/frontend/version.py
	homeassistant/components/frontend/www_static/core.js.gz
	homeassistant/components/frontend/www_static/frontend.html
	homeassistant/components/frontend/www_static/frontend.html.gz
	homeassistant/components/frontend/www_static/home-assistant-polymer
	homeassistant/components/frontend/www_static/service_worker.js
	homeassistant/components/frontend/www_static/service_worker.js.gz

* Add a default OPTIONS handler for wsgi (#2301)

When a browser makes a CORS request, it often makes a 'preflight'
options request in order to make sure the resource is valid, and that
it has the right CORS access. This adds a default OPTIONS handler for
all views. If a view needs to customize the OPTIONS handler for some
reason, it's free to, but this way CORS will work.

* Version bump to 0.21.2
2016-06-14 19:54:09 -07:00
Josh Wright 3fcc07af04 Add a default OPTIONS handler for wsgi (#2301)
When a browser makes a CORS request, it often makes a 'preflight'
options request in order to make sure the resource is valid, and that
it has the right CORS access. This adds a default OPTIONS handler for
all views. If a view needs to customize the OPTIONS handler for some
reason, it's free to, but this way CORS will work.
2016-06-14 19:44:12 -07:00
Paulus Schoutsen 65750f667b Update frontend 2016-06-14 18:39:44 -07:00
Per Sandström f07ba1e9a6 Merge pull request #2298 from persandstrom/verisure_lower_severity_of_message
lower severity of non critical error
2016-06-14 20:56:49 +02:00
Per Sandström 6e5e0e7acc lower severity of non critical error 2016-06-14 20:21:42 +02:00
Paulus Schoutsen 9d7c9d1262 Update frontend 2016-06-13 20:11:01 -07:00
Paulus Schoutsen 42c5475284 Fix Wink discovery 2016-06-13 20:06:32 -07:00
Edward Romano 8e839be938 Refactor Forecast.io (#2217)
* Refactor Forecast.io

* Some more refactoring and code review workoff

* Dict switch refactor

* CamelCase for data lookup

* Fixing unit_of_measure update

* Better default return for unit_of_measurement

* Test fix
2016-06-13 18:54:49 -07:00
Paulus Schoutsen ab48010d14 Add 1024x1024 favicon 2016-06-13 00:04:54 -07:00
Matthew Treinish 1381984b77 Add ssh public key support to the asuswrt component (#2287)
The pexpect.pxssh module has support for using public key
authentication. [1] This commit adds support for leveraging that and
establishing a ssh connection with a public key instead of a password.

[1] http://pexpect.readthedocs.io/en/stable/api/pxssh.html#pexpect.pxssh.pxssh.login
2016-06-12 21:27:41 -07:00
Paulus Schoutsen 6dcf3682df Tweak event helper 2016-06-12 20:37:37 -07:00
Nick Touran 65d1f7af50 Added Pandora radio media player (#2274)
* Added Pandora media player utilizing the Pianobar client

* Added Pandora to .coveragerc ignore

* Fixes some docstring formats in Pandora

* More minor formatting tweaks for Pandora

* Eliminated non-portable assumption from Pandora component

* Updated Pandora to properly update currently-playing song.

* Docstring fixes in Pandora

* Added check to ensure Pianobar client is available in path for Pandora.

* Made Pandora client verification a function instead of method.

* Better handling of dependency verification in Pandora.
2016-06-12 18:35:12 -07:00
Jesse Zoldak 16f4695a13 Add tests for forecast.io (#2227)
* Add tests for forecast.io

* Fix linting items and don't call a platform a component
2016-06-12 17:22:58 -07:00
Landrash c7ee74a573 Local file - Camera platform (#2282) 2016-06-12 16:26:29 -07:00
arsaboo 8e2c1ff4aa Include the Voltage sensor (#2285)
The API provides the voltage information and will be useful for people to troubleshoot their BloomSky.
2016-06-12 16:19:13 -07:00
thejacko12354 e437151881 Update samsungtv.py (#2286)
Changed line 75 'KEY_POWER' to 'KEY'
Fixes the problem that every 10 sec the tv interprets that the Up-button is pressed
2016-06-12 16:03:40 -07:00
Martin Hjelmare 81ca175906 Add mysensors IR switch device and service (#2239)
* Add mysensors IR switch device and service

* Add MySensorsIRSwitch as child class to MySensorsSwitch.
* Add platform specific service mysensors_send_ir_code. Only call
	device method in service function if device is IR device.
* Add service and required attribute to state helper to support scenes.
* Move V_IR_SEND type from sensor.mysensors to switch.mysensors
	platform.
* Populate switch.services.yaml with service descriptions.

* Fix check of entity_id in service function

Since multiple entity_ids can be passed as service data, and the
entity_id service attribute is forced to a list by the service
validation schema, the check in the service function should iterate
over any entity ids.
2016-06-12 23:04:45 +02:00
Paulus Schoutsen ebe4c39020 Merge branch '0-21-1' into dev
Conflicts:
	homeassistant/components/frontend/www_static/core.js.gz
	homeassistant/components/frontend/www_static/frontend.html.gz
	homeassistant/components/frontend/www_static/service_worker.js.gz
	homeassistant/const.py
	requirements_all.txt
	setup.py
2016-06-12 00:25:36 -07:00
Paulus Schoutsen 952afeb717 Merge pull request #2281 from home-assistant/0-21-1
Hotfix 0.21.1
2016-06-12 00:23:03 -07:00
Paulus Schoutsen 40be883c0e version bump to 0.21.1 2016-06-12 00:06:37 -07:00
Paulus Schoutsen 5c87883c86 Update frontend 2016-06-12 00:04:37 -07:00
St. John Johnson b2b1804f5e Fixing MJPEG streaming in Werkzeug by taking advantage of direct_passthrough (#2277) 2016-06-12 00:03:18 -07:00
Paulus Schoutsen f5fc4cd97f Alexa: run script before generating response text (#2276) 2016-06-12 00:03:18 -07:00
Paulus Schoutsen bc78997bbd Bugfixes random (#2270)
* Fix Z-Wave autoheal network

* Make config_per_platform handle bad config better
2016-06-12 00:03:18 -07:00
Paulus Schoutsen 35dd3b8d0d Update screenshot README 2016-06-12 00:03:17 -07:00
Nick Touran 491c06f53b Recover from rare error condition from LIRC (#2267)
* More resilient accessing of LIRC codes to handle rare error case.

* Line length fix in LIRC
2016-06-12 00:03:17 -07:00
Gergely Imreh 31c1b7f6ad sensor/gtfs: add sanity check, origin earlier than destination (#2265)
Previously experienced issues on routes where services operate in both
directions. The query picked up not just paths where service goes
from Origin ->  Destination, but trips going Destination -> Origin,
and shown bogus results.

Ensure that this doesn't happen by requiring the origin station's
stop_sequence value to be lower than the destination station.
2016-06-12 00:03:17 -07:00
Paulus Schoutsen da5b50848a Add eventlet to base requirements (#2264)
Conflicts:
	requirements_all.txt
	setup.py
2016-06-12 00:02:58 -07:00
Paulus Schoutsen 586f69ac95 Update frontend 2016-06-11 23:57:24 -07:00
St. John Johnson 3723c3a7e8 Fixing MJPEG streaming in Werkzeug by taking advantage of direct_passthrough (#2277) 2016-06-11 20:50:10 -07:00
Paulus Schoutsen 145c98c40c Alexa: run script before generating response text (#2276) 2016-06-11 17:57:04 -07:00
Paulus Schoutsen 30f74bb3ca Migrate to generic discovery method (#2271)
* Migrate to generic discovery method

* Add tests for discovery
2016-06-11 17:43:13 -07:00
Paulus Schoutsen c9756c40e2 Bugfixes random (#2270)
* Fix Z-Wave autoheal network

* Make config_per_platform handle bad config better
2016-06-10 22:53:31 -07:00
Paulus Schoutsen b60806583c Update asuswrt.py 2016-06-10 21:14:11 -07:00
Michaël Arnauts 868c08e34b Add stop command to google cast component (#2269)
* Add stop command to google cast component

* Add SUPPORT_STOP capabilities to google cast component
2016-06-10 21:12:50 -07:00
Paulus Schoutsen 71eb09ee5e Fix configurator tests 2016-06-10 20:50:04 -07:00
Paulus Schoutsen 809e613148 Update frontend 2016-06-10 19:45:15 -07:00
Paulus Schoutsen 0dbc023f5b Fix lint errors 2016-06-09 23:41:26 -07:00
Joseph Piron b6d75e6c5a Netio Switch platform support (#2181)
* WSGI based request handler

with a bit of polishing

Signed-off-by: eagleamon <joseph.piron@gmail.com>

* removed stale comment and fixed version, but failed tests do not seem to be related

* removing the wrapper hack

* added in requirements file

* Found the caved in lint error..
2016-06-09 23:40:14 -07:00
wind-rider c78e6c088e Add a swagger.yaml file (#2182)
* Add a swagger.yaml file

@balloob
I created a swagger configuration file that will help people create clients (apps / frontends) for Home Assistant more easily. Based upon this code it is even possible to generate client code for several programming languages.

I created it by hand now, so when the API changes it will need to be updated. That's why it would be better to generate this specification automatically. This is possible for API frameworks but I don't know whether it is possible for the handwritten endpoints in Home Assistant. Maybe you could assist here?

This documentation could be used to replace a part of https://home-assistant.io/developers/rest_api/.

* Added restrict parameter

* Moved swagger file to docs folder
2016-06-09 23:35:47 -07:00
Thiago Oliveira 02f342b670 add fan_min_on_time service to ecobee (#2159) 2016-06-09 23:34:29 -07:00
Hugo Dupras 213a738240 Add Netatmo component and add support for Netatmo Welcome Camera (#2233)
* Introducing the Netatmo component

As Netatmo is providing several type of device (sensor, camera), a new Netatmo
component needs to be created in order to centralize the Netatmo login data.
Currently this change only impacts the Netatmo Weather station

* Add new Netatmo library

This new API will provide access to the Welcome Camera

* Basic support for Netatmo Welcome camera

This change introduces support for Netatmo Welcome camera. Currently, it will
add all detected camera to Home Assistant, camera filtering (similar to the one
used for weather station modules) will be added later

* Remove useless REQUIREMENTS

* Fixes for Netatmo Welcome support

* Allow to filter Welcome cameras by name and/or home

* Update requirements for Netatmo components

* Fix multi-camera support for Welcome

* Fix pep8 error/warning

* This commit also adds improved logging for bad credentials

* Add Throttle decorator for Welcome update function

As the update function updates the data for all cameras, we should prevent this
function to be called several time during an interval
2016-06-09 23:31:36 -07:00
Paulus Schoutsen e4fe8336cc Update frontend 2016-06-09 23:27:35 -07:00
Paulus Schoutsen 068e62623d Update frontend 2016-06-09 22:12:45 -07:00
Jeffrey Lin 30f5727b40 Added support for AP mode in asuswrt (#2263)
* Added support for AP mode in asuswrt

* Corrected number of return values in asuswrt
2016-06-09 21:30:47 -07:00
Paulus Schoutsen 815a6999b1 Update screenshot README 2016-06-09 21:23:20 -07:00
Nick Touran c229d9e90f Recover from rare error condition from LIRC (#2267)
* More resilient accessing of LIRC codes to handle rare error case.

* Line length fix in LIRC
2016-06-09 20:53:41 -07:00
Gergely Imreh abc353c083 sensor/gtfs: add sanity check, origin earlier than destination (#2265)
Previously experienced issues on routes where services operate in both
directions. The query picked up not just paths where service goes
from Origin ->  Destination, but trips going Destination -> Origin,
and shown bogus results.

Ensure that this doesn't happen by requiring the origin station's
stop_sequence value to be lower than the destination station.
2016-06-09 20:48:12 -07:00
Paulus Schoutsen 38639d26ea Add eventlet to base requirements (#2264) 2016-06-09 18:47:35 -07:00
Hugo Dupras 1c637558bf Round download speed for nzbget sensor (#2255) 2016-06-09 08:06:01 -07:00
mikebarris 5223d20668 Removed webcolors dependency in favor of dictionary lookup. (#2215)
* Removed webcolors dependency in favor of dictionary lookup.

* Fixed code style errors.

* Moved color dictionary to module per suggestion.

* Removed try/except per suggestion.
2016-06-08 22:25:32 -07:00
Dan Sullivan ce829d194c Added Sonos snapshot feature (#2240)
* Added Sonos snapshot feature

* Fix lint errors

* Use snake case

* Import dependency in a method
2016-06-08 21:47:49 -07:00
srirams 4a5ad24ae0 fix zwave thermostat with multiple setpoints (#2237)
* fix zwave thermostat with multiple setpoints

* fix zwave thermostat with multiple setpoints
2016-06-08 21:39:44 -07:00
Fabian Affolter 33cb1b3be6 SNMP sensor (#2244)
* Add snmp sensor

* Add ATTR_UNIT_OF_MEASUREMENT
2016-06-08 21:16:43 -07:00
Paulus Schoutsen 0525af920c Update betamax casettes 2016-06-08 21:06:14 -07:00
Fabian Affolter 831799a7af Upgrade betamax to 0.7.0 2016-06-08 21:06:14 -07:00
Fabian Affolter 8e5da5776d Add missing key 'forecast' (#2256) 2016-06-08 20:59:20 -07:00
Fabian Affolter be9730cc6c Upgrade astral to 1.2 (#2259) 2016-06-08 20:58:16 -07:00
Daniel Høyer Iversen e44c2a4016 Improve config validation for group (#2206)
* Improve config validation if invalid entity for groups

* Improve error message when entity id is invalid
2016-06-08 20:55:08 -07:00
Paulus Schoutsen 29ffa5c282 Version bump to 0.22.0.dev0 2016-06-07 19:28:13 -07:00
202 changed files with 10973 additions and 2147 deletions
+21 -1
View File
@@ -20,6 +20,9 @@ omit =
homeassistant/components/ecobee.py
homeassistant/components/*/ecobee.py
homeassistant/components/envisalink.py
homeassistant/components/*/envisalink.py
homeassistant/components/insteon_hub.py
homeassistant/components/*/insteon_hub.py
@@ -78,6 +81,12 @@ omit =
homeassistant/components/enocean.py
homeassistant/components/*/enocean.py
homeassistant/components/netatmo.py
homeassistant/components/*/netatmo.py
homeassistant/components/homematic.py
homeassistant/components/*/homematic.py
homeassistant/components/alarm_control_panel/alarmdotcom.py
homeassistant/components/alarm_control_panel/nx584.py
homeassistant/components/binary_sensor/arest.py
@@ -92,6 +101,7 @@ omit =
homeassistant/components/device_tracker/aruba.py
homeassistant/components/device_tracker/asuswrt.py
homeassistant/components/device_tracker/bluetooth_tracker.py
homeassistant/components/device_tracker/bt_home_hub_5.py
homeassistant/components/device_tracker/ddwrt.py
homeassistant/components/device_tracker/fritz.py
homeassistant/components/device_tracker/icloud.py
@@ -107,6 +117,8 @@ omit =
homeassistant/components/downloader.py
homeassistant/components/feedreader.py
homeassistant/components/garage_door/wink.py
homeassistant/components/garage_door/rpi_gpio.py
homeassistant/components/hdmi_cec.py
homeassistant/components/ifttt.py
homeassistant/components/keyboard.py
homeassistant/components/light/blinksticklight.py
@@ -116,7 +128,9 @@ omit =
homeassistant/components/light/limitlessled.py
homeassistant/components/light/osramlightify.py
homeassistant/components/lirc.py
homeassistant/components/media_player/braviatv.py
homeassistant/components/media_player/cast.py
homeassistant/components/media_player/cmus.py
homeassistant/components/media_player/denon.py
homeassistant/components/media_player/firetv.py
homeassistant/components/media_player/gpmdp.py
@@ -126,6 +140,7 @@ omit =
homeassistant/components/media_player/mpd.py
homeassistant/components/media_player/onkyo.py
homeassistant/components/media_player/panasonic_viera.py
homeassistant/components/media_player/pandora.py
homeassistant/components/media_player/pioneer.py
homeassistant/components/media_player/plex.py
homeassistant/components/media_player/roku.py
@@ -165,22 +180,26 @@ omit =
homeassistant/components/sensor/efergy.py
homeassistant/components/sensor/eliqonline.py
homeassistant/components/sensor/fitbit.py
homeassistant/components/sensor/fixer.py
homeassistant/components/sensor/forecast.py
homeassistant/components/sensor/glances.py
homeassistant/components/sensor/google_travel_time.py
homeassistant/components/sensor/gtfs.py
homeassistant/components/sensor/lastfm.py
homeassistant/components/sensor/loopenergy.py
homeassistant/components/sensor/netatmo.py
homeassistant/components/sensor/neurio_energy.py
homeassistant/components/sensor/nzbget.py
homeassistant/components/sensor/onewire.py
homeassistant/components/sensor/openweathermap.py
homeassistant/components/sensor/openexchangerates.py
homeassistant/components/sensor/plex.py
homeassistant/components/sensor/rest.py
homeassistant/components/sensor/sabnzbd.py
homeassistant/components/sensor/snmp.py
homeassistant/components/sensor/speedtest.py
homeassistant/components/sensor/steam_online.py
homeassistant/components/sensor/supervisord.py
homeassistant/components/sensor/swiss_hydrological_data.py
homeassistant/components/sensor/swiss_public_transport.py
homeassistant/components/sensor/systemmonitor.py
homeassistant/components/sensor/temper.py
@@ -196,6 +215,7 @@ omit =
homeassistant/components/switch/edimax.py
homeassistant/components/switch/hikvisioncam.py
homeassistant/components/switch/mystrom.py
homeassistant/components/switch/netio.py
homeassistant/components/switch/orvibo.py
homeassistant/components/switch/pulseaudio_loopback.py
homeassistant/components/switch/rest.py
+3
View File
@@ -22,6 +22,9 @@ http:
# Set to 1 to enable development mode
# development: 1
frontend:
# enable the frontend
light:
# platform: hue
Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 232 KiB

+567
View File
@@ -0,0 +1,567 @@
swagger: '2.0'
info:
title: Home Assistant
description: Home Assistant REST API
version: "1.0.0"
# the domain of the service
host: localhost:8123
# array of all schemes that your API supports
schemes:
- http
- https
securityDefinitions:
api_key:
type: apiKey
description: API password
name: api_password
in: query
# api_key:
# type: apiKey
# description: API password
# name: x-ha-access
# in: header
# will be prefixed to all paths
basePath: /api
consumes:
- application/json
produces:
- application/json
paths:
/:
get:
summary: API alive message
description: Returns message if API is up and running.
tags:
- Core
responses:
200:
description: API is up and running
schema:
$ref: '#/definitions/Message'
default:
description: Error
schema:
$ref: '#/definitions/Message'
/config:
get:
summary: API alive message
description: Returns the current configuration as JSON.
tags:
- Core
responses:
200:
description: Current configuration
schema:
$ref: '#/definitions/ApiConfig'
default:
description: Error
schema:
$ref: '#/definitions/Message'
/discovery_info:
get:
summary: Basic information about Home Assistant instance
tags:
- Core
responses:
200:
description: Basic information
schema:
$ref: '#/definitions/DiscoveryInfo'
default:
description: Error
schema:
$ref: '#/definitions/Message'
/bootstrap:
get:
summary: Returns all data needed to bootstrap Home Assistant.
tags:
- Core
responses:
200:
description: Bootstrap information
schema:
$ref: '#/definitions/BootstrapInfo'
default:
description: Error
schema:
$ref: '#/definitions/Message'
/events:
get:
summary: Array of event objects.
description: Returns an array of event objects. Each event object contain event name and listener count.
tags:
- Events
responses:
200:
description: Events
schema:
type: array
items:
$ref: '#/definitions/Event'
default:
description: Error
schema:
$ref: '#/definitions/Message'
/services:
get:
summary: Array of service objects.
description: Returns an array of service objects. Each object contains the domain and which services it contains.
tags:
- Services
responses:
200:
description: Services
schema:
type: array
items:
$ref: '#/definitions/Service'
default:
description: Error
schema:
$ref: '#/definitions/Message'
/history:
get:
summary: Array of state changes in the past.
description: Returns an array of state changes in the past. Each object contains further detail for the entities.
tags:
- State
responses:
200:
description: State changes
schema:
type: array
items:
$ref: '#/definitions/History'
default:
description: Error
schema:
$ref: '#/definitions/Message'
/states:
get:
summary: Array of state objects.
description: |
Returns an array of state objects. Each state has the following attributes: entity_id, state, last_changed and attributes.
tags:
- State
responses:
200:
description: States
schema:
type: array
items:
$ref: '#/definitions/State'
default:
description: Error
schema:
$ref: '#/definitions/Message'
/states/{entity_id}:
get:
summary: Specific state object.
description: |
Returns a state object for specified entity_id.
tags:
- State
parameters:
- name: entity_id
in: path
description: entity_id of the entity to query
required: true
type: string
responses:
200:
description: State
schema:
$ref: '#/definitions/State'
404:
description: Not found
schema:
$ref: '#/definitions/Message'
default:
description: Error
schema:
$ref: '#/definitions/Message'
post:
description: |
Updates or creates the current state of an entity.
tags:
- State
consumes:
- application/json
parameters:
- name: entity_id
in: path
description: entity_id to set the state of
required: true
type: string
- $ref: '#/parameters/State'
responses:
200:
description: State of existing entity was set
schema:
$ref: '#/definitions/State'
201:
description: State of new entity was set
schema:
$ref: '#/definitions/State'
headers:
location:
type: string
description: location of the new entity
default:
description: Error
schema:
$ref: '#/definitions/Message'
/error_log:
get:
summary: Error log
description: |
Retrieve all errors logged during the current session of Home Assistant as a plaintext response.
tags:
- Core
produces:
- text/plain
responses:
200:
description: Plain text error log
default:
description: Error
schema:
$ref: '#/definitions/Message'
/camera_proxy/camera.{entity_id}:
get:
summary: Camera image.
description: |
Returns the data (image) from the specified camera entity_id.
tags:
- Camera
produces:
- image/jpeg
parameters:
- name: entity_id
in: path
description: entity_id of the camera to query
required: true
type: string
responses:
200:
description: Camera image
schema:
type: file
default:
description: Error
schema:
$ref: '#/definitions/Message'
/events/{event_type}:
post:
description: |
Fires an event with event_type
tags:
- Events
consumes:
- application/json
parameters:
- name: event_type
in: path
description: event_type to fire event with
required: true
type: string
- $ref: '#/parameters/EventData'
responses:
200:
description: Response message
schema:
$ref: '#/definitions/Message'
default:
description: Error
schema:
$ref: '#/definitions/Message'
/services/{domain}/{service}:
post:
description: |
Calls a service within a specific domain. Will return when the service has been executed or 10 seconds has past, whichever comes first.
tags:
- Services
consumes:
- application/json
parameters:
- name: domain
in: path
description: domain of the service
required: true
type: string
- name: service
in: path
description: service to call
required: true
type: string
- $ref: '#/parameters/ServiceData'
responses:
200:
description: List of states that have changed while the service was being executed. The result will include any changed states that changed while the service was being executed, even if their change was the result of something else happening in the system.
schema:
type: array
items:
$ref: '#/definitions/State'
default:
description: Error
schema:
$ref: '#/definitions/Message'
/template:
post:
description: |
Render a Home Assistant template.
tags:
- Template
consumes:
- application/json
produces:
- text/plain
parameters:
- $ref: '#/parameters/Template'
responses:
200:
description: Returns the rendered template in plain text.
schema:
type: string
default:
description: Error
schema:
$ref: '#/definitions/Message'
/event_forwarding:
post:
description: |
Setup event forwarding to another Home Assistant instance.
tags:
- Core
consumes:
- application/json
parameters:
- $ref: '#/parameters/EventForwarding'
responses:
200:
description: It will return a message if event forwarding was setup successful.
schema:
$ref: '#/definitions/Message'
default:
description: Error
schema:
$ref: '#/definitions/Message'
delete:
description: |
Cancel event forwarding to another Home Assistant instance.
tags:
- Core
consumes:
- application/json
parameters:
- $ref: '#/parameters/EventForwarding'
responses:
200:
description: It will return a message if event forwarding was cancelled successful.
schema:
$ref: '#/definitions/Message'
default:
description: Error
schema:
$ref: '#/definitions/Message'
/stream:
get:
summary: Server-sent events
description: The server-sent events feature is a one-way channel from your Home Assistant server to a client which is acting as a consumer.
tags:
- Core
- Events
produces:
- text/event-stream
parameters:
- name: restrict
in: query
description: comma-separated list of event_types to filter
required: false
type: string
responses:
default:
description: Stream of events
schema:
type: object
x-events:
state_changed:
type: object
properties:
entity_id:
type: string
old_state:
$ref: '#/definitions/State'
new_state:
$ref: '#/definitions/State'
definitions:
ApiConfig:
type: object
properties:
components:
type: array
description: List of component types
items:
type: string
description: Component type
latitude:
type: number
format: float
description: Latitude of Home Assistant server
longitude:
type: number
format: float
description: Longitude of Home Assistant server
location_name:
type: string
temperature_unit:
type: string
time_zone:
type: string
version:
type: string
DiscoveryInfo:
type: object
properties:
base_url:
type: string
location_name:
type: string
requires_api_password:
type: boolean
version:
type: string
BootstrapInfo:
type: object
properties:
config:
$ref: '#/definitions/ApiConfig'
events:
type: array
items:
$ref: '#/definitions/Event'
services:
type: array
items:
$ref: '#/definitions/Service'
states:
type: array
items:
$ref: '#/definitions/State'
Event:
type: object
properties:
event:
type: string
listener_count:
type: integer
Service:
type: object
properties:
domain:
type: string
services:
type: object
additionalProperties:
$ref: '#/definitions/DomainService'
DomainService:
type: object
properties:
description:
type: string
fields:
type: object
description: Object with service fields that can be called
State:
type: object
properties:
attributes:
$ref: '#/definitions/StateAttributes'
state:
type: string
entity_id:
type: string
last_changed:
type: string
format: date-time
StateAttributes:
type: object
additionalProperties:
type: string
History:
allOf:
- $ref: '#/definitions/State'
- type: object
properties:
last_updated:
type: string
format: date-time
Message:
type: object
properties:
message:
type: string
parameters:
State:
name: body
in: body
description: State parameter
required: false
schema:
type: object
required:
- state
properties:
attributes:
$ref: '#/definitions/StateAttributes'
state:
type: string
EventData:
name: body
in: body
description: event_data
required: false
schema:
type: object
ServiceData:
name: body
in: body
description: service_data
required: false
schema:
type: object
Template:
name: body
in: body
description: Template to render
required: true
schema:
type: object
required:
- template
properties:
template:
description: Jinja2 template string
type: string
EventForwarding:
name: body
in: body
description: Event Forwarding parameter
required: true
schema:
type: object
required:
- host
- api_password
properties:
host:
type: string
api_password:
type: string
port:
type: integer
+16 -117
View File
@@ -3,7 +3,6 @@
import logging
import logging.handlers
import os
import shutil
import sys
from collections import defaultdict
from threading import RLock
@@ -11,22 +10,16 @@ from threading import RLock
import voluptuous as vol
import homeassistant.components as core_components
import homeassistant.components.group as group
import homeassistant.config as config_util
from homeassistant.components import group, persistent_notification
import homeassistant.config as conf_util
import homeassistant.core as core
import homeassistant.helpers.config_validation as cv
import homeassistant.loader as loader
import homeassistant.util.dt as date_util
import homeassistant.util.location as loc_util
import homeassistant.util.package as pkg_util
from homeassistant.const import (
CONF_CUSTOMIZE, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME,
CONF_TEMPERATURE_UNIT, CONF_TIME_ZONE, EVENT_COMPONENT_LOADED,
TEMP_CELSIUS, TEMP_FAHRENHEIT, PLATFORM_FORMAT, __version__)
from homeassistant.const import EVENT_COMPONENT_LOADED, PLATFORM_FORMAT
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import (
event_decorators, service, config_per_platform, extract_domain_configs)
from homeassistant.helpers.entity import Entity
_LOGGER = logging.getLogger(__name__)
_SETUP_LOCK = RLock()
@@ -208,11 +201,6 @@ def prepare_setup_platform(hass, config, domain, platform_name):
return platform
def mount_local_lib_path(config_dir):
"""Add local library to Python Path."""
sys.path.insert(0, os.path.join(config_dir, 'deps'))
# pylint: disable=too-many-branches, too-many-statements, too-many-arguments
def from_config_dict(config, hass=None, config_dir=None, enable_log=True,
verbose=False, skip_pip=False,
@@ -226,18 +214,17 @@ def from_config_dict(config, hass=None, config_dir=None, enable_log=True,
if config_dir is not None:
config_dir = os.path.abspath(config_dir)
hass.config.config_dir = config_dir
mount_local_lib_path(config_dir)
_mount_local_lib_path(config_dir)
core_config = config.get(core.DOMAIN, {})
try:
process_ha_core_config(hass, config_util.CORE_CONFIG_SCHEMA(
core_config))
except vol.MultipleInvalid as ex:
conf_util.process_ha_core_config(hass, core_config)
except vol.Invalid as ex:
cv.log_exception(_LOGGER, ex, 'homeassistant', core_config)
return None
process_ha_config_upgrade(hass)
conf_util.process_ha_config_upgrade(hass)
if enable_log:
enable_logging(hass, verbose, log_rotate_days)
@@ -262,9 +249,10 @@ def from_config_dict(config, hass=None, config_dir=None, enable_log=True,
if not core_components.setup(hass, config):
_LOGGER.error('Home Assistant core failed to initialize. '
'Further initialization aborted.')
return hass
persistent_notification.setup(hass, config)
_LOGGER.info('Home Assistant core initialized')
# Give event decorators access to HASS
@@ -291,12 +279,12 @@ def from_config_file(config_path, hass=None, verbose=False, skip_pip=True,
# Set config dir to directory holding config file
config_dir = os.path.abspath(os.path.dirname(config_path))
hass.config.config_dir = config_dir
mount_local_lib_path(config_dir)
_mount_local_lib_path(config_dir)
enable_logging(hass, verbose, log_rotate_days)
try:
config_dict = config_util.load_yaml_config_file(config_path)
config_dict = conf_util.load_yaml_config_file(config_path)
except HomeAssistantError:
return None
@@ -355,101 +343,12 @@ def enable_logging(hass, verbose=False, log_rotate_days=None):
'Unable to setup error log %s (access denied)', err_log_path)
def process_ha_config_upgrade(hass):
"""Upgrade config if necessary."""
version_path = hass.config.path('.HA_VERSION')
try:
with open(version_path, 'rt') as inp:
conf_version = inp.readline().strip()
except FileNotFoundError:
# Last version to not have this file
conf_version = '0.7.7'
if conf_version == __version__:
return
_LOGGER.info('Upgrading config directory from %s to %s', conf_version,
__version__)
# This was where dependencies were installed before v0.18
# Probably should keep this around until ~v0.20.
lib_path = hass.config.path('lib')
if os.path.isdir(lib_path):
shutil.rmtree(lib_path)
lib_path = hass.config.path('deps')
if os.path.isdir(lib_path):
shutil.rmtree(lib_path)
with open(version_path, 'wt') as outp:
outp.write(__version__)
def process_ha_core_config(hass, config):
"""Process the [homeassistant] section from the config."""
hac = hass.config
def set_time_zone(time_zone_str):
"""Helper method to set time zone."""
if time_zone_str is None:
return
time_zone = date_util.get_time_zone(time_zone_str)
if time_zone:
hac.time_zone = time_zone
date_util.set_default_time_zone(time_zone)
else:
_LOGGER.error('Received invalid time zone %s', time_zone_str)
for key, attr in ((CONF_LATITUDE, 'latitude'),
(CONF_LONGITUDE, 'longitude'),
(CONF_NAME, 'location_name')):
if key in config:
setattr(hac, attr, config[key])
if CONF_TIME_ZONE in config:
set_time_zone(config.get(CONF_TIME_ZONE))
for entity_id, attrs in config.get(CONF_CUSTOMIZE).items():
Entity.overwrite_attribute(entity_id, attrs.keys(), attrs.values())
if CONF_TEMPERATURE_UNIT in config:
hac.temperature_unit = config[CONF_TEMPERATURE_UNIT]
# If we miss some of the needed values, auto detect them
if None not in (
hac.latitude, hac.longitude, hac.temperature_unit, hac.time_zone):
return
_LOGGER.warning('Incomplete core config. Auto detecting location and '
'temperature unit')
info = loc_util.detect_location_info()
if info is None:
_LOGGER.error('Could not detect location information')
return
if hac.latitude is None and hac.longitude is None:
hac.latitude = info.latitude
hac.longitude = info.longitude
if hac.temperature_unit is None:
if info.use_fahrenheit:
hac.temperature_unit = TEMP_FAHRENHEIT
else:
hac.temperature_unit = TEMP_CELSIUS
if hac.location_name is None:
hac.location_name = info.city
if hac.time_zone is None:
set_time_zone(info.time_zone)
def _ensure_loader_prepared(hass):
"""Ensure Home Assistant loader is prepared."""
if not loader.PREPARED:
loader.prepare(hass)
def _mount_local_lib_path(config_dir):
"""Add local library to Python Path."""
sys.path.insert(0, os.path.join(config_dir, 'deps'))
+24
View File
@@ -19,6 +19,8 @@ from homeassistant.const import (
_LOGGER = logging.getLogger(__name__)
SERVICE_RELOAD_CORE_CONFIG = 'reload_core_config'
def is_on(hass, entity_id=None):
"""Load up the module to call the is_on method.
@@ -73,6 +75,11 @@ def toggle(hass, entity_id=None, **service_data):
hass.services.call(ha.DOMAIN, SERVICE_TOGGLE, service_data)
def reload_core_config(hass):
"""Reload the core config."""
hass.services.call(ha.DOMAIN, SERVICE_RELOAD_CORE_CONFIG)
def setup(hass, config):
"""Setup general services related to Home Assistant."""
def handle_turn_service(service):
@@ -111,4 +118,21 @@ def setup(hass, config):
hass.services.register(ha.DOMAIN, SERVICE_TURN_ON, handle_turn_service)
hass.services.register(ha.DOMAIN, SERVICE_TOGGLE, handle_turn_service)
def handle_reload_config(call):
"""Service handler for reloading core config."""
from homeassistant.exceptions import HomeAssistantError
from homeassistant import config as conf_util
try:
path = conf_util.find_config_file(hass.config.config_dir)
conf = conf_util.load_yaml_config_file(path)
except HomeAssistantError as err:
_LOGGER.error(err)
return
conf_util.process_ha_core_config(hass, conf.get(ha.DOMAIN) or {})
hass.services.register(ha.DOMAIN, SERVICE_RELOAD_CORE_CONFIG,
handle_reload_config)
return True
@@ -9,7 +9,6 @@ import os
import voluptuous as vol
from homeassistant.components import verisure
from homeassistant.const import (
ATTR_CODE, ATTR_CODE_FORMAT, ATTR_ENTITY_ID, SERVICE_ALARM_TRIGGER,
SERVICE_ALARM_DISARM, SERVICE_ALARM_ARM_HOME, SERVICE_ALARM_ARM_AWAY)
@@ -24,11 +23,6 @@ SCAN_INTERVAL = 30
ENTITY_ID_FORMAT = DOMAIN + '.{}'
# Maps discovered services to their platforms
DISCOVERY_PLATFORMS = {
verisure.DISCOVER_ALARMS: 'verisure'
}
SERVICE_TO_METHOD = {
SERVICE_ALARM_DISARM: 'alarm_disarm',
SERVICE_ALARM_ARM_HOME: 'alarm_arm_home',
@@ -50,8 +44,7 @@ ALARM_SERVICE_SCHEMA = vol.Schema({
def setup(hass, config):
"""Track states and offer events for sensors."""
component = EntityComponent(
logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL,
DISCOVERY_PLATFORMS)
logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL)
component.setup(config)
@@ -0,0 +1,105 @@
"""
Support for Envisalink-based alarm control panels (Honeywell/DSC).
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.envisalink/
"""
import logging
import homeassistant.components.alarm_control_panel as alarm
from homeassistant.components.envisalink import (EVL_CONTROLLER,
EnvisalinkDevice,
PARTITION_SCHEMA,
CONF_CODE,
CONF_PARTITIONNAME,
SIGNAL_PARTITION_UPDATE,
SIGNAL_KEYPAD_UPDATE)
from homeassistant.const import (
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED,
STATE_UNKNOWN, STATE_ALARM_TRIGGERED)
DEPENDENCIES = ['envisalink']
_LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Perform the setup for Envisalink alarm panels."""
_configured_partitions = discovery_info['partitions']
_code = discovery_info[CONF_CODE]
for part_num in _configured_partitions:
_device_config_data = PARTITION_SCHEMA(
_configured_partitions[part_num])
_device = EnvisalinkAlarm(
part_num,
_device_config_data[CONF_PARTITIONNAME],
_code,
EVL_CONTROLLER.alarm_state['partition'][part_num],
EVL_CONTROLLER)
add_devices_callback([_device])
return True
class EnvisalinkAlarm(EnvisalinkDevice, alarm.AlarmControlPanel):
"""Represents the Envisalink-based alarm panel."""
# pylint: disable=too-many-arguments
def __init__(self, partition_number, alarm_name, code, info, controller):
"""Initialize the alarm panel."""
from pydispatch import dispatcher
self._partition_number = partition_number
self._code = code
_LOGGER.debug('Setting up alarm: ' + alarm_name)
EnvisalinkDevice.__init__(self, alarm_name, info, controller)
dispatcher.connect(self._update_callback,
signal=SIGNAL_PARTITION_UPDATE,
sender=dispatcher.Any)
dispatcher.connect(self._update_callback,
signal=SIGNAL_KEYPAD_UPDATE,
sender=dispatcher.Any)
def _update_callback(self, partition):
"""Update HA state, if needed."""
if partition is None or int(partition) == self._partition_number:
self.update_ha_state()
@property
def code_format(self):
"""The characters if code is defined."""
return self._code
@property
def state(self):
"""Return the state of the device."""
if self._info['status']['alarm']:
return STATE_ALARM_TRIGGERED
elif self._info['status']['armed_away']:
return STATE_ALARM_ARMED_AWAY
elif self._info['status']['armed_stay']:
return STATE_ALARM_ARMED_HOME
elif self._info['status']['alpha']:
return STATE_ALARM_DISARMED
else:
return STATE_UNKNOWN
def alarm_disarm(self, code=None):
"""Send disarm command."""
if self._code:
EVL_CONTROLLER.disarm_partition(str(code),
self._partition_number)
def alarm_arm_home(self, code=None):
"""Send arm home command."""
if self._code:
EVL_CONTROLLER.arm_stay_partition(str(code),
self._partition_number)
def alarm_arm_away(self, code=None):
"""Send arm away command."""
if self._code:
EVL_CONTROLLER.arm_away_partition(str(code),
self._partition_number)
def alarm_trigger(self, code=None):
"""Alarm trigger command. Not possible for us."""
raise NotImplementedError()
+3 -3
View File
@@ -96,6 +96,9 @@ class AlexaView(HomeAssistantView):
card = config.get(CONF_CARD)
action = config.get(CONF_ACTION)
if action is not None:
action.run(response.variables)
# pylint: disable=unsubscriptable-object
if speech is not None:
response.add_speech(SpeechType[speech['type']], speech['text'])
@@ -104,9 +107,6 @@ class AlexaView(HomeAssistantView):
response.add_card(CardType[card['type']], card['title'],
card['content'])
if action is not None:
action.run(response.variables)
return self.json(response)
+13 -27
View File
@@ -6,7 +6,7 @@ https://home-assistant.io/developers/api/
"""
import json
import logging
from time import time
import queue
import homeassistant.core as ha
import homeassistant.remote as rem
@@ -72,19 +72,14 @@ class APIEventStream(HomeAssistantView):
def get(self, request):
"""Provide a streaming interface for the event bus."""
from eventlet.queue import LightQueue, Empty
import eventlet
cur_hub = eventlet.hubs.get_hub()
request.environ['eventlet.minimum_write_chunk_size'] = 0
to_write = LightQueue()
stop_obj = object()
to_write = queue.Queue()
restrict = request.args.get('restrict')
if restrict:
restrict = restrict.split(',')
restrict = restrict.split(',') + [EVENT_HOMEASSISTANT_STOP]
def thread_forward_events(event):
def forward_events(event):
"""Forward events to the open request."""
if event.event_type == EVENT_TIME_CHANGED:
return
@@ -99,28 +94,20 @@ class APIEventStream(HomeAssistantView):
else:
data = json.dumps(event, cls=rem.JSONEncoder)
cur_hub.schedule_call_global(0, lambda: to_write.put(data))
to_write.put(data)
def stream():
"""Stream events to response."""
self.hass.bus.listen(MATCH_ALL, thread_forward_events)
self.hass.bus.listen(MATCH_ALL, forward_events)
_LOGGER.debug('STREAM %s ATTACHED', id(stop_obj))
last_msg = time()
# Fire off one message right away to have browsers fire open event
to_write.put(STREAM_PING_PAYLOAD)
while True:
try:
# Somehow our queue.get sometimes takes too long to
# be notified of arrival of data. Probably
# because of our spawning on hub in other thread
# hack. Because current goal is to get this out,
# We just timeout every second because it will
# return right away if qsize() > 0.
# So yes, we're basically polling :(
payload = to_write.get(timeout=1)
payload = to_write.get(timeout=STREAM_PING_INTERVAL)
if payload is stop_obj:
break
@@ -129,15 +116,13 @@ class APIEventStream(HomeAssistantView):
_LOGGER.debug('STREAM %s WRITING %s', id(stop_obj),
msg.strip())
yield msg.encode("UTF-8")
last_msg = time()
except Empty:
if time() - last_msg > 50:
to_write.put(STREAM_PING_PAYLOAD)
except queue.Empty:
to_write.put(STREAM_PING_PAYLOAD)
except GeneratorExit:
_LOGGER.debug('STREAM %s RESPONSE CLOSED', id(stop_obj))
break
self.hass.bus.remove_listener(MATCH_ALL, thread_forward_events)
_LOGGER.debug('STREAM %s RESPONSE CLOSED', id(stop_obj))
self.hass.bus.remove_listener(MATCH_ALL, forward_events)
return self.Response(stream(), mimetype='text/event-stream')
@@ -204,11 +189,12 @@ class APIEntityStateView(HomeAssistantView):
return self.json_message('No state specified', HTTP_BAD_REQUEST)
attributes = request.json.get('attributes')
force_update = request.json.get('force_update', False)
is_new_state = self.hass.states.get(entity_id) is None
# Write state
self.hass.states.set(entity_id, new_state, attributes)
self.hass.states.set(entity_id, new_state, attributes, force_update)
# Read the state back for our response
resp = self.json(self.hass.states.get(entity_id))
@@ -9,8 +9,6 @@ import logging
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity import Entity
from homeassistant.const import (STATE_ON, STATE_OFF)
from homeassistant.components import (
bloomsky, mysensors, zwave, vera, wemo, wink)
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
DOMAIN = 'binary_sensor'
@@ -35,22 +33,11 @@ SENSOR_CLASSES = [
'vibration', # On means vibration detected, Off means no vibration
]
# Maps discovered services to their platforms
DISCOVERY_PLATFORMS = {
bloomsky.DISCOVER_BINARY_SENSORS: 'bloomsky',
mysensors.DISCOVER_BINARY_SENSORS: 'mysensors',
zwave.DISCOVER_BINARY_SENSORS: 'zwave',
vera.DISCOVER_BINARY_SENSORS: 'vera',
wemo.DISCOVER_BINARY_SENSORS: 'wemo',
wink.DISCOVER_BINARY_SENSORS: 'wink'
}
def setup(hass, config):
"""Track states and offer events for binary sensors."""
component = EntityComponent(
logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL,
DISCOVERY_PLATFORMS)
logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL)
component.setup(config)
@@ -0,0 +1,71 @@
"""
Support for Envisalink zone states- represented as binary sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.envisalink/
"""
import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.envisalink import (EVL_CONTROLLER,
ZONE_SCHEMA,
CONF_ZONENAME,
CONF_ZONETYPE,
EnvisalinkDevice,
SIGNAL_ZONE_UPDATE)
from homeassistant.const import ATTR_LAST_TRIP_TIME
DEPENDENCIES = ['envisalink']
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Perform the setup for Envisalink sensor devices."""
_configured_zones = discovery_info['zones']
for zone_num in _configured_zones:
_device_config_data = ZONE_SCHEMA(_configured_zones[zone_num])
_device = EnvisalinkBinarySensor(
zone_num,
_device_config_data[CONF_ZONENAME],
_device_config_data[CONF_ZONETYPE],
EVL_CONTROLLER.alarm_state['zone'][zone_num],
EVL_CONTROLLER)
add_devices_callback([_device])
class EnvisalinkBinarySensor(EnvisalinkDevice, BinarySensorDevice):
"""Representation of an envisalink Binary Sensor."""
# pylint: disable=too-many-arguments
def __init__(self, zone_number, zone_name, zone_type, info, controller):
"""Initialize the binary_sensor."""
from pydispatch import dispatcher
self._zone_type = zone_type
self._zone_number = zone_number
_LOGGER.debug('Setting up zone: ' + zone_name)
EnvisalinkDevice.__init__(self, zone_name, info, controller)
dispatcher.connect(self._update_callback,
signal=SIGNAL_ZONE_UPDATE,
sender=dispatcher.Any)
@property
def device_state_attributes(self):
"""Return the state attributes."""
attr = {}
attr[ATTR_LAST_TRIP_TIME] = self._info['last_fault']
return attr
@property
def is_on(self):
"""Return true if sensor is on."""
return self._info['status']['open']
@property
def sensor_class(self):
"""Return the class of this sensor, from SENSOR_CLASSES."""
return self._zone_type
def _update_callback(self, zone):
"""Update the zone's state, if needed."""
if zone is None or int(zone) == self._zone_number:
self.update_ha_state()
@@ -0,0 +1,100 @@
"""
Support for Homematic binary sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.homematic/
"""
import logging
from homeassistant.const import STATE_UNKNOWN
from homeassistant.components.binary_sensor import BinarySensorDevice
import homeassistant.components.homematic as homematic
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['homematic']
SENSOR_TYPES_CLASS = {
"Remote": None,
"ShutterContact": "opening",
"Smoke": "smoke",
"SmokeV2": "smoke",
"Motion": "motion",
"MotionV2": "motion",
"RemoteMotion": None
}
def setup_platform(hass, config, add_callback_devices, discovery_info=None):
"""Setup the Homematic binary sensor platform."""
if discovery_info is None:
return
return homematic.setup_hmdevice_discovery_helper(HMBinarySensor,
discovery_info,
add_callback_devices)
class HMBinarySensor(homematic.HMDevice, BinarySensorDevice):
"""Representation of a binary Homematic device."""
@property
def is_on(self):
"""Return true if switch is on."""
if not self.available:
return False
return bool(self._hm_get_state())
@property
def sensor_class(self):
"""Return the class of this sensor, from SENSOR_CLASSES."""
if not self.available:
return None
# If state is MOTION (RemoteMotion works only)
if self._state == "MOTION":
return "motion"
return SENSOR_TYPES_CLASS.get(self._hmdevice.__class__.__name__, None)
def _check_hm_to_ha_object(self):
"""Check if possible to use the HM Object as this HA type."""
from pyhomematic.devicetypes.sensors import HMBinarySensor\
as pyHMBinarySensor
# Check compatibility from HMDevice
if not super()._check_hm_to_ha_object():
return False
# check if the Homematic device correct for this HA device
if not isinstance(self._hmdevice, pyHMBinarySensor):
_LOGGER.critical("This %s can't be use as binary", self._name)
return False
# if exists user value?
if self._state and self._state not in self._hmdevice.BINARYNODE:
_LOGGER.critical("This %s have no binary with %s", self._name,
self._state)
return False
# only check and give a warning to the user
if self._state is None and len(self._hmdevice.BINARYNODE) > 1:
_LOGGER.critical("%s have multiple binary params. It use all "
"binary nodes as one. Possible param values: %s",
self._name, str(self._hmdevice.BINARYNODE))
return False
return True
def _init_data_struct(self):
"""Generate a data struct (self._data) from the Homematic metadata."""
super()._init_data_struct()
# object have 1 binary
if self._state is None and len(self._hmdevice.BINARYNODE) == 1:
for value in self._hmdevice.BINARYNODE:
self._state = value
# add state to data struct
if self._state:
_LOGGER.debug("%s init datastruct with main node '%s'", self._name,
self._state)
self._data.update({self._state: STATE_UNKNOWN})
@@ -35,7 +35,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
rest.update()
if rest.data is None:
_LOGGER.error('Unable to fetch Rest data')
_LOGGER.error('Unable to fetch REST data')
return False
add_devices([RestBinarySensor(
@@ -57,6 +57,7 @@ class RestBinarySensor(BinarySensorDevice):
self._name = name
self._sensor_class = sensor_class
self._state = False
self._previous_data = None
self._value_template = value_template
self.update()
@@ -77,9 +78,14 @@ class RestBinarySensor(BinarySensorDevice):
return False
if self._value_template is not None:
self.rest.data = template.render_with_possible_json_value(
response = template.render_with_possible_json_value(
self._hass, self._value_template, self.rest.data, False)
return bool(int(self.rest.data))
try:
return bool(int(response))
except ValueError:
return {"true": True, "on": True, "open": True,
"yes": True}.get(response.lower(), False)
def update(self):
"""Get the latest data from REST API and updates the state."""
+7 -37
View File
@@ -7,10 +7,12 @@ at https://home-assistant.io/components/sensor.wink/
import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.const import CONF_ACCESS_TOKEN, ATTR_BATTERY_LEVEL
from homeassistant.components.sensor.wink import WinkDevice
from homeassistant.const import CONF_ACCESS_TOKEN
from homeassistant.helpers.entity import Entity
from homeassistant.loader import get_component
REQUIREMENTS = ['python-wink==0.7.6']
REQUIREMENTS = ['python-wink==0.7.8', 'pubnub==3.7.8']
# These are the available sensors mapped to binary_sensor class
SENSOR_TYPES = {
@@ -41,14 +43,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
add_devices([WinkBinarySensorDevice(sensor)])
class WinkBinarySensorDevice(BinarySensorDevice, Entity):
class WinkBinarySensorDevice(WinkDevice, BinarySensorDevice, Entity):
"""Representation of a Wink sensor."""
def __init__(self, wink):
"""Initialize the Wink binary sensor."""
self.wink = wink
super().__init__(wink)
wink = get_component('wink')
self._unit_of_measurement = self.wink.UNIT
self._battery = self.wink.battery_level
self.capability = self.wink.capability()
@property
@@ -67,35 +69,3 @@ class WinkBinarySensorDevice(BinarySensorDevice, Entity):
def sensor_class(self):
"""Return the class of this sensor, from SENSOR_CLASSES."""
return SENSOR_TYPES.get(self.capability)
@property
def unique_id(self):
"""Return the ID of this wink sensor."""
return "{}.{}".format(self.__class__, self.wink.device_id())
@property
def name(self):
"""Return the name of the sensor if any."""
return self.wink.name()
@property
def available(self):
"""True if connection == True."""
return self.wink.available
def update(self):
"""Update state of the sensor."""
self.wink.update_state()
@property
def device_state_attributes(self):
"""Return the state attributes."""
if self._battery:
return {
ATTR_BATTERY_LEVEL: self._battery_level,
}
@property
def _battery_level(self):
"""Return the battery level."""
return self.wink.battery_level * 100
+3 -11
View File
@@ -9,9 +9,8 @@ from datetime import timedelta
import requests
from homeassistant.components import discovery
from homeassistant.const import CONF_API_KEY
from homeassistant.helpers import validate_config
from homeassistant.helpers import validate_config, discovery
from homeassistant.util import Throttle
DOMAIN = "bloomsky"
@@ -23,10 +22,6 @@ _LOGGER = logging.getLogger(__name__)
# no point in polling the API more frequently
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=300)
DISCOVER_SENSORS = 'bloomsky.sensors'
DISCOVER_BINARY_SENSORS = 'bloomsky.binary_sensor'
DISCOVER_CAMERAS = 'bloomsky.camera'
# pylint: disable=unused-argument,too-few-public-methods
def setup(hass, config):
@@ -45,11 +40,8 @@ def setup(hass, config):
except RuntimeError:
return False
for component, discovery_service in (
('camera', DISCOVER_CAMERAS), ('sensor', DISCOVER_SENSORS),
('binary_sensor', DISCOVER_BINARY_SENSORS)):
discovery.discover(hass, discovery_service, component=component,
hass_config=config)
for component in 'camera', 'binary_sensor', 'sensor':
discovery.load_platform(hass, component, DOMAIN, {}, config)
return True
+9 -17
View File
@@ -6,10 +6,10 @@ For more details about this component, please refer to the documentation at
https://home-assistant.io/components/camera/
"""
import logging
import time
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.components import bloomsky
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
from homeassistant.components.http import HomeAssistantView
@@ -18,11 +18,6 @@ DEPENDENCIES = ['http']
SCAN_INTERVAL = 30
ENTITY_ID_FORMAT = DOMAIN + '.{}'
# Maps discovered services to their platforms
DISCOVERY_PLATFORMS = {
bloomsky.DISCOVER_CAMERAS: 'bloomsky',
}
STATE_RECORDING = 'recording'
STATE_STREAMING = 'streaming'
STATE_IDLE = 'idle'
@@ -34,8 +29,7 @@ ENTITY_IMAGE_URL = '/api/camera_proxy/{0}?token={1}'
def setup(hass, config):
"""Setup the camera component."""
component = EntityComponent(
logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL,
DISCOVERY_PLATFORMS)
logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL)
hass.wsgi.register_view(CameraImageView(hass, component.entities))
hass.wsgi.register_view(CameraMjpegStream(hass, component.entities))
@@ -88,10 +82,6 @@ class Camera(Entity):
def mjpeg_stream(self, response):
"""Generate an HTTP MJPEG stream from camera images."""
import eventlet
response.content_type = ('multipart/x-mixed-replace; '
'boundary=--jpegboundary')
def stream():
"""Stream images as mjpeg stream."""
try:
@@ -108,13 +98,15 @@ class Camera(Entity):
last_image = img_bytes
eventlet.sleep(0.5)
time.sleep(0.5)
except GeneratorExit:
pass
response.response = stream()
return response
return response(
stream(),
content_type=('multipart/x-mixed-replace; '
'boundary=--jpegboundary')
)
@property
def state(self):
@@ -196,4 +188,4 @@ class CameraMjpegStream(CameraView):
def handle(self, camera):
"""Serve camera image."""
return camera.mjpeg_stream(self.Response())
return camera.mjpeg_stream(self.Response)
+1 -1
View File
@@ -49,7 +49,7 @@ class FoscamCamera(Camera):
def camera_image(self):
"""Return a still image reponse from the camera."""
# Send the request to snap a picture and return raw jpg data
response = requests.get(self._snap_picture_url)
response = requests.get(self._snap_picture_url, timeout=10)
return response.content
+3 -2
View File
@@ -43,13 +43,14 @@ class GenericCamera(Camera):
try:
response = requests.get(
self._still_image_url,
auth=HTTPBasicAuth(self._username, self._password))
auth=HTTPBasicAuth(self._username, self._password),
timeout=10)
except requests.exceptions.RequestException as error:
_LOGGER.error('Error getting camera image: %s', error)
return None
else:
try:
response = requests.get(self._still_image_url)
response = requests.get(self._still_image_url, timeout=10)
except requests.exceptions.RequestException as error:
_LOGGER.error('Error getting camera image: %s', error)
return None
@@ -0,0 +1,53 @@
"""Camera that loads a picture from a local file."""
import logging
import os
from homeassistant.components.camera import Camera
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Camera."""
# check for missing required configuration variable
if config.get("file_path") is None:
_LOGGER.error("Missing required variable: file_path")
return False
setup_config = (
{
"name": config.get("name", "Local File"),
"file_path": config.get("file_path")
}
)
# check filepath given is readable
if not os.access(setup_config["file_path"], os.R_OK):
_LOGGER.error("file path is not readable")
return False
add_devices([
LocalFile(setup_config)
])
class LocalFile(Camera):
"""Local camera."""
def __init__(self, device_info):
"""Initialize Local File Camera component."""
super().__init__()
self._name = device_info["name"]
self._config = device_info
def camera_image(self):
"""Return image response."""
with open(self._config["file_path"], 'rb') as file:
return file.read()
@property
def name(self):
"""Return the name of this camera."""
return self._name
+7 -6
View File
@@ -46,10 +46,9 @@ class MjpegCamera(Camera):
return requests.get(self._mjpeg_url,
auth=HTTPBasicAuth(self._username,
self._password),
stream=True)
stream=True, timeout=10)
else:
return requests.get(self._mjpeg_url,
stream=True)
return requests.get(self._mjpeg_url, stream=True, timeout=10)
def camera_image(self):
"""Return a still image response from the camera."""
@@ -70,9 +69,11 @@ class MjpegCamera(Camera):
def mjpeg_stream(self, response):
"""Generate an HTTP MJPEG stream from the camera."""
stream = self.camera_stream()
response.mimetype = stream.headers[CONTENT_TYPE_HEADER]
response.response = stream.iter_content(chunk_size=1024)
return response
return response(
stream.iter_content(chunk_size=1024),
mimetype=stream.headers[CONTENT_TYPE_HEADER],
direct_passthrough=True
)
@property
def name(self):
+104
View File
@@ -0,0 +1,104 @@
"""
Support for the Netatmo Welcome camera.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.netatmo/
"""
import logging
from datetime import timedelta
import requests
from homeassistant.util import Throttle
from homeassistant.components.camera import Camera
from homeassistant.loader import get_component
DEPENDENCIES = ["netatmo"]
_LOGGER = logging.getLogger(__name__)
CONF_HOME = 'home'
ATTR_CAMERAS = 'cameras'
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=10)
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Setup access to Netatmo Welcome cameras."""
netatmo = get_component('netatmo')
home = config.get(CONF_HOME, None)
data = WelcomeData(netatmo.NETATMO_AUTH, home)
for camera_name in data.get_camera_names():
if ATTR_CAMERAS in config:
if camera_name not in config[ATTR_CAMERAS]:
continue
add_devices_callback([WelcomeCamera(data, camera_name, home)])
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."""
super(WelcomeCamera, self).__init__()
self._data = data
self._camera_name = camera_name
if home:
self._name = home + ' / ' + camera_name
else:
self._name = camera_name
self._vpnurl, self._localurl = self._data.welcomedata.cameraUrls(
camera=camera_name
)
def camera_image(self):
"""Return a still image response from the camera."""
try:
if self._localurl:
response = requests.get('{0}/live/snapshot_720.jpg'.format(
self._localurl), timeout=10)
else:
response = requests.get('{0}/live/snapshot_720.jpg'.format(
self._vpnurl), timeout=10)
except requests.exceptions.RequestException as error:
_LOGGER.error('Welcome VPN url changed: %s', error)
self._data.update()
(self._vpnurl, self._localurl) = \
self._data.welcomedata.cameraUrls(camera=self._camera_name)
return None
return response.content
@property
def name(self):
"""Return the name of this Netatmo Welcome device."""
return self._name
class WelcomeData(object):
"""Get the latest data from NetAtmo."""
def __init__(self, auth, home=None):
"""Initialize the data object."""
self.auth = auth
self.welcomedata = None
self.camera_names = []
self.home = home
def get_camera_names(self):
"""Return all module available on the API as a list."""
self.update()
if not self.home:
for home in self.welcomedata.cameras.keys():
for camera in self.welcomedata.cameras[home].values():
self.camera_names.append(camera['name'])
else:
for camera in self.welcomedata.cameras[self.home].values():
self.camera_names.append(camera['name'])
return self.camera_names
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Call the NetAtmo API to update the data."""
import lnetatmo
self.welcomedata = lnetatmo.WelcomeData(self.auth)
@@ -1,5 +1,9 @@
"""Camera platform that has a Raspberry Pi camera."""
"""
Camera platform that has a Raspberry Pi camera.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.rpi_camera/
"""
import os
import subprocess
import logging
@@ -43,7 +47,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class RaspberryCamera(Camera):
"""Raspberry Pi camera."""
"""Representation of a Raspberry Pi camera."""
def __init__(self, device_info):
"""Initialize Raspberry Pi camera component."""
+2 -1
View File
@@ -8,7 +8,7 @@ the user has submitted configuration information.
"""
import logging
from homeassistant.const import EVENT_TIME_CHANGED
from homeassistant.const import EVENT_TIME_CHANGED, ATTR_FRIENDLY_NAME
from homeassistant.helpers.entity import generate_entity_id
DOMAIN = "configurator"
@@ -118,6 +118,7 @@ class Configurator(object):
data = {
ATTR_CONFIGURE_ID: request_id,
ATTR_FIELDS: fields,
ATTR_FRIENDLY_NAME: name,
}
data.update({
+20 -1
View File
@@ -37,6 +37,7 @@ def setup(hass, config):
"""Setup a demo environment."""
group = loader.get_component('group')
configurator = loader.get_component('configurator')
persistent_notification = loader.get_component('persistent_notification')
config.setdefault(ha.DOMAIN, {})
config.setdefault(DOMAIN, {})
@@ -59,6 +60,11 @@ def setup(hass, config):
demo_config[component] = {CONF_PLATFORM: 'demo'}
bootstrap.setup_component(hass, component, demo_config)
# Setup example persistent notification
persistent_notification.create(
hass, 'This is an example of a persistent notification.',
title='Example Notification')
# Setup room groups
lights = sorted(hass.states.entity_ids('light'))
switches = sorted(hass.states.entity_ids('switch'))
@@ -67,7 +73,9 @@ def setup(hass, config):
lights[1], switches[0], 'input_select.living_room_preset',
'rollershutter.living_room_window', media_players[1],
'scene.romantic_lights'])
group.Group(hass, 'bedroom', [lights[0], switches[1], media_players[0]])
group.Group(hass, 'bedroom', [
lights[0], switches[1], media_players[0],
'input_slider.noise_allowance'])
group.Group(hass, 'kitchen', [
lights[2], 'rollershutter.kitchen_window', 'lock.kitchen_door'])
group.Group(hass, 'doors', [
@@ -145,6 +153,17 @@ def setup(hass, config):
{'input_boolean': {'notify': {'icon': 'mdi:car',
'initial': False,
'name': 'Notify Anne Therese is home'}}})
# Set up input boolean
bootstrap.setup_component(
hass, 'input_slider',
{'input_slider': {
'noise_allowance': {'icon': 'mdi:bell-ring',
'min': 0,
'max': 10,
'name': 'Allowed Noise',
'unit_of_measurement': 'dB'}}})
# Set up weblink
bootstrap.setup_component(
hass, 'weblink',
@@ -12,10 +12,11 @@ import os
import threading
from homeassistant.bootstrap import prepare_setup_platform
from homeassistant.components import discovery, group, zone
from homeassistant.components import group, zone
from homeassistant.components.discovery import SERVICE_NETGEAR
from homeassistant.config import load_yaml_config_file
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_per_platform
from homeassistant.helpers import config_per_platform, discovery
from homeassistant.helpers.entity import Entity
import homeassistant.helpers.config_validation as cv
import homeassistant.util as util
@@ -62,7 +63,7 @@ ATTR_GPS = 'gps'
ATTR_BATTERY = 'battery'
DISCOVERY_PLATFORMS = {
discovery.SERVICE_NETGEAR: 'netgear',
SERVICE_NETGEAR: 'netgear',
}
_LOGGER = logging.getLogger(__name__)
@@ -95,8 +96,11 @@ def setup(hass, config):
yaml_path = hass.config.path(YAML_DEVICES)
conf = config.get(DOMAIN, {})
if isinstance(conf, list) and len(conf) > 0:
conf = conf[0]
# Config can be an empty list. In that case, substitute a dict
if isinstance(conf, list):
conf = conf[0] if len(conf) > 0 else {}
consider_home = timedelta(
seconds=util.convert(conf.get(CONF_CONSIDER_HOME), int,
DEFAULT_CONSIDER_HOME))
@@ -6,8 +6,10 @@ https://home-assistant.io/components/device_tracker.asuswrt/
"""
import logging
import re
import socket
import telnetlib
import threading
from collections import namedtuple
from datetime import timedelta
from homeassistant.components.device_tracker import DOMAIN
@@ -28,6 +30,21 @@ _LEASES_REGEX = re.compile(
r'(?P<ip>([0-9]{1,3}[\.]){3}[0-9]{1,3})\s' +
r'(?P<host>([^\s]+))')
# command to get both 5GHz and 2.4GHz clients
_WL_CMD = '{ wl -i eth2 assoclist & wl -i eth1 assoclist ; }'
_WL_REGEX = re.compile(
r'\w+\s' +
r'(?P<mac>(([0-9A-F]{2}[:-]){5}([0-9A-F]{2})))')
_ARP_CMD = 'arp -n'
_ARP_REGEX = re.compile(
r'.+\s' +
r'\((?P<ip>([0-9]{1,3}[\.]){3}[0-9]{1,3})\)\s' +
r'.+\s' +
r'(?P<mac>(([0-9a-f]{2}[:-]){5}([0-9a-f]{2})))' +
r'\s' +
r'.*')
_IP_NEIGH_CMD = 'ip neigh'
_IP_NEIGH_REGEX = re.compile(
r'(?P<ip>([0-9]{1,3}[\.]){3}[0-9]{1,3})\s' +
@@ -41,24 +58,35 @@ _IP_NEIGH_REGEX = re.compile(
def get_scanner(hass, config):
"""Validate the configuration and return an ASUS-WRT scanner."""
if not validate_config(config,
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
{DOMAIN: [CONF_HOST, CONF_USERNAME]},
_LOGGER):
return None
elif CONF_PASSWORD not in config[DOMAIN] and \
'pub_key' not in config[DOMAIN]:
_LOGGER.error("Either a public key or password must be provided")
return None
scanner = AsusWrtDeviceScanner(config[DOMAIN])
return scanner if scanner.success_init else None
AsusWrtResult = namedtuple('AsusWrtResult', 'neighbors leases arp')
class AsusWrtDeviceScanner(object):
"""This class queries a router running ASUSWRT firmware."""
# pylint: disable=too-many-instance-attributes, too-many-branches
# Eighth attribute needed for mode (AP mode vs router mode)
def __init__(self, config):
"""Initialize the scanner."""
self.host = config[CONF_HOST]
self.username = str(config[CONF_USERNAME])
self.password = str(config[CONF_PASSWORD])
self.password = str(config.get(CONF_PASSWORD, ""))
self.pub_key = str(config.get('pub_key', ""))
self.protocol = config.get('protocol')
self.mode = config.get('mode')
self.lock = threading.Lock()
@@ -106,21 +134,40 @@ class AsusWrtDeviceScanner(object):
def ssh_connection(self):
"""Retrieve data from ASUSWRT via the ssh protocol."""
from pexpect import pxssh
from pexpect import pxssh, exceptions
try:
ssh = pxssh.pxssh()
ssh.login(self.host, self.username, self.password)
if self.pub_key:
ssh.login(self.host, self.username, ssh_key=self.pub_key)
elif self.password:
ssh.login(self.host, self.username, self.password)
else:
_LOGGER.error('No password or public key specified')
return None
ssh.sendline(_IP_NEIGH_CMD)
ssh.prompt()
neighbors = ssh.before.split(b'\n')[1:-1]
ssh.sendline(_LEASES_CMD)
ssh.prompt()
leases_result = ssh.before.split(b'\n')[1:-1]
if self.mode == 'ap':
ssh.sendline(_ARP_CMD)
ssh.prompt()
arp_result = ssh.before.split(b'\n')[1:-1]
ssh.sendline(_WL_CMD)
ssh.prompt()
leases_result = ssh.before.split(b'\n')[1:-1]
else:
arp_result = ['']
ssh.sendline(_LEASES_CMD)
ssh.prompt()
leases_result = ssh.before.split(b'\n')[1:-1]
ssh.logout()
return (neighbors, leases_result)
return AsusWrtResult(neighbors, leases_result, arp_result)
except pxssh.ExceptionPxssh as exc:
_LOGGER.exception('Unexpected response from router: %s', exc)
return ('', '')
_LOGGER.error('Unexpected response from router: %s', exc)
return None
except exceptions.EOF:
_LOGGER.error('Connection refused or no route to host')
return None
def telnet_connection(self):
"""Retrieve data from ASUSWRT via the telnet protocol."""
@@ -133,47 +180,99 @@ class AsusWrtDeviceScanner(object):
prompt_string = telnet.read_until(b'#').split(b'\n')[-1]
telnet.write('{}\n'.format(_IP_NEIGH_CMD).encode('ascii'))
neighbors = telnet.read_until(prompt_string).split(b'\n')[1:-1]
telnet.write('{}\n'.format(_LEASES_CMD).encode('ascii'))
leases_result = telnet.read_until(prompt_string).split(b'\n')[1:-1]
if self.mode == 'ap':
telnet.write('{}\n'.format(_ARP_CMD).encode('ascii'))
arp_result = (telnet.read_until(prompt_string).
split(b'\n')[1:-1])
telnet.write('{}\n'.format(_WL_CMD).encode('ascii'))
leases_result = (telnet.read_until(prompt_string).
split(b'\n')[1:-1])
else:
arp_result = ['']
telnet.write('{}\n'.format(_LEASES_CMD).encode('ascii'))
leases_result = (telnet.read_until(prompt_string).
split(b'\n')[1:-1])
telnet.write('exit\n'.encode('ascii'))
return (neighbors, leases_result)
return AsusWrtResult(neighbors, leases_result, arp_result)
except EOFError:
_LOGGER.exception("Unexpected response from router")
return ('', '')
_LOGGER.error("Unexpected response from router")
return None
except ConnectionRefusedError:
_LOGGER.exception("Connection refused by router,"
" is telnet enabled?")
return ('', '')
_LOGGER.error("Connection refused by router, is telnet enabled?")
return None
except socket.gaierror as exc:
_LOGGER.error("Socket exception: %s", exc)
return None
except OSError as exc:
_LOGGER.error("OSError: %s", exc)
return None
def get_asuswrt_data(self):
"""Retrieve data from ASUSWRT and return parsed result."""
if self.protocol == 'telnet':
neighbors, leases_result = self.telnet_connection()
if self.protocol == 'ssh':
result = self.ssh_connection()
elif self.protocol == 'telnet':
result = self.telnet_connection()
else:
neighbors, leases_result = self.ssh_connection()
# autodetect protocol
result = self.ssh_connection()
if result:
self.protocol = 'ssh'
else:
result = self.telnet_connection()
if result:
self.protocol = 'telnet'
if not result:
return {}
devices = {}
for lease in leases_result:
match = _LEASES_REGEX.search(lease.decode('utf-8'))
if self.mode == 'ap':
for lease in result.leases:
match = _WL_REGEX.search(lease.decode('utf-8'))
if not match:
_LOGGER.warning("Could not parse lease row: %s", lease)
continue
if not match:
_LOGGER.warning("Could not parse wl row: %s", lease)
continue
# For leases where the client doesn't set a hostname, ensure it is
# blank and not '*', which breaks the entity_id down the line.
host = match.group('host')
if host == '*':
host = ''
devices[match.group('ip')] = {
'host': host,
'status': '',
'ip': match.group('ip'),
'mac': match.group('mac').upper(),
}
# match mac addresses to IP addresses in ARP table
for arp in result.arp:
if match.group('mac').lower() in arp.decode('utf-8'):
arp_match = _ARP_REGEX.search(arp.decode('utf-8'))
if not arp_match:
_LOGGER.warning("Could not parse arp row: %s", arp)
continue
for neighbor in neighbors:
devices[arp_match.group('ip')] = {
'host': host,
'status': '',
'ip': arp_match.group('ip'),
'mac': match.group('mac').upper(),
}
else:
for lease in result.leases:
match = _LEASES_REGEX.search(lease.decode('utf-8'))
if not match:
_LOGGER.warning("Could not parse lease row: %s", lease)
continue
# For leases where the client doesn't set a hostname, ensure it
# is blank and not '*', which breaks entity_id down the line.
host = match.group('host')
if host == '*':
host = ''
devices[match.group('ip')] = {
'host': host,
'status': '',
'ip': match.group('ip'),
'mac': match.group('mac').upper(),
}
for neighbor in result.neighbors:
match = _IP_NEIGH_REGEX.search(neighbor.decode('utf-8'))
if not match:
_LOGGER.warning("Could not parse neighbor row: %s", neighbor)
@@ -0,0 +1,141 @@
"""
Support for BT Home Hub 5.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.bt_home_hub_5/
"""
import logging
import re
import threading
from datetime import timedelta
import xml.etree.ElementTree as ET
import json
from urllib.parse import unquote
import requests
from homeassistant.helpers import validate_config
from homeassistant.components.device_tracker import DOMAIN
from homeassistant.const import CONF_HOST
from homeassistant.util import Throttle
# Return cached results if last scan was less then this time ago.
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
_LOGGER = logging.getLogger(__name__)
_MAC_REGEX = re.compile(r'(([0-9A-Fa-f]{1,2}\:){5}[0-9A-Fa-f]{1,2})')
# pylint: disable=unused-argument
def get_scanner(hass, config):
"""Return a BT Home Hub 5 scanner if successful."""
if not validate_config(config,
{DOMAIN: [CONF_HOST]},
_LOGGER):
return None
scanner = BTHomeHub5DeviceScanner(config[DOMAIN])
return scanner if scanner.success_init else None
class BTHomeHub5DeviceScanner(object):
"""This class queries a BT Home Hub 5."""
def __init__(self, config):
"""Initialise the scanner."""
_LOGGER.info("Initialising BT Home Hub 5")
self.host = config.get(CONF_HOST, '192.168.1.254')
self.lock = threading.Lock()
self.last_results = {}
self.url = 'http://{}/nonAuth/home_status.xml'.format(self.host)
# Test the router is accessible
data = _get_homehub_data(self.url)
self.success_init = data is not None
def scan_devices(self):
"""Scan for new devices and return a list with found device IDs."""
self._update_info()
return (device for device in self.last_results)
def get_device_name(self, device):
"""Return the name of the given device or None if we don't know."""
with self.lock:
# If not initialised and not already scanned and not found.
if device not in self.last_results:
self._update_info()
if not self.last_results:
return None
return self.last_results.get(device)
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
"""Ensure the information from the BT Home Hub 5 is up to date.
Return boolean if scanning successful.
"""
if not self.success_init:
return False
with self.lock:
_LOGGER.info("Scanning")
data = _get_homehub_data(self.url)
if not data:
_LOGGER.warning('Error scanning devices')
return False
self.last_results = data
return True
def _get_homehub_data(url):
"""Retrieve data from BT Home Hub 5 and return parsed result."""
try:
response = requests.get(url, timeout=5)
except requests.exceptions.Timeout:
_LOGGER.exception("Connection to the router timed out")
return
if response.status_code == 200:
return _parse_homehub_response(response.text)
else:
_LOGGER.error("Invalid response from Home Hub: %s", response)
def _parse_homehub_response(data_str):
"""Parse the BT Home Hub 5 data format."""
root = ET.fromstring(data_str)
dirty_json = root.find('known_device_list').get('value')
# Normalise the JavaScript data to JSON.
clean_json = unquote(dirty_json.replace('\'', '\"')
.replace('{', '{\"')
.replace(':\"', '\":\"')
.replace('\",', '\",\"'))
known_devices = [x for x in json.loads(clean_json) if x]
devices = {}
for device in known_devices:
name = device.get('name')
mac = device.get('mac')
if _MAC_REGEX.match(mac) or ',' in mac:
for mac_addr in mac.split(','):
if _MAC_REGEX.match(mac_addr):
devices[mac_addr] = name
else:
devices[mac] = name
return devices
@@ -26,7 +26,7 @@ class LocativeView(HomeAssistantView):
"""View to handle locative requests."""
url = "/api/locative"
name = "api:bootstrap"
name = "api:locative"
def __init__(self, hass, see):
"""Initialize Locative url endpoints."""
+18 -90
View File
@@ -9,100 +9,30 @@ loaded before the EVENT_PLATFORM_DISCOVERED is fired.
import logging
import threading
from homeassistant import bootstrap
from homeassistant.const import (
ATTR_DISCOVERED, ATTR_SERVICE, EVENT_HOMEASSISTANT_START,
EVENT_PLATFORM_DISCOVERED)
from homeassistant.const import EVENT_HOMEASSISTANT_START
from homeassistant.helpers.discovery import load_platform, discover
DOMAIN = "discovery"
REQUIREMENTS = ['netdisco==0.6.7']
SCAN_INTERVAL = 300 # seconds
LOAD_PLATFORM = 'load_platform'
SERVICE_WEMO = 'belkin_wemo'
SERVICE_HUE = 'philips_hue'
SERVICE_CAST = 'google_cast'
SERVICE_NETGEAR = 'netgear_router'
SERVICE_SONOS = 'sonos'
SERVICE_PLEX = 'plex_mediaserver'
SERVICE_SQUEEZEBOX = 'logitech_mediaserver'
SERVICE_PANASONIC_VIERA = 'panasonic_viera'
SERVICE_ROKU = 'roku'
SERVICE_HANDLERS = {
SERVICE_WEMO: "wemo",
SERVICE_CAST: "media_player",
SERVICE_HUE: "light",
SERVICE_NETGEAR: 'device_tracker',
SERVICE_SONOS: 'media_player',
SERVICE_PLEX: 'media_player',
SERVICE_SQUEEZEBOX: 'media_player',
SERVICE_PANASONIC_VIERA: 'media_player',
SERVICE_ROKU: 'media_player',
SERVICE_NETGEAR: ('device_tracker', None),
SERVICE_WEMO: ('wemo', None),
'philips_hue': ('light', 'hue'),
'google_cast': ('media_player', 'cast'),
'panasonic_viera': ('media_player', 'panasonic_viera'),
'plex_mediaserver': ('media_player', 'plex'),
'roku': ('media_player', 'roku'),
'sonos': ('media_player', 'sonos'),
'logitech_mediaserver': ('media_player', 'squeezebox'),
}
def listen(hass, service, callback):
"""Setup listener for discovery of specific service.
Service can be a string or a list/tuple.
"""
if isinstance(service, str):
service = (service,)
else:
service = tuple(service)
def discovery_event_listener(event):
"""Listen for discovery events."""
if ATTR_SERVICE in event.data and event.data[ATTR_SERVICE] in service:
callback(event.data[ATTR_SERVICE], event.data.get(ATTR_DISCOVERED))
hass.bus.listen(EVENT_PLATFORM_DISCOVERED, discovery_event_listener)
def discover(hass, service, discovered=None, component=None, hass_config=None):
"""Fire discovery event. Can ensure a component is loaded."""
if component is not None:
bootstrap.setup_component(hass, component, hass_config)
data = {
ATTR_SERVICE: service
}
if discovered is not None:
data[ATTR_DISCOVERED] = discovered
hass.bus.fire(EVENT_PLATFORM_DISCOVERED, data)
def load_platform(hass, component, platform, info=None, hass_config=None):
"""Helper method for generic platform loading.
This method allows a platform to be loaded dynamically without it being
known at runtime (in the DISCOVERY_PLATFORMS list of the component).
Advantages of using this method:
- Any component & platforms combination can be dynamically added
- A component (i.e. light) does not have to import every component
that can dynamically add a platform (e.g. wemo, wink, insteon_hub)
- Custom user components can take advantage of discovery/loading
Target components will be loaded and an EVENT_PLATFORM_DISCOVERED will be
fired to load the platform. The event will contain:
{ ATTR_SERVICE = LOAD_PLATFORM + '.' + <<component>>
ATTR_DISCOVERED = {LOAD_PLATFORM: <<platform>>} }
* dev note: This listener can be found in entity_component.py
"""
if info is None:
info = {LOAD_PLATFORM: platform}
else:
info[LOAD_PLATFORM] = platform
discover(hass, LOAD_PLATFORM + '.' + component, info, component,
hass_config)
def setup(hass, config):
"""Start a discovery service."""
logger = logging.getLogger(__name__)
@@ -119,20 +49,18 @@ def setup(hass, config):
with lock:
logger.info("Found new service: %s %s", service, info)
component = SERVICE_HANDLERS.get(service)
comp_plat = SERVICE_HANDLERS.get(service)
# We do not know how to handle this service.
if not component:
if not comp_plat:
return
# This component cannot be setup.
if not bootstrap.setup_component(hass, component, config):
return
component, platform = comp_plat
hass.bus.fire(EVENT_PLATFORM_DISCOVERED, {
ATTR_SERVICE: service,
ATTR_DISCOVERED: info
})
if platform is None:
discover(hass, service, info, component, config)
else:
load_platform(hass, component, platform, info, config)
# pylint: disable=unused-argument
def start_discovery(event):
+6 -21
View File
@@ -8,21 +8,18 @@ import logging
import os
from datetime import timedelta
from homeassistant import bootstrap
from homeassistant.const import (
ATTR_DISCOVERED, ATTR_SERVICE, CONF_API_KEY, EVENT_PLATFORM_DISCOVERED)
from homeassistant.helpers import discovery
from homeassistant.const import CONF_API_KEY
from homeassistant.loader import get_component
from homeassistant.util import Throttle
DOMAIN = "ecobee"
DISCOVER_THERMOSTAT = "ecobee.thermostat"
DISCOVER_SENSORS = "ecobee.sensor"
NETWORK = None
HOLD_TEMP = 'hold_temp'
REQUIREMENTS = [
'https://github.com/nkgilley/python-ecobee-api/archive/'
'4a884bc146a93991b4210f868f3d6aecf0a181e6.zip#python-ecobee==0.0.5']
'4856a704670c53afe1882178a89c209b5f98533d.zip#python-ecobee==0.0.6']
_LOGGER = logging.getLogger(__name__)
@@ -70,23 +67,11 @@ def setup_ecobee(hass, network, config):
configurator = get_component('configurator')
configurator.request_done(_CONFIGURING.pop('ecobee'))
# Ensure component is loaded
bootstrap.setup_component(hass, 'thermostat', config)
bootstrap.setup_component(hass, 'sensor', config)
hold_temp = config[DOMAIN].get(HOLD_TEMP, False)
# Fire thermostat discovery event
hass.bus.fire(EVENT_PLATFORM_DISCOVERED, {
ATTR_SERVICE: DISCOVER_THERMOSTAT,
ATTR_DISCOVERED: {'hold_temp': hold_temp}
})
# Fire sensor discovery event
hass.bus.fire(EVENT_PLATFORM_DISCOVERED, {
ATTR_SERVICE: DISCOVER_SENSORS,
ATTR_DISCOVERED: {}
})
discovery.load_platform(hass, 'thermostat', DOMAIN,
{'hold_temp': hold_temp}, config)
discovery.load_platform(hass, 'sensor', DOMAIN, {}, config)
# pylint: disable=too-few-public-methods
+210
View File
@@ -0,0 +1,210 @@
"""
Support for Envisalink devices.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/envisalink/
"""
import logging
import time
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.helpers.entity import Entity
from homeassistant.components.discovery import load_platform
REQUIREMENTS = ['pyenvisalink==1.0', 'pydispatcher==2.0.5']
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'envisalink'
EVL_CONTROLLER = None
CONF_EVL_HOST = 'host'
CONF_EVL_PORT = 'port'
CONF_PANEL_TYPE = 'panel_type'
CONF_EVL_VERSION = 'evl_version'
CONF_CODE = 'code'
CONF_USERNAME = 'user_name'
CONF_PASS = 'password'
CONF_EVL_KEEPALIVE = 'keepalive_interval'
CONF_ZONEDUMP_INTERVAL = 'zonedump_interval'
CONF_ZONES = 'zones'
CONF_PARTITIONS = 'partitions'
CONF_ZONENAME = 'name'
CONF_ZONETYPE = 'type'
CONF_PARTITIONNAME = 'name'
DEFAULT_PORT = 4025
DEFAULT_EVL_VERSION = 3
DEFAULT_KEEPALIVE = 60
DEFAULT_ZONEDUMP_INTERVAL = 30
DEFAULT_ZONETYPE = 'opening'
SIGNAL_ZONE_UPDATE = 'zones_updated'
SIGNAL_PARTITION_UPDATE = 'partition_updated'
SIGNAL_KEYPAD_UPDATE = 'keypad_updated'
ZONE_SCHEMA = vol.Schema({
vol.Required(CONF_ZONENAME): cv.string,
vol.Optional(CONF_ZONETYPE, default=DEFAULT_ZONETYPE): cv.string})
PARTITION_SCHEMA = vol.Schema({
vol.Required(CONF_PARTITIONNAME): cv.string})
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_EVL_HOST): cv.string,
vol.Required(CONF_PANEL_TYPE):
vol.All(cv.string, vol.In(['HONEYWELL', 'DSC'])),
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASS): cv.string,
vol.Required(CONF_CODE): cv.string,
vol.Optional(CONF_ZONES): {vol.Coerce(int): ZONE_SCHEMA},
vol.Optional(CONF_PARTITIONS): {vol.Coerce(int): PARTITION_SCHEMA},
vol.Optional(CONF_EVL_PORT, default=DEFAULT_PORT):
vol.All(vol.Coerce(int), vol.Range(min=1, max=65535)),
vol.Optional(CONF_EVL_VERSION, default=DEFAULT_EVL_VERSION):
vol.All(vol.Coerce(int), vol.Range(min=3, max=4)),
vol.Optional(CONF_EVL_KEEPALIVE, default=DEFAULT_KEEPALIVE):
vol.All(vol.Coerce(int), vol.Range(min=15)),
vol.Optional(CONF_ZONEDUMP_INTERVAL,
default=DEFAULT_ZONEDUMP_INTERVAL):
vol.All(vol.Coerce(int), vol.Range(min=15)),
}),
}, extra=vol.ALLOW_EXTRA)
# pylint: disable=unused-argument, too-many-function-args, too-many-locals
# pylint: disable=too-many-return-statements
def setup(hass, base_config):
"""Common setup for Envisalink devices."""
from pyenvisalink import EnvisalinkAlarmPanel
from pydispatch import dispatcher
global EVL_CONTROLLER
config = base_config.get(DOMAIN)
_host = config.get(CONF_EVL_HOST)
_port = config.get(CONF_EVL_PORT)
_code = config.get(CONF_CODE)
_panel_type = config.get(CONF_PANEL_TYPE)
_version = config.get(CONF_EVL_VERSION)
_user = config.get(CONF_USERNAME)
_pass = config.get(CONF_PASS)
_keep_alive = config.get(CONF_EVL_KEEPALIVE)
_zone_dump = config.get(CONF_ZONEDUMP_INTERVAL)
_zones = config.get(CONF_ZONES)
_partitions = config.get(CONF_PARTITIONS)
_connect_status = {}
EVL_CONTROLLER = EnvisalinkAlarmPanel(_host,
_port,
_panel_type,
_version,
_user,
_pass,
_zone_dump,
_keep_alive)
def login_fail_callback(data):
"""Callback for when the evl rejects our login."""
_LOGGER.error("The envisalink rejected your credentials.")
_connect_status['fail'] = 1
def connection_fail_callback(data):
"""Network failure callback."""
_LOGGER.error("Could not establish a connection with the envisalink.")
_connect_status['fail'] = 1
def connection_success_callback(data):
"""Callback for a successful connection."""
_LOGGER.info("Established a connection with the envisalink.")
_connect_status['success'] = 1
def zones_updated_callback(data):
"""Handle zone timer updates."""
_LOGGER.info("Envisalink sent a zone update event. Updating zones...")
dispatcher.send(signal=SIGNAL_ZONE_UPDATE,
sender=None,
zone=data)
def alarm_data_updated_callback(data):
"""Handle non-alarm based info updates."""
_LOGGER.info("Envisalink sent new alarm info. Updating alarms...")
dispatcher.send(signal=SIGNAL_KEYPAD_UPDATE,
sender=None,
partition=data)
def partition_updated_callback(data):
"""Handle partition changes thrown by evl (including alarms)."""
_LOGGER.info("The envisalink sent a partition update event.")
dispatcher.send(signal=SIGNAL_PARTITION_UPDATE,
sender=None,
partition=data)
def stop_envisalink(event):
"""Shutdown envisalink connection and thread on exit."""
_LOGGER.info("Shutting down envisalink.")
EVL_CONTROLLER.stop()
def start_envisalink(event):
"""Startup process for the envisalink."""
EVL_CONTROLLER.start()
for _ in range(10):
if 'success' in _connect_status:
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_envisalink)
return True
elif 'fail' in _connect_status:
return False
else:
time.sleep(1)
_LOGGER.error("Timeout occurred while establishing evl connection.")
return False
EVL_CONTROLLER.callback_zone_timer_dump = zones_updated_callback
EVL_CONTROLLER.callback_zone_state_change = zones_updated_callback
EVL_CONTROLLER.callback_partition_state_change = partition_updated_callback
EVL_CONTROLLER.callback_keypad_update = alarm_data_updated_callback
EVL_CONTROLLER.callback_login_failure = login_fail_callback
EVL_CONTROLLER.callback_login_timeout = connection_fail_callback
EVL_CONTROLLER.callback_login_success = connection_success_callback
_result = start_envisalink(None)
if not _result:
return False
# Load sub-components for envisalink
if _partitions:
load_platform(hass, 'alarm_control_panel', 'envisalink',
{'partitions': _partitions,
'code': _code}, config)
load_platform(hass, 'sensor', 'envisalink',
{'partitions': _partitions,
'code': _code}, config)
if _zones:
load_platform(hass, 'binary_sensor', 'envisalink',
{'zones': _zones}, config)
return True
class EnvisalinkDevice(Entity):
"""Representation of an envisalink devicetity."""
def __init__(self, name, info, controller):
"""Initialize the device."""
self._controller = controller
self._info = info
self._name = name
@property
def name(self):
"""Return the name of the device."""
return self._name
@property
def should_poll(self):
"""No polling needed."""
return False
@@ -75,6 +75,7 @@
Home Assistant had trouble<br>connecting to the server.<br><br><a href='/'>TRY AGAIN</a>
</div>
<home-assistant icons='{{ icons }}'></home-assistant>
{# <script src='/static/home-assistant-polymer/build/_demo_data_compiled.js'></script> #}
<script src='/static/{{ core_url }}'></script>
<link rel='import' href='/static/{{ ui_url }}' onerror='initError()' async>
<link rel='import' href='/static/{{ icons_url }}' async>
+2 -2
View File
@@ -1,3 +1,3 @@
"""DO NOT MODIFY. Auto-generated by build_frontend script."""
CORE = "d0b415dac66c8056d81380b258af5767"
UI = "b0ea2672fff86b1ab86dd86135d4b43a"
CORE = "db0bb387f4d3bcace002d62b94baa348"
UI = "5b306b7e7d36799b7b67f592cbe94703"
File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

@@ -4,6 +4,7 @@
"start_url": "/",
"display": "standalone",
"theme_color": "#03A9F4",
"background_color": "#FFFFFF",
"icons": [
{
"src": "/static/favicon-192x192.png",
@@ -14,6 +15,16 @@
"src": "/static/favicon-384x384.png",
"sizes": "384x384",
"type": "image/png"
},
{
"src": "/static/favicon-512x512.png",
"sizes": "512x512",
"type": "image/png"
},
{
"src": "/static/favicon-1024x1024.png",
"sizes": "1024x1024",
"type": "image/png"
}
]
}
@@ -29,7 +29,7 @@
/* eslint-disable quotes, comma-spacing */
var PrecacheConfig = [["/","69818e2c5b6f4ca764c46ac78d2fea04"],["/devEvent","69818e2c5b6f4ca764c46ac78d2fea04"],["/devInfo","69818e2c5b6f4ca764c46ac78d2fea04"],["/devService","69818e2c5b6f4ca764c46ac78d2fea04"],["/devState","69818e2c5b6f4ca764c46ac78d2fea04"],["/devTemplate","69818e2c5b6f4ca764c46ac78d2fea04"],["/history","69818e2c5b6f4ca764c46ac78d2fea04"],["/logbook","69818e2c5b6f4ca764c46ac78d2fea04"],["/map","69818e2c5b6f4ca764c46ac78d2fea04"],["/states","69818e2c5b6f4ca764c46ac78d2fea04"],["/static/core-d0b415dac66c8056d81380b258af5767.js","dfafa8e9e34f53e8c36dd8b3f7299b2a"],["/static/frontend-b0ea2672fff86b1ab86dd86135d4b43a.html","69818e2c5b6f4ca764c46ac78d2fea04"],["/static/mdi-9ee3d4466a65bef35c2c8974e91b37c0.html","9a6846935116cd29279c91e0ee0a26d0"],["static/favicon-192x192.png","419903b8422586a7e28021bbe9011175"],["static/fonts/roboto/Roboto-Bold.ttf","d329cc8b34667f114a95422aaad1b063"],["static/fonts/roboto/Roboto-Light.ttf","7b5fb88f12bec8143f00e21bc3222124"],["static/fonts/roboto/Roboto-Medium.ttf","fe13e4170719c2fc586501e777bde143"],["static/fonts/roboto/Roboto-Regular.ttf","ac3f799d5bbaf5196fab15ab8de8431c"],["static/images/card_media_player_bg.png","a34281d1c1835d338a642e90930e61aa"],["static/webcomponents-lite.min.js","b0f32ad3c7749c40d486603f31c9d8b1"]];
var PrecacheConfig = [["/","70eeeca780a5f23c7632c2876dd1795a"],["/devEvent","70eeeca780a5f23c7632c2876dd1795a"],["/devInfo","70eeeca780a5f23c7632c2876dd1795a"],["/devService","70eeeca780a5f23c7632c2876dd1795a"],["/devState","70eeeca780a5f23c7632c2876dd1795a"],["/devTemplate","70eeeca780a5f23c7632c2876dd1795a"],["/history","70eeeca780a5f23c7632c2876dd1795a"],["/logbook","70eeeca780a5f23c7632c2876dd1795a"],["/map","70eeeca780a5f23c7632c2876dd1795a"],["/states","70eeeca780a5f23c7632c2876dd1795a"],["/static/core-db0bb387f4d3bcace002d62b94baa348.js","f938163a392465dc87af3a0094376621"],["/static/frontend-5b306b7e7d36799b7b67f592cbe94703.html","70eeeca780a5f23c7632c2876dd1795a"],["/static/mdi-9ee3d4466a65bef35c2c8974e91b37c0.html","9a6846935116cd29279c91e0ee0a26d0"],["static/favicon-192x192.png","419903b8422586a7e28021bbe9011175"],["static/fonts/roboto/Roboto-Bold.ttf","d329cc8b34667f114a95422aaad1b063"],["static/fonts/roboto/Roboto-Light.ttf","7b5fb88f12bec8143f00e21bc3222124"],["static/fonts/roboto/Roboto-Medium.ttf","fe13e4170719c2fc586501e777bde143"],["static/fonts/roboto/Roboto-Regular.ttf","ac3f799d5bbaf5196fab15ab8de8431c"],["static/images/card_media_player_bg.png","a34281d1c1835d338a642e90930e61aa"],["static/webcomponents-lite.min.js","b0f32ad3c7749c40d486603f31c9d8b1"]];
/* eslint-enable quotes, comma-spacing */
var CacheNamePrefix = 'sw-precache-v1--' + (self.registration ? self.registration.scope : '') + '-';
@@ -17,7 +17,7 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.const import (
STATE_CLOSED, STATE_OPEN, STATE_UNKNOWN, SERVICE_CLOSE, SERVICE_OPEN,
ATTR_ENTITY_ID)
from homeassistant.components import (group, wink)
from homeassistant.components import group
DOMAIN = 'garage_door'
SCAN_INTERVAL = 30
@@ -27,11 +27,6 @@ ENTITY_ID_ALL_GARAGE_DOORS = group.ENTITY_ID_FORMAT.format('all_garage_doors')
ENTITY_ID_FORMAT = DOMAIN + '.{}'
# Maps discovered services to their platforms
DISCOVERY_PLATFORMS = {
wink.DISCOVER_GARAGE_DOORS: 'wink'
}
GARAGE_DOOR_SERVICE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
})
@@ -60,8 +55,7 @@ def open_door(hass, entity_id=None):
def setup(hass, config):
"""Track states and offer events for garage door."""
component = EntityComponent(
_LOGGER, DOMAIN, hass, SCAN_INTERVAL, DISCOVERY_PLATFORMS,
GROUP_NAME_ALL_GARAGE_DOORS)
_LOGGER, DOMAIN, hass, SCAN_INTERVAL, GROUP_NAME_ALL_GARAGE_DOORS)
component.setup(config)
def handle_garage_door_service(service):
@@ -0,0 +1,96 @@
"""
Support for building a Raspberry Pi garage controller in HA.
Instructions for building the controller can be found here
https://github.com/andrewshilliday/garage-door-controller
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/garage_door.rpi_gpio/
"""
import logging
from time import sleep
import voluptuous as vol
from homeassistant.components.garage_door import GarageDoorDevice
import homeassistant.components.rpi_gpio as rpi_gpio
import homeassistant.helpers.config_validation as cv
DEPENDENCIES = ['rpi_gpio']
_LOGGER = logging.getLogger(__name__)
_DOORS_SCHEMA = vol.All(
cv.ensure_list,
[
vol.Schema({
'name': str,
'relay_pin': int,
'state_pin': int,
})
]
)
PLATFORM_SCHEMA = vol.Schema({
'platform': str,
vol.Required('doors'): _DOORS_SCHEMA,
})
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the garage door platform."""
doors = []
doors_conf = config.get('doors')
for door in doors_conf:
doors.append(RPiGPIOGarageDoor(door['name'], door['relay_pin'],
door['state_pin']))
add_devices(doors)
class RPiGPIOGarageDoor(GarageDoorDevice):
"""Representation of a Raspberry garage door."""
def __init__(self, name, relay_pin, state_pin):
"""Initialize the garage door."""
self._name = name
self._state = False
self._relay_pin = relay_pin
self._state_pin = state_pin
rpi_gpio.setup_output(self._relay_pin)
rpi_gpio.setup_input(self._state_pin, 'DOWN')
rpi_gpio.write_output(self._relay_pin, True)
@property
def unique_id(self):
"""Return the ID of this garage door."""
return "{}.{}".format(self.__class__, self._name)
@property
def name(self):
"""Return the name of the garage door if any."""
return self._name
def update(self):
"""Update the state of the garage door."""
self._state = rpi_gpio.read_input(self._state_pin) is True
@property
def is_closed(self):
"""Return true if door is closed."""
return self._state
def _trigger(self):
"""Trigger the door."""
rpi_gpio.write_output(self._relay_pin, False)
sleep(0.2)
rpi_gpio.write_output(self._relay_pin, True)
def close_door(self):
"""Close the door."""
if not self.is_closed:
self._trigger()
def open_door(self):
"""Open the door."""
if self.is_closed:
self._trigger()
+5 -37
View File
@@ -7,9 +7,10 @@ https://home-assistant.io/components/garage_door.wink/
import logging
from homeassistant.components.garage_door import GarageDoorDevice
from homeassistant.const import CONF_ACCESS_TOKEN, ATTR_BATTERY_LEVEL
from homeassistant.components.wink import WinkDevice
from homeassistant.const import CONF_ACCESS_TOKEN
REQUIREMENTS = ['python-wink==0.7.6']
REQUIREMENTS = ['python-wink==0.7.8', 'pubnub==3.7.8']
def setup_platform(hass, config, add_devices, discovery_info=None):
@@ -31,38 +32,18 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
pywink.get_garage_doors())
class WinkGarageDoorDevice(GarageDoorDevice):
class WinkGarageDoorDevice(WinkDevice, GarageDoorDevice):
"""Representation of a Wink garage door."""
def __init__(self, wink):
"""Initialize the garage door."""
self.wink = wink
self._battery = self.wink.battery_level
@property
def unique_id(self):
"""Return the ID of this wink garage door."""
return "{}.{}".format(self.__class__, self.wink.device_id())
@property
def name(self):
"""Return the name of the garage door if any."""
return self.wink.name()
def update(self):
"""Update the state of the garage door."""
self.wink.update_state()
WinkDevice.__init__(self, wink)
@property
def is_closed(self):
"""Return true if door is closed."""
return self.wink.state() == 0
@property
def available(self):
"""True if connection == True."""
return self.wink.available
def close_door(self):
"""Close the door."""
self.wink.set_state(0)
@@ -70,16 +51,3 @@ class WinkGarageDoorDevice(GarageDoorDevice):
def open_door(self):
"""Open the door."""
self.wink.set_state(1)
@property
def device_state_attributes(self):
"""Return the state attributes."""
if self._battery:
return {
ATTR_BATTERY_LEVEL: self._battery_level,
}
@property
def _battery_level(self):
"""Return the battery level."""
return self.wink.battery_level * 100
@@ -0,0 +1,70 @@
"""
Support for Zwave garage door components.
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/garagedoor.zwave/
"""
# Because we do not compile openzwave on CI
# pylint: disable=import-error
import logging
from homeassistant.components.garage_door import DOMAIN
from homeassistant.components.zwave import ZWaveDeviceEntity
from homeassistant.components import zwave
from homeassistant.components.garage_door import GarageDoorDevice
COMMAND_CLASS_SWITCH_BINARY = 0x25 # 37
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Find and return Z-Wave garage door device."""
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]]
if value.command_class != zwave.COMMAND_CLASS_SWITCH_BINARY:
return
if value.type != zwave.TYPE_BOOL:
return
if value.genre != zwave.GENRE_USER:
return
value.set_change_verified(False)
add_devices([ZwaveGarageDoor(value)])
class ZwaveGarageDoor(zwave.ZWaveDeviceEntity, GarageDoorDevice):
"""Representation of an Zwave garage door device."""
def __init__(self, value):
"""Initialize the zwave garage door."""
from openzwave.network import ZWaveNetwork
from pydispatch import dispatcher
ZWaveDeviceEntity.__init__(self, value, DOMAIN)
self._node = value.node
self._state = value.data
dispatcher.connect(
self.value_changed, ZWaveNetwork.SIGNAL_VALUE_CHANGED)
def value_changed(self, value):
"""Called when a value has changed on the network."""
if self._value.value_id == value.value_id:
self._state = value.data
self.update_ha_state(True)
_LOGGER.debug("Value changed on network %s", value)
@property
def is_closed(self):
"""Return the current position of Zwave garage door."""
return not self._state
def close_door(self):
"""Close the garage door."""
self._value.node.set_switch(self._value.value_id, False)
def open_door(self):
"""Open the garage door."""
self._value.node.set_switch(self._value.value_id, True)
+1 -1
View File
@@ -39,7 +39,7 @@ def _conf_preprocess(value):
return value
_SINGLE_GROUP_CONFIG = vol.Schema(vol.All(_conf_preprocess, {
vol.Optional(CONF_ENTITIES): vol.Any(None, cv.entity_ids),
vol.Optional(CONF_ENTITIES): vol.Any(cv.entity_ids, None),
CONF_VIEW: bool,
CONF_NAME: str,
CONF_ICON: cv.icon,
+122
View File
@@ -0,0 +1,122 @@
"""
CEC component.
Requires libcec + Python bindings.
"""
import logging
import voluptuous as vol
from homeassistant.const import EVENT_HOMEASSISTANT_START
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
_CEC = None
DOMAIN = 'hdmi_cec'
SERVICE_SELECT_DEVICE = 'select_device'
SERVICE_POWER_ON = 'power_on'
SERVICE_STANDBY = 'standby'
CONF_DEVICES = 'devices'
ATTR_DEVICE = 'device'
MAX_DEPTH = 4
# pylint: disable=unnecessary-lambda
DEVICE_SCHEMA = vol.Schema({
vol.All(cv.positive_int): vol.Any(lambda devices: DEVICE_SCHEMA(devices),
cv.string)
})
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_DEVICES): DEVICE_SCHEMA
})
}, extra=vol.ALLOW_EXTRA)
def parse_mapping(mapping, parents=None):
"""Parse configuration device mapping."""
if parents is None:
parents = []
for addr, val in mapping.items():
cur = parents + [str(addr)]
if isinstance(val, dict):
yield from parse_mapping(val, cur)
elif isinstance(val, str):
yield (val, cur)
def pad_physical_address(addr):
"""Right-pad a physical address."""
return addr + ['0'] * (MAX_DEPTH - len(addr))
def setup(hass, config):
"""Setup CEC capability."""
global _CEC
# cec is only available if libcec is properly installed
# and the Python bindings are accessible.
try:
import cec
except ImportError:
_LOGGER.error("libcec must be installed")
return False
# Parse configuration into a dict of device name
# to physical address represented as a list of
# four elements.
flat = {}
for pair in parse_mapping(config[DOMAIN].get(CONF_DEVICES, {})):
flat[pair[0]] = pad_physical_address(pair[1])
# Configure libcec.
cfg = cec.libcec_configuration()
cfg.strDeviceName = 'HASS'
cfg.bActivateSource = 0
cfg.bMonitorOnly = 1
cfg.clientVersion = cec.LIBCEC_VERSION_CURRENT
# Set up CEC adapter.
_CEC = cec.ICECAdapter.Create(cfg)
def _power_on(call):
"""Power on all devices."""
_CEC.PowerOnDevices()
def _standby(call):
"""Standby all devices."""
_CEC.StandbyDevices()
def _select_device(call):
"""Select the active device."""
path = flat.get(call.data[ATTR_DEVICE])
if not path:
_LOGGER.error("Device not found: %s", call.data[ATTR_DEVICE])
cmds = []
for i in range(1, MAX_DEPTH - 1):
addr = pad_physical_address(path[:i])
cmds.append('1f:82:{}{}:{}{}'.format(*addr))
cmds.append('1f:86:{}{}:{}{}'.format(*addr))
for cmd in cmds:
_CEC.Transmit(_CEC.CommandFromString(cmd))
_LOGGER.info("Selected %s", call.data[ATTR_DEVICE])
def _start_cec(event):
"""Open CEC adapter."""
adapters = _CEC.DetectAdapters()
if len(adapters) == 0:
_LOGGER.error("No CEC adapter found")
return
if _CEC.Open(adapters[0].strComName):
hass.services.register(DOMAIN, SERVICE_POWER_ON, _power_on)
hass.services.register(DOMAIN, SERVICE_STANDBY, _standby)
hass.services.register(DOMAIN, SERVICE_SELECT_DEVICE,
_select_device)
else:
_LOGGER.error("Failed to open adapter")
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, _start_cec)
return True
+534
View File
@@ -0,0 +1,534 @@
"""
Support for Homematic devices.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/homematic/
"""
import time
import logging
from functools import partial
from homeassistant.const import EVENT_HOMEASSISTANT_STOP, STATE_UNKNOWN
from homeassistant.helpers import discovery
from homeassistant.helpers.entity import Entity
DOMAIN = 'homematic'
REQUIREMENTS = ['pyhomematic==0.1.8']
HOMEMATIC = None
HOMEMATIC_LINK_DELAY = 0.5
DISCOVER_SWITCHES = "homematic.switch"
DISCOVER_LIGHTS = "homematic.light"
DISCOVER_SENSORS = "homematic.sensor"
DISCOVER_BINARY_SENSORS = "homematic.binary_sensor"
DISCOVER_ROLLERSHUTTER = "homematic.rollershutter"
DISCOVER_THERMOSTATS = "homematic.thermostat"
ATTR_DISCOVER_DEVICES = "devices"
ATTR_PARAM = "param"
ATTR_CHANNEL = "channel"
ATTR_NAME = "name"
ATTR_ADDRESS = "address"
EVENT_KEYPRESS = "homematic.keypress"
HM_DEVICE_TYPES = {
DISCOVER_SWITCHES: ["Switch", "SwitchPowermeter"],
DISCOVER_LIGHTS: ["Dimmer"],
DISCOVER_SENSORS: ["SwitchPowermeter", "Motion", "MotionV2",
"RemoteMotion", "ThermostatWall", "AreaThermostat",
"RotaryHandleSensor", "WaterSensor"],
DISCOVER_THERMOSTATS: ["Thermostat", "ThermostatWall", "MAXThermostat"],
DISCOVER_BINARY_SENSORS: ["ShutterContact", "Smoke", "SmokeV2",
"Motion", "MotionV2", "RemoteMotion"],
DISCOVER_ROLLERSHUTTER: ["Blind"]
}
HM_IGNORE_DISCOVERY_NODE = [
"ACTUAL_TEMPERATURE"
]
HM_ATTRIBUTE_SUPPORT = {
"LOWBAT": ["Battery", {0: "High", 1: "Low"}],
"ERROR": ["Sabotage", {0: "No", 1: "Yes"}],
"RSSI_DEVICE": ["RSSI", {}],
"VALVE_STATE": ["Valve", {}],
"BATTERY_STATE": ["Battery", {}],
"CONTROL_MODE": ["Mode", {0: "Auto", 1: "Manual", 2: "Away", 3: "Boost"}],
"POWER": ["Power", {}],
"CURRENT": ["Current", {}],
"VOLTAGE": ["Voltage", {}]
}
HM_PRESS_EVENTS = [
"PRESS_SHORT",
"PRESS_LONG",
"PRESS_CONT",
"PRESS_LONG_RELEASE"
]
_LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument
def setup(hass, config):
"""Setup the Homematic component."""
global HOMEMATIC, HOMEMATIC_LINK_DELAY
from pyhomematic import HMConnection
local_ip = config[DOMAIN].get("local_ip", None)
local_port = config[DOMAIN].get("local_port", 8943)
remote_ip = config[DOMAIN].get("remote_ip", None)
remote_port = config[DOMAIN].get("remote_port", 2001)
resolvenames = config[DOMAIN].get("resolvenames", False)
username = config[DOMAIN].get("username", "Admin")
password = config[DOMAIN].get("password", "")
HOMEMATIC_LINK_DELAY = config[DOMAIN].get("delay", 0.5)
if remote_ip is None or local_ip is None:
_LOGGER.error("Missing remote CCU/Homegear or local address")
return False
# Create server thread
bound_system_callback = partial(system_callback_handler, hass, config)
HOMEMATIC = HMConnection(local=local_ip,
localport=local_port,
remote=remote_ip,
remoteport=remote_port,
systemcallback=bound_system_callback,
resolvenames=resolvenames,
rpcusername=username,
rpcpassword=password,
interface_id="homeassistant")
# Start server thread, connect to peer, initialize to receive events
HOMEMATIC.start()
# Stops server when Homeassistant is shutting down
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, HOMEMATIC.stop)
hass.config.components.append(DOMAIN)
return True
# pylint: disable=too-many-branches
def system_callback_handler(hass, config, src, *args):
"""Callback handler."""
if src == 'newDevices':
_LOGGER.debug("newDevices with: %s", str(args))
# pylint: disable=unused-variable
(interface_id, dev_descriptions) = args
key_dict = {}
# Get list of all keys of the devices (ignoring channels)
for dev in dev_descriptions:
key_dict[dev['ADDRESS'].split(':')[0]] = True
# Register EVENTS
# Search all device with a EVENTNODE that include data
bound_event_callback = partial(_hm_event_handler, hass)
for dev in key_dict:
if dev not in HOMEMATIC.devices:
continue
hmdevice = HOMEMATIC.devices.get(dev)
# have events?
if len(hmdevice.EVENTNODE) > 0:
_LOGGER.debug("Register Events from %s", dev)
hmdevice.setEventCallback(callback=bound_event_callback,
bequeath=True)
# If configuration allows autodetection of devices,
# all devices not configured are added.
if key_dict:
for component_name, discovery_type in (
('switch', DISCOVER_SWITCHES),
('light', DISCOVER_LIGHTS),
('rollershutter', DISCOVER_ROLLERSHUTTER),
('binary_sensor', DISCOVER_BINARY_SENSORS),
('sensor', DISCOVER_SENSORS),
('thermostat', DISCOVER_THERMOSTATS)):
# Get all devices of a specific type
found_devices = _get_devices(discovery_type, key_dict)
# When devices of this type are found
# they are setup in HA and an event is fired
if found_devices:
# Fire discovery event
discovery.load_platform(hass, component_name, DOMAIN, {
ATTR_DISCOVER_DEVICES: found_devices
}, config)
def _get_devices(device_type, keys):
"""Get the Homematic devices."""
# run
device_arr = []
for key in keys:
device = HOMEMATIC.devices[key]
class_name = device.__class__.__name__
metadata = {}
# is class supported by discovery type
if class_name not in HM_DEVICE_TYPES[device_type]:
continue
# Load metadata if needed to generate a param list
if device_type == DISCOVER_SENSORS:
metadata.update(device.SENSORNODE)
elif device_type == DISCOVER_BINARY_SENSORS:
metadata.update(device.BINARYNODE)
params = _create_params_list(device, metadata, device_type)
if params:
# 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 = dict(platform="homematic",
address=key,
name=name,
channel=channel)
if param is not None:
device_dict[ATTR_PARAM] = param
# Add new device
device_arr.append(device_dict)
else:
_LOGGER.debug("Channel %i not in params", channel)
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 == DISCOVER_SENSORS:
merge = True
elif device_type == 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
if channel == 1 and param is None:
return name
# Has multiple elements/channels
if channel > 1 and param is None:
return "{} {}".format(name, channel)
# With multiple param first elements
if channel == 1 and param is not None:
return "{} {}".format(name, param)
# Multiple param on object with multiple elements
if channel > 1 and param is not None:
return "{} {} {}".format(name, channel, param)
def setup_hmdevice_discovery_helper(hmdevicetype, discovery_info,
add_callback_devices):
"""Helper to setup Homematic devices with discovery info."""
for config in discovery_info[ATTR_DISCOVER_DEVICES]:
_LOGGER.debug("Add device %s from config: %s",
str(hmdevicetype), str(config))
# create object and add to HA
new_device = hmdevicetype(config)
add_callback_devices([new_device])
# link to HM
new_device.link_homematic()
return True
def _hm_event_handler(hass, device, caller, attribute, value):
"""Handle all pyhomematic device events."""
try:
channel = int(device.split(":")[1])
address = device.split(":")[0]
hmdevice = HOMEMATIC.devices.get(address)
except (TypeError, ValueError):
_LOGGER.error("Event handling channel convert error!")
return
# is not a event?
if attribute not in hmdevice.EVENTNODE:
return
_LOGGER.debug("Event %s for %s channel %i", attribute,
hmdevice.NAME, channel)
# a keypress event
if attribute in HM_PRESS_EVENTS:
hass.bus.fire(EVENT_KEYPRESS, {
ATTR_NAME: hmdevice.NAME,
ATTR_PARAM: attribute,
ATTR_CHANNEL: channel
})
return
_LOGGER.warning("Event is unknown and not forwarded to HA")
class HMDevice(Entity):
"""The Homematic device base object."""
# pylint: disable=too-many-instance-attributes
def __init__(self, config):
"""Initialize a generic Homematic device."""
self._name = config.get(ATTR_NAME, None)
self._address = config.get(ATTR_ADDRESS, None)
self._channel = config.get(ATTR_CHANNEL, 1)
self._state = config.get(ATTR_PARAM, None)
self._data = {}
self._hmdevice = None
self._connected = False
self._available = False
# Set param to uppercase
if self._state:
self._state = self._state.upper()
# Generate name
if not self._name:
self._name = _create_ha_name(name=self._address,
channel=self._channel,
param=self._state)
@property
def should_poll(self):
"""Return false. Homematic states are pushed by the XML RPC Server."""
return False
@property
def name(self):
"""Return the name of the device."""
return self._name
@property
def assumed_state(self):
"""Return true if unable to access real state of the device."""
return not self._available
@property
def available(self):
"""Return true if device is available."""
return self._available
@property
def device_state_attributes(self):
"""Return device specific state attributes."""
attr = {}
# no data available to create
if not self.available:
return attr
# Generate an attributes list
for node, data in HM_ATTRIBUTE_SUPPORT.items():
# Is an attributes and exists for this object
if node in self._data:
value = data[1].get(self._data[node], self._data[node])
attr[data[0]] = value
# static attributes
attr["ID"] = self._hmdevice.ADDRESS
return attr
def link_homematic(self):
"""Connect to Homematic."""
# device is already linked
if self._connected:
return True
# Does a HMDevice from pyhomematic exist?
if self._address in HOMEMATIC.devices:
# Init
self._hmdevice = HOMEMATIC.devices[self._address]
self._connected = True
# Check if Homematic class is okay for HA class
_LOGGER.info("Start linking %s to %s", self._address, self._name)
if self._check_hm_to_ha_object():
try:
# Init datapoints of this object
self._init_data_struct()
if HOMEMATIC_LINK_DELAY:
# We delay / pause loading of data to avoid overloading
# of CCU / Homegear when doing auto detection
time.sleep(HOMEMATIC_LINK_DELAY)
self._load_init_data_from_hm()
_LOGGER.debug("%s datastruct: %s",
self._name, str(self._data))
# Link events from pyhomatic
self._subscribe_homematic_events()
self._available = not self._hmdevice.UNREACH
# pylint: disable=broad-except
except Exception as err:
self._connected = False
_LOGGER.error("Exception while linking %s: %s",
self._address, str(err))
else:
_LOGGER.critical("Delink %s object from HM", self._name)
self._connected = False
# Update HA
_LOGGER.debug("%s linking done, send update_ha_state", self._name)
self.update_ha_state()
else:
_LOGGER.debug("%s not found in HOMEMATIC.devices", self._address)
def _hm_event_callback(self, device, caller, attribute, value):
"""Handle all pyhomematic device events."""
_LOGGER.debug("%s received event '%s' value: %s", self._name,
attribute, value)
have_change = False
# Is data needed for this instance?
if attribute in self._data:
# Did data change?
if self._data[attribute] != value:
self._data[attribute] = value
have_change = True
# If available it has changed
if attribute is "UNREACH":
self._available = bool(value)
have_change = True
# If it has changed data point, update HA
if have_change:
_LOGGER.debug("%s update_ha_state after '%s'", self._name,
attribute)
self.update_ha_state()
def _subscribe_homematic_events(self):
"""Subscribe all required events to handle job."""
channels_to_sub = {}
# Push data to channels_to_sub from hmdevice metadata
for metadata in (self._hmdevice.SENSORNODE, self._hmdevice.BINARYNODE,
self._hmdevice.ATTRIBUTENODE,
self._hmdevice.WRITENODE, self._hmdevice.EVENTNODE,
self._hmdevice.ACTIONNODE):
for node, channel 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:
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)
# Set callbacks
for channel in channels_to_sub:
_LOGGER.debug("Subscribe channel %s from %s",
str(channel), self._name)
self._hmdevice.setEventCallback(callback=self._hm_event_callback,
bequeath=False,
channel=channel)
def _load_init_data_from_hm(self):
"""Load first value from pyhomematic."""
if not self._connected:
return False
# Read data from pyhomematic
for metadata, funct in (
(self._hmdevice.ATTRIBUTENODE,
self._hmdevice.getAttributeData),
(self._hmdevice.WRITENODE, self._hmdevice.getWriteData),
(self._hmdevice.SENSORNODE, self._hmdevice.getSensorData),
(self._hmdevice.BINARYNODE, self._hmdevice.getBinaryData)):
for node in metadata:
if node in self._data:
self._data[node] = funct(name=node, channel=self._channel)
return True
def _hm_set_state(self, value):
"""Set data to main datapoint."""
if self._state in self._data:
self._data[self._state] = value
def _hm_get_state(self):
"""Get data from main datapoint."""
if self._state in self._data:
return self._data[self._state]
return None
def _check_hm_to_ha_object(self):
"""Check if it is possible to use the Homematic object as this HA type.
NEEDS overwrite by inherit!
"""
if not self._connected or self._hmdevice is None:
_LOGGER.error("HA object is not linked to homematic.")
return False
# Check if button option is correctly set for this object
if self._channel > self._hmdevice.ELEMENT:
_LOGGER.critical("Button option is not correct for this object!")
return False
return True
def _init_data_struct(self):
"""Generate a data dict (self._data) from the Homematic metadata.
NEEDS overwrite by inherit!
"""
# Add all attributes to data dict
for data_note in self._hmdevice.ATTRIBUTENODE:
self._data.update({data_note: STATE_UNKNOWN})
+82 -17
View File
@@ -1,25 +1,31 @@
"""This module provides WSGI application to serve the Home Assistant API."""
"""
This module provides WSGI application to serve the Home Assistant API.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/http/
"""
import hmac
import json
import logging
import mimetypes
import threading
import re
import ssl
import voluptuous as vol
import homeassistant.core as ha
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_HEADERS, ALLOWED_CORS_HEADERS)
HTTP_HEADER_ACCESS_CONTROL_ALLOW_HEADERS, ALLOWED_CORS_HEADERS,
EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_START)
from homeassistant.helpers.entity import split_entity_id
import homeassistant.util.dt as dt_util
import homeassistant.helpers.config_validation as cv
DOMAIN = "http"
REQUIREMENTS = ("eventlet==0.19.0", "static3==0.7.0", "Werkzeug==0.11.5",)
REQUIREMENTS = ("cherrypy==6.0.2", "static3==0.7.0", "Werkzeug==0.11.10")
CONF_API_PASSWORD = "api_password"
CONF_SERVER_HOST = "server_host"
@@ -31,6 +37,25 @@ CONF_CORS_ORIGINS = 'cors_allowed_origins'
DATA_API_PASSWORD = 'api_password'
# TLS configuation follows the best-practice guidelines
# specified here: https://wiki.mozilla.org/Security/Server_Side_TLS
# Intermediate guidelines are followed.
SSL_VERSION = ssl.PROTOCOL_SSLv23
SSL_OPTS = ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 | ssl.OP_NO_COMPRESSION
CIPHERS = "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:" \
"ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:" \
"ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:" \
"DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:" \
"ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:" \
"ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:" \
"ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:" \
"ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:" \
"DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:" \
"DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:" \
"ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:" \
"AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:" \
"AES256-SHA:DES-CBC3-SHA:!DSS"
_FINGERPRINT = re.compile(r'^(.+)-[a-z0-9]{32}\.(\w+)$', re.IGNORECASE)
_LOGGER = logging.getLogger(__name__)
@@ -93,11 +118,17 @@ def setup(hass, config):
cors_origins=cors_origins
)
hass.bus.listen_once(
ha.EVENT_HOMEASSISTANT_START,
lambda event:
threading.Thread(target=server.start, daemon=True,
name='WSGI-server').start())
def start_wsgi_server(event):
"""Start the WSGI server."""
server.start()
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_wsgi_server)
def stop_wsgi_server(event):
"""Stop the WSGI server."""
server.stop()
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_wsgi_server)
hass.wsgi = server
hass.config.api = rem.API(server_host if server_host != '0.0.0.0'
@@ -216,6 +247,7 @@ class HomeAssistantWSGI(object):
self.server_port = server_port
self.cors_origins = cors_origins
self.event_forwarder = None
self.server = None
def register_view(self, view):
"""Register a view with the WSGI server.
@@ -283,14 +315,34 @@ class HomeAssistantWSGI(object):
def start(self):
"""Start the wsgi server."""
from eventlet import wsgi
import eventlet
from cherrypy import wsgiserver
from cherrypy.wsgiserver.ssl_builtin import BuiltinSSLAdapter
# pylint: disable=too-few-public-methods,super-init-not-called
class ContextSSLAdapter(BuiltinSSLAdapter):
"""SSL Adapter that takes in an SSL context."""
def __init__(self, context):
self.context = context
# pylint: disable=no-member
self.server = wsgiserver.CherryPyWSGIServer(
(self.server_host, self.server_port), self,
server_name='Home Assistant')
sock = eventlet.listen((self.server_host, self.server_port))
if self.ssl_certificate:
sock = eventlet.wrap_ssl(sock, certfile=self.ssl_certificate,
keyfile=self.ssl_key, server_side=True)
wsgi.server(sock, self, log=_LOGGER)
context = ssl.SSLContext(SSL_VERSION)
context.options |= SSL_OPTS
context.set_ciphers(CIPHERS)
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()
def stop(self):
"""Stop the wsgi server."""
self.server.stop()
def dispatch_request(self, request):
"""Handle incoming request."""
@@ -337,6 +389,10 @@ class HomeAssistantWSGI(object):
"""Handle a request for base app + extra apps."""
from werkzeug.wsgi import DispatcherMiddleware
if not self.hass.is_running:
from werkzeug.exceptions import BadRequest
return BadRequest()(environ, start_response)
app = DispatcherMiddleware(self.base_app, self.extra_apps)
# Strip out any cachebusting MD5 fingerprints
fingerprinted = _FINGERPRINT.match(environ.get('PATH_INFO', ''))
@@ -395,7 +451,12 @@ class HomeAssistantView(object):
self.hass.wsgi.api_password):
authenticated = True
if self.requires_auth and not authenticated:
if authenticated:
_LOGGER.info('Successful login/request from %s',
request.remote_addr)
elif self.requires_auth and not authenticated:
_LOGGER.warning('Login attempt or request with an invalid'
'password from %s', request.remote_addr)
raise Unauthorized()
request.authenticated = authenticated
@@ -437,9 +498,13 @@ class HomeAssistantView(object):
mimetype = mimetypes.guess_type(fil)[0]
try:
fil = open(fil)
fil = open(fil, mode='br')
except IOError:
raise NotFound()
return self.Response(wrap_file(request.environ, fil),
mimetype=mimetype, direct_passthrough=True)
def options(self, request):
"""Default handler for OPTIONS (necessary for CORS preflight)."""
return self.Response('', status=200)
+10 -16
View File
@@ -14,7 +14,6 @@ import homeassistant.util as util
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.temperature import convert
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
from homeassistant.components import zwave
from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_TEMPERATURE, STATE_ON, STATE_OFF, STATE_UNKNOWN,
TEMP_CELCIUS)
@@ -57,10 +56,6 @@ ATTR_SWING_LIST = "swing_list"
_LOGGER = logging.getLogger(__name__)
DISCOVERY_PLATFORMS = {
zwave.DISCOVER_HVAC: 'zwave'
}
def set_away_mode(hass, away_mode, entity_id=None):
"""Turn all or specified hvac away mode on."""
@@ -139,8 +134,7 @@ def set_swing_mode(hass, swing_mode, entity_id=None):
# pylint: disable=too-many-branches
def setup(hass, config):
"""Setup hvacs."""
component = EntityComponent(_LOGGER, DOMAIN, hass,
SCAN_INTERVAL, DISCOVERY_PLATFORMS)
component = EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL)
component.setup(config)
descriptions = load_yaml_config_file(
@@ -431,39 +425,39 @@ class HvacDevice(Entity):
def set_temperature(self, temperature):
"""Set new target temperature."""
pass
raise NotImplementedError()
def set_humidity(self, humidity):
"""Set new target humidity."""
pass
raise NotImplementedError()
def set_fan_mode(self, fan):
"""Set new target fan mode."""
pass
raise NotImplementedError()
def set_operation_mode(self, operation_mode):
"""Set new target operation mode."""
pass
raise NotImplementedError()
def set_swing_mode(self, swing_mode):
"""Set new target swing operation."""
pass
raise NotImplementedError()
def turn_away_mode_on(self):
"""Turn away mode on."""
pass
raise NotImplementedError()
def turn_away_mode_off(self):
"""Turn away mode off."""
pass
raise NotImplementedError()
def turn_aux_heat_on(self):
"""Turn auxillary heater on."""
pass
raise NotImplementedError()
def turn_aux_heat_off(self):
"""Turn auxillary heater off."""
pass
raise NotImplementedError()
@property
def min_temp(self):
+6 -2
View File
@@ -58,7 +58,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
discovery_info, zwave.NETWORK)
# pylint: disable=too-many-arguments
# pylint: disable=too-many-arguments, abstract-method
class ZWaveHvac(ZWaveDeviceEntity, HvacDevice):
"""Represents a HeatControl hvac."""
@@ -98,7 +98,7 @@ class ZWaveHvac(ZWaveDeviceEntity, HvacDevice):
def value_changed(self, value):
"""Called when a value has changed on the network."""
if self._value.node == value.node:
if self._value.value_id == value.value_id:
self.update_properties()
self.update_ha_state(True)
_LOGGER.debug("Value changed on network %s", value)
@@ -211,6 +211,7 @@ class ZWaveHvac(ZWaveDeviceEntity, HvacDevice):
value.data = int(round(temperature, 0))
else:
value.data = int(temperature)
break
def set_fan_mode(self, fan):
"""Set new target fan mode."""
@@ -218,6 +219,7 @@ class ZWaveHvac(ZWaveDeviceEntity, HvacDevice):
class_id=COMMAND_CLASS_THERMOSTAT_FAN_MODE).values():
if value.command_class == 68 and value.index == 0:
value.data = bytes(fan, 'utf-8')
break
def set_operation_mode(self, operation_mode):
"""Set new target operation mode."""
@@ -225,6 +227,7 @@ class ZWaveHvac(ZWaveDeviceEntity, HvacDevice):
class_id=COMMAND_CLASS_THERMOSTAT_MODE).values():
if value.command_class == 64 and value.index == 0:
value.data = bytes(operation_mode, 'utf-8')
break
def set_swing_mode(self, swing_mode):
"""Set new target swing mode."""
@@ -233,3 +236,4 @@ class ZWaveHvac(ZWaveDeviceEntity, HvacDevice):
class_id=COMMAND_CLASS_CONFIGURATION).values():
if value.command_class == 112 and value.index == 33:
value.data = int(swing_mode)
break
+1 -1
View File
@@ -23,7 +23,7 @@ DEFAULT_DATABASE = 'home_assistant'
DEFAULT_SSL = False
DEFAULT_VERIFY_SSL = False
REQUIREMENTS = ['influxdb==2.12.0']
REQUIREMENTS = ['influxdb==3.0.0']
CONF_HOST = 'host'
CONF_PORT = 'port'
+15 -8
View File
@@ -8,7 +8,7 @@ import logging
import voluptuous as vol
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.const import ATTR_ENTITY_ID, ATTR_UNIT_OF_MEASUREMENT
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
@@ -68,18 +68,18 @@ def setup(hass, config):
name = cfg.get(CONF_NAME)
minimum = cfg.get(CONF_MIN)
maximum = cfg.get(CONF_MAX)
state = cfg.get(CONF_INITIAL)
step = cfg.get(CONF_STEP)
state = cfg.get(CONF_INITIAL, minimum)
step = cfg.get(CONF_STEP, 1)
icon = cfg.get(CONF_ICON)
unit = cfg.get(ATTR_UNIT_OF_MEASUREMENT)
if state < minimum:
state = minimum
if state > maximum:
state = maximum
entities.append(
InputSlider(object_id, name, state, minimum, maximum, step, icon)
)
entities.append(InputSlider(object_id, name, state, minimum, maximum,
step, icon, unit))
if not entities:
return False
@@ -103,8 +103,9 @@ def setup(hass, config):
class InputSlider(Entity):
"""Represent an slider."""
# pylint: disable=too-many-arguments
def __init__(self, object_id, name, state, minimum, maximum, step, icon):
# pylint: disable=too-many-arguments, too-many-instance-attributes
def __init__(self, object_id, name, state, minimum, maximum, step, icon,
unit):
"""Initialize a select input."""
self.entity_id = ENTITY_ID_FORMAT.format(object_id)
self._name = name
@@ -113,6 +114,7 @@ class InputSlider(Entity):
self._maximum = maximum
self._step = step
self._icon = icon
self._unit = unit
@property
def should_poll(self):
@@ -134,6 +136,11 @@ class InputSlider(Entity):
"""State of the component."""
return self._current_value
@property
def unit_of_measurement(self):
"""Unit of measurement of slider."""
return self._unit
@property
def state_attributes(self):
"""State attributes."""
+4 -14
View File
@@ -6,17 +6,12 @@ https://home-assistant.io/components/insteon_hub/
"""
import logging
import homeassistant.bootstrap as bootstrap
from homeassistant.const import (
ATTR_DISCOVERED, ATTR_SERVICE, CONF_API_KEY, CONF_PASSWORD, CONF_USERNAME,
EVENT_PLATFORM_DISCOVERED)
from homeassistant.helpers import validate_config
from homeassistant.loader import get_component
from homeassistant.const import CONF_API_KEY, CONF_PASSWORD, CONF_USERNAME
from homeassistant.helpers import validate_config, discovery
DOMAIN = "insteon_hub"
REQUIREMENTS = ['insteon_hub==0.4.5']
INSTEON = None
DISCOVER_LIGHTS = "insteon_hub.lights"
_LOGGER = logging.getLogger(__name__)
@@ -44,11 +39,6 @@ def setup(hass, config):
_LOGGER.error("Could not connect to Insteon service.")
return
comp_name = 'light'
discovery = DISCOVER_LIGHTS
component = get_component(comp_name)
bootstrap.setup_component(hass, component.DOMAIN, config)
hass.bus.fire(
EVENT_PLATFORM_DISCOVERED,
{ATTR_SERVICE: discovery, ATTR_DISCOVERED: {}})
discovery.load_platform(hass, 'light', DOMAIN, {}, config)
return True
+7 -17
View File
@@ -7,19 +7,15 @@ https://home-assistant.io/components/isy994/
import logging
from urllib.parse import urlparse
from homeassistant import bootstrap
from homeassistant.const import (
ATTR_DISCOVERED, ATTR_SERVICE, CONF_HOST, CONF_PASSWORD, CONF_USERNAME,
EVENT_HOMEASSISTANT_STOP, EVENT_PLATFORM_DISCOVERED)
from homeassistant.helpers import validate_config
CONF_HOST, CONF_PASSWORD, CONF_USERNAME,
EVENT_HOMEASSISTANT_STOP)
from homeassistant.helpers import validate_config, discovery
from homeassistant.helpers.entity import ToggleEntity
from homeassistant.loader import get_component
DOMAIN = "isy994"
REQUIREMENTS = ['PyISY==1.0.6']
DISCOVER_LIGHTS = "isy994.lights"
DISCOVER_SWITCHES = "isy994.switches"
DISCOVER_SENSORS = "isy994.sensors"
ISY = None
SENSOR_STRING = 'Sensor'
HIDDEN_STRING = '{HIDE ME}'
@@ -76,15 +72,9 @@ def setup(hass, config):
# Listen for HA stop to disconnect.
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop)
# Load components for the devices in the ISY controller that we support.
for comp_name, discovery in ((('sensor', DISCOVER_SENSORS),
('light', DISCOVER_LIGHTS),
('switch', DISCOVER_SWITCHES))):
component = get_component(comp_name)
bootstrap.setup_component(hass, component.DOMAIN, config)
hass.bus.fire(EVENT_PLATFORM_DISCOVERED,
{ATTR_SERVICE: discovery,
ATTR_DISCOVERED: {}})
# Load platforms for the devices in the ISY controller that we support.
for component in ('sensor', 'light', 'switch'):
discovery.load_platform(hass, component, DOMAIN, {}, config)
ISY.auto_update = True
return True
+4 -19
View File
@@ -10,9 +10,7 @@ import csv
import voluptuous as vol
from homeassistant.components import (
group, discovery, wemo, wink, isy994,
zwave, insteon_hub, mysensors, tellstick, vera)
from homeassistant.components import group
from homeassistant.config import load_yaml_config_file
from homeassistant.const import (
STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE,
@@ -60,19 +58,6 @@ EFFECT_WHITE = "white"
LIGHT_PROFILES_FILE = "light_profiles.csv"
# Maps discovered services to their platforms.
DISCOVERY_PLATFORMS = {
wemo.DISCOVER_LIGHTS: 'wemo',
wink.DISCOVER_LIGHTS: 'wink',
insteon_hub.DISCOVER_LIGHTS: 'insteon_hub',
isy994.DISCOVER_LIGHTS: 'isy994',
discovery.SERVICE_HUE: 'hue',
zwave.DISCOVER_LIGHTS: 'zwave',
mysensors.DISCOVER_LIGHTS: 'mysensors',
tellstick.DISCOVER_LIGHTS: 'tellstick',
vera.DISCOVER_LIGHTS: 'vera',
}
PROP_TO_ATTR = {
'brightness': ATTR_BRIGHTNESS,
'color_temp': ATTR_COLOR_TEMP,
@@ -172,8 +157,7 @@ def toggle(hass, entity_id=None, transition=None):
def setup(hass, config):
"""Expose light control via statemachine and services."""
component = EntityComponent(
_LOGGER, DOMAIN, hass, SCAN_INTERVAL, DISCOVERY_PLATFORMS,
GROUP_NAME_ALL_LIGHTS)
_LOGGER, DOMAIN, hass, SCAN_INTERVAL, GROUP_NAME_ALL_LIGHTS)
component.setup(config)
# Load built-in profiles and custom profiles
@@ -264,7 +248,8 @@ def setup(hass, config):
class Light(ToggleEntity):
"""Representation of a light."""
# pylint: disable=no-self-use
# pylint: disable=no-self-use, abstract-method
@property
def brightness(self):
"""Return the brightness of this light between 0..255."""
+1 -2
View File
@@ -4,7 +4,6 @@ Support for EnOcean light sources.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.enocean/
"""
import logging
import math
@@ -86,7 +85,7 @@ class EnOceanLight(enocean.EnOceanDevice, Light):
self._on_state = False
def value_changed(self, val):
"""Update the internal state of this device in HA."""
"""Update the internal state of this device."""
self._brightness = math.floor(val / 100.0 * 256.0)
self._on_state = bool(val != 0)
self.update_ha_state()
+102
View File
@@ -0,0 +1,102 @@
"""
Support for Homematic lighs.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.homematic/
"""
import logging
from homeassistant.components.light import (ATTR_BRIGHTNESS, Light)
from homeassistant.const import STATE_UNKNOWN
import homeassistant.components.homematic as homematic
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['homematic']
def setup_platform(hass, config, add_callback_devices, discovery_info=None):
"""Setup the Homematic light platform."""
if discovery_info is None:
return
return homematic.setup_hmdevice_discovery_helper(HMLight,
discovery_info,
add_callback_devices)
class HMLight(homematic.HMDevice, Light):
"""Representation of a Homematic light."""
@property
def brightness(self):
"""Return the brightness of this light between 0..255."""
if not self.available:
return None
# Is dimmer?
if self._state is "LEVEL":
return int(self._hm_get_state() * 255)
else:
return None
@property
def is_on(self):
"""Return true if light is on."""
try:
return self._hm_get_state() > 0
except TypeError:
return False
def turn_on(self, **kwargs):
"""Turn the light on."""
if not self.available:
return
if ATTR_BRIGHTNESS in kwargs and self._state is "LEVEL":
percent_bright = float(kwargs[ATTR_BRIGHTNESS]) / 255
self._hmdevice.set_level(percent_bright, self._channel)
else:
self._hmdevice.on(self._channel)
def turn_off(self, **kwargs):
"""Turn the light off."""
if self.available:
self._hmdevice.off(self._channel)
def _check_hm_to_ha_object(self):
"""Check if possible to use the Homematic object as this HA type."""
from pyhomematic.devicetypes.actors import Dimmer, Switch
# Check compatibility from HMDevice
if not super()._check_hm_to_ha_object():
return False
# Check if the Homematic device is correct for this HA device
if isinstance(self._hmdevice, Switch):
return True
if isinstance(self._hmdevice, Dimmer):
return True
_LOGGER.critical("This %s can't be use as light", self._name)
return False
def _init_data_struct(self):
"""Generate a data dict (self._data) from the Homematic metadata."""
from pyhomematic.devicetypes.actors import Dimmer, Switch
super()._init_data_struct()
# Use STATE
if isinstance(self._hmdevice, Switch):
self._state = "STATE"
# Use LEVEL
if isinstance(self._hmdevice, Dimmer):
self._state = "LEVEL"
# Add state to data dict
if self._state:
_LOGGER.debug("%s init datadict with main node '%s'", self._name,
self._state)
self._data.update({self._state: STATE_UNKNOWN})
else:
_LOGGER.critical("Can't correctly init light %s.", self._name)
@@ -4,6 +4,7 @@ Support for LimitlessLED bulbs.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.limitlessled/
"""
# pylint: disable=abstract-method
import logging
from homeassistant.components.light import (
@@ -4,6 +4,7 @@ Support for MySensors lights.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.mysensors/
"""
# pylint: disable=abstract-method
import logging
from homeassistant.components import mysensors
@@ -1,19 +1,9 @@
"""
Support for Osram Lightify.
Uses: https://github.com/aneumeier/python-lightify for the Osram light
interface.
In order to use the platform just add the following to the configuration.yaml:
light:
platform: osramlightify
host: <hostname_or_ip>
Todo:
Add support for Non RGBW lights.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.osramlightify/
"""
import logging
import socket
from datetime import timedelta
@@ -40,7 +30,7 @@ MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(milliseconds=100)
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Find and return lights."""
"""Setup Osram Lightify lights."""
import lightify
host = config.get(CONF_HOST)
if host:
@@ -85,7 +75,7 @@ def setup_bridge(bridge, add_devices_callback):
class OsramLightifyLight(Light):
"""Defines an Osram Lightify Light."""
"""Representation of an Osram Lightify Light."""
def __init__(self, light_id, light, update_lights):
"""Initialize the light."""
+5 -27
View File
@@ -8,12 +8,13 @@ import logging
from homeassistant.components.light import ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, \
Light, ATTR_RGB_COLOR
from homeassistant.components.wink import WinkDevice
from homeassistant.const import CONF_ACCESS_TOKEN
from homeassistant.util import color as color_util
from homeassistant.util.color import \
color_temperature_mired_to_kelvin as mired_to_kelvin
REQUIREMENTS = ['python-wink==0.7.6']
REQUIREMENTS = ['python-wink==0.7.8', 'pubnub==3.7.8']
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
@@ -35,26 +36,12 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
WinkLight(light) for light in pywink.get_bulbs())
class WinkLight(Light):
class WinkLight(WinkDevice, Light):
"""Representation of a Wink light."""
def __init__(self, wink):
"""
Initialize the light.
:type wink: pywink.devices.standard.bulb.WinkBulb
"""
self.wink = wink
@property
def unique_id(self):
"""Return the ID of this Wink light."""
return "{}.{}".format(self.__class__, self.wink.device_id())
@property
def name(self):
"""Return the name of the light if any."""
return self.wink.name()
"""Initialize the Wink device."""
WinkDevice.__init__(self, wink)
@property
def is_on(self):
@@ -66,11 +53,6 @@ class WinkLight(Light):
"""Return the brightness of the light."""
return int(self.wink.brightness() * 255)
@property
def available(self):
"""True if connection == True."""
return self.wink.available
@property
def xy_color(self):
"""Current bulb color in CIE 1931 (XY) color space."""
@@ -112,7 +94,3 @@ class WinkLight(Light):
def turn_off(self):
"""Turn the switch off."""
self.wink.set_state(False)
def update(self):
"""Update state of the light."""
self.wink.update_state(require_desired_state_fulfilled=True)
+205 -6
View File
@@ -4,13 +4,31 @@ Support for Z-Wave lights.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.zwave/
"""
import logging
# Because we do not compile openzwave on CI
# pylint: disable=import-error
from threading import Timer
from homeassistant.components.light import ATTR_BRIGHTNESS, DOMAIN, Light
from homeassistant.components.light import ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, \
ATTR_RGB_COLOR, DOMAIN, Light
from homeassistant.components import zwave
from homeassistant.const import STATE_OFF, STATE_ON
from homeassistant.util.color import HASS_COLOR_MAX, HASS_COLOR_MIN, \
color_temperature_mired_to_kelvin, color_temperature_to_rgb
_LOGGER = logging.getLogger(__name__)
COLOR_CHANNEL_WARM_WHITE = 0x01
COLOR_CHANNEL_COLD_WHITE = 0x02
COLOR_CHANNEL_RED = 0x04
COLOR_CHANNEL_GREEN = 0x08
COLOR_CHANNEL_BLUE = 0x10
# Generate midpoint color temperatures for bulbs that have limited
# support for white light colors
TEMP_MID_HASS = (HASS_COLOR_MAX - HASS_COLOR_MIN) / 2 + HASS_COLOR_MIN
TEMP_WARM_HASS = (HASS_COLOR_MAX - HASS_COLOR_MIN) / 3 * 2 + HASS_COLOR_MIN
TEMP_COLD_HASS = (HASS_COLOR_MAX - HASS_COLOR_MIN) / 3 + HASS_COLOR_MIN
def setup_platform(hass, config, add_devices, discovery_info=None):
@@ -29,7 +47,17 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
return
value.set_change_verified(False)
add_devices([ZwaveDimmer(value)])
if node.has_command_class(zwave.COMMAND_CLASS_COLOR):
try:
add_devices([ZwaveColorLight(value)])
except ValueError as exception:
_LOGGER.warning(
"Error initializing as color bulb: %s "
"Initializing as standard dimmer.", exception)
add_devices([ZwaveDimmer(value)])
else:
add_devices([ZwaveDimmer(value)])
def brightness_state(value):
@@ -50,8 +78,9 @@ class ZwaveDimmer(zwave.ZWaveDeviceEntity, Light):
from pydispatch import dispatcher
zwave.ZWaveDeviceEntity.__init__(self, value, DOMAIN)
self._brightness, self._state = brightness_state(value)
self._brightness = None
self._state = None
self.update_properties()
# Used for value change event handling
self._refreshing = False
@@ -60,6 +89,11 @@ class ZwaveDimmer(zwave.ZWaveDeviceEntity, Light):
dispatcher.connect(
self._value_changed, ZWaveNetwork.SIGNAL_VALUE_CHANGED)
def update_properties(self):
"""Update internal properties based on zwave values."""
# Brightness
self._brightness, self._state = brightness_state(self._value)
def _value_changed(self, value):
"""Called when a value has changed on the network."""
if self._value.value_id != value.value_id:
@@ -67,7 +101,7 @@ class ZwaveDimmer(zwave.ZWaveDeviceEntity, Light):
if self._refreshing:
self._refreshing = False
self._brightness, self._state = brightness_state(value)
self.update_properties()
else:
def _refresh_value():
"""Used timer callback for delayed value refresh."""
@@ -108,3 +142,168 @@ class ZwaveDimmer(zwave.ZWaveDeviceEntity, Light):
"""Turn the device off."""
if self._value.node.set_dimmer(self._value.value_id, 0):
self._state = STATE_OFF
def ct_to_rgb(temp):
"""Convert color temperature (mireds) to RGB."""
colorlist = list(
color_temperature_to_rgb(color_temperature_mired_to_kelvin(temp)))
return [int(val) for val in colorlist]
class ZwaveColorLight(ZwaveDimmer):
"""Representation of a Z-Wave color changing light."""
def __init__(self, value):
"""Initialize the light."""
self._value_color = None
self._value_color_channels = None
self._color_channels = None
self._rgb = None
self._ct = None
# Here we attempt to find a zwave color value with the same instance
# id as the dimmer value. Currently zwave nodes that change colors
# only include one dimmer and one color command, but this will
# hopefully provide some forward compatibility for new devices that
# have multiple color changing elements.
for value_color in value.node.get_rgbbulbs().values():
if value.instance == value_color.instance:
self._value_color = value_color
if self._value_color is None:
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():
self._value_color_channels = value_color_channels
if self._value_color_channels is None:
raise ValueError("Color Channels not found.")
super().__init__(value)
def update_properties(self):
"""Update internal properties based on zwave values."""
super().update_properties()
# Color Channels
self._color_channels = self._value_color_channels.data
# Color Data String
data = self._value_color.data
# RGB is always present in the openzwave color data string.
self._rgb = [
int(data[1:3], 16),
int(data[3:5], 16),
int(data[5:7], 16)]
# Parse remaining color channels. Openzwave appends white channels
# that are present.
index = 7
# Warm white
if self._color_channels & COLOR_CHANNEL_WARM_WHITE:
warm_white = int(data[index:index+2], 16)
index += 2
else:
warm_white = 0
# Cold white
if self._color_channels & COLOR_CHANNEL_COLD_WHITE:
cold_white = int(data[index:index+2], 16)
index += 2
else:
cold_white = 0
# Color temperature. With two white channels, only two color
# temperatures are supported for the bulb. The channel values
# indicate brightness for warm/cold color temperature.
if (self._color_channels & COLOR_CHANNEL_WARM_WHITE and
self._color_channels & COLOR_CHANNEL_COLD_WHITE):
if warm_white > 0:
self._ct = TEMP_WARM_HASS
self._rgb = ct_to_rgb(self._ct)
elif cold_white > 0:
self._ct = TEMP_COLD_HASS
self._rgb = ct_to_rgb(self._ct)
else:
# RGB color is being used. Just report midpoint.
self._ct = TEMP_MID_HASS
# If only warm white is reported 0-255 is color temperature.
elif self._color_channels & COLOR_CHANNEL_WARM_WHITE:
self._ct = HASS_COLOR_MIN + (HASS_COLOR_MAX - HASS_COLOR_MIN) * (
warm_white / 255)
self._rgb = ct_to_rgb(self._ct)
# If only cold white is reported 0-255 is negative color temperature.
elif self._color_channels & COLOR_CHANNEL_COLD_WHITE:
self._ct = HASS_COLOR_MIN + (HASS_COLOR_MAX - HASS_COLOR_MIN) * (
(255 - cold_white) / 255)
self._rgb = ct_to_rgb(self._ct)
# If no rgb channels supported, report None.
if not (self._color_channels & COLOR_CHANNEL_RED or
self._color_channels & COLOR_CHANNEL_GREEN or
self._color_channels & COLOR_CHANNEL_BLUE):
self._rgb = None
@property
def rgb_color(self):
"""Return the rgb color."""
return self._rgb
@property
def color_temp(self):
"""Return the color temperature."""
return self._ct
def turn_on(self, **kwargs):
"""Turn the device on."""
rgbw = None
if ATTR_COLOR_TEMP in kwargs:
# With two white channels, only two color temperatures are
# supported for the bulb.
if (self._color_channels & COLOR_CHANNEL_WARM_WHITE and
self._color_channels & COLOR_CHANNEL_COLD_WHITE):
if kwargs[ATTR_COLOR_TEMP] > TEMP_MID_HASS:
self._ct = TEMP_WARM_HASS
rgbw = b'#000000FF00'
else:
self._ct = TEMP_COLD_HASS
rgbw = b'#00000000FF'
# If only warm white is reported 0-255 is color temperature
elif self._color_channels & COLOR_CHANNEL_WARM_WHITE:
rgbw = b'#000000'
temp = (
(kwargs[ATTR_COLOR_TEMP] - HASS_COLOR_MIN) /
(HASS_COLOR_MAX - HASS_COLOR_MIN) * 255)
rgbw += format(int(temp)).encode('utf-8')
# If only cold white is reported 0-255 is negative color temp
elif self._color_channels & COLOR_CHANNEL_COLD_WHITE:
rgbw = b'#000000'
temp = (
255 - (kwargs[ATTR_COLOR_TEMP] - HASS_COLOR_MIN) /
(HASS_COLOR_MAX - HASS_COLOR_MIN) * 255)
rgbw += format(int(temp)).encode('utf-8')
elif ATTR_RGB_COLOR in kwargs:
self._rgb = kwargs[ATTR_RGB_COLOR]
rgbw = b'#'
for colorval in self._rgb:
rgbw += format(colorval, '02x').encode('utf-8')
rgbw += b'0000'
if rgbw is None:
_LOGGER.warning("rgbw string was not generated for turn_on")
else:
self._value_color.node.set_rgbw(self._value_color.value_id, rgbw)
super().turn_on(**kwargs)
+9 -2
View File
@@ -65,8 +65,14 @@ class LircInterface(threading.Thread):
def run(self):
"""Main loop of LIRC interface thread."""
import lirc
_LOGGER.debug('LIRC interface thread started')
while not self.stopped.isSet():
code = lirc.nextcode() # list; empty if no buttons pressed
try:
code = lirc.nextcode() # list; empty if no buttons pressed
except lirc.NextCodeError:
_LOGGER.warning('Encountered error reading '
'next code from LIRC')
code = None
# interpret result from python-lirc
if code:
code = code[0]
@@ -75,4 +81,5 @@ class LircInterface(threading.Thread):
{BUTTON_NAME: code})
else:
time.sleep(0.2)
_LOGGER.info('LIRC interface thread stopped')
lirc.deinit()
_LOGGER.debug('LIRC interface thread stopped')
+2 -10
View File
@@ -18,7 +18,7 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.const import (
ATTR_CODE, ATTR_CODE_FORMAT, ATTR_ENTITY_ID, STATE_LOCKED, STATE_UNLOCKED,
STATE_UNKNOWN, SERVICE_LOCK, SERVICE_UNLOCK)
from homeassistant.components import (group, verisure, wink, zwave)
from homeassistant.components import group
DOMAIN = 'lock'
SCAN_INTERVAL = 30
@@ -30,13 +30,6 @@ ENTITY_ID_FORMAT = DOMAIN + '.{}'
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
# Maps discovered services to their platforms
DISCOVERY_PLATFORMS = {
wink.DISCOVER_LOCKS: 'wink',
verisure.DISCOVER_LOCKS: 'verisure',
zwave.DISCOVER_LOCKS: 'zwave',
}
LOCK_SERVICE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional(ATTR_CODE): cv.string,
@@ -76,8 +69,7 @@ def unlock(hass, entity_id=None, code=None):
def setup(hass, config):
"""Track states and offer events for locks."""
component = EntityComponent(
_LOGGER, DOMAIN, hass, SCAN_INTERVAL, DISCOVERY_PLATFORMS,
GROUP_NAME_ALL_LOCKS)
_LOGGER, DOMAIN, hass, SCAN_INTERVAL, GROUP_NAME_ALL_LOCKS)
component.setup(config)
def handle_lock_service(service):
+65
View File
@@ -0,0 +1,65 @@
"""
Support for Vera locks.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/lock.vera/
"""
import logging
from homeassistant.components.lock import LockDevice
from homeassistant.const import (
ATTR_BATTERY_LEVEL, STATE_LOCKED, STATE_UNLOCKED)
from homeassistant.components.vera import (
VeraDevice, VERA_DEVICES, VERA_CONTROLLER)
DEPENDENCIES = ['vera']
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Find and return Vera locks."""
add_devices_callback(
VeraLock(device, VERA_CONTROLLER) for
device in VERA_DEVICES['lock'])
class VeraLock(VeraDevice, LockDevice):
"""Representation of a Vera lock."""
def __init__(self, vera_device, controller):
"""Initialize the Vera device."""
self._state = None
VeraDevice.__init__(self, vera_device, controller)
@property
def device_state_attributes(self):
"""Return the state attributes of the device."""
attr = {}
if self.vera_device.has_battery:
attr[ATTR_BATTERY_LEVEL] = self.vera_device.battery_level + '%'
attr['Vera Device Id'] = self.vera_device.vera_device_id
return attr
def lock(self, **kwargs):
"""Lock the device."""
self.vera_device.lock()
self._state = STATE_LOCKED
self.update_ha_state()
def unlock(self, **kwargs):
"""Unlock the device."""
self.vera_device.unlock()
self._state = STATE_UNLOCKED
self.update_ha_state()
@property
def is_locked(self):
"""Return true if device is on."""
return self._state == STATE_LOCKED
def update(self):
"""Called by the Vera device callback to update state."""
self._state = (STATE_LOCKED if self.vera_device.is_locked(True)
else STATE_UNLOCKED)
+5 -37
View File
@@ -7,9 +7,10 @@ https://home-assistant.io/components/lock.wink/
import logging
from homeassistant.components.lock import LockDevice
from homeassistant.const import CONF_ACCESS_TOKEN, ATTR_BATTERY_LEVEL
from homeassistant.components.wink import WinkDevice
from homeassistant.const import CONF_ACCESS_TOKEN
REQUIREMENTS = ['python-wink==0.7.6']
REQUIREMENTS = ['python-wink==0.7.8', 'pubnub==3.7.8']
def setup_platform(hass, config, add_devices, discovery_info=None):
@@ -30,38 +31,18 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
add_devices(WinkLockDevice(lock) for lock in pywink.get_locks())
class WinkLockDevice(LockDevice):
class WinkLockDevice(WinkDevice, LockDevice):
"""Representation of a Wink lock."""
def __init__(self, wink):
"""Initialize the lock."""
self.wink = wink
self._battery = self.wink.battery_level
@property
def unique_id(self):
"""Return the id of this wink lock."""
return "{}.{}".format(self.__class__, self.wink.device_id())
@property
def name(self):
"""Return the name of the lock if any."""
return self.wink.name()
def update(self):
"""Update the state of the lock."""
self.wink.update_state()
WinkDevice.__init__(self, wink)
@property
def is_locked(self):
"""Return true if device is locked."""
return self.wink.state()
@property
def available(self):
"""True if connection == True."""
return self.wink.available
def lock(self, **kwargs):
"""Lock the device."""
self.wink.set_state(True)
@@ -69,16 +50,3 @@ class WinkLockDevice(LockDevice):
def unlock(self, **kwargs):
"""Unlock the device."""
self.wink.set_state(False)
@property
def device_state_attributes(self):
"""Return the state attributes."""
if self._battery:
return {
ATTR_BATTERY_LEVEL: self._battery_level,
}
@property
def _battery_level(self):
"""Return the battery level."""
return self.wink.battery_level * 100
@@ -9,7 +9,6 @@ import os
import voluptuous as vol
from homeassistant.components import discovery
from homeassistant.config import load_yaml_config_file
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
@@ -30,15 +29,6 @@ SCAN_INTERVAL = 10
ENTITY_ID_FORMAT = DOMAIN + '.{}'
DISCOVERY_PLATFORMS = {
discovery.SERVICE_CAST: 'cast',
discovery.SERVICE_SONOS: 'sonos',
discovery.SERVICE_PLEX: 'plex',
discovery.SERVICE_SQUEEZEBOX: 'squeezebox',
discovery.SERVICE_PANASONIC_VIERA: 'panasonic_viera',
discovery.SERVICE_ROKU: 'roku',
}
SERVICE_PLAY_MEDIA = 'play_media'
SERVICE_SELECT_SOURCE = 'select_source'
@@ -285,8 +275,7 @@ def select_source(hass, source, entity_id=None):
def setup(hass, config):
"""Track states and offer events for media_players."""
component = EntityComponent(
logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL,
DISCOVERY_PLATFORMS)
logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL)
component.setup(config)
@@ -0,0 +1,370 @@
"""
Support for interface with a Sony Bravia TV.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.braviatv/
"""
import logging
import os
import json
import re
from homeassistant.loader import get_component
from homeassistant.components.media_player import (
SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK,
SUPPORT_TURN_OFF, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP,
SUPPORT_VOLUME_SET, SUPPORT_SELECT_SOURCE, MediaPlayerDevice)
from homeassistant.const import (
CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON)
REQUIREMENTS = [
'https://github.com/aparraga/braviarc/archive/0.3.2.zip'
'#braviarc==0.3.2']
BRAVIA_CONFIG_FILE = 'bravia.conf'
CLIENTID_PREFIX = 'HomeAssistant'
NICKNAME = 'Home Assistant'
# Map ip to request id for configuring
_CONFIGURING = {}
_LOGGER = logging.getLogger(__name__)
SUPPORT_BRAVIA = SUPPORT_PAUSE | SUPPORT_VOLUME_STEP | \
SUPPORT_VOLUME_MUTE | SUPPORT_VOLUME_SET | \
SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | \
SUPPORT_TURN_OFF | SUPPORT_SELECT_SOURCE
def _get_mac_address(ip_address):
"""Get the MAC address of the device."""
from subprocess import Popen, PIPE
pid = Popen(["arp", "-n", ip_address], stdout=PIPE)
pid_component = pid.communicate()[0]
mac = re.search(r"(([a-f\d]{1,2}\:){5}[a-f\d]{1,2})".encode('UTF-8'),
pid_component).groups()[0]
return mac
def _config_from_file(filename, config=None):
"""Create the configuration from a file."""
if config:
# We're writing configuration
bravia_config = _config_from_file(filename)
if bravia_config is None:
bravia_config = {}
new_config = bravia_config.copy()
new_config.update(config)
try:
with open(filename, 'w') as fdesc:
fdesc.write(json.dumps(new_config))
except IOError as error:
_LOGGER.error('Saving config file failed: %s', error)
return False
return True
else:
# We're reading config
if os.path.isfile(filename):
try:
with open(filename, 'r') as fdesc:
return json.loads(fdesc.read())
except ValueError as error:
return {}
except IOError as error:
_LOGGER.error('Reading config file failed: %s', error)
# This won't work yet
return False
else:
return {}
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Setup the Sony Bravia TV platform."""
host = config.get(CONF_HOST)
if host is None:
return # if no host configured, do not continue
pin = None
bravia_config = _config_from_file(hass.config.path(BRAVIA_CONFIG_FILE))
while len(bravia_config):
# Setup a configured TV
host_ip, host_config = bravia_config.popitem()
if host_ip == host:
pin = host_config['pin']
mac = host_config['mac']
name = config.get(CONF_NAME)
add_devices_callback([BraviaTVDevice(host, mac, name, pin)])
return
setup_bravia(config, pin, hass, add_devices_callback)
# pylint: disable=too-many-branches
def setup_bravia(config, pin, hass, add_devices_callback):
"""Setup a Sony Bravia TV based on host parameter."""
host = config.get(CONF_HOST)
name = config.get(CONF_NAME)
if name is None:
name = "Sony Bravia TV"
if pin is None:
request_configuration(config, hass, add_devices_callback)
return
else:
mac = _get_mac_address(host)
if mac is not None:
mac = mac.decode('utf8')
# If we came here and configuring this host, mark as done
if host in _CONFIGURING:
request_id = _CONFIGURING.pop(host)
configurator = get_component('configurator')
configurator.request_done(request_id)
_LOGGER.info('Discovery configuration done!')
# Save config
if not _config_from_file(
hass.config.path(BRAVIA_CONFIG_FILE),
{host: {'pin': pin, 'host': host, 'mac': mac}}):
_LOGGER.error('failed to save config file')
add_devices_callback([BraviaTVDevice(host, mac, name, pin)])
def request_configuration(config, hass, add_devices_callback):
"""Request configuration steps from the user."""
host = config.get(CONF_HOST)
name = config.get(CONF_NAME)
if name is None:
name = "Sony Bravia"
configurator = get_component('configurator')
# We got an error if this method is called while we are configuring
if host in _CONFIGURING:
configurator.notify_errors(
_CONFIGURING[host], "Failed to register, please try again.")
return
def bravia_configuration_callback(data):
"""Callback after user enter PIN."""
from braviarc import braviarc
pin = data.get('pin')
braviarc = braviarc.BraviaRC(host)
braviarc.connect(pin, CLIENTID_PREFIX, NICKNAME)
if braviarc.is_connected():
setup_bravia(config, pin, hass, add_devices_callback)
else:
request_configuration(config, hass, add_devices_callback)
_CONFIGURING[host] = configurator.request_config(
hass, name, bravia_configuration_callback,
description='Enter the Pin shown on your Sony Bravia TV.' +
'If no Pin is shown, enter 0000 to let TV show you a Pin.',
description_image="/static/images/smart-tv.png",
submit_caption="Confirm",
fields=[{'id': 'pin', 'name': 'Enter the pin', 'type': ''}]
)
# pylint: disable=abstract-method, too-many-public-methods,
# pylint: disable=too-many-instance-attributes, too-many-arguments
class BraviaTVDevice(MediaPlayerDevice):
"""Representation of a Sony Bravia TV."""
def __init__(self, host, mac, name, pin):
"""Initialize the Sony Bravia device."""
from braviarc import braviarc
self._pin = pin
self._braviarc = braviarc.BraviaRC(host, mac)
self._name = name
self._state = STATE_OFF
self._muted = False
self._program_name = None
self._channel_name = None
self._channel_number = None
self._source = None
self._source_list = []
self._original_content_list = []
self._content_mapping = {}
self._duration = None
self._content_uri = None
self._id = None
self._playing = False
self._start_date_time = None
self._program_media_type = None
self._min_volume = None
self._max_volume = None
self._volume = None
self._braviarc.connect(pin, CLIENTID_PREFIX, NICKNAME)
if self._braviarc.is_connected():
self.update()
else:
self._state = STATE_OFF
def update(self):
"""Update TV info."""
if not self._braviarc.is_connected():
self._braviarc.connect(self._pin, CLIENTID_PREFIX, NICKNAME)
if not self._braviarc.is_connected():
return
# Retrieve the latest data.
try:
if self._state == STATE_ON:
# refresh volume info:
self._refresh_volume()
self._refresh_channels()
playing_info = self._braviarc.get_playing_info()
if playing_info is None or len(playing_info) == 0:
self._state = STATE_OFF
else:
self._state = STATE_ON
self._program_name = playing_info.get('programTitle')
self._channel_name = playing_info.get('title')
self._program_media_type = playing_info.get(
'programMediaType')
self._channel_number = playing_info.get('dispNum')
self._source = playing_info.get('source')
self._content_uri = playing_info.get('uri')
self._duration = playing_info.get('durationSec')
self._start_date_time = playing_info.get('startDateTime')
except Exception as exception_instance: # pylint: disable=broad-except
_LOGGER.error(exception_instance)
self._state = STATE_OFF
def _refresh_volume(self):
"""Refresh volume information."""
volume_info = self._braviarc.get_volume_info()
if volume_info is not None:
self._volume = volume_info.get('volume')
self._min_volume = volume_info.get('minVolume')
self._max_volume = volume_info.get('maxVolume')
self._muted = volume_info.get('mute')
def _refresh_channels(self):
if len(self._source_list) == 0:
self._content_mapping = self._braviarc. \
load_source_list()
self._source_list = []
for key in self._content_mapping:
self._source_list.append(key)
@property
def name(self):
"""Return the name of the device."""
return self._name
@property
def state(self):
"""Return the state of the device."""
return self._state
@property
def source(self):
"""Return the current input source."""
return self._source
@property
def source_list(self):
"""List of available input sources."""
return self._source_list
@property
def volume_level(self):
"""Volume level of the media player (0..1)."""
if self._volume is not None:
return self._volume / 100
else:
return None
@property
def is_volume_muted(self):
"""Boolean if volume is currently muted."""
return self._muted
@property
def supported_media_commands(self):
"""Flag of media commands that are supported."""
return SUPPORT_BRAVIA
@property
def media_title(self):
"""Title of current playing media."""
return_value = None
if self._channel_name is not None:
return_value = self._channel_name
if self._program_name is not None:
return_value = return_value + ': ' + self._program_name
return return_value
@property
def media_content_id(self):
"""Content ID of current playing media."""
return self._channel_name
@property
def media_duration(self):
"""Duration of current playing media in seconds."""
return self._duration
def set_volume_level(self, volume):
"""Set volume level, range 0..1."""
self._braviarc.set_volume_level(volume)
def turn_on(self):
"""Turn the media player on."""
self._braviarc.turn_on()
def turn_off(self):
"""Turn off media player."""
self._braviarc.turn_off()
def volume_up(self):
"""Volume up the media player."""
self._braviarc.volume_up()
def volume_down(self):
"""Volume down media player."""
self._braviarc.volume_down()
def mute_volume(self, mute):
"""Send mute command."""
self._braviarc.mute_volume(mute)
def select_source(self, source):
"""Set the input source."""
if source in self._content_mapping:
uri = self._content_mapping[source]
self._braviarc.play_content(uri)
def media_play_pause(self):
"""Simulate play pause media player."""
if self._playing:
self.media_pause()
else:
self.media_play()
def media_play(self):
"""Send play command."""
self._playing = True
self._braviarc.media_play()
def media_pause(self):
"""Send media pause command to media player."""
self._playing = False
self._braviarc.media_pause()
def media_next_track(self):
"""Send next track command."""
self._braviarc.media_next_track()
def media_previous_track(self):
"""Send the previous track command."""
self._braviarc.media_previous_track()
@@ -11,7 +11,7 @@ from homeassistant.components.media_player import (
MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO, SUPPORT_NEXT_TRACK,
SUPPORT_PAUSE, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK,
SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
MediaPlayerDevice)
SUPPORT_STOP, MediaPlayerDevice)
from homeassistant.const import (
CONF_HOST, STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING,
STATE_UNKNOWN)
@@ -21,7 +21,7 @@ CONF_IGNORE_CEC = 'ignore_cec'
CAST_SPLASH = 'https://home-assistant.io/images/cast/splash.png'
SUPPORT_CAST = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_PREVIOUS_TRACK | \
SUPPORT_NEXT_TRACK | SUPPORT_PLAY_MEDIA
SUPPORT_NEXT_TRACK | SUPPORT_PLAY_MEDIA | SUPPORT_STOP
KNOWN_HOSTS = []
DEFAULT_PORT = 8009
@@ -241,6 +241,10 @@ class CastDevice(MediaPlayerDevice):
"""Send pause command."""
self.cast.media_controller.pause()
def media_stop(self):
"""Send stop command."""
self.cast.media_controller.stop()
def media_previous_track(self):
"""Send previous track command."""
self.cast.media_controller.rewind()
@@ -0,0 +1,213 @@
"""
Support for interacting with and controlling the cmus music player.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.cmus/
"""
import logging
from homeassistant.components.media_player import (
MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE,
SUPPORT_PREVIOUS_TRACK, SUPPORT_TURN_OFF, SUPPORT_TURN_ON,
SUPPORT_VOLUME_SET, SUPPORT_PLAY_MEDIA, SUPPORT_SEEK,
MediaPlayerDevice)
from homeassistant.const import (STATE_OFF, STATE_PAUSED, STATE_PLAYING,
CONF_HOST, CONF_NAME, CONF_PASSWORD,
CONF_PORT)
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['pycmus==0.1.0']
SUPPORT_CMUS = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_TURN_OFF | \
SUPPORT_TURN_ON | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | \
SUPPORT_PLAY_MEDIA | SUPPORT_SEEK
def setup_platform(hass, config, add_devices, discover_info=None):
"""Setup the CMUS platform."""
from pycmus import exceptions
host = config.get(CONF_HOST, None)
password = config.get(CONF_PASSWORD, None)
port = config.get(CONF_PORT, None)
name = config.get(CONF_NAME, None)
if host and not password:
_LOGGER.error("A password must be set if using a remote cmus server")
return False
try:
cmus_remote = CmusDevice(host, password, port, name)
except exceptions.InvalidPassword:
_LOGGER.error("The provided password was rejected by cmus")
return False
add_devices([cmus_remote])
class CmusDevice(MediaPlayerDevice):
"""Representation of a running CMUS."""
# pylint: disable=no-member, too-many-public-methods, abstract-method
def __init__(self, server, password, port, name):
"""Initialize the CMUS device."""
from pycmus import remote
if server:
port = port or 3000
self.cmus = remote.PyCmus(server=server, password=password,
port=port)
auto_name = "cmus-%s" % server
else:
self.cmus = remote.PyCmus()
auto_name = "cmus-local"
self._name = name or auto_name
self.status = {}
self.update()
def update(self):
"""Get the latest data and update the state."""
status = self.cmus.get_status_dict()
if not status:
_LOGGER.warning("Recieved no status from cmus")
else:
self.status = status
@property
def name(self):
"""Return the name of the device."""
return self._name
@property
def state(self):
"""Return the media state."""
if 'status' not in self.status:
self.update()
if self.status['status'] == 'playing':
return STATE_PLAYING
elif self.status['status'] == 'paused':
return STATE_PAUSED
else:
return STATE_OFF
@property
def media_content_id(self):
"""Content ID of current playing media."""
return self.status.get('file')
@property
def content_type(self):
"""Content type of the current playing media."""
return MEDIA_TYPE_MUSIC
@property
def media_duration(self):
"""Duration of current playing media in seconds."""
return self.status.get('duration')
@property
def media_title(self):
"""Title of current playing media."""
return self.status['tag'].get('title')
@property
def media_artist(self):
"""Artist of current playing media, music track only."""
return self.status['tag'].get('artist')
@property
def media_track(self):
"""Track number of current playing media, music track only."""
return self.status['tag'].get('tracknumber')
@property
def media_album_name(self):
"""Album name of current playing media, music track only."""
return self.status['tag'].get('album')
@property
def media_album_artist(self):
"""Album artist of current playing media, music track only."""
return self.status['tag'].get('albumartist')
@property
def volume_level(self):
"""Return the volume level."""
left = self.status['set'].get('vol_left')[0]
right = self.status['set'].get('vol_right')[0]
if left != right:
volume = float(left + right) / 2
else:
volume = left
return int(volume)/100
@property
def supported_media_commands(self):
"""Flag of media commands that are supported."""
return SUPPORT_CMUS
def turn_off(self):
"""Service to send the CMUS the command to stop playing."""
self.cmus.player_stop()
def turn_on(self):
"""Service to send the CMUS the command to start playing."""
self.cmus.player_play()
def set_volume_level(self, volume):
"""Set volume level, range 0..1."""
self.cmus.set_volume(int(volume * 100))
def volume_up(self):
"""Function to send CMUS the command for volume up."""
left = self.status['set'].get('vol_left')
right = self.status['set'].get('vol_right')
if left != right:
current_volume = float(left + right) / 2
else:
current_volume = left
if current_volume <= 100:
self.cmus.set_volume(int(current_volume) + 5)
def volume_down(self):
"""Function to send CMUS the command for volume down."""
left = self.status['set'].get('vol_left')
right = self.status['set'].get('vol_right')
if left != right:
current_volume = float(left + right) / 2
else:
current_volume = left
if current_volume <= 100:
self.cmus.set_volume(int(current_volume) - 5)
def play_media(self, media_type, media_id, **kwargs):
"""Send the play command."""
if media_type in [MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST]:
self.cmus.player_play_file(media_id)
else:
_LOGGER.error(
"Invalid media type %s. Only %s and %s are supported",
media_type, MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST)
def media_pause(self):
"""Send the pause command."""
self.cmus.player_pause()
def media_next_track(self):
"""Send next track command."""
self.cmus.player_next()
def media_previous_track(self):
"""Send next track command."""
self.cmus.player_prev()
def media_seek(self, position):
"""Send seek command."""
self.cmus.seek(position)
def media_play(self):
"""Send the play command."""
self.cmus.player_play()
def media_stop(self):
"""Send the stop command."""
self.cmus.stop()
@@ -15,7 +15,7 @@ from homeassistant.const import (
STATE_PLAYING, STATE_PAUSED, STATE_OFF)
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['websocket-client==0.35.0']
REQUIREMENTS = ['websocket-client==0.37.0']
SUPPORT_GPMDP = SUPPORT_PAUSE | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK
@@ -15,7 +15,7 @@ from homeassistant.const import (
STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING)
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['jsonrpc-requests==0.2']
REQUIREMENTS = ['jsonrpc-requests==0.3']
SUPPORT_KODI = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_SEEK | \
@@ -301,4 +301,4 @@ class KodiDevice(MediaPlayerDevice):
def play_media(self, media_type, media_id, **kwargs):
"""Send the play_media command to the media player."""
self._server.Player.Open({media_type: media_id}, {})
self._server.Player.Open({"item": {"file": str(media_id)}})
@@ -0,0 +1,368 @@
"""
Component for controlling Pandora stations through the pianobar client.
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/media_player.pandora/
"""
import logging
import re
import os
import signal
from datetime import timedelta
import shutil
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.components.media_player import (
SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, MEDIA_TYPE_MUSIC,
SUPPORT_TURN_OFF, SUPPORT_TURN_ON,
SUPPORT_SELECT_SOURCE, SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PLAY_PAUSE,
SERVICE_MEDIA_PLAY, SERVICE_VOLUME_UP, SERVICE_VOLUME_DOWN,
MediaPlayerDevice)
from homeassistant.const import (STATE_OFF, STATE_PAUSED, STATE_PLAYING,
STATE_IDLE)
from homeassistant import util
REQUIREMENTS = ['pexpect==4.0.1']
_LOGGER = logging.getLogger(__name__)
# SUPPORT_VOLUME_SET is close to available but we need volume up/down
# controls in the GUI.
PANDORA_SUPPORT = \
SUPPORT_PAUSE | \
SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_NEXT_TRACK | \
SUPPORT_SELECT_SOURCE
CMD_MAP = {SERVICE_MEDIA_NEXT_TRACK: 'n',
SERVICE_MEDIA_PLAY_PAUSE: 'p',
SERVICE_MEDIA_PLAY: 'p',
SERVICE_VOLUME_UP: ')',
SERVICE_VOLUME_DOWN: '('}
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=2)
CURRENT_SONG_PATTERN = re.compile(r'"(.*?)"\s+by\s+"(.*?)"\son\s+"(.*?)"',
re.MULTILINE)
STATION_PATTERN = re.compile(r'Station\s"(.+?)"', re.MULTILINE)
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the media player pandora platform."""
if not _pianobar_exists():
return False
pandora = PandoraMediaPlayer('Pandora')
# make sure we end the pandora subprocess on exit in case user doesn't
# power it down.
def _stop_pianobar(_event):
pandora.turn_off()
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, _stop_pianobar)
add_devices([pandora])
# pylint: disable=too-many-instance-attributes
class PandoraMediaPlayer(MediaPlayerDevice):
"""A media player that uses the Pianobar interface to Pandora."""
# pylint: disable=abstract-method
def __init__(self, name):
"""Initialize the demo device."""
MediaPlayerDevice.__init__(self)
self._name = name
self._player_state = STATE_OFF
self._station = ''
self._media_title = ''
self._media_artist = ''
self._media_album = ''
self._stations = []
self._time_remaining = 0
self._media_duration = 0
self._pianobar = None
@property
def should_poll(self):
"""Should be polled for current state."""
return True
@property
def name(self):
"""Return the name of the media player."""
return self._name
@property
def state(self):
"""Return the state of the player."""
return self._player_state
def turn_on(self):
"""Turn the media player on."""
import pexpect
if self._player_state != STATE_OFF:
return
self._pianobar = pexpect.spawn('pianobar')
_LOGGER.info('Started pianobar subprocess')
mode = self._pianobar.expect(['Receiving new playlist',
'Select station:',
'Email:'])
if mode == 1:
# station list was presented. dismiss it.
self._pianobar.sendcontrol('m')
elif mode == 2:
_LOGGER.warning('The pianobar client is not configured to log in. '
'Please create a config file for it as described '
'at https://home-assistant.io'
'/components/media_player.pandora/')
# pass through the email/password prompts to quit cleanly
self._pianobar.sendcontrol('m')
self._pianobar.sendcontrol('m')
self._pianobar.terminate()
self._pianobar = None
return
self._update_stations()
self.update_playing_status()
self._player_state = STATE_IDLE
self.update_ha_state()
def turn_off(self):
"""Turn the media player off."""
import pexpect
if self._pianobar is None:
_LOGGER.info('Pianobar subprocess already stopped')
return
self._pianobar.send('q')
try:
_LOGGER.info('Stopped Pianobar subprocess')
self._pianobar.terminate()
except pexpect.exceptions.TIMEOUT:
# kill the process group
os.killpg(os.getpgid(self._pianobar.pid), signal.SIGTERM)
_LOGGER.info('Killed Pianobar subprocess')
self._pianobar = None
self._player_state = STATE_OFF
self.update_ha_state()
def media_play(self):
"""Send play command."""
self._send_pianobar_command(SERVICE_MEDIA_PLAY_PAUSE)
self._player_state = STATE_PLAYING
self.update_ha_state()
def media_pause(self):
"""Send pause command."""
self._send_pianobar_command(SERVICE_MEDIA_PLAY_PAUSE)
self._player_state = STATE_PAUSED
self.update_ha_state()
def media_next_track(self):
"""Go to next track."""
self._send_pianobar_command(SERVICE_MEDIA_NEXT_TRACK)
self.update_ha_state()
@property
def supported_media_commands(self):
"""Show what this supports."""
return PANDORA_SUPPORT
@property
def source(self):
"""Name of the current input source."""
return self._station
@property
def source_list(self):
"""List of available input sources."""
return self._stations
@property
def media_title(self):
"""Title of current playing media."""
self.update_playing_status()
return self._media_title
@property
def media_content_type(self):
"""Content type of current playing media."""
return MEDIA_TYPE_MUSIC
@property
def media_artist(self):
"""Artist of current playing media, music track only."""
return self._media_artist
@property
def media_album_name(self):
"""Album name of current playing media, music track only."""
return self._media_album
@property
def media_duration(self):
"""Duration of current playing media in seconds."""
return self._media_duration
def select_source(self, source):
"""Choose a different Pandora station and play it."""
try:
station_index = self._stations.index(source)
except ValueError:
_LOGGER.warning('Station `%s` is not in list', source)
return
_LOGGER.info('Setting station %s, %d', source, station_index)
self._send_station_list_command()
self._pianobar.sendline('{}'.format(station_index))
self._pianobar.expect('\r\n')
self._player_state = STATE_PLAYING
def _send_station_list_command(self):
"""Send a station list command."""
import pexpect
self._pianobar.send('s')
try:
self._pianobar.expect('Select station:', timeout=1)
except pexpect.exceptions.TIMEOUT:
# try again. Buffer was contaminated.
self._clear_buffer()
self._pianobar.send('s')
self._pianobar.expect('Select station:')
def update_playing_status(self):
"""Query pianobar for info about current media_title, station."""
response = self._query_for_playing_status()
if not response:
return
self._update_current_station(response)
self._update_current_song(response)
self._update_song_position()
def _query_for_playing_status(self):
"""Query system for info about current track."""
import pexpect
self._clear_buffer()
self._pianobar.send('i')
try:
match_idx = self._pianobar.expect([br'(\d\d):(\d\d)/(\d\d):(\d\d)',
'No song playing',
'Select station',
'Receiving new playlist'])
except pexpect.exceptions.EOF:
_LOGGER.info('Pianobar process already exited.')
return None
self._log_match()
if match_idx == 1:
# idle.
response = None
elif match_idx == 2:
# stuck on a station selection dialog. Clear it.
_LOGGER.warning('On unexpected station list page.')
self._pianobar.sendcontrol('m') # press enter
self._pianobar.sendcontrol('m') # do it again b/c an 'i' got in
response = self.update_playing_status()
elif match_idx == 3:
_LOGGER.debug('Received new playlist list.')
response = self.update_playing_status()
else:
response = self._pianobar.before.decode('utf-8')
return response
def _update_current_station(self, response):
"""Update current station."""
station_match = re.search(STATION_PATTERN, response)
if station_match:
self._station = station_match.group(1)
_LOGGER.debug('Got station as: %s', self._station)
else:
_LOGGER.warning('No station match. ')
def _update_current_song(self, response):
"""Update info about current song."""
song_match = re.search(CURRENT_SONG_PATTERN, response)
if song_match:
(self._media_title, self._media_artist,
self._media_album) = song_match.groups()
_LOGGER.debug('Got song as: %s', self._media_title)
else:
_LOGGER.warning('No song match.')
@util.Throttle(MIN_TIME_BETWEEN_UPDATES)
def _update_song_position(self):
"""
Get the song position and duration.
It's hard to predict whether or not the music will start during init
so we have to detect state by checking the ticker.
"""
(cur_minutes, cur_seconds,
total_minutes, total_seconds) = self._pianobar.match.groups()
time_remaining = int(cur_minutes) * 60 + int(cur_seconds)
self._media_duration = int(total_minutes) * 60 + int(total_seconds)
if (time_remaining != self._time_remaining and
time_remaining != self._media_duration):
self._player_state = STATE_PLAYING
elif self._player_state == STATE_PLAYING:
self._player_state = STATE_PAUSED
self._time_remaining = time_remaining
def _log_match(self):
"""Log grabbed values from console."""
_LOGGER.debug('Before: %s\nMatch: %s\nAfter: %s',
repr(self._pianobar.before),
repr(self._pianobar.match),
repr(self._pianobar.after))
def _send_pianobar_command(self, service_cmd):
"""Send a command to Pianobar."""
command = CMD_MAP.get(service_cmd)
_LOGGER.debug('Sending pinaobar command %s for %s',
command, service_cmd)
if command is None:
_LOGGER.info('Command %s not supported yet', service_cmd)
self._clear_buffer()
self._pianobar.sendline(command)
def _update_stations(self):
"""List defined Pandora stations."""
self._send_station_list_command()
station_lines = self._pianobar.before.decode('utf-8')
_LOGGER.debug('Getting stations: %s', station_lines)
self._stations = []
for line in station_lines.split('\r\n'):
match = re.search(r'\d+\).....(.+)', line)
if match:
station = match.group(1).strip()
_LOGGER.debug('Found station %s', station)
self._stations.append(station)
else:
_LOGGER.debug('No station match on `%s`', line)
self._pianobar.sendcontrol('m') # press enter with blank line
self._pianobar.sendcontrol('m') # do it twice in case an 'i' got in
def _clear_buffer(self):
"""
Clear buffer from pexpect.
This is necessary because there are a bunch of 00:00 in the buffer
"""
import pexpect
try:
while not self._pianobar.expect('.+', timeout=0.1):
pass
except pexpect.exceptions.TIMEOUT:
pass
def _pianobar_exists():
"""Verify that Pianobar is properly installed."""
pianobar_exe = shutil.which('pianobar')
if pianobar_exe:
return True
else:
_LOGGER.warning('The Pandora component depends on the Pianobar '
'client, which cannot be found. Please install '
'using instructions at'
'https://home-assistant.io'
'/components/media_player.pandora/')
return False
@@ -4,7 +4,6 @@ Support for the roku media player.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.roku/
"""
import logging
from homeassistant.components.media_player import (
@@ -77,7 +76,8 @@ class RokuDevice(MediaPlayerDevice):
self.current_app = self.roku.current_app
else:
self.current_app = None
except requests.exceptions.ConnectionError:
except (requests.exceptions.ConnectionError,
requests.exceptions.ReadTimeout):
self.current_app = None
def get_source_list(self):
@@ -72,7 +72,7 @@ class SamsungTVDevice(MediaPlayerDevice):
def update(self):
"""Retrieve the latest data."""
# Send an empty key to see if we are still connected
return self.send_key('KEY_POWER')
return self.send_key('KEY')
def get_remote(self):
"""Create or return a remote control instance."""
@@ -153,3 +153,27 @@ sonos_group_players:
entity_id:
description: Name(s) of entites that will coordinate the grouping. Platform dependent.
example: 'media_player.living_room_sonos'
sonos_unjoin:
description: Unjoin the player from a group.
fields:
entity_id:
description: Name(s) of entites that will be unjoined from their group. Platform dependent.
example: 'media_player.living_room_sonos'
sonos_snapshot:
description: Take a snapshot of the media player.
fields:
entity_id:
description: Name(s) of entites that will be snapshot. Platform dependent.
example: 'media_player.living_room_sonos'
sonos_restore:
description: Restore a snapshot of the media player.
fields:
entity_id:
description: Name(s) of entites that will be restored. Platform dependent.
example: 'media_player.living_room_sonos'
@@ -4,7 +4,6 @@ Support for interacting with Snapcast clients.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.snapcast/
"""
import logging
import socket
+52 -4
View File
@@ -34,9 +34,12 @@ SUPPORT_SONOS = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE |\
SUPPORT_SEEK
SERVICE_GROUP_PLAYERS = 'sonos_group_players'
SERVICE_UNJOIN = 'sonos_unjoin'
SERVICE_SNAPSHOT = 'sonos_snapshot'
SERVICE_RESTORE = 'sonos_restore'
# pylint: disable=unused-argument
# pylint: disable=unused-argument, too-many-locals
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Sonos platform."""
import soco
@@ -70,8 +73,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
add_devices(devices)
_LOGGER.info('Added %s Sonos speakers', len(players))
def group_players_service(service):
"""Group media players, use player as coordinator."""
def _apply_service(service, service_func, *service_func_args):
"""Internal func for applying a service."""
entity_id = service.data.get('entity_id')
if entity_id:
@@ -81,9 +84,25 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
_devices = devices
for device in _devices:
device.group_players()
service_func(device, *service_func_args)
device.update_ha_state(True)
def group_players_service(service):
"""Group media players, use player as coordinator."""
_apply_service(service, SonosDevice.group_players)
def unjoin_service(service):
"""Unjoin the player from a group."""
_apply_service(service, SonosDevice.unjoin)
def snapshot_service(service):
"""Take a snapshot."""
_apply_service(service, SonosDevice.snapshot)
def restore_service(service):
"""Restore a snapshot."""
_apply_service(service, SonosDevice.restore)
descriptions = load_yaml_config_file(
path.join(path.dirname(__file__), 'services.yaml'))
@@ -91,6 +110,18 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
group_players_service,
descriptions.get(SERVICE_GROUP_PLAYERS))
hass.services.register(DOMAIN, SERVICE_UNJOIN,
unjoin_service,
descriptions.get(SERVICE_UNJOIN))
hass.services.register(DOMAIN, SERVICE_SNAPSHOT,
snapshot_service,
descriptions.get(SERVICE_SNAPSHOT))
hass.services.register(DOMAIN, SERVICE_RESTORE,
restore_service,
descriptions.get(SERVICE_RESTORE))
return True
@@ -136,6 +167,8 @@ class SonosDevice(MediaPlayerDevice):
super(SonosDevice, self).__init__()
self._player = player
self.update()
from soco.snapshot import Snapshot
self.soco_snapshot = Snapshot(self._player)
@property
def should_poll(self):
@@ -315,6 +348,21 @@ class SonosDevice(MediaPlayerDevice):
"""Group all players under this coordinator."""
self._player.partymode()
@only_if_coordinator
def unjoin(self):
"""Unjoin the player from a group."""
self._player.unjoin()
@only_if_coordinator
def snapshot(self):
"""Snapshot the player."""
self.soco_snapshot.snapshot()
@only_if_coordinator
def restore(self):
"""Restore snapshot for the player."""
self.soco_snapshot.restore(True)
@property
def available(self):
"""Return True if player is reachable, False otherwise."""
@@ -4,7 +4,6 @@ Combination of multiple media players into one for a universal controller.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.universal/
"""
import logging
# pylint: disable=import-error
from copy import copy
+1 -1
View File
@@ -29,7 +29,7 @@ MQTT_CLIENT = None
SERVICE_PUBLISH = 'publish'
EVENT_MQTT_MESSAGE_RECEIVED = 'mqtt_message_received'
REQUIREMENTS = ['paho-mqtt==1.1']
REQUIREMENTS = ['paho-mqtt==1.2']
CONF_EMBEDDED = 'embedded'
CONF_BROKER = 'broker'
+5 -27
View File
@@ -7,14 +7,11 @@ https://home-assistant.io/components/sensor.mysensors/
import logging
import socket
import homeassistant.bootstrap as bootstrap
from homeassistant.const import (ATTR_BATTERY_LEVEL, ATTR_DISCOVERED,
ATTR_SERVICE, CONF_OPTIMISTIC,
from homeassistant.const import (ATTR_BATTERY_LEVEL, CONF_OPTIMISTIC,
EVENT_HOMEASSISTANT_START,
EVENT_HOMEASSISTANT_STOP,
EVENT_PLATFORM_DISCOVERED, STATE_OFF,
STATE_ON, TEMP_CELSIUS)
from homeassistant.helpers import validate_config
STATE_OFF, STATE_ON, TEMP_CELSIUS)
from homeassistant.helpers import validate_config, discovery
CONF_GATEWAYS = 'gateways'
CONF_DEVICE = 'device'
@@ -40,19 +37,6 @@ ATTR_DEVICE = 'device'
GATEWAYS = None
DISCOVER_SENSORS = 'mysensors.sensors'
DISCOVER_SWITCHES = 'mysensors.switches'
DISCOVER_LIGHTS = 'mysensors.lights'
DISCOVER_BINARY_SENSORS = 'mysensors.binary_sensor'
# Maps discovered services to their platforms
DISCOVERY_COMPONENTS = [
('sensor', DISCOVER_SENSORS),
('switch', DISCOVER_SWITCHES),
('light', DISCOVER_LIGHTS),
('binary_sensor', DISCOVER_BINARY_SENSORS),
]
def setup(hass, config): # pylint: disable=too-many-locals
"""Setup the MySensors component."""
@@ -124,14 +108,8 @@ def setup(hass, config): # pylint: disable=too-many-locals
GATEWAYS[device] = setup_gateway(
device, persistence_file, baud_rate, tcp_port)
for (component, discovery_service) in DISCOVERY_COMPONENTS:
# Ensure component is loaded
if not bootstrap.setup_component(hass, component, config):
return False
# Fire discovery event
hass.bus.fire(EVENT_PLATFORM_DISCOVERED, {
ATTR_SERVICE: discovery_service,
ATTR_DISCOVERED: {}})
for component in 'sensor', 'switch', 'light', 'binary_sensor':
discovery.load_platform(hass, component, DOMAIN, {}, config)
return True
+56
View File
@@ -0,0 +1,56 @@
"""
Support for the Netatmo devices (Weather Station and Welcome camera).
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/netatmo/
"""
import logging
from urllib.error import HTTPError
from homeassistant.const import (
CONF_API_KEY, CONF_PASSWORD, CONF_USERNAME)
from homeassistant.helpers import validate_config, discovery
REQUIREMENTS = [
'https://github.com/jabesq/netatmo-api-python/archive/'
'v0.5.0.zip#lnetatmo==0.5.0']
_LOGGER = logging.getLogger(__name__)
CONF_SECRET_KEY = 'secret_key'
DOMAIN = "netatmo"
NETATMO_AUTH = None
_LOGGER = logging.getLogger(__name__)
def setup(hass, config):
"""Setup the Netatmo devices."""
if not validate_config(config,
{DOMAIN: [CONF_API_KEY,
CONF_USERNAME,
CONF_PASSWORD,
CONF_SECRET_KEY]},
_LOGGER):
return None
import lnetatmo
global NETATMO_AUTH
try:
NETATMO_AUTH = lnetatmo.ClientAuth(config[DOMAIN][CONF_API_KEY],
config[DOMAIN][CONF_SECRET_KEY],
config[DOMAIN][CONF_USERNAME],
config[DOMAIN][CONF_PASSWORD],
"read_station read_camera "
"access_camera")
except HTTPError:
_LOGGER.error(
"Connection error "
"Please check your settings for NatAtmo API.")
return False
for component in 'camera', 'sensor':
discovery.load_platform(hass, component, DOMAIN, {}, config)
return True
+13 -2
View File
@@ -7,7 +7,7 @@ https://home-assistant.io/components/notify.pushover/
import logging
from homeassistant.components.notify import (
ATTR_TITLE, DOMAIN, BaseNotificationService)
ATTR_TITLE, ATTR_TARGET, ATTR_DATA, DOMAIN, BaseNotificationService)
from homeassistant.const import CONF_API_KEY
from homeassistant.helpers import validate_config
@@ -51,7 +51,18 @@ class PushoverNotificationService(BaseNotificationService):
"""Send a message to a user."""
from pushover import RequestError
# Make a copy and use empty dict if necessary
data = dict(kwargs.get(ATTR_DATA) or {})
data['title'] = kwargs.get(ATTR_TITLE)
target = kwargs.get(ATTR_TARGET)
if target is not None:
data['device'] = target
try:
self.pushover.send_message(message, title=kwargs.get(ATTR_TITLE))
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")
+1 -1
View File
@@ -10,7 +10,7 @@ from homeassistant.components.notify import DOMAIN, BaseNotificationService
from homeassistant.const import CONF_API_KEY
from homeassistant.helpers import validate_config
REQUIREMENTS = ['slacker==0.9.16']
REQUIREMENTS = ['slacker==0.9.17']
_LOGGER = logging.getLogger(__name__)
+1 -1
View File
@@ -14,7 +14,7 @@ from homeassistant.helpers import validate_config
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['python-telegram-bot==4.2.0']
REQUIREMENTS = ['python-telegram-bot==4.2.1']
def get_service(hass, config):
+1 -2
View File
@@ -9,9 +9,8 @@ import logging
import time
import requests
from homeassistant.components import discovery
from homeassistant.const import CONF_API_KEY, CONF_HOST
from homeassistant.helpers import validate_config
from homeassistant.helpers import validate_config, discovery
DOMAIN = "octoprint"
OCTOPRINT = None
@@ -0,0 +1,80 @@
"""
A component which is collecting configuration errors.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/persistent_notification/
"""
import logging
import voluptuous as vol
from homeassistant.exceptions import TemplateError
from homeassistant.helpers import template, config_validation as cv
from homeassistant.helpers.entity import generate_entity_id
from homeassistant.util import slugify
DOMAIN = 'persistent_notification'
ENTITY_ID_FORMAT = DOMAIN + '.{}'
SERVICE_CREATE = 'create'
ATTR_TITLE = 'title'
ATTR_MESSAGE = 'message'
ATTR_NOTIFICATION_ID = 'notification_id'
SCHEMA_SERVICE_CREATE = vol.Schema({
vol.Required(ATTR_MESSAGE): cv.template,
vol.Optional(ATTR_TITLE): cv.template,
vol.Optional(ATTR_NOTIFICATION_ID): cv.string,
})
DEFAULT_OBJECT_ID = 'notification'
_LOGGER = logging.getLogger(__name__)
def create(hass, message, title=None, notification_id=None):
"""Turn all or specified light off."""
data = {
key: value for key, value in [
(ATTR_TITLE, title),
(ATTR_MESSAGE, message),
(ATTR_NOTIFICATION_ID, notification_id),
] if value is not None
}
hass.services.call(DOMAIN, SERVICE_CREATE, data)
def setup(hass, config):
"""Setup the persistent notification component."""
def create_service(call):
"""Handle a create notification service call."""
title = call.data.get(ATTR_TITLE)
message = call.data.get(ATTR_MESSAGE)
notification_id = call.data.get(ATTR_NOTIFICATION_ID)
if notification_id is not None:
entity_id = ENTITY_ID_FORMAT.format(slugify(notification_id))
else:
entity_id = generate_entity_id(ENTITY_ID_FORMAT, DEFAULT_OBJECT_ID,
hass=hass)
attr = {}
if title is not None:
try:
title = template.render(hass, title)
except TemplateError as ex:
_LOGGER.error('Error rendering title %s: %s', title, ex)
attr[ATTR_TITLE] = title
try:
message = template.render(hass, message)
except TemplateError as ex:
_LOGGER.error('Error rendering message %s: %s', message, ex)
hass.states.set(entity_id, message, attr)
hass.services.register(DOMAIN, SERVICE_CREATE, create_service, {},
SCHEMA_SERVICE_CREATE)
return True
@@ -29,9 +29,6 @@ ENTITY_ID_ALL_ROLLERSHUTTERS = group.ENTITY_ID_FORMAT.format(
ENTITY_ID_FORMAT = DOMAIN + '.{}'
# Maps discovered services to their platforms
DISCOVERY_PLATFORMS = {}
_LOGGER = logging.getLogger(__name__)
ATTR_CURRENT_POSITION = 'current_position'
@@ -68,8 +65,7 @@ def stop(hass, entity_id=None):
def setup(hass, config):
"""Track states and offer events for roller shutters."""
component = EntityComponent(
_LOGGER, DOMAIN, hass, SCAN_INTERVAL, DISCOVERY_PLATFORMS,
GROUP_NAME_ALL_ROLLERSHUTTERS)
_LOGGER, DOMAIN, hass, SCAN_INTERVAL, GROUP_NAME_ALL_ROLLERSHUTTERS)
component.setup(config)
def handle_rollershutter_service(service):
@@ -0,0 +1,101 @@
"""
The homematic rollershutter platform.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/rollershutter.homematic/
Important: For this platform to work the homematic component has to be
properly configured.
"""
import logging
from homeassistant.const import (STATE_OPEN, STATE_CLOSED, STATE_UNKNOWN)
from homeassistant.components.rollershutter import RollershutterDevice,\
ATTR_CURRENT_POSITION
import homeassistant.components.homematic as homematic
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['homematic']
def setup_platform(hass, config, add_callback_devices, discovery_info=None):
"""Setup the platform."""
if discovery_info is None:
return
return homematic.setup_hmdevice_discovery_helper(HMRollershutter,
discovery_info,
add_callback_devices)
class HMRollershutter(homematic.HMDevice, RollershutterDevice):
"""Represents a Homematic Rollershutter in Home Assistant."""
@property
def current_position(self):
"""
Return current position of rollershutter.
None is unknown, 0 is closed, 100 is fully open.
"""
if self.available:
return int((1 - self._hm_get_state()) * 100)
return None
def position(self, **kwargs):
"""Move to a defined position: 0 (closed) and 100 (open)."""
if self.available:
if ATTR_CURRENT_POSITION in kwargs:
position = float(kwargs[ATTR_CURRENT_POSITION])
position = min(100, max(0, position))
level = (100 - position) / 100.0
self._hmdevice.set_level(level, self._channel)
@property
def state(self):
"""Return the state of the rollershutter."""
current = self.current_position
if current is None:
return STATE_UNKNOWN
return STATE_CLOSED if current == 100 else STATE_OPEN
def move_up(self, **kwargs):
"""Move the rollershutter up."""
if self.available:
self._hmdevice.move_up(self._channel)
def move_down(self, **kwargs):
"""Move the rollershutter down."""
if self.available:
self._hmdevice.move_down(self._channel)
def stop(self, **kwargs):
"""Stop the device if in motion."""
if self.available:
self._hmdevice.stop(self._channel)
def _check_hm_to_ha_object(self):
"""Check if possible to use the HM Object as this HA type."""
from pyhomematic.devicetypes.actors import Blind
# Check compatibility from HMDevice
if not super()._check_hm_to_ha_object():
return False
# Check if the homematic device is correct for this HA device
if isinstance(self._hmdevice, Blind):
return True
_LOGGER.critical("This %s can't be use as rollershutter!", self._name)
return False
def _init_data_struct(self):
"""Generate a data dict (self._data) from hm metadata."""
super()._init_data_struct()
# Add state to data dict
self._state = "LEVEL"
self._data.update({self._state: STATE_UNKNOWN})
@@ -0,0 +1,73 @@
"""
Support for Wink Shades.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/rollershutter.wink/
"""
import logging
from homeassistant.components.rollershutter import RollershutterDevice
from homeassistant.components.wink import WinkDevice
from homeassistant.const import CONF_ACCESS_TOKEN
REQUIREMENTS = ['python-wink==0.7.8', 'pubnub==3.7.8']
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Wink rollershutter platform."""
import pywink
if discovery_info is None:
token = config.get(CONF_ACCESS_TOKEN)
if token is None:
logging.getLogger(__name__).error(
"Missing wink access_token. "
"Get one at https://winkbearertoken.appspot.com/")
return
pywink.set_bearer_token(token)
add_devices(WinkRollershutterDevice(shade) for shade in
pywink.get_shades())
class WinkRollershutterDevice(WinkDevice, RollershutterDevice):
"""Representation of a Wink rollershutter (shades)."""
def __init__(self, wink):
"""Initialize the rollershutter."""
WinkDevice.__init__(self, wink)
@property
def should_poll(self):
"""Wink Shades don't track their position."""
return False
def move_down(self):
"""Close the shade."""
self.wink.set_state(0)
def move_up(self):
"""Open the shade."""
self.wink.set_state(1)
@property
def current_position(self):
"""Return current position of roller shutter.
Wink reports blind shade positions as 0 or 1.
home-assistant expects:
None is unknown, 0 is closed, 100 is fully open.
"""
state = self.wink.state()
if state == 0:
return 0
elif state == 1:
return 100
else:
return None
def stop(self):
"""Can't stop Wink rollershutter due to API."""
pass
@@ -0,0 +1,76 @@
"""
Support for Zwave roller shutter components.
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/rollershutter.zwave/
"""
# Because we do not compile openzwave on CI
# pylint: disable=import-error
import logging
from homeassistant.components.rollershutter import DOMAIN
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
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Find and return Z-Wave roller shutters."""
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]]
if value.command_class != zwave.COMMAND_CLASS_SWITCH_MULTILEVEL:
return
if value.index != 0:
return
value.set_change_verified(False)
add_devices([ZwaveRollershutter(value)])
class ZwaveRollershutter(zwave.ZWaveDeviceEntity, RollershutterDevice):
"""Representation of an Zwave roller shutter."""
def __init__(self, value):
"""Initialize the zwave rollershutter."""
from openzwave.network import ZWaveNetwork
from pydispatch import dispatcher
ZWaveDeviceEntity.__init__(self, value, DOMAIN)
self._node = value.node
dispatcher.connect(
self.value_changed, ZWaveNetwork.SIGNAL_VALUE_CHANGED)
def value_changed(self, value):
"""Called when a value has changed on the network."""
if self._value.value_id == value.value_id:
self.update_ha_state(True)
_LOGGER.debug("Value changed on network %s", value)
@property
def current_position(self):
"""Return the current position of Zwave roller shutter."""
return self._value.data
def move_up(self, **kwargs):
"""Move the roller shutter up."""
self._node.set_dimmer(self._value.value_id, 100)
def move_down(self, **kwargs):
"""Move the roller shutter down."""
self._node.set_dimmer(self._value.value_id, 0)
def stop(self, **kwargs):
"""Stop the roller shutter."""
for value in self._node.get_values(
class_id=COMMAND_CLASS_SWITCH_BINARY).values():
# Rollershutter will toggle between UP (True), DOWN (False).
# It also stops the shutter if the same value is sent while moving.
value.data = value.data
break
+1 -18
View File
@@ -8,34 +8,17 @@ import logging
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
from homeassistant.components import (
wink, zwave, isy994, verisure, ecobee, tellduslive, mysensors,
bloomsky, vera)
DOMAIN = 'sensor'
SCAN_INTERVAL = 30
ENTITY_ID_FORMAT = DOMAIN + '.{}'
# Maps discovered services to their platforms
DISCOVERY_PLATFORMS = {
bloomsky.DISCOVER_SENSORS: 'bloomsky',
wink.DISCOVER_SENSORS: 'wink',
zwave.DISCOVER_SENSORS: 'zwave',
isy994.DISCOVER_SENSORS: 'isy994',
verisure.DISCOVER_SENSORS: 'verisure',
ecobee.DISCOVER_SENSORS: 'ecobee',
tellduslive.DISCOVER_SENSORS: 'tellduslive',
mysensors.DISCOVER_SENSORS: 'mysensors',
vera.DISCOVER_SENSORS: 'vera',
}
def setup(hass, config):
"""Track states and offer events for sensors."""
component = EntityComponent(
logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL,
DISCOVERY_PLATFORMS)
logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL)
component.setup(config)
+5 -3
View File
@@ -17,16 +17,18 @@ SENSOR_TYPES = ["Temperature",
"Humidity",
"Pressure",
"Luminance",
"UVIndex"]
"UVIndex",
"Voltage"]
# Sensor units - these do not currently align with the API documentation
SENSOR_UNITS = {"Temperature": TEMP_FAHRENHEIT,
"Humidity": "%",
"Pressure": "inHg",
"Luminance": "cd/m²"}
"Luminance": "cd/m²",
"Voltage": "mV"}
# Which sensors to format numerically
FORMAT_NUMBERS = ["Temperature", "Pressure"]
FORMAT_NUMBERS = ["Temperature", "Pressure", "Voltage"]
# pylint: disable=unused-argument
@@ -1,38 +1,43 @@
"""
Support for information about the German trans system.
Support for information about the German train system.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.deutsche_bahn/
"""
import logging
from datetime import timedelta, datetime
from datetime import timedelta
import voluptuous as vol
from homeassistant.const import (CONF_PLATFORM)
import homeassistant.helpers.config_validation as cv
from homeassistant.util import Throttle
from homeassistant.helpers.entity import Entity
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['schiene==0.17']
CONF_START = 'from'
CONF_DESTINATION = 'to'
ICON = 'mdi:train'
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = vol.Schema({
vol.Required(CONF_PLATFORM): 'deutsche_bahn',
vol.Required(CONF_START): cv.string,
vol.Required(CONF_DESTINATION): cv.string,
})
# Return cached results if last scan was less then this time ago.
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=120)
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Deutsche Bahn Sensor."""
start = config.get('from')
goal = config.get('to')
start = config.get(CONF_START)
destination = config.get(CONF_DESTINATION)
if start is None:
_LOGGER.error('Missing required variable: "from"')
return False
if goal is None:
_LOGGER.error('Missing required variable: "to"')
return False
dev = []
dev.append(DeutscheBahnSensor(start, goal))
add_devices_callback(dev)
add_devices([DeutscheBahnSensor(start, destination)])
# pylint: disable=too-few-public-methods
@@ -63,16 +68,17 @@ class DeutscheBahnSensor(Entity):
@property
def state_attributes(self):
"""Return the state attributes."""
return self.data.connections[0]
connections = self.data.connections[0]
connections['next'] = self.data.connections[1]['departure']
connections['next_on'] = self.data.connections[2]['departure']
return connections
def update(self):
"""Get the latest delay from bahn.de and updates the state."""
self.data.update()
self._state = self.data.connections[0].get('departure', 'Unknown')
if self.data.connections[0]['delay'] != 0:
self._state += " + {}".format(
self.data.connections[0]['delay']
)
self._state += " + {}".format(self.data.connections[0]['delay'])
# pylint: disable=too-few-public-methods
@@ -90,18 +96,15 @@ class SchieneData(object):
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Update the connection data."""
self.connections = self.schiene.connections(self.start,
self.goal,
datetime.now())
self.connections = self.schiene.connections(self.start, self.goal)
for con in self.connections:
# Details info is not useful.
# Having a more consistent interface simplifies
# usage of Template sensors later on
# Detail info is not useful. Having a more consistent interface
# simplifies usage of template sensors.
if 'details' in con:
con.pop('details')
delay = con.get('delay',
{'delay_departure': 0,
'delay_arrival': 0})
# IMHO only delay_departure is usefull
delay = con.get('delay', {'delay_departure': 0,
'delay_arrival': 0})
# IMHO only delay_departure is useful
con['delay'] = delay['delay_departure']
con['ontime'] = con.get('ontime', False)
@@ -0,0 +1,68 @@
"""
Support for Envisalink sensors (shows panel info).
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.envisalink/
"""
import logging
from homeassistant.components.envisalink import (EVL_CONTROLLER,
PARTITION_SCHEMA,
CONF_PARTITIONNAME,
EnvisalinkDevice,
SIGNAL_KEYPAD_UPDATE)
DEPENDENCIES = ['envisalink']
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Perform the setup for Envisalink sensor devices."""
_configured_partitions = discovery_info['partitions']
for part_num in _configured_partitions:
_device_config_data = PARTITION_SCHEMA(
_configured_partitions[part_num])
_device = EnvisalinkSensor(
_device_config_data[CONF_PARTITIONNAME],
part_num,
EVL_CONTROLLER.alarm_state['partition'][part_num],
EVL_CONTROLLER)
add_devices_callback([_device])
class EnvisalinkSensor(EnvisalinkDevice):
"""Representation of an envisalink keypad."""
def __init__(self, partition_name, partition_number, info, controller):
"""Initialize the sensor."""
from pydispatch import dispatcher
self._icon = 'mdi:alarm'
self._partition_number = partition_number
_LOGGER.debug('Setting up sensor for partition: ' + partition_name)
EnvisalinkDevice.__init__(self,
partition_name + ' Keypad',
info,
controller)
dispatcher.connect(self._update_callback,
signal=SIGNAL_KEYPAD_UPDATE,
sender=dispatcher.Any)
@property
def icon(self):
"""Return the icon if any."""
return self._icon
@property
def state(self):
"""Return the overall state."""
return self._info['status']['alpha']
@property
def device_state_attributes(self):
"""Return the state attributes."""
return self._info['status']
def _update_callback(self, partition):
"""Update the partition state in HA, if needed."""
if partition is None or int(partition) == self._partition_number:
self.update_ha_state()

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