Compare commits

...

851 Commits

Author SHA1 Message Date
Paulus Schoutsen ab48a94d2a Version bump to 0.7.7.dev0 2015-10-26 21:32:07 -07:00
Paulus Schoutsen 55c0ee6b32 Version bump to 0.7.6 2015-10-26 21:27:50 -07:00
Paulus Schoutsen 6b881ce1cd Merge pull request #547 from krzynio/dev
Add new OpenWRT presence detection routine based on ubus instead of luci
2015-10-26 21:26:13 -07:00
Paulus Schoutsen 9ab9d0e383 Update netdisco requirement 2015-10-26 20:49:20 -07:00
pavoni dbc05450a0 Bump requirements_all.txt version, remove pylint disable 2015-10-26 16:32:12 +00:00
Krzysztof Koziarek 649124d162 Added ubus.py to .coveragerc 2015-10-26 11:55:20 +01:00
Krzysztof Koziarek fbb73dd5da fixed pylint warnings 2015-10-26 11:50:09 +01:00
Krzysztof Koziarek c9f1dce6a2 Coding style fixes 2015-10-26 11:32:00 +01:00
pavoni ef6c209c6f Revise for clarity, disable pylink check 2015-10-26 09:03:57 +00:00
pavoni 0826ae2742 Revise pywemo version, update discovery.device_from_description parameters 2015-10-26 08:37:13 +00:00
Paulus Schoutsen 18747f8ae1 Update some docs 2015-10-25 23:12:10 -07:00
Paulus Schoutsen 06c8c1b168 Update to latest version frontend 2015-10-25 23:12:10 -07:00
Paulus Schoutsen 160fb6fcc8 Merge pull request #553 from balloob/download_relative
Download - relative / absolute path
2015-10-25 21:06:33 -07:00
Paulus Schoutsen f1aa685cf2 Add version to config API 2015-10-25 21:00:22 -07:00
Paulus Schoutsen da259d75a2 Update frontend with imprpved charts 2015-10-25 19:01:51 -07:00
Tom Duijf 9104ca815d Indentation 2015-10-26 00:13:47 +00:00
Tom Duijf 52193611cd Check for relative path 2015-10-26 00:10:32 +00:00
Paulus Schoutsen a2256f6c97 Update version frontend 2015-10-25 15:39:50 -07:00
Paulus Schoutsen 004bad7f00 Merge pull request #551 from balloob/mp_plex_discovery
Media_player/plex discovery
2015-10-25 15:38:42 -07:00
Paulus Schoutsen e490388843 Merge pull request #544 from MakeMeASandwich/hyperion
Hyperion ambilight remote support
2015-10-25 14:34:22 -07:00
Fabian Affolter fb8edca942 Add link to docs, fix typo, and update log output 2015-10-25 22:21:25 +01:00
Tom Duijf bc8c5766d4 Logic fixes 2015-10-25 17:54:48 +00:00
Tom Duijf 5b25d9ccd6 flake8,pylint and other code cleanup 2015-10-25 17:00:54 +00:00
Fabian Affolter 0d05930765 Update 2015-10-25 15:58:58 +01:00
Fabian Affolter f93282d636 Add link to docs 2015-10-25 15:58:58 +01:00
Fabian Affolter 78ad2686d4 Add link to docs 2015-10-25 15:58:58 +01:00
Fabian Affolter 3c36d13e8d Add link to docs 2015-10-25 15:58:58 +01:00
Fabian Affolter 77ba0c0393 Add link to docs 2015-10-25 15:58:58 +01:00
Fabian Affolter 6a3316ed12 Add link to docs 2015-10-25 15:58:58 +01:00
Fabian Affolter 415d650860 Add link to docs 2015-10-25 15:58:58 +01:00
Fabian Affolter c3c248bc0a Add link to docs 2015-10-25 15:58:58 +01:00
Fabian Affolter bf027fcd48 Add link to docs 2015-10-25 15:58:58 +01:00
Fabian Affolter 5c79fc0ae3 Add link to docs 2015-10-25 15:58:58 +01:00
Fabian Affolter 0aaf280bf5 Add link to docs 2015-10-25 15:58:58 +01:00
Fabian Affolter 55de563511 Add link to docs 2015-10-25 15:58:58 +01:00
Fabian Affolter 1a018e3ee7 Remove configuration details 2015-10-25 15:58:58 +01:00
Fabian Affolter b023348795 Add link to docs 2015-10-25 15:58:58 +01:00
Tom Duijf 847d9736aa Configurator works, config saving basic implementation 2015-10-25 13:34:34 +00:00
Tom Duijf 884525df33 Basic discovery works, added plex logo for configurator. Missing configurator support for fields. Todo: config save on successful connect 2015-10-25 13:34:34 +00:00
Tom Duijf 6a82504e5e further discovery integration into plex 2015-10-25 13:34:34 +00:00
Tom Duijf db7e46abd1 Intermediate save 2015-10-25 13:33:47 +00:00
Tom Duijf 8e9cafd29d Updated requirements_all.txt. Added placeholder to the empty deviceClass filter. Will remove this if deemed unneeded, later 2015-10-25 13:32:15 +00:00
Tom Duijf a236b87ccf new attempt for PR 2015-10-25 13:32:15 +00:00
MakeMeASandwich e379e3d902 Merge branch 'dev' of https://github.com/balloob/home-assistant into hyperion 2015-10-25 11:29:02 +01:00
MakeMeASandwich 1be48f54c0 light/hyperion: close sockets, report setup success 2015-10-25 11:08:59 +01:00
Paulus Schoutsen 96181a555a Allow pipes in command sensors and services 2015-10-24 12:40:36 -07:00
Paulus Schoutsen e461ceae36 discovery: update Netdisco requirement 2015-10-24 12:18:48 -07:00
Paulus Schoutsen 83e6c24f18 Re-enable Z-Wave for Docker 2015-10-24 11:36:34 -07:00
Krzysztof Koziarek 50fbd83b3d corrected flake8 warnings 2015-10-24 11:20:57 +02:00
Fabian Affolter 649275044a Remove configuration details 2015-10-24 09:13:54 +02:00
Fabian Affolter 0f81fc60ad Remove configuration details 2015-10-24 09:10:31 +02:00
Paulus Schoutsen ade8681511 Exclude rest switch from coverage 2015-10-23 23:44:17 -07:00
Paulus Schoutsen 7a699fd819 Merge pull request #539 from nkgilley/forecastio-units
Forecastio: Added support for specifying unit system in the configuration file.
2015-10-23 22:54:31 -07:00
Paulus Schoutsen b0c0659acc Merge branch 'clean-up-heat-control' into dev
Conflicts:
	homeassistant/components/thermostat/heat_control.py
2015-10-23 22:51:00 -07:00
Paulus Schoutsen 80a9422a9a Merge pull request #545 from MakeMeASandwich/denon
Refactor denon remote
2015-10-23 22:40:37 -07:00
Fabian Affolter 060cbaf66b Add return value 2015-10-24 00:38:19 +02:00
Fabian Affolter c19120e012 Check import 2015-10-24 00:34:49 +02:00
Fabian Affolter 1e0e48fcd7 Use logger the same as other platforms do 2015-10-24 00:29:47 +02:00
Fabian Affolter f2fda2914a Fix continuation 2015-10-24 00:29:02 +02:00
Fabian Affolter 7e3483ab03 Remove configuration details 2015-10-24 00:24:14 +02:00
Fabian Affolter bffce11a9a Remove configuration details 2015-10-24 00:20:18 +02:00
Fabian Affolter 8a895390ef Merge pull request #534 from bachp/dev
Add simple REST switch
2015-10-23 23:52:40 +02:00
Fabian Affolter 97f81ad7a6 Add more details 2015-10-23 23:48:57 +02:00
Pascal Bach 67d5581a1b Add simple REST switch
The switch can get the state via GET and set the state via POST
on a given REST resource.
It is not able to control arbitrary urls but it allows controlling
switches exposed via "real" REST.
2015-10-23 23:40:14 +02:00
Fabian Affolter 3406b41b0c Fix return value 2015-10-23 23:08:27 +02:00
Fabian Affolter 2e7912157b Remove configuration details 2015-10-23 23:00:20 +02:00
Fabian Affolter 5dbdf82ec7 Fix typo 2015-10-23 22:58:00 +02:00
Fabian Affolter 9f4a3f4aea Use the logger the same way as other platforms do 2015-10-23 22:57:07 +02:00
Fabian Affolter 4f3b3a9e34 Use the logger the same way as other platforms 2015-10-23 22:53:58 +02:00
Fabian Affolter 3ea167203f Remove configuration details 2015-10-23 22:48:30 +02:00
Fabian Affolter 756cbe1b08 Remove configuration details 2015-10-23 22:41:13 +02:00
Fabian Affolter 87e55820e7 Add link docs 2015-10-23 22:39:04 +02:00
Fabian Affolter f828ee044d UPdate docstring 2015-10-23 22:34:23 +02:00
Fabian Affolter a8e2f9cbb7 Remove configuration details 2015-10-23 22:34:02 +02:00
Fabian Affolter 2e3f462474 Update docstring 2015-10-23 22:32:36 +02:00
Fabian Affolter 3f6780d9be Remove configuration details 2015-10-23 22:31:37 +02:00
Fabian Affolter e21921823e Update docstring 2015-10-23 22:29:22 +02:00
Fabian Affolter f9b2e0058e Fix typo 2015-10-23 19:01:38 +02:00
Fabian Affolter a155587693 Remove configuration details 2015-10-23 19:01:19 +02:00
Fabian Affolter 0e145ec130 Remove configuration details 2015-10-23 18:39:50 +02:00
Fabian Affolter 44b08a06e7 Remove configuration details 2015-10-23 18:26:36 +02:00
Fabian Affolter 75f737144a Remove configuration details 2015-10-23 18:24:07 +02:00
Fabian Affolter 170742b0a7 Remove configuration details 2015-10-23 18:15:12 +02:00
Fabian Affolter 6115be7c42 Remove configuration details 2015-10-23 18:13:45 +02:00
Fabian Affolter 84a9a300d6 Fix link 2015-10-23 18:13:28 +02:00
Fabian Affolter 55718aac66 Remove configuration details 2015-10-23 18:10:32 +02:00
MakeMeASandwich b6e6512367 media_player/denon: refactor
* connect only if necessary
* do not throw errors if offline
2015-10-23 17:35:08 +02:00
Krzysztof Koziarek 29c9c5a7ec Add new OpenWRT presence detection routine based on ubus instead of luci 2015-10-23 17:01:42 +02:00
Nolan Gilley dd787ea5ce remove suggestion for uk unit system. change default to use si or us based on default temperature. added more sensor types. 2015-10-23 10:10:44 -04:00
Paulus Schoutsen c2d75efb4d Add missing docstring to heat control thermo 2015-10-22 22:14:40 -07:00
Paulus Schoutsen 3d972abdab Clean up the heat control thermostat 2015-10-22 22:04:37 -07:00
Fabian Affolter 7801489149 Remove configuration details 2015-10-21 23:05:54 +02:00
Fabian Affolter 0fda89e983 Remove configuration details 2015-10-21 23:05:38 +02:00
Fabian Affolter 07a75c5eeb Remove configuration details 2015-10-21 23:05:13 +02:00
Fabian Affolter 0b7c407519 Remove configuration details 2015-10-21 22:54:42 +02:00
Fabian Affolter 3c34f3dac2 Remove configuration details 2015-10-21 22:53:26 +02:00
Fabian Affolter d45074f9dc Remove configuration details 2015-10-21 22:53:09 +02:00
Fabian Affolter 4ff1b0fdb2 Add link to docs 2015-10-21 21:26:16 +02:00
Fabian Affolter ba13f13442 Remove configuration details 2015-10-21 21:12:25 +02:00
Fabian Affolter e615755eb9 Remove configuration details 2015-10-21 21:11:51 +02:00
Fabian Affolter 352d3532e7 Remove configuration details 2015-10-21 21:11:18 +02:00
Fabian Affolter 7b60f6ca77 Remove configuration details 2015-10-21 19:43:24 +02:00
Fabian Affolter a2e8fcbc77 Remove newline 2015-10-21 19:37:34 +02:00
Fabian Affolter e10fd0d28b Remove configuration details 2015-10-21 19:36:52 +02:00
Fabian Affolter 89964ad793 Remove configuration details 2015-10-21 16:46:31 +02:00
Fabian Affolter bddd02bd58 Remove configuration details 2015-10-21 16:45:45 +02:00
Fabian Affolter cfb3384ee3 Add link to docs 2015-10-21 16:38:33 +02:00
Fabian Affolter 490e9ee95d Add link to docs 2015-10-21 16:37:41 +02:00
Fabian Affolter 0d0eb7e7c0 Add link to docs 2015-10-21 16:10:47 +02:00
Fabian Affolter 3d4af8c229 Remove configuration details 2015-10-21 10:56:32 +02:00
Fabian Affolter 7e23c241da Update docstring 2015-10-21 10:49:25 +02:00
Fabian Affolter 7ec1424825 Add link to docs 2015-10-21 10:47:31 +02:00
Fabian Affolter 9162149598 Add link to docs 2015-10-21 10:47:12 +02:00
Fabian Affolter da31b54d06 Add link to docs 2015-10-21 10:45:08 +02:00
Fabian Affolter b20a757454 Remove configuration details 2015-10-21 10:44:29 +02:00
Fabian Affolter 761f225c86 Update link 2015-10-20 22:20:58 +02:00
Fabian Affolter 5580309d98 Remove configuration details 2015-10-20 22:20:58 +02:00
Fabian Affolter 52b4c3b5a2 Remove configuration details 2015-10-20 22:20:58 +02:00
Fabian Affolter aed61cecff Remove configuration details 2015-10-20 22:20:58 +02:00
Fabian Affolter ef129639bd Remove configuration details 2015-10-20 22:20:58 +02:00
Fabian Affolter f945a3a692 Move configuration details to docs 2015-10-20 22:20:58 +02:00
Fabian Affolter 5e56eae28f Move configuration details to docs 2015-10-20 22:20:58 +02:00
Fabian Affolter 72ad1387f0 Move configuration details to docs 2015-10-20 22:20:58 +02:00
Fabian Affolter 02cfc70ad5 Add link to component 2015-10-20 22:20:58 +02:00
Fabian Affolter 5a21b677a1 Add link to component 2015-10-20 22:20:58 +02:00
Fabian Affolter c1a73d250a Add link to component 2015-10-20 22:20:58 +02:00
Fabian Affolter 8c544a89c9 Remove configuration details 2015-10-20 22:20:58 +02:00
Fabian Affolter c473d426e3 Remove configuration details 2015-10-20 22:20:58 +02:00
Fabian Affolter f5a62f8381 Remove configuration details 2015-10-20 22:20:58 +02:00
Fabian Affolter 0e8e4a73fe Remove configuration details 2015-10-20 22:20:58 +02:00
Fabian Affolter 74700e4b10 Add link to doc and remove configuration details 2015-10-20 22:20:58 +02:00
Fabian Affolter 4d5c9581bf Add link to docs 2015-10-20 22:20:58 +02:00
Fabian Affolter f45e0eabe3 Add link to docs 2015-10-20 22:20:58 +02:00
Fabian Affolter bbed4a262c Fix typo 2015-10-20 22:20:44 +02:00
Fabian Affolter f8590f7d1d Include resource in error message 2015-10-20 22:20:44 +02:00
Nolan Gilley 293ed275ab Added support for specifying units in the configuration file. If no units are specified in the config file it will use location to determine the units. 2015-10-20 14:29:22 -04:00
MakeMeASandwich 2e9ee28637 light/hyperion: use RGB, clean code 2015-10-20 17:30:23 +02:00
Paulus Schoutsen 73cb23f599 Merge pull request #535 from toddeye/radiotherm-dev
radiotherm platform bug fix and configuration parameter
2015-10-19 23:28:10 -07:00
Todd Ingarfield 661f4c594e formatting 2015-10-19 16:54:42 -05:00
Todd Ingarfield 9464e2a13b Add hass configuration parameter hold_temp & config documentation 2015-10-19 16:18:45 -05:00
Todd Ingarfield 27d5248937 Fix configuration for multiple hosts and add example configuration.yaml section 2015-10-19 15:33:23 -05:00
MakeMeASandwich c5a7e3abd1 Merge branch 'dev' of https://github.com/balloob/home-assistant into dev 2015-10-19 20:09:38 +02:00
Paulus Schoutsen 0a6424a81d Merge pull request #529 from persandstrom/lms_pause
bugfix, LMS pause
2015-10-18 13:01:43 -07:00
Per Sandström 5b7389de55 bugfix, 1 = force pause 2015-10-18 21:05:30 +02:00
Paulus Schoutsen 0fe4e0330a Merge pull request #528 from fabaff/arest-sensor
Add pins feature to arest sensor
2015-10-18 10:32:57 -07:00
Fabian Affolter 40b095b866 Add option to use pins 2015-10-18 18:05:25 +02:00
MakeMeASandwich 7141a99927 fix flake warning 2015-10-18 09:10:41 +02:00
Fabian Affolter f01d2b1263 Allow to overwrite the device name 2015-10-18 00:42:02 +02:00
Fabian Affolter 3c6420c538 Allow to overwrite the device name 2015-10-18 00:41:12 +02:00
MakeMeASandwich e3304caf06 add hyperion light support 2015-10-17 19:36:52 +02:00
Fabian Affolter 91a1fb0240 Remove wrong file 2015-10-16 09:12:37 +02:00
Paulus Schoutsen 6fd32e83c8 Update .coveragerc 2015-10-15 13:50:06 -07:00
Paulus Schoutsen b41caa093c Merge pull request #503 from toddeye/radiotherm-platform
Add Radio Thermostat platform
2015-10-15 13:45:52 -07:00
Paulus Schoutsen 806c71c803 Merge pull request #522 from balloob/script-cleanup
Script clean up
2015-10-15 13:44:46 -07:00
Todd Ingarfield b0bafa32b7 fixed typo in requirements_all.txt 2015-10-15 11:44:19 -05:00
Fabian Affolter 332ac794a4 Modify the import style 2015-10-15 18:22:42 +02:00
Paulus Schoutsen 5dfd0d2502 Fix another manual alarm regression 2015-10-15 08:39:38 -07:00
Todd Ingarfield 3d838c307f merged upstream and fixed conflict 2015-10-15 10:13:02 -05:00
Paulus Schoutsen 2dd77f9477 Merge pull request #523 from fabaff/cpuinfo
CPU Speed sensor
2015-10-15 07:33:18 -07:00
Fabian Affolter 1279bf7c68 Add py-cpuinfo 2015-10-15 12:13:35 +02:00
Fabian Affolter 323d301072 Add cpuspeed sensor 2015-10-15 12:13:16 +02:00
Fabian Affolter b1815075ac Add cpuspeed sensor 2015-10-15 12:13:04 +02:00
Paulus Schoutsen 7ba4263284 Fix regression manual alarm 2015-10-14 23:38:42 -07:00
Paulus Schoutsen 347597ebdc Base Script on entity 2015-10-14 23:15:48 -07:00
Todd Ingarfield ddeb84cb9c Removed name and netdisco functions, implemented update method to caches values, radiotherm lib to coveragerc and requirements_all.txt 2015-10-14 11:11:33 -05:00
Todd Ingarfield b2e39884f9 Removed name and netdisco functions, implemented update method to caches values, radiotherm lib to coveragerc and requirements_all.txt 2015-10-14 11:02:07 -05:00
Fabian Affolter 8d99c4a0cc Move configuration details to docs 2015-10-14 10:39:51 +02:00
Fabian Affolter 80e4f2f51f Add link to doc 2015-10-14 10:39:51 +02:00
Fabian Affolter 6064fffc8e Move configuration details to docs 2015-10-14 10:39:51 +02:00
Fabian Affolter 7da354c4c5 Move configuration details to docs 2015-10-14 10:39:51 +02:00
Paulus Schoutsen 49de153ecf Add alarm component to demo component 2015-10-13 23:21:47 -07:00
Paulus Schoutsen 716376081d Add tests for MQTT alarm 2015-10-13 23:08:12 -07:00
Paulus Schoutsen d37b70556d manual alarm: Test disarm with invalid code 2015-10-13 22:41:35 -07:00
Paulus Schoutsen 32bb950b5f Add tests for manual alarm control panel platform 2015-10-13 22:36:21 -07:00
Fabian Affolter 0e89418cbe Move configuration desciption to docs 2015-10-13 23:44:27 +02:00
Fabian Affolter fe032be352 Upgrade psutil to 3.2.2 2015-10-13 23:42:27 +02:00
Fabian Affolter 64a78d7b4f Upgrade psutil to 3.2.2 2015-10-13 23:41:46 +02:00
Fabian Affolter 24e4b9e012 Move configuration description to docs 2015-10-13 23:08:56 +02:00
Fabian Affolter a44a39003d Move configuration description to docs 2015-10-13 23:07:26 +02:00
Fabian Affolter f019b4f697 Move configuration details to docs 2015-10-13 22:56:12 +02:00
Fabian Affolter e353dae3a6 Move vonfiguration details to docs 2015-10-13 22:45:36 +02:00
Fabian Affolter fb84c0ce6b Move configuration details to docs 2015-10-13 22:41:53 +02:00
Fabian Affolter fd382871a1 Move configuration details to docs 2015-10-13 22:30:21 +02:00
Fabian Affolter 405025a00b Remove configuration details 2015-10-13 22:26:32 +02:00
Fabian Affolter 912ddbb4fc Add link to docs 2015-10-13 22:26:27 +02:00
Fabian Affolter 91138b8679 Move configuration details to docs 2015-10-13 22:16:26 +02:00
Fabian Affolter 893b9fc8ac Move configuration details to docs 2015-10-13 22:08:11 +02:00
Fabian Affolter 185ba000dd Add link to docs 2015-10-13 21:09:11 +02:00
Fabian Affolter 771118caaf Add link to docs 2015-10-13 21:08:51 +02:00
Fabian Affolter 17d9df0da5 Add link to docs 2015-10-13 21:08:34 +02:00
Fabian Affolter e067398134 Add link to docs 2015-10-13 21:08:18 +02:00
Fabian Affolter cb69ac30ec Add link to docs 2015-10-13 21:07:53 +02:00
Fabian Affolter 241ff45c5e Add link to docs 2015-10-13 21:07:40 +02:00
Fabian Affolter c5c2f0c5f3 Add link to docs 2015-10-13 21:07:24 +02:00
Fabian Affolter 0874cb364f Move configuration details to docs 2015-10-13 21:00:44 +02:00
Fabian Affolter 44418b509c Move configuration details to docs 2015-10-13 21:00:28 +02:00
Fabian Affolter 403889bbeb Move configuration details to docs 2015-10-13 20:55:45 +02:00
Fabian Affolter 6ca50d8b5c Move configuration details to docs 2015-10-13 20:55:15 +02:00
Fabian Affolter 0369a9ee0d Move configuration details to docs 2015-10-13 20:54:48 +02:00
Fabian Affolter 5a6ff9a69a Move configuration details to docs 2015-10-13 20:54:15 +02:00
Fabian Affolter 796cce78bc Move configuration details to docs 2015-10-13 20:52:30 +02:00
Fabian Affolter 77430c0687 Move configuration details to docs 2015-10-13 20:52:08 +02:00
Fabian Affolter c33942d6e2 Move configuration details to docs 2015-10-13 20:51:30 +02:00
Fabian Affolter 3b91f89173 Move configuration details to docs 2015-10-13 20:50:53 +02:00
Fabian Affolter c74f46794e Move configuration details to docs 2015-10-13 20:50:15 +02:00
Fabian Affolter 966fd8f24d Move configuration details to docs 2015-10-13 20:49:28 +02:00
Fabian Affolter 8253fdfc13 Add newline 2015-10-13 20:49:14 +02:00
Fabian Affolter cb7b5f8d15 Move configuration details to docs 2015-10-13 20:45:29 +02:00
Fabian Affolter b0d8eaeda9 Move configuration details to docs 2015-10-13 20:44:18 +02:00
Fabian Affolter 62cfb8aeb2 Move configuration details to docs 2015-10-13 20:42:05 +02:00
Fabian Affolter 47448d1dc0 Add link to docs 2015-10-13 20:40:59 +02:00
Fabian Affolter a583525110 Move configuration details to docs 2015-10-13 20:01:23 +02:00
Paulus Schoutsen 38e1cef30e Update frontend for style fix 2015-10-13 08:58:15 -07:00
Paulus Schoutsen e2f187879c Merge pull request #511 from wind-rider/geofancy
Geofancy
2015-10-13 00:05:17 -07:00
Paulus Schoutsen 925cde200f Merge pull request #514 from balloob/scene-turn-off-remove
Remove turning off scenes
2015-10-12 23:43:29 -07:00
Paulus Schoutsen 383efee470 Scene turn off for frontend 2015-10-12 23:40:09 -07:00
Paulus Schoutsen d5eb90160f Merge pull request #461 from sfam/manual-alarm
Add manual alarm
2015-10-12 23:13:37 -07:00
Paulus Schoutsen 2f946cc6de Merge pull request #516 from mKeRix/dev
Fix for newest tplink firmware, fixes #415
2015-10-12 23:10:30 -07:00
sfam 1b7ce2146c replace sleeps with track_point_in_time 2015-10-13 00:56:24 +00:00
Heiko Rothe 021a374a6a Merge branch 'dev' of https://github.com/mKeRix/home-assistant into dev 2015-10-12 22:44:20 +02:00
Heiko Rothe bbec34d0e6 Merge remote-tracking branch 'refs/remotes/balloob/dev' into dev 2015-10-12 22:43:55 +02:00
Heiko Rothe a6cb19b27d Fixed an issue with the initiation of the new attributes 2015-10-12 22:42:45 +02:00
Hans Bakker b74e70d4e0 Fixes based on balloob's comments 2015-10-12 20:58:24 +02:00
Paulus Schoutsen 5cd283e999 Merge pull request #513 from balloob/component-command
Add shell_command component
2015-10-12 08:43:32 -07:00
Heiko Rothe 2f2bd7a616 Fixed pylint and pep8 violations 2015-10-12 09:18:55 +02:00
Paulus Schoutsen cddc87b0ab Remove turn off from scene 2015-10-11 23:51:59 -07:00
Paulus Schoutsen d6bbc67112 Add tests for scene 2015-10-11 23:48:17 -07:00
Paulus Schoutsen 6d77b15e44 Few more tests 2015-10-11 21:41:44 -07:00
Paulus Schoutsen 916c453d2b Add test for shell command 2015-10-11 21:30:17 -07:00
Paulus Schoutsen 7786b52d93 Add shell_command component 2015-10-11 20:11:30 -07:00
Paulus Schoutsen 90d4a2c0b8 Update frontend version 2015-10-11 20:10:32 -07:00
Paulus Schoutsen b6d26597c0 Automation - state platfor: Flag if user makes config error 2015-10-11 18:30:25 -07:00
Paulus Schoutsen bf1970b78c Make thermostat more robust 2015-10-11 18:16:55 -07:00
Hans Bakker 1eb3610a11 Style fixes 2015-10-12 00:28:39 +02:00
Paulus Schoutsen f081f7c4ff Merge pull request #494 from andythigpen/mysensors-update
Update to latest mysensors library.
2015-10-11 15:14:23 -07:00
Hans Bakker 6a969208e9 Initial commit for Geofancy device tracker. 2015-10-12 00:14:05 +02:00
Andrew Thigpen 384b3d0d17 Update to latest mysensors library.
* Adds JSON persistence support.
* Adds documentation comments for configuration options.
2015-10-11 17:05:08 -05:00
Paulus Schoutsen ad5b650661 Merge pull request #504 from happyleavesaoc/dev
Amazon Fire TV device support
2015-10-11 14:54:55 -07:00
Paulus Schoutsen b05f2e3221 Fix style issue 2015-10-11 11:04:16 -07:00
Paulus Schoutsen dcfc91e71c Fix throttle applied to methods 2015-10-11 10:42:42 -07:00
Todd Ingarfield 6c1c243000 start away mode 2015-10-11 11:42:24 -05:00
Paulus Schoutsen c2117b3eaf Merge pull request #507 from gsabbe/dev
asuswrt gives a traceback when ipv6 is enabled on the router
2015-10-11 09:26:11 -07:00
Todd Ingarfield 84c72ebf63 Add support for multiple thermostats (via hass-config) and auto-discovery via ratiotherm module 2015-10-11 09:28:25 -05:00
Guillaume SABBE a1e5bea3ab When IPv6 is enabled, dnsmasq has a configuration line with the DUID.
This looks like this
61072 b8:27:eb:e1:4e:4d 192.168.0.4 domotycoon *
61072 b8:27:eb:b8:10:6b 192.168.0.5 pimonitor *
duid 00:03:00:01:ac:22:0b:e9:98:50
When using match.group() without testing if match != None, you get a traceback.
2015-10-11 15:21:53 +02:00
Hans Bakker bee5c0adfb Merge branch 'dev' of https://github.com/balloob/home-assistant 2015-10-10 23:40:59 +02:00
happyleavesaoc 7ca21f577d fixed merge conflict 2015-10-10 16:53:55 -04:00
happyleavesaoc 168516f5da addressed PR comments 2015-10-10 16:45:13 -04:00
Paulus Schoutsen 94df5acbf3 Version bump to 0.7.6.dev0 2015-10-10 11:45:25 -07:00
Paulus Schoutsen 853a9fd4cd Merge pull request #506 from balloob/dev
0.7.5rc1
2015-10-10 11:45:06 -07:00
Paulus Schoutsen 6a18205d2e Update to version 0.7.5 2015-10-10 11:39:29 -07:00
Paulus Schoutsen 3a3b8bbb45 Fix packaging issues 2015-10-10 10:33:09 -07:00
Todd Ingarfield 37278aab20 add set_time and begin discovery 2015-10-10 11:36:34 -05:00
Fabian Affolter d3c4722529 Add some other components 2015-10-09 23:45:36 +02:00
Fabian Affolter c3de67041a Add plex 2015-10-09 23:40:06 +02:00
Fabian Affolter f07d07432d Add telegram 2015-10-09 23:38:28 +02:00
Fabian Affolter 47f994b867 Move configuration details to docs 2015-10-09 23:33:59 +02:00
Fabian Affolter a8a172c8b7 Add link to docs and remove configuration details from file header 2015-10-09 23:24:26 +02:00
happyleavesaoc d4d91bfdbb Amazon Fire TV device support 2015-10-09 17:06:35 -04:00
Fabian Affolter 7432bbd70c Merge pull request #500 from balloob/arest-fix
Throttle per instance (fixes arest)
2015-10-09 22:46:55 +02:00
Todd Ingarfield a3d295d885 Correct formatting 2015-10-09 11:38:39 -05:00
Todd Ingarfield 0cf909cce9 Correct ci failed tests 2015-10-09 11:34:14 -05:00
Todd Ingarfield fc1cf49fd3 added REQUIREMENTS for radiotherm python module 2015-10-09 10:49:54 -05:00
Todd Ingarfield e5d68d8a1e set name of device through hass config 2015-10-09 10:43:14 -05:00
Fabian Affolter 8fc2f5fe36 Update and equalize comments 2015-10-09 17:41:07 +02:00
Paulus Schoutsen c2c18bdbd5 Merge pull request #501 from fabaff/telegram
Telegram notifications
2015-10-09 07:40:40 -07:00
Fabian Affolter f8efe3f00f Update link to docs 2015-10-09 14:48:58 +02:00
Fabian Affolter db53e46705 Add link to docs and remove configuration details 2015-10-09 14:44:59 +02:00
Fabian Affolter 526a163563 Update link 2015-10-09 14:41:35 +02:00
Fabian Affolter e29f857f43 Update header (docstring) 2015-10-09 14:40:48 +02:00
Fabian Affolter 9f6ce868e2 Add telegram 2015-10-09 14:13:05 +02:00
Fabian Affolter 3ef5e7c161 Add telegram 2015-10-09 14:12:49 +02:00
Fabian Affolter fe5bb89a68 Add telegram notifier 2015-10-09 14:04:29 +02:00
Paulus Schoutsen be8089bcde Cleanup arest 2015-10-08 23:50:04 -07:00
Paulus Schoutsen 47fc1deecb Fix throttle to work on instance-level 2015-10-08 23:49:55 -07:00
Paulus Schoutsen 8a04e1f5f4 Device tracker configuration fix
Fixes #498
2015-10-08 22:19:15 -07:00
Paulus Schoutsen 9f33b8f541 DDWRT - match multiple output variants
Fixes #481
2015-10-08 22:15:12 -07:00
Paulus Schoutsen 0624725e21 Ignore nmap style issue - pylint bug 2015-10-08 21:45:51 -07:00
Paulus Schoutsen dc5f0ef314 NMap: fix hostname resolver
Fixes #482
2015-10-08 21:01:38 -07:00
Paulus Schoutsen cb2943c247 Merge pull request #499 from balloob/handle-states-for-media-player
Prioritize play_media over state change
2015-10-08 18:07:27 -07:00
Jon Maddox 45f0911640 move play_media to the top so it catches first 2015-10-08 20:37:59 -04:00
Todd Ingarfield 4ac9e9fc4c initial commit 2015-10-08 17:48:03 -05:00
Fabian Affolter 28b107ffa9 Move details from header to docs 2015-10-09 00:27:29 +02:00
Paulus Schoutsen e0149c4ee4 Merge pull request #488 from balloob/itunes-play-media
iTunes play_media
2015-10-08 12:48:35 -07:00
Paulus Schoutsen 455a5916fd Merge pull request #496 from tomduijf/local_www
Allowing custom/local files (images, etc) to be used in the webinterface
2015-10-08 12:46:23 -07:00
Tom Duijf cbf94aae55 Merge remote-tracking branch 'upstream/dev' into local_www 2015-10-08 19:32:28 +00:00
Paulus Schoutsen 39ced09727 Merge pull request #493 from tomduijf/dev_tracker_snmp
device_tracker snmp
2015-10-08 12:29:15 -07:00
Tom Duijf ad417bfdfb Merge remote-tracking branch 'upstream/dev' into local_www 2015-10-08 15:06:01 +00:00
Tom Duijf f682fd7c1f Merge remote-tracking branch 'upstream/dev' into dev_tracker_snmp 2015-10-08 15:04:15 +00:00
Tom Duijf ee23c0fe14 cleaner logging 2015-10-08 14:54:20 +00:00
Tom Duijf 5322789c14 Ability to store icons/pictures in config_dir/www for e.g. device_tracker pictures 2015-10-08 14:10:33 +00:00
Paulus Schoutsen 05cec772d0 Merge pull request #495 from kennedyshead/dev
Fix for KeyError in kodi.py
2015-10-08 06:59:26 -07:00
magnusknutas 75c3e42064 Removes log for cleanup 2015-10-08 14:00:23 +02:00
magnusknutas 61c955779b Logging with info 2015-10-08 13:55:01 +02:00
magnusknutas a015df7b01 Test for media_content_id KeyError 2015-10-08 13:41:58 +02:00
Tom Duijf 721c1d0f54 styling fix for flake 2015-10-08 10:24:55 +00:00
Tom Duijf fe37a6aecc Merge remote-tracking branch 'upstream/dev' into dev_tracker_snmp 2015-10-08 10:01:24 +00:00
Tom Duijf 85bf6cb568 Added pylint disables 2015-10-08 10:01:10 +00:00
Fabian Affolter 9f10ab5e7a Update logger output 2015-10-08 11:10:05 +02:00
Fabian Affolter 3b7f6d3b67 Update docstrings 2015-10-08 11:09:00 +02:00
Fabian Affolter d8aefb5d55 Update docstrings 2015-10-08 11:08:47 +02:00
Fabian Affolter 06cac7f9ef Update docstrings 2015-10-08 11:08:32 +02:00
Fabian Affolter 6d3f18d094 Update docstrings 2015-10-08 11:08:17 +02:00
Tom Duijf 050f90d07a merge with upstream 2015-10-08 08:24:38 +00:00
Fabian Affolter bf9b179441 Update docstrings 2015-10-08 10:23:19 +02:00
Tom Duijf 4f0f7eff5e Merge remote-tracking branch 'upstream/dev' into dev 2015-10-08 08:22:14 +00:00
Tom Duijf 4edbdab4c0 Merge remote-tracking branch 'upstream/master' into dev 2015-10-08 08:09:56 +00:00
Tom Duijf 729f59625e Merge branch 'dev' into dev_tracker_snmp 2015-10-08 08:05:03 +00:00
Tom Duijf 213a1fe4ba Various fixes, CI validation 2015-10-08 08:00:30 +00:00
Paulus Schoutsen c1899609a4 Merge branch 'pr/483' into dev
Conflicts:
	.coveragerc
2015-10-08 00:28:52 -07:00
Paulus Schoutsen 1b4ef3856a Merge pull request #471 from alanbowman/blinkstick_support
[WIP] Add blinkstick support
2015-10-08 00:05:42 -07:00
Paulus Schoutsen 4673a82c90 Merge pull request #490 from CCOSTAN/patch-3
Added # comment for Sensor
2015-10-07 23:52:45 -07:00
Tom Duijf ae6f651c7d styling and version for requirement 2015-10-07 23:22:29 +00:00
Tom Duijf 7cb0f805ee fixed loop 2015-10-07 22:17:49 +00:00
Tom Duijf d556e5979a Updated misc files and code styling 2015-10-07 21:45:24 +00:00
Fabian Affolter d149f9d64c Update doc string (Fix #491) 2015-10-07 23:28:56 +02:00
Tom Duijf 9377b647f5 removed debug logging 2015-10-07 21:05:27 +00:00
Tom Duijf 469f35d25f various fixes, initial working version 2015-10-07 21:04:34 +00:00
Carlo Costanzo 17865c78c4 Added # comment for Sensor
Comments for unique sensor labels.
2015-10-07 17:02:07 -04:00
badele a5dae78155 Refactoring the rfxtrx components 2015-10-07 19:57:40 +02:00
badele 46f5ef54a1 Refactoring test instance type 2015-10-07 19:15:50 +02:00
badele 496e4cf784 Exclude rfxtrx component files 2015-10-07 19:07:19 +02:00
badele 11fc521e60 Replace REQUIREMENTS by DEPENDENCIES variable 2015-10-07 19:04:03 +02:00
Tom Duijf a58382e763 Fixed b/octet to mac adress conversion 2015-10-07 16:57:01 +00:00
Alan Bowman 9d4aa7e519 Update tests for RGB color support 2015-10-07 13:58:21 +01:00
Jon Maddox ffbaf0cd5a simpler 2015-10-07 02:13:13 -04:00
Jon Maddox 3b58e8628d style 2015-10-07 02:02:25 -04:00
Jon Maddox c2fe977778 style 2015-10-07 01:55:15 -04:00
Jon Maddox 85338887b4 wrap it 2015-10-07 01:42:50 -04:00
Jon Maddox 9a3c76c263 these are required 2015-10-07 01:41:57 -04:00
Jon Maddox 6ab4b80486 Merge branch 'dev' into itunes-play-media 2015-10-07 01:41:21 -04:00
Paulus Schoutsen 5e0a4c316f Merge pull request #487 from balloob/media-player-play-media
Media Player play_media function
2015-10-06 22:40:36 -07:00
Jon Maddox 26939ce554 style 2015-10-07 01:37:40 -04:00
Jon Maddox c83324d4cf nope 2015-10-07 01:34:37 -04:00
Jon Maddox dbcc3a76ea style 2015-10-07 01:29:55 -04:00
Jon Maddox faa3e98921 module level play_media 2015-10-07 01:28:58 -04:00
Jon Maddox 1c4ac6017d fix typo while were in here 2015-10-07 01:21:41 -04:00
Jon Maddox 25a690691b import it from the right place 2015-10-07 01:11:19 -04:00
Jon Maddox bb997deb85 COMMMMAAAAAAAAAAAA 2015-10-07 01:06:27 -04:00
Jon Maddox 6c4b2fd638 derp 2015-10-07 01:01:25 -04:00
Jon Maddox c4f8017a3f silence warning 2015-10-07 00:56:36 -04:00
Jon Maddox 6afb846d04 avoid key errors 2015-10-07 00:56:14 -04:00
Jon Maddox ad549be353 support play_media for state restoration (for scenes) 2015-10-07 00:39:38 -04:00
Jon Maddox 9012ba53fd add play_media service to tests 2015-10-06 23:18:24 -04:00
Jon Maddox bdb42bf4a2 support play_media 2015-10-06 23:12:48 -04:00
Jon Maddox 1b22f71a19 implement play_media 2015-10-06 23:12:41 -04:00
Jon Maddox e84ddb036f return what playlist is playing 2015-10-06 23:12:30 -04:00
Jon Maddox 4be33bb15b add a way to play a playlist with the client 2015-10-06 23:12:20 -04:00
Jon Maddox d17174d43d play_media as a service 2015-10-06 23:11:21 -04:00
Jon Maddox e64846e2fd add ability to support play_media 2015-10-06 23:11:09 -04:00
Jon Maddox d454cad5a6 add a play_media function 2015-10-06 23:10:39 -04:00
Jon Maddox dcf52332ca add new properties for Channel or Playlist 2015-10-06 23:09:53 -04:00
Jon Maddox 87599df41b add some new media types 2015-10-06 23:00:29 -04:00
Tom Duijf e535f50e03 Merge branch 'master' into dev_tracker_snmp 2015-10-06 21:30:36 +00:00
Tom Duijf df7fbf664e Added constants needed for snmp 2015-10-06 21:27:04 +00:00
Tom Duijf 0fb9e1b16c Initial commit of snmp device tracker 2015-10-06 21:26:32 +00:00
Alan Bowman 047cff6596 Add blinkstick support 2015-10-06 11:10:16 +01:00
badele 32f1791c5a Check flake & pylint style 2015-10-06 08:44:15 +02:00
Paulus Schoutsen 3b49d1e876 Update version to 0.7.5dev0 2015-10-05 22:31:21 -07:00
Paulus Schoutsen 4d1dce2519 Merge branch 'dev' 2015-10-05 22:18:45 -07:00
Paulus Schoutsen 01d097b9b0 Bump version to 0.7.4 2015-10-05 22:18:34 -07:00
Alan Bowman 6d53944fa1 Support RGB colors 2015-10-05 13:25:09 +01:00
Paulus Schoutsen 7f60f1e662 Merge pull request #478 from balloob/dev
0.7.4rc1
2015-10-04 11:30:36 -07:00
Paulus Schoutsen bc6c285945 Update zone doc 2015-10-04 01:40:38 -07:00
Paulus Schoutsen d4d8c9ae65 Update frontend version 2015-10-04 01:40:38 -07:00
Paulus Schoutsen 9292891836 Update documentation 2015-10-04 01:40:38 -07:00
Paulus Schoutsen 035df68d6c Merge pull request #479 from balloob/balloob-patch-1
Tweak caching on CI
2015-10-03 11:41:14 -07:00
Paulus Schoutsen c611be96ad Another try, caching is enabled before activating virtualenv 2015-10-03 11:36:39 -07:00
Paulus Schoutsen d46720ee2c Tweak caching on CI 2015-10-03 11:31:28 -07:00
Paulus Schoutsen c1f464f478 Fix style issue 2015-10-03 11:26:57 -07:00
Paulus Schoutsen 8c5759e460 Improve Logbook device tracker locations handling 2015-10-03 11:20:22 -07:00
Paulus Schoutsen 8490d6126a OwnTracks robustness improvement 2015-10-03 10:29:00 -07:00
badele 7f71706f08 Log RFXCOM events 2015-10-03 11:26:18 +02:00
Paulus Schoutsen 8b5b580287 Merge pull request #477 from fabaff/worldclock
Worldclock sensor
2015-10-03 00:03:14 -07:00
Paulus Schoutsen d35f5b9f97 Tests for MQTT sensor/switch 2015-10-02 23:57:26 -07:00
Paulus Schoutsen 6de64d7695 Cache pip in Travis 2015-10-02 16:49:10 -07:00
Fabian Affolter 7f1da8b7bc Add worldclock sensor 2015-10-02 23:49:32 +02:00
Fabian Affolter 58ac4be24c Add worldclock sensor 2015-10-02 23:49:00 +02:00
Paulus Schoutsen b2919c6504 Fix gps accuracy issue 2015-10-02 13:49:55 -07:00
badele db509ccf18 Add a light & switch rfxtrx sender capability 2015-10-02 22:39:30 +02:00
Paulus Schoutsen 3863d2985a Merge pull request #475 from toddeye/group-state-openclose
Add STATE_OPEN/STATE_CLOSED to groupable states
2015-10-02 09:30:27 -07:00
Paulus Schoutsen 0180c056e1 Add away mode to heat control 2015-10-02 08:57:38 -07:00
Paulus Schoutsen e6cd9a6dc7 Merge pull request #459 from auchter/limitlessled-white
Add support for white LimitlessLED devices and multiple bridges
2015-10-02 08:55:34 -07:00
Todd Ingarfield 33028dd143 Add STATE_OPEN/STATE_CLOSED to groupable states 2015-10-02 10:53:36 -05:00
Paulus Schoutsen 9bdfa89b7c More robust geofence checking 2015-10-02 08:16:53 -07:00
Paulus Schoutsen 42b80868d4 Update netdisco dependency 2015-10-02 06:48:46 -07:00
Fabian Affolter 707ca4b752 Update docstrings 2015-10-02 13:42:06 +02:00
Fabian Affolter c7d2a09097 Update docstring 2015-10-02 13:41:51 +02:00
Fabian Affolter fb9f83f8ad Update docstrings 2015-10-02 13:17:18 +02:00
Michael Auchter 52ebb2fb3b limitlessled: Add support for White Limitless LED bulbs
LimitlessLED bulbs actually come in three flavors: RGB, RGBW, and White. The
ledcontroller library used to control these bulbs only supports RGBW and White
bulbs. This changelist adds support for the White bulb variant.

The White bulbs are a bit annoying in that they don't support absolute
brightness or color temperature adjustments; they only support a relative
"increase" or "decrease" adjustment. This, along with the unreliable, one-way
communication medium that requires repeats to be "sure" that the bulb received a
command, makes implementing brightness control difficult. So, for now, these
bulbs are more limited than the RGBW variants and only support On/Off control.
2015-10-01 22:38:50 -05:00
Michael Auchter ea7ca48ba2 limitlessled: Add support for previous configuration format
Quick hack that preserves functionality of existing configuration formats to
ease upgrades.
2015-10-01 22:38:50 -05:00
Michael Auchter ab80af099c limitlessled: Add support for multiple bridges
This adds support for a controlling multiple Limitless LED bridges.
2015-10-01 22:38:50 -05:00
Michael Auchter 34531895a0 limitlessled: Use LedControllerPool
This change is in preparation for adapting this component to support multiple
LimitlessLED bridges. Ultimately LedControllerPool helps to maintain the
mandatory 100ms pauses across multiple controllers so messages are reliably
received.
2015-10-01 22:38:50 -05:00
Michael Auchter 645cd89406 limitlessled: fix docstring 2015-10-01 22:38:49 -05:00
Paulus Schoutsen cc5217d818 Merge pull request #463 from adrienbrault/plex
Finish plex implementation
2015-10-01 18:30:24 -07:00
Adrien Brault e454806669 Finish plex implementation 2015-10-01 21:14:29 +02:00
Paulus Schoutsen 726557b2f6 Sensor.rest: verify SSL by default 2015-09-30 23:17:08 -07:00
Paulus Schoutsen c7e22e6910 Merge pull request #467 from adrienbrault/sensor-path-nossl
Allow to skip ssl and specify variable path for rest sensor
2015-09-30 23:14:24 -07:00
Adrien Brault f66a020bfc Allow to skip ssl and specify variable path for rest sensor 2015-10-01 05:54:31 +02:00
Paulus Schoutsen 64a73f6b67 Update pywemo dependency 2015-09-30 00:12:00 -07:00
Paulus Schoutsen ad7f034805 MQTT: Auto provide cloudmqtt cert 2015-09-30 00:09:35 -07:00
Paulus Schoutsen 76674d4de9 MQTT: Allow certificates 2015-09-30 00:09:07 -07:00
Paulus Schoutsen 0dc9f2a9f8 Move MQTT to own folder 2015-09-29 23:55:16 -07:00
Paulus Schoutsen ce47b58a8b Report MQTT connect issues 2015-09-29 23:34:17 -07:00
Paulus Schoutsen 5d71d5560e update rpi_gpio comment 2015-09-29 23:11:32 -07:00
Paulus Schoutsen 1dc9bfdf73 Update config zones 2015-09-29 23:08:37 -07:00
badele cc47e39006 Add send capability 2015-09-29 22:47:22 +02:00
Paulus Schoutsen 2eb36c18bd Add geofencing to automation 2015-09-29 00:18:52 -07:00
badele d64f0ddd41 Refactoring the code for pylint & flake test 2015-09-29 08:20:25 +02:00
Paulus Schoutsen 5ad27d8cdb Add support for zones to Home Assistant 2015-09-28 23:13:13 -07:00
Paulus Schoutsen 68c2b539ee More flexible domain config extraction 2015-09-28 23:09:05 -07:00
sfam e57b3ae847 add manual alarm 2015-09-28 23:36:46 +00:00
Paulus Schoutsen 755234369d New frontend build 2015-09-27 22:05:03 -07:00
Paulus Schoutsen 0a34e8de02 Fix services.yaml in packaging 2015-09-27 21:56:15 -07:00
Paulus Schoutsen 52ed25fc21 Merge pull request #394 from pavoni/hue-scenes
Fuzzy match for float attributes for Philips Hue scenes
2015-09-27 21:41:59 -07:00
Paulus Schoutsen 9e866680d4 Merge pull request #456 from balloob/service-fields
Service fields
2015-09-27 21:07:48 -07:00
pavoni 80c89d218b Avoid throwing an exception when a wemo device with attributes isn't found 2015-09-27 21:05:45 -07:00
Paulus Schoutsen 4f1bf7b2bf Merge pull request #443 from CCOSTAN/patch-2
Added some additional examples.
2015-09-27 20:48:01 -07:00
Paulus Schoutsen e557e355db Merge pull request #458 from AnthemisFoundry/fix_vera_dict
Fix Vera bug
2015-09-27 18:26:47 -07:00
badele 174aeacd76 Fix duplicate devices insertion 2015-09-27 23:51:19 +02:00
pavoni e7320fe969 Default dict if parent class returned None 2015-09-27 17:06:49 +01:00
badele 321a603bfe Add a light & switch rfxtrx support 2015-09-27 11:13:49 +02:00
Paulus Schoutsen 4e3bd5f2a9 Add service descriptions 2015-09-26 23:17:04 -07:00
Paulus Schoutsen 9a6b2c1831 Add utf-8 encoding to const file 2015-09-26 06:57:22 -07:00
sfam ca0b6ebd99 Merge pull request #397 from sfam/dev
Add MQTT alarm
2015-09-25 23:55:47 +01:00
sfam 47cd0b20a0 Merge branch 'persandstrom-sfam-dev' into dev 2015-09-25 17:02:37 +00:00
sfam 98d051f870 Merge branch 'sfam-dev' of https://github.com/persandstrom/home-assistant into persandstrom-sfam-dev 2015-09-25 17:02:20 +00:00
Carlo Costanzo 5f98705100 Changed the automation example
Changed the automation example to match the examples on the website.
2015-09-25 11:03:16 -04:00
pavoni 63bf4db969 Remove trace 2015-09-25 15:51:09 +01:00
pavoni 3ec00ce4fe Fix format errors 2015-09-25 15:49:56 +01:00
Paulus Schoutsen 74a0e47ba6 Update frontend with badge fixes 2015-09-25 07:44:58 -07:00
pavoni 476e4f0517 Add doc strings 2015-09-25 13:37:47 +01:00
pavoni 61fb8271e5 Change scene matching to use fuzzy logic for float values, if requested 2015-09-25 13:26:43 +01:00
Per Sandström 5cf9bd7223 updates to support ui 2015-09-25 06:23:04 +02:00
Paulus Schoutsen 9f986c55e6 Merge pull request #435 from toddeye/notify-smtp-retry
Added retry logic if the SMTP connection is disconnected by the server.
2015-09-24 17:15:05 -07:00
sfam 94eb54ff00 Merge branch 'dev' of https://github.com/balloob/home-assistant into dev 2015-09-24 21:19:21 +00:00
Per Sandström f28b392f1a Merge branch 'dev' of https://github.com/sfam/home-assistant into sfam-dev 2015-09-24 22:21:51 +02:00
Carlo Costanzo fa71d5fac9 Update configuration.yaml.example 2015-09-24 15:57:23 -04:00
Carlo Costanzo 21fd53b05d Added some additional examples.
- Added an eample of Groups within Groups.
- Took away the Automation 2 and used the new -Alias format.
- Added a second sensor to demonstrate sensor:, sensor 2: format.
2015-09-24 15:56:26 -04:00
Todd Ingarfield b0b3c2f73f formatting correction 2015-09-24 11:20:25 -05:00
Paulus Schoutsen d660d2b3dc Update frontend (group toggle updates) 2015-09-24 09:04:22 -07:00
Todd Ingarfield a89bfcf342 removed exception attributes 2015-09-24 10:55:24 -05:00
Todd Ingarfield a42347e6e7 corrected formating and style issues 2015-09-24 10:47:19 -05:00
Paulus Schoutsen faee3e8447 Merge pull request #360 from fabaff/rest-sensor
Rest sensor
2015-09-23 23:53:09 -07:00
Paulus Schoutsen 3625646c34 Fix reproduce_state 2015-09-23 23:35:08 -07:00
Paulus Schoutsen 5a562f3db8 Update frontend 2015-09-23 23:32:41 -07:00
Paulus Schoutsen 19705ab40a Hide auto groups from logbook 2015-09-23 23:20:20 -07:00
Paulus Schoutsen 20bf9f7ea1 Update frontend with group toggle 2015-09-23 23:20:20 -07:00
Paulus Schoutsen 6399c873f9 Add gps location to device tracker demo 2015-09-23 23:20:20 -07:00
Paulus Schoutsen 4be1053f1c Merge pull request #437 from balloob/handle-play-states
Support media_player Play States When Resolving/Reproducing State
2015-09-23 22:52:38 -07:00
Jon Maddox efdd0c9e8a don't break the chain 2015-09-24 01:35:08 -04:00
Jon Maddox 8d42e42230 style 2015-09-24 00:38:18 -04:00
Paulus Schoutsen 4b6878f91c Restrict data from stream API 2015-09-23 21:35:23 -07:00
Jon Maddox 90f35b35cd moar derp 2015-09-24 00:20:17 -04:00
Jon Maddox 082920abe0 moar constants 2015-09-24 00:20:05 -04:00
Jon Maddox 4a8bbc52e0 derp 2015-09-24 00:15:36 -04:00
Jon Maddox 1674c8309a Support playing, pausing states for media players when reproducing state
This allows the state helper to call the correct service call for
media_players when attempting to resolve state.
2015-09-24 00:06:05 -04:00
Paulus Schoutsen 62f016e7d2 Filter api password from arguments 2015-09-23 20:56:34 -07:00
Stefan Jonasson 34e5ecb8ab Merge pull request #433 from stefan-jonasson/fix_telldus_libary_cleanup
Telldus libary version update + added callback cleanup
2015-09-23 12:15:31 +02:00
Stefan Jonasson 8f95885e3a Codestyle cleanup 2015-09-23 11:47:53 +02:00
Stefan Jonasson 94db1ac142 Codestyle cleanup 2015-09-23 11:46:55 +02:00
Stefan Jonasson f48e65096a Removed logging. 2015-09-23 11:38:47 +02:00
Stefan Jonasson 3244975489 Removed logging. 2015-09-23 11:37:45 +02:00
Stefan Jonasson 10327795e9 Added more logging. 2015-09-23 11:34:20 +02:00
Stefan Jonasson bcbb8edd59 Added more logging. 2015-09-23 11:30:46 +02:00
Stefan Jonasson 86270e1a37 Added more logging. 2015-09-23 11:27:25 +02:00
Stefan Jonasson de7a34b648 Added more logging. 2015-09-23 11:25:08 +02:00
Stefan Jonasson 82a06279de Added more logging. 2015-09-23 11:22:32 +02:00
Stefan Jonasson 62af1fcc57 Added more logging. 2015-09-23 11:19:27 +02:00
Stefan Jonasson 6afe99dcc7 Added more logging. 2015-09-23 11:14:47 +02:00
Stefan Jonasson b6bf398859 Added callback logging. 2015-09-23 11:07:37 +02:00
Stefan Jonasson 48df06d1c0 Added callback logging. 2015-09-23 10:18:45 +02:00
Stefan Jonasson b4ca691822 Removed the check for callback_dispatcher 2015-09-23 09:52:58 +02:00
Stefan Jonasson 16c2827465 Removed the check for callback_dispatcher 2015-09-23 09:50:12 +02:00
Stefan Jonasson e90fd3d654 Removed the check for callback_dispatcher 2015-09-23 09:43:16 +02:00
Stefan Jonasson 7d0ff6884c Added the req consts 2015-09-23 09:32:11 +02:00
Stefan Jonasson a9ea8972dd Updated required tellcore version 2015-09-23 08:29:57 +02:00
Stefan Jonasson a0c1202ad6 Try to make the connection to the tellcore library more stable 2015-09-23 08:26:40 +02:00
Fabian Affolter 1bf45c8f33 Merge branch 'rest-sensor' of github.com:fabaff/home-assistant into rest-sensor 2015-09-23 01:25:53 +02:00
Fabian Affolter c5094438de Add post option, correction_factor, and decimal_places 2015-09-23 01:17:28 +02:00
Fabian Affolter 60d45ebf79 Add return value 2015-09-23 01:17:28 +02:00
Fabian Affolter 5df2a1cf76 Add new checks and move var check to setup 2015-09-23 01:17:28 +02:00
Fabian Affolter f5b2fa6fbe Remove left-over 2015-09-23 01:17:28 +02:00
Fabian Affolter 03b2ced24e Add rest sensor 2015-09-23 01:17:28 +02:00
Fabian Affolter 6c18f264f3 Add rest sensor 2015-09-23 01:17:28 +02:00
sfam cdc371c3ee merge requires_code and code_format properties 2015-09-22 21:40:45 +00:00
Heiko Rothe 1553844279 Added support for the newest tp-link firmware
Currently this seemingly only applies to the Archer C9
2015-09-22 22:48:43 +02:00
Paulus Schoutsen 826b3be087 Update coveragerc 2015-09-22 13:25:18 -07:00
Paulus Schoutsen f5000d401b Merge pull request #430 from CCOSTAN/patch-1
Minor comment about weather components & LONG:LAT.
2015-09-22 13:11:24 -07:00
Carlo Costanzo 7443f4faf8 Minor comment about weather components & LONG:LAT.
Super Small edit adding in a # about the weather related information.
2015-09-22 15:53:16 -04:00
Heiko Rothe 582ed1fc8d Merge remote-tracking branch 'balloob/dev' into dev 2015-09-22 19:48:39 +02:00
Paulus Schoutsen 3158db9553 Update tile provider 2015-09-21 23:19:42 -07:00
Paulus Schoutsen 46a0173e31 Add demo device tracker platform 2015-09-21 22:46:08 -07:00
Paulus Schoutsen 7e511bcacf Fix iPhone map issues 2015-09-21 22:23:17 -07:00
Paulus Schoutsen d7fd2ccdaf Merge pull request #316 from fabaff/systemd
Systemd service unit file
2015-09-21 19:45:12 -07:00
Todd Ingarfield b2999ae325 Added retry logic if the SMTP connection is disconnected by the server. 2015-09-21 18:54:30 -05:00
Per Sandström 5033c1fcb7 Merge branch 'dev' of https://github.com/sfam/home-assistant into sfam-dev 2015-09-21 21:18:46 +02:00
Jeff Schroeder e492be299b Merge pull request #417 from miniconfig/plex-dev
Plex media player component
2015-09-21 11:44:33 -05:00
miniconfig 03e7281406 Moved plexapi import into setup_platform().
Changed CONTRIBUTING.md to refer to requirements_all.txt instead of requirements.txt
2015-09-21 11:59:55 -04:00
miniconfig 16d75b2981 Added plexapi library to requirements_all.txt 2015-09-21 11:45:52 -04:00
miniconfig cc7784889a Pylint errors 2015-09-21 11:11:38 -04:00
miniconfig d267f0a04c Removed references to the frontend device parameter in the directions and added some clarification.
Fixed plexapi version number.
2015-09-21 10:59:34 -04:00
miniconfig a8e0ca6d3f Fixed various property methods to make sure they all had a fall through return and removed unnecessary "else" statements 2015-09-21 10:44:24 -04:00
Stefan Jonasson f8175adbdc Merge pull request #420 from stefan-jonasson/dev
Fixed Pylint issue
2015-09-21 13:03:16 +02:00
Stefan Jonasson 6437f6f6b4 Desperate try to fix travis ci reporting a unused-argument 2015-09-21 12:57:11 +02:00
Paulus Schoutsen 27bbfbae62 Fix compilation issue frontend 2015-09-21 00:42:23 -07:00
Stefan Jonasson 2785c373fb E302 expected 2 blank lines, found 1 2015-09-21 08:26:14 +02:00
Paulus Schoutsen acddae3747 Initial support for maps in frontend 2015-09-20 23:14:58 -07:00
Stefan Jonasson d3e9a22759 Added pylint hint! 2015-09-21 08:14:11 +02:00
Paulus Schoutsen ca698ff063 remove debug statement 2015-09-20 20:24:31 -07:00
Paulus Schoutsen a866d515f7 Make owntracks more robust 2015-09-20 20:09:53 -07:00
Paulus Schoutsen 3af4f267b3 Lint script would incorrectly report success 2015-09-20 19:56:10 -07:00
Paulus Schoutsen bd61555698 discovery: Update to netdisco 0.4.1 2015-09-20 19:46:33 -07:00
miniconfig 5027acfda1 Fixed additional pylint and flake issues 2015-09-20 16:13:26 -04:00
stefan-jonasson f0991d63d1 Merge pull request #416 from stefan-jonasson/dev
Fix states not updating after command was sent!
2015-09-20 22:00:12 +02:00
Stefan Jonasson 34f36479c6 Fix states not updating after command was sent! 2015-09-20 21:29:38 +02:00
Paulus Schoutsen 506c88dbaf Fix owntracks bugs 2015-09-20 12:13:51 -07:00
Paulus Schoutsen 98a1addc18 Merge pull request #413 from balloob/owntracks
initial owntracks support
2015-09-20 12:00:11 -07:00
Paulus Schoutsen 30492cc685 Fix tests and linting 2015-09-20 11:46:01 -07:00
Paulus Schoutsen 0d09e2e1df Attempt to fix CI scripts 2015-09-20 11:00:35 -07:00
Paulus Schoutsen 81085c7467 Merge pull request #414 from stefan-jonasson/telldus_callback_fix
Telldus callback fix
2015-09-20 10:25:34 -07:00
Paulus Schoutsen 19d40612e6 Add home_range to device tracker 2015-09-20 09:35:03 -07:00
miniconfig 48306ddbf6 Fixed Requirements URL 2015-09-20 08:19:21 -04:00
Stefan Jonasson a60a9202a5 cleanup 2015-09-20 14:17:32 +02:00
Stefan Jonasson ab81231e6d Changed flow so we got one callback per platorm instead of per device which caused race conditions in the telldus library. 2015-09-20 14:11:42 +02:00
Paulus Schoutsen 68286dcef8 initial owntracks support 2015-09-20 00:27:50 -07:00
Paulus Schoutsen 6e96f915f6 Merge pull request #411 from stefan-jonasson/tellstick_callbacks
Fix for issue: #204
2015-09-19 22:54:48 -07:00
Paulus Schoutsen 620a7eadf4 Add release script 2015-09-19 21:33:24 -07:00
Paulus Schoutsen 2332548cf4 Update version to 0.7.4dev 2015-09-19 21:19:45 -07:00
Paulus Schoutsen dc55525206 Merge branch 'dev' 2015-09-19 21:19:20 -07:00
Paulus Schoutsen 9318b36ac2 0.7.3 release 2015-09-19 21:17:10 -07:00
Paulus Schoutsen 46f6653183 New version frontend 2015-09-19 21:16:47 -07:00
Paulus Schoutsen 9736761968 Merge pull request #412 from balloob/dev
0.7.3rc2
2015-09-19 21:07:24 -07:00
Paulus Schoutsen 6352f10d9e Device tracker minor tweak 2015-09-19 21:02:54 -07:00
Paulus Schoutsen 2a3b911d7b Remove debug statement 2015-09-19 21:02:38 -07:00
Paulus Schoutsen 720e5876a7 Fix broken automation test 2015-09-19 21:02:28 -07:00
Paulus Schoutsen 85489010bc Merge pull request #404 from stefan-jonasson/automation_confg_list
Automation confg lists
2015-09-19 20:53:26 -07:00
Stefan Jonasson 60d8266ce0 Fix for issue:
Tellstick switches status changes aren't realtime #204
2015-09-20 00:57:04 +02:00
sfam e29deb0202 add a code_format property on alarm object and a optional code for its MQTT platform 2015-09-19 22:22:37 +00:00
Paulus Schoutsen 268b0f17d0 Merge pull request #409 from persandstrom/initd_restart
hass-daemon restart fix
2015-09-19 14:32:19 -07:00
Per Sandström 49ce85f2e4 fixed restart 2015-09-19 22:45:30 +02:00
Paulus Schoutsen 1771f8b1b3 Fix logbook crashing on custom state_changed events 2015-09-19 13:13:28 -07:00
Paulus Schoutsen 8cd1c42e80 Merge pull request #407 from balloob/testing-upgrade
Fix CI
2015-09-19 12:55:09 -07:00
Paulus Schoutsen ec1d5e617e Fix CI 2015-09-19 12:29:23 -07:00
Stefan Jonasson 40651ef2bc Fixed old config value conversion
Added a new unit test for the config list mode
2015-09-19 21:13:09 +02:00
miniconfig 64741a95b8 Added requirements 2015-09-19 14:16:57 -04:00
miniconfig a24b38aacc Initial version of plex media player component 2015-09-19 13:48:45 -04:00
Paulus Schoutsen 55f6ff86e4 Merge pull request #405 from balloob/automation-event
Event automation fuzzy matches on data
2015-09-19 10:43:02 -07:00
Paulus Schoutsen 79cdda2bd9 Merge pull request #406 from balloob/automation-action-config
Change automation action config keys
2015-09-19 10:42:37 -07:00
sfam 35eed93443 add a requires_code property on alarm object 2015-09-19 17:32:37 +00:00
Paulus Schoutsen dd4e1cbd1d Change automation action config keys 2015-09-19 08:43:56 -07:00
Stefan Jonasson 2084976bc2 Fixed suggestions from @balloob 2015-09-19 17:42:21 +02:00
Paulus Schoutsen 9019d654d7 Event automation fuzzy matches on data 2015-09-19 08:27:34 -07:00
Paulus Schoutsen e0f6239ef3 Merge pull request #403 from stefan-jonasson/script_entity_id_buggfix
Buggfix consistent configuration for scripts calling scripts
2015-09-19 08:20:23 -07:00
Stefan Jonasson b9e1b3eb99 Fixed var name + flake8 2015-09-19 15:51:50 +02:00
Stefan Jonasson e1a7b8f988 Merge branch 'dev' of https://github.com/balloob/home-assistant into automation_confg_list 2015-09-19 15:27:46 +02:00
Stefan Jonasson be9cfbdeb0 Fixed docblock 2015-09-19 14:45:56 +02:00
Fabian Affolter a32229b4ce Allow decimal numbers (Thanks @luxus) 2015-09-19 11:48:24 +02:00
Paulus Schoutsen 7da104af4e Merge pull request #399 from persandstrom/redirect_output
remove output in terminal after service is started
2015-09-18 15:47:33 -07:00
Per Sandström 9d7aef94e0 remove output in terminal after service is started 2015-09-18 21:51:24 +02:00
Stefan Jonasson e4c5108c9d Implemented configuration loading from 2015-09-18 18:12:27 +02:00
sfam fc946da5db Add MQTT alarm 2015-09-18 15:30:34 +00:00
Fabian Affolter b33714bca3 Fix default value for correction_factor (Thanks @luxus) 2015-09-18 16:41:35 +02:00
Fabian Affolter 722af9014d Update import style 2015-09-18 16:25:52 +02:00
pavoni 6abaebb248 More consistant naming 2015-09-18 14:40:00 +01:00
pavoni c01e9bea2b Fix inconsistant naming 2015-09-18 14:38:15 +01:00
Fabian Affolter 5ce4ade737 Add user and change config dir 2015-09-18 14:18:49 +02:00
Fabian Affolter 9ce8f385d2 Rename service unit file 2015-09-18 14:15:42 +02:00
Fabian Affolter d7464aea86 Initial systemd service unit file 2015-09-18 14:14:10 +02:00
Fabian Affolter 071952462c initial systemd service unit file 2015-09-18 13:49:46 +02:00
pavoni ab79b8a541 First cut of write after set for scenes 2015-09-18 12:34:24 +01:00
pavoni 3a3374ed4b Remove incorrect change 2015-09-18 12:34:02 +01:00
pavoni 4d53fa0173 First draft of read_after_set for scenes 2015-09-18 12:21:08 +01:00
pavoni 5369d8c61c Merge remote-tracking branch 'upstream/dev' into dev 2015-09-18 08:51:54 +01:00
Paulus Schoutsen 6c1f44242c Update setup script 2015-09-17 23:55:47 -07:00
Paulus Schoutsen 4371355be1 Better errors on time automation trigger 2015-09-17 23:12:55 -07:00
Paulus Schoutsen 5bb88909a0 Merge pull request #392 from balloob/test-runner
Use pytest for running tests
2015-09-17 19:21:33 -07:00
Paulus Schoutsen 4b0c416844 Use pytest for running tests 2015-09-17 09:08:58 -07:00
Paulus Schoutsen 737d7c9d22 Add travis install section back 2015-09-17 08:54:56 -07:00
Paulus Schoutsen 15be5ced9a Merge pull request #391 from balloob/scripts-to-rule-them-all
First pass for scripts to rule them all
2015-09-17 08:49:41 -07:00
Paulus Schoutsen 7c549db2d6 Merge pull request #386 from SEJeff/quiet-logging
[RFC] Quiet logging
2015-09-17 08:48:00 -07:00
Paulus Schoutsen 6e6aa15f7c Merge pull request #390 from balloob/restart-osx
Add a Way to Restart on OS X
2015-09-17 08:42:54 -07:00
Jon Maddox e0c1885a71 add blank line 2015-09-17 03:52:04 -04:00
Paulus Schoutsen 049cd159ce Fix dev dependency pytest 2015-09-17 00:44:22 -07:00
Paulus Schoutsen 95e05d4fc9 Make script/bootstrap_server executable 2015-09-17 00:42:15 -07:00
Paulus Schoutsen bf14067eb0 Add exec + doc header 2015-09-17 00:38:52 -07:00
Paulus Schoutsen 8c77418b6a First pass for scripts to rule them all 2015-09-17 00:35:26 -07:00
Jon Maddox d25a42426a add a way to restart on os x 2015-09-17 03:25:36 -04:00
Paulus Schoutsen 3ed102cd88 Merge pull request #388 from stefan-jonasson/dev
[Bugfix] - Time trigger fired all the time when using the "from" param
2015-09-17 00:10:13 -07:00
Stefan Jonasson 90e2aefd23 flake8 fix 2015-09-17 08:55:17 +02:00
Stefan Jonasson 47af247d6a flake8 fix 2015-09-17 08:39:41 +02:00
Paulus Schoutsen 3947ed3c2b Merge pull request #389 from balloob/pip-fixes
Fix pip not detecting package installed
2015-09-16 23:38:59 -07:00
Stefan Jonasson 1a00d4a095 pylint fix 2015-09-17 08:35:18 +02:00
Fabian Affolter ccecc0181d Remove blank line 2015-09-17 08:34:26 +02:00
Fabian Affolter e90dbad37e Update docstrings 2015-09-17 08:34:10 +02:00
Fabian Affolter 8ec0c36457 Fix return value 2015-09-17 08:29:50 +02:00
Stefan Jonasson e68cc83e64 return and output error if none of the 4 keys provided
only parse hour/minute/second if after is not available
2015-09-17 08:24:06 +02:00
Paulus Schoutsen 4ad4d74ed4 Fix pip not detecting package installed 2015-09-16 23:18:47 -07:00
Jeff Schroeder 550f31d4c3 Quiet down some of the logging in the sonos platform
This is due to the soco library logging very excessively and it using
requests to connect to each Sonos speaker every 10 seconds (by default).

This makes the logs much more pleasant to use for finding real issues.
2015-09-16 23:11:57 -05:00
Jeff Schroeder 7e42b35b62 Set logging of SQL queries to sqlite as debug log messages 2015-09-16 23:11:57 -05:00
Stefan Jonasson 9b96471182 Fixed after param 2015-09-16 22:46:21 +02:00
Paulus Schoutsen 3c3eadbef5 Update frontend with alarm ui 2015-09-16 08:59:42 -07:00
Paulus Schoutsen f375bc527a Merge pull request #358 from persandstrom/alarmui
Alarm Control Panel
2015-09-16 08:56:03 -07:00
Paulus Schoutsen 6de04d78ed Merge pull request #381 from heathbar/foscam-support
Foscam support
2015-09-15 23:37:33 -07:00
Paulus Schoutsen 86aea83f64 Device tracker improvements 2015-09-15 23:35:28 -07:00
Heath Paddock 98feb3cd93 Fixed pylint errors 2015-09-16 00:40:51 -05:00
Paulus Schoutsen 5af1643297 Add warning when entity not found in reproduce_state 2015-09-15 22:23:07 -07:00
Heath Paddock 3dcd18af9e Fixed flake8 errors 2015-09-16 00:09:16 -05:00
Heath Paddock 2fd7b98cab minor code cleanup 2015-09-15 23:45:12 -05:00
Heath Paddock 90e21791f6 Removed obsolete code 2015-09-15 23:39:03 -05:00
Heath Paddock 9678613a13 foscam: made 'port' configurable and added additional documentation 2015-09-15 23:32:55 -05:00
Heath Paddock 5de89316b2 Initial implementation of Foscam FI9821W support 2015-09-15 22:58:46 -05:00
Paulus Schoutsen 95eabe7c0e Freeze time for sun automation test 2015-09-15 20:18:24 -07:00
Paulus Schoutsen 9bec0316ea Merge pull request #380 from balloob/better-itunes-speaker-names
Append 'AirTunes Speaker' to Name of Devices Shared via itunes-api MediaPlayer
2015-09-15 18:53:47 -07:00
Jon Maddox 61685ea13d tag on " AirTunes Speaker" instead 2015-09-15 21:40:39 -04:00
Jon Maddox 77b9a12687 Tags the name of the device to the end of the name
This helps the media player be more explicit about itself and what it
is. It also namespaces it self a little better in the system. Rather
than be `media_player.family_room` it is
`media_player.family_room_apple_tv`. This helps for cases when there’s
another actual media player like Kodi or Chromecast in there.
2015-09-15 21:07:49 -04:00
Paulus Schoutsen 08f2a67de4 Allow falsy values for media player attributes 2015-09-15 12:58:19 -07:00
Paulus Schoutsen 58c3b03b79 Merge pull request #377 from balloob/automation-improvements
Automation improvements
2015-09-15 12:46:13 -07:00
Paulus Schoutsen c18294ee76 Allow triggers to be used as condition 2015-09-15 08:56:06 -07:00
pavoni 408f0cff78 Merge remote-tracking branch 'upstream/dev' into dev 2015-09-15 14:30:01 +01:00
Paulus Schoutsen 0584c10ef9 Style fix 2015-09-15 00:11:24 -07:00
Paulus Schoutsen ae527e9c6f Fix broken sun automation test 2015-09-15 00:07:49 -07:00
Paulus Schoutsen 1ec5178f66 Remove scheduler component 2015-09-15 00:05:20 -07:00
Paulus Schoutsen 2978e0dabe Add sun automation trigger 2015-09-15 00:02:54 -07:00
Paulus Schoutsen e26f0f7b7d Update stale header doc 2015-09-15 00:02:46 -07:00
Paulus Schoutsen 2ff2a78e97 Merge pull request #376 from balloob/launchd
launchd Script for Starting at Boot and Backgrounding on OS X
2015-09-15 00:02:11 -07:00
Jon Maddox bb172d8c98 indention 2015-09-15 02:58:13 -04:00
Jon Maddox acb288f9e7 error handling when writing 2015-09-15 02:54:22 -04:00
Jon Maddox c7565baa6d NOPE 2015-09-15 02:54:11 -04:00
Jon Maddox fb29611c15 🔥 codecs 2015-09-15 02:51:23 -04:00
Jon Maddox 37cd62447e let it get overwritten 2015-09-15 02:50:15 -04:00
Jon Maddox 5cbcd72912 dupe 2015-09-15 02:48:23 -04:00
Jon Maddox 8bba0b88fd blocks! 2015-09-15 02:46:06 -04:00
Paulus Schoutsen b1f17c2cd4 Merge pull request #356 from fabaff/command-sensor
Command sensor
2015-09-14 23:46:02 -07:00
Fabian Affolter 8017f7f241 Merge branch 'command-sensor' of github.com:fabaff/home-assistant into command-sensor 2015-09-15 08:42:47 +02:00
Fabian Affolter 1a73c1b991 Fix pylint issue 2015-09-15 08:40:54 +02:00
Fabian Affolter 039c5cd847 Change import ordering 2015-09-15 08:40:38 +02:00
Jon Maddox 3b27bef1ac DOCS 2015-09-15 02:35:20 -04:00
Jon Maddox 1fc2204ca9 get the right path 2015-09-15 02:30:19 -04:00
Jon Maddox 834ce5269d we don't actually have to do this 2015-09-15 02:30:13 -04:00
Jon Maddox 9ada5e6b2b move launchd script inside package 2015-09-15 02:29:57 -04:00
Paulus Schoutsen f17ef0327c Merge pull request #366 from fabaff/glances
Glances sensor
2015-09-14 23:24:20 -07:00
Fabian Affolter 56a151b196 Add return value 2015-09-15 08:21:58 +02:00
Jon Maddox 6e927d68e5 don't need these anymore 2015-09-15 02:20:40 -04:00
Jon Maddox fcad068016 strip the dash 2015-09-15 02:17:01 -04:00
Jon Maddox e12cc2fbbf attempts at dodging pep8 terror 2015-09-15 02:12:31 -04:00
Jon Maddox 9588fcc5cc install scripts 2015-09-15 02:09:02 -04:00
Jon Maddox a4aa2e4383 change vars 2015-09-15 02:08:43 -04:00
Fabian Affolter fe074835f0 Fix pylint issue 2015-09-15 07:56:08 +02:00
Paulus Schoutsen b2ad8db86b Add condition type to automation component 2015-09-14 22:51:28 -07:00
Paulus Schoutsen 20f021d05f Another style fix. Who comes up with this? 2015-09-14 22:14:15 -07:00
Paulus Schoutsen fc43135ddd Style fix 2015-09-14 22:12:51 -07:00
Jon Maddox e86ee9eae7 install/uninstall scripts for OS X 2015-09-15 01:07:25 -04:00
Jon Maddox 332f7621ce launchd script for loading HA at boot and background on OS X 2015-09-15 01:06:31 -04:00
Paulus Schoutsen 68c1dd7cd4 Refactor automation configuration 2015-09-14 22:05:40 -07:00
Paulus Schoutsen fe2a9bb83e Fix numeric state if 2015-09-14 20:46:57 -07:00
Paulus Schoutsen 2f8591205f Merge pull request #375 from SEJeff/fix-asuswrt
Fix the asuswrt device tracker for dhcp leases with no hostname
2015-09-14 20:23:44 -07:00
Paulus Schoutsen 65c3184856 Merge pull request #373 from SEJeff/fix-sensor-entity-names
Make the entity names for systemmonitor sensors a bit nicer
2015-09-14 20:17:01 -07:00
Jeff Schroeder 0afb6114c5 Make the entity names for systemmonitor sensors a bit nicer
This prevents them from having trailing whitespace, which makes them
end with `_`.
2015-09-14 21:20:41 -05:00
Jeff Schroeder 7c7b6ca05c Fix the asuswrt device tracker for dhcp leases with no hostname
Sometimes, hosts request dhcp leases without sending the hostname
they want to the dhcp server. This results in the entity_id being
`device_tracker.` as the dev_id is empty and things go downhill
from there.

The dhcp lease file looks like:
    admin@RT-AC66R:/tmp/home/root# cat /var/lib/misc/dnsmasq.leases
    86400 5c:c5:d4:79:4c:ad 192.168.1.226 chit-jsl3 *
    85242 8c:77:12:ad:d9:23 192.168.1.126 android-2c94abebaab16255 01:8c:77:12:ad:d9:23
    61985 b8:e9:37:73:47:f0 192.168.1.204 * 01:b8:e9:37:73:47:f0
    61982 b8:e9:37:ec:0d:7e 192.168.1.132 * 01:b8:e9:37:ec:0d:7e
    84584 00:20:6b:ca:31:c1 192.168.1.182 MC4650-CA31C1 01:00:20:6b:ca:31:c1
    86306 fc:e9:98:d6:4b:90 192.168.1.173 iLol 01:fc:e9:98:d6:4b:90
    74343 20:3a:07:f3:7e:ae 192.168.1.246 gatekeeper 01:20:3a:07:f3:7e:ae
    72374 b8:e9:37:5f:3d:06 192.168.1.34 SonosZP 01:b8:e9:37:5f:3d:06
    64697 00:0e:58:6f:59:d2 192.168.1.171 SonosZB 01:00:0e:58:6f:59:d2

Confirmed working on an Asus RT-AC66R with fw version: 3.0.0.4.376_3861
2015-09-14 20:33:14 -05:00
Paulus Schoutsen 2fe8b154f1 Fix state automation configuration 2015-09-14 18:22:49 -07:00
Paulus Schoutsen bf64956265 Merge pull request #368 from stefan-jonasson/dev
Implemented the if condition support in numeric state
2015-09-14 17:57:34 -07:00
Paulus Schoutsen eb11486e76 Merge pull request #370 from balloob/airplay-speakers
Add AirPlay Speakers as media_players
2015-09-14 16:22:09 -07:00
Jon Maddox e8c3eaab33 style tweaks 2015-09-14 17:39:43 -04:00
Jon Maddox fcbeddeb57 describe airplay part 2015-09-14 17:34:57 -04:00
Jon Maddox 50b23e1969 adds airplay speakers as media_players 2015-09-14 17:27:00 -04:00
Fabian Affolter 984f01359c Fix docstring 2015-09-14 21:48:29 +02:00
Stefan Jonasson d5198d4242 Implemented the if condition support in numeric state 2015-09-14 20:33:01 +02:00
Per Sandström f5d1da1d53 and pylint... 2015-09-14 19:42:36 +02:00
Per Sandström 13ca42e187 fixes from review 2015-09-14 17:33:43 +02:00
Fabian Affolter 74eb577c58 Add glances sensor 2015-09-14 14:09:24 +02:00
Fabian Affolter fe7134b897 Add glances sensor 2015-09-14 14:08:30 +02:00
Fabian Affolter 27845d3fc5 Allow decimal places to be set 2015-09-14 10:44:07 +02:00
Fabian Affolter 6606d2a73c Add command sensor 2015-09-14 10:07:27 +02:00
Fabian Affolter 6dc877d8de Add command sensor 2015-09-14 10:07:27 +02:00
Fabian Affolter b0441aadc4 Add new checks and move var check to setup 2015-09-14 10:06:40 +02:00
Paulus Schoutsen dd71e4fdd1 Record in logbook when automation triggered 2015-09-14 00:02:33 -07:00
Fabian Affolter 7e066e11ad Remove left-over 2015-09-14 08:55:20 +02:00
Paulus Schoutsen 13d40fe6ec Allow firing events in script 2015-09-13 23:54:48 -07:00
Paulus Schoutsen 7e75add144 Update nmap dependency 2015-09-13 23:35:12 -07:00
Paulus Schoutsen 2df26a0d1a Fix sensor.systemmonitor 2015-09-13 23:29:13 -07:00
Paulus Schoutsen 965730eb60 Allow setting name for command switch 2015-09-13 23:04:49 -07:00
Paulus Schoutsen 4c0ac6051f Merge pull request #364 from balloob/automation-if
Add if-condition to automation
2015-09-13 23:00:50 -07:00
Paulus Schoutsen 2a11d02fe4 Add if to automation 2015-09-13 22:27:27 -07:00
Paulus Schoutsen 046c5653cb Add latest version of polymer repo 2015-09-13 20:58:22 -07:00
Paulus Schoutsen f86fcdcaf5 Merge pull request #363 from balloob/logbook-entry
Add custom entries to logbook
2015-09-13 20:52:09 -07:00
Paulus Schoutsen 835bc1c492 Fix style issue 2015-09-13 18:40:54 -07:00
Paulus Schoutsen de5a2fee83 Add custom entries to logbook 2015-09-13 18:30:44 -07:00
Roy Hooper 209499e82b Reduce media player scan frequency to 10s 2015-09-13 20:54:20 -04:00
Roy Hooper 9b47241a46 switch to default polling cycle to solve multiple instance issue 2015-09-13 20:49:09 -04:00
Paulus Schoutsen 513f6e9c3c Merge pull request #353 from stefan-jonasson/dev
numeric_state automation platform
2015-09-13 17:13:06 -07:00
Paulus Schoutsen 9582eae48e Merge pull request #359 from rhooper/sonos-netdisco-fix
Prevent duplicate instances of sonos devices during netdisco
2015-09-13 13:56:10 -07:00
Roy Hooper d4834ff408 Add hass property to Entity to prevent 'Attribute hass is None' error during self.update_ha_state 2015-09-13 16:53:31 -04:00
Roy Hooper ce22f3c82d Implement unique_id to prevent duplicate devices 2015-09-13 16:53:31 -04:00
Fabian Affolter 246184507c Add rest sensor 2015-09-13 22:41:37 +02:00
Fabian Affolter 3f3b475d76 Add rest sensor 2015-09-13 22:41:16 +02:00
Fabian Affolter 40aa661340 Update docsstring 2015-09-13 22:27:28 +02:00
Per Sandström 6c3a78df30 fixed spelling 2015-09-13 21:07:16 +02:00
Per Sandström 964a1f9aef merge from dev 2015-09-13 21:00:51 +02:00
Stefan Jonasson 8360ab265c Not used to pylint and flake8 ... 2015-09-13 20:34:45 +02:00
Stefan Jonasson e3dcb45879 Fixed pylint error 2015-09-13 20:27:11 +02:00
Per Sandström 683a80f5f4 tests pass 2015-09-13 20:21:02 +02:00
Stefan Jonasson 9904727cde homeassistant/components/automation/numeric_state.py:61:80: E501 line too long (80 > 79 characters)
The command "flake8 homeassistant" exited with 1.
2015-09-13 20:16:51 +02:00
Stefan Jonasson e9da02d70c Fixed value error exception
Fixed unittest
2015-09-13 19:59:26 +02:00
Paulus Schoutsen b0b88e606c Merge pull request #355 from SEJeff/minor-sonos-fix
Minor sonos fix
2015-09-13 10:17:05 -07:00
Jeff Schroeder 57a833f1a7 Fix a bug which causes the sonos component to occasionally pop
Had this happen when Sonos surround sound is playing from a TV. See this
for more details:

https://github.com/SoCo/SoCo/blob/af9a5152fe942fc665b0269b0f245330db0671ec/soco/core.py#L1060
2015-09-13 12:13:35 -05:00
Paulus Schoutsen e5e577108c Merge pull request #357 from balloob/sonos-discovery
Discover sonos devices
2015-09-13 08:16:09 -07:00
Paulus Schoutsen 51dd718282 Fix broken thermostat demo and prevent happening again 2015-09-13 08:08:46 -07:00
Paulus Schoutsen 40340ea832 Discover sonos devices 2015-09-13 07:48:50 -07:00
Stefan Jonasson a2ca60159d Fixed logic 2015-09-13 13:05:36 +02:00
Stefan Jonasson 50f5f1860c Added a numeric_state automation platform test ( UNTESTED ) 2015-09-13 12:53:37 +02:00
Stefan Jonasson 8e89308a15 Added better handling if we did not get a value for the numeric check 2015-09-13 12:15:21 +02:00
Paulus Schoutsen 96cfff192a Fix space after HA started in logbook 2015-09-13 01:21:30 -07:00
Paulus Schoutsen 067993c8ab Logbook reverse sorting 2015-09-13 01:12:05 -07:00
Paulus Schoutsen eef1e65244 Fix converting config device tracker 2015-09-13 00:48:52 -07:00
Paulus Schoutsen 134c870d2b Merge pull request #345 from balloob/device-tracker
Device tracker rewrite
2015-09-13 00:15:30 -07:00
Paulus Schoutsen 5edc4f148f Fix style 2015-09-13 00:10:59 -07:00
Paulus Schoutsen 880b5f0ad1 Add device_tracker.see service 2015-09-13 00:02:28 -07:00
Paulus Schoutsen 804b7669b7 Setup device tracker group at end of init 2015-09-12 23:08:16 -07:00
Paulus Schoutsen 81288cc988 Remove netgear discovery hack 2015-09-12 23:08:00 -07:00
Paulus Schoutsen d4174f5e42 Fix device sun light trigger tests 2015-09-12 22:57:31 -07:00
Paulus Schoutsen cfc23b0091 Speed up tests 2015-09-12 22:56:49 -07:00
Paulus Schoutsen bb42e264cb Device tracker sets up group again 2015-09-12 22:56:31 -07:00
Per Sandström c9bccadc40 fixed merge error 2015-09-13 07:48:34 +02:00
Per Sandström ab6cb43d5b alarm component 2015-09-13 07:42:38 +02:00
Jeff Schroeder 4fa379419d Don't blow up if no sonos speakers are found
Also move the imports up so the latest pep8 doesn't complain
2015-09-12 23:10:24 -05:00
Paulus Schoutsen b01ff81b47 Merge pull request #354 from rhooper/sonos
squash bug in volume_level (bad if statement)
2015-09-12 18:47:06 -07:00
Roy Hooper 6dcb87c54d squash bug in volume_level (bad if statement) 2015-09-12 21:42:36 -04:00
Paulus Schoutsen 6cfca09daf Merge pull request #352 from SEJeff/minor-fixes
A few minor bugfixes
2015-09-12 13:52:37 -07:00
Stefan Jonasson 4eba1250e9 Added a numeric_state automation platform 2015-09-12 21:42:52 +02:00
Jeff Schroeder d4d798d71f Error gracefully when unable to connect to home.nest.com 2015-09-12 14:27:12 -05:00
Jeff Schroeder 3dc1dc6c6a A few minor cleanups in the http debug api server 2015-09-12 14:27:07 -05:00
Jeff Schroeder 473047f3dd Fix a small tyop in the history component 2015-09-12 14:27:03 -05:00
Jeff Schroeder f5b5d3f65a Use str.split maxsplit in the time component 2015-09-12 14:26:59 -05:00
Jeff Schroeder 776c7dae07 Fix a tyop in the arduino switch component 2015-09-12 14:26:55 -05:00
Paulus Schoutsen 4ccedca3e5 Fix tests for device tracker 2015-09-12 09:15:28 -07:00
Paulus Schoutsen d9b97ad5b4 Merge pull request #348 from Zyell/dev
Initial Thermostat Range Support
2015-09-12 08:07:52 -07:00
zyell de89de890f Move state constants to __init__ for all thermostats 2015-09-12 07:27:05 -07:00
Paulus Schoutsen 5338b29edf Merge pull request #351 from maddox/itunes
Add iTunes media component
2015-09-11 21:53:03 -07:00
Jon Maddox 395dbe8804 drop the try 2015-09-12 00:50:40 -04:00
Jon Maddox f41786d893 STYLE!!!! 2015-09-12 00:49:34 -04:00
Jon Maddox 34dee0c134 style and docs 2015-09-12 00:42:11 -04:00
Jon Maddox 705238eb78 dat slash 2015-09-12 00:23:12 -04:00
Jon Maddox 2b6e0da405 add docstring 2015-09-12 00:23:04 -04:00
Jon Maddox 9d750368ff moar style fixes 2015-09-12 00:16:51 -04:00
Paulus Schoutsen 7252861b83 Merge pull request #350 from rhooper/sonos
rudimentary sonos support
2015-09-11 21:00:53 -07:00
Roy Hooper db2140782f follow proper calling convention for track_utc_time_change callback 2015-09-11 23:57:34 -04:00
Jon Maddox b9f5ec9e2c style fixes 2015-09-11 23:49:43 -04:00
Jon Maddox 6d9b618f1c add mention of iTunes to README 2015-09-11 23:06:48 -04:00
Jon Maddox a459368998 add itunes.py to .coveragerc 2015-09-11 23:06:17 -04:00
Jon Maddox cb3f14a862 add iTunes component 2015-09-11 23:06:03 -04:00
Roy Hooper e9367d5369 use own track_utc_time_change to poll every 5 seconds 2015-09-11 22:44:37 -04:00
Roy Hooper c3dd94ba04 remove unnecessary self.update_ha_state calls 2015-09-11 22:43:55 -04:00
Paulus Schoutsen 6624cfefd6 Update kodi error reporting 2015-09-11 18:03:02 -07:00
Roy Hooper 350ed9f764 remove and disable pylint: disable=abstract-method for play_youtube() 2015-09-11 19:48:34 -04:00
Roy Hooper 3679a8078a put back play_youtube override 2015-09-11 19:44:18 -04:00
Roy Hooper a25f7eed2b Enable polling and fix metadata updating.
Remove unnecessary methods.
Include SoCo in requirements_all.txt for CI.
Lock down SoCo version to 0.11.1
Add sonos.py to exclusions in .coveragerc
2015-09-11 19:38:42 -04:00
Roy Hooper ae058b7847 tidy up formatting to make travis happy. 2015-09-11 18:55:23 -04:00
Roy Hooper aa74c4e57a fix initialization 2015-09-11 18:52:31 -04:00
Roy Hooper 1b874c603b rudimentary sonos support 2015-09-11 18:44:42 -04:00
Paulus Schoutsen 050fe809e1 Merge pull request #343 from fabaff/arest-switch
aREST switch
2015-09-10 23:53:43 -07:00
Fabian Affolter e2b02f2fd2 Update error message 2015-09-11 08:07:16 +02:00
zyell 775d3198ae Fix logic coverage in target_temperature 2015-09-10 17:46:59 -07:00
zyell 21812ba717 Bug fixes and state adjustment for initial thermostat range support 2015-09-10 15:42:34 -07:00
zyell 2d54fdd979 Initial code for generic thermostat range support and nest compliance 2015-09-10 15:11:59 -07:00
Fabian Affolter e093abc366 Add arest switch 2015-09-10 21:26:51 +02:00
Fabian Affolter 5d3e929599 Add timeout 2015-09-10 21:23:33 +02:00
Fabian Affolter 1ec392a494 Add update 2015-09-10 21:23:33 +02:00
Fabian Affolter d719dd72fe Add arest switch 2015-09-10 21:23:33 +02:00
Fabian Affolter 53b43dc4db Add timeout for requests 2015-09-10 21:21:14 +02:00
Fabian Affolter f21d97d5a2 Add timeout for requests 2015-09-10 21:21:14 +02:00
Paulus Schoutsen f9b17ab026 Device tracker rewrite 2015-09-09 23:37:15 -07:00
Paulus Schoutsen e88fabbe6d Set development version number 2015-09-09 19:38:28 -07:00
Paulus Schoutsen 6e458114f4 Bump version 0.7.2 2015-09-09 19:38:04 -07:00
Paulus Schoutsen 0509b478e9 Bump version 0.7.2 2015-09-09 19:37:44 -07:00
Paulus Schoutsen 73797dad2d Merge pull request #341 from balloob/dev
0.7.2rc1
2015-09-09 19:36:47 -07:00
Paulus Schoutsen 2e8573b6bd Add arest sensor to coveragerc 2015-09-09 19:32:47 -07:00
Paulus Schoutsen 3a8119af2b Merge pull request #328 from MakeMeASandwich/dev
media_player: add Denon remote support
2015-09-09 19:28:35 -07:00
Paulus Schoutsen 9f9755c014 Fix wink dependencies 2015-09-09 12:40:28 -07:00
MakeMeASandwich 117a0018a5 media_player: remove debug messages, change IDLE to ON 2015-09-09 19:41:57 +02:00
MakeMeASandwich 34a6524019 Merge branch 'dev' of https://github.com/balloob/home-assistant into dev 2015-09-09 19:37:48 +02:00
Paulus Schoutsen c971e50a68 Remove external from scripts and package info 2015-09-09 09:08:06 -07:00
Fabian Affolter 4d05650744 Merge pull request #342 from alanbowman/use-ozone-units
Use Dobson Units for ozone
2015-09-09 14:17:22 +02:00
Alan Bowman c66f938919 Use Dobson Units for ozone 2015-09-09 09:48:43 +01:00
Fabian Affolter fc21451446 Update docstring 2015-09-09 10:47:09 +02:00
Fabian Affolter 6a54ccb6b4 Update docstring 2015-09-09 09:37:45 +02:00
Paulus Schoutsen ad99bd6a41 Merge pull request #340 from balloob/package-fixes
Package fixes
2015-09-08 20:35:48 -07:00
Paulus Schoutsen dd23a0b3eb Fix sabnzbd imports 2015-09-08 20:22:13 -07:00
Paulus Schoutsen 89bdead44c Remove latest git submodules 2015-09-08 20:11:25 -07:00
Paulus Schoutsen c68ee2dd0f Change dev version to adhere Python versioning 2015-09-08 19:49:51 -07:00
Paulus Schoutsen 326d23de38 Fix pip checking if zip files are installed 2015-09-08 19:49:27 -07:00
Paulus Schoutsen 3520255b7c Fix setup.py unicode version errors 2015-09-08 19:47:05 -07:00
Paulus Schoutsen b0bd1fadac Fix encoding issue in setup.py 2015-09-08 17:43:41 -07:00
Paulus Schoutsen 4cd01f5516 Merge pull request #339 from persandstrom/squeezebox_player_id
Squeezebox: use id instead of name when updating players
2015-09-08 15:32:15 -07:00
Per Sandstrom 2fb2d5c1d6 use id instead of name when updating players 2015-09-08 20:59:54 +02:00
Paulus Schoutsen 77892dfa0d Merge pull request #331 from sfam/dev
Add optional QoS config parameter to MQTT sensor and switch
2015-09-08 08:38:55 -07:00
Paulus Schoutsen 8a3d9e6b8d Merge pull request #337 from fabaff/transmission
Update transmission configuration
2015-09-08 08:37:59 -07:00
Paulus Schoutsen e61299a46f Merge pull request #326 from rmkraus/fix_pip
Fix pip installation issues.
2015-09-08 08:06:23 -07:00
Paulus Schoutsen 5b69719e95 Merge pull request #327 from fabaff/arest
aREST sensor
2015-09-08 08:02:25 -07:00
Fabian Affolter 0fb69c5ce4 Conditions are required 2015-09-08 16:33:13 +02:00
Fabian Affolter 922da1da44 Update docstring ('- type:' was removed a while ago) 2015-09-08 16:31:30 +02:00
Fabian Affolter da508236e6 Remove '- type:' from configuration 2015-09-08 13:05:58 +02:00
Paulus Schoutsen 985f20d281 Merge pull request #335 from rhooper/utf-8-in-config
Handle UTF-8 in config file
2015-09-07 15:50:06 -07:00
Fabian Affolter 914a6dff5e Update docstring (config file) and attempt to honor PEP0257 2015-09-07 19:40:09 +02:00
Fabian Affolter 78a555faf5 Update docstring (config file) and attempt to honor PEP0257 2015-09-07 19:39:16 +02:00
Fabian Affolter f18928d85b Update docstring (config file) and attempt to honor PEP0257 2015-09-07 19:23:24 +02:00
Fabian Affolter e824bc4c55 Update docstring (config file) and attempt to honor PEP0257 2015-09-07 19:21:33 +02:00
Fabian Affolter 514b8eddb9 Update docstring (config file) and attempt to honor PEP0257 2015-09-07 19:19:11 +02:00
Fabian Affolter 1ed8e58679 Update docstring (config file) and attempt to honor PEP0257 2015-09-07 19:05:37 +02:00
Fabian Affolter e55922eb9e Update docstring (config file) and attempt to honor PEP0257 2015-09-07 18:55:58 +02:00
Fabian Affolter e196c136c1 Update docstring (config file) and attempt to honor PEP0257 2015-09-07 18:38:49 +02:00
Fabian Affolter 1d910f3a84 Update docstring (config file) and attempt to honor PEP0257 more 2015-09-07 18:35:00 +02:00
Fabian Affolter f9cecdee28 Update docstring (config file) and attempt to honor PEP0257 2015-09-07 18:26:20 +02:00
Roy Hooper d0cda964ac Handle UTF-8 in config file. 2015-09-07 10:56:16 -04:00
sfam 0f68b9d22b Add optional QoS config parameter to MQTT sensor and switch (pylint) 2015-09-07 00:28:45 +00:00
sfam c5fc5cba61 Add optional QoS config parameter to MQTT sensor and switch 2015-09-07 00:16:31 +00:00
Fabian Affolter 1a88e48986 add throttle and other minor improvements 2015-09-06 23:41:01 +02:00
MakeMeASandwich c7a8f5d6ca media_player: add Denon remote support 2015-09-06 12:07:12 +02:00
Fabian Affolter 72426e08b8 update errror message 2015-09-05 13:26:29 +02:00
Fabian Affolter 1c3fa89914 add arest sensor 2015-09-05 13:09:55 +02:00
Ryan Kraus a097e9caf2 Reverted a line in package.py to its previous state. 2015-09-05 04:53:44 -04:00
Ryan Kraus 34c4bb585a Fix pip installation issues.
This commit is to fix issue #325.
There were three issues with the PIP installations.

1) If multiple instances of the same platform were found, pip could
attempt to install the same dependency multiple times at once by being
run simultaneously in different processes. This would cause pip
failures due to race conditions. This has been fixed by using a thread
lock to allow only one instance of PIP to run at a time.

2) PIP would not check the target if the dependency was already met.
This would lead to PIP attempting to reinstall every dependency on
every boot. This would eventually fail because the package was already
installed, but it significantly increased boot time, especially on
Raspberry Pis.

3) PIP would not upgrade packages that were already installed. Usually,
when a version is specified to PIP, it will install the specified
version if it is not already installed, even without the \-\-upgrade
flag. This behavior did not work when using the \-\-target flag. When
using the target flag, a new install is always attempted, but nothing
will be overwritten unless the \-\-upgrade flag is also given. This
caused new packages to not be installed when their dependencies were
increased. This is fixed by defaulting towards using the
\-\-upgrade flag.
2015-09-05 04:50:35 -04:00
Paulus Schoutsen 97eb84919b Merge pull request #324 from andythigpen/log-rotate
Add option to rotate log file daily.
2015-09-05 01:45:53 -07:00
Andrew Thigpen 2e636f598e Add option to rotate log file daily.
Adds a command line option to rotate the log daily at midnight and
retain up to the specified amount of days.
2015-09-04 19:52:59 -05:00
Paulus Schoutsen 03d187eceb Merge pull request #323 from andythigpen/skip-pip
Add option to skip pip install on startup.
2015-09-04 16:06:30 -07:00
sfam bb9c50d0f1 Merge pull request #318 from sfam/dev
Support for trigger Maker IFTTT
2015-09-04 23:57:15 +01:00
Andrew Thigpen 6519e589b5 Add option to skip pip install on startup.
Since the requirements only change when the software is updated,
this adds a command line switch to disable pip installs on
startup.  The default behavior is maintained when the switch is
not specified.  Skipping pip helps a lot with startup on older RPi
hardware.
2015-09-04 16:50:57 -05:00
sfam 5b7dab6556 Support for trigger Maker IFTTT (fix pylint) 2015-09-04 22:42:11 +01:00
sfam f9ad12920e Support for trigger Maker IFTTT (fix pylint) 2015-09-04 22:14:28 +01:00
sfam 56151a07a5 Support for trigger Maker IFTTT (pyfttt 0.3) 2015-09-04 21:58:09 +01:00
sfam 6c70ef2e6d upgrade pyfttt version to 0.3 2015-09-04 21:57:15 +01:00
Paulus Schoutsen 450ca842ca Update version number to 0.7.2-pre 2015-09-04 12:58:03 -07:00
Paulus Schoutsen 9cb735f48e Merge pull request #322 from Zyell/dev
Upstream bug fixed that caused error adding some nest thermostats
2015-09-04 12:42:47 -07:00
zyell d10cecde7c Upstream bug fixed that caused error adding some nest thermostats 2015-09-04 12:00:47 -07:00
sfam 14fc4f6f99 Support for trigger Maker IFTTT (fix pylint) 2015-09-04 16:55:55 +01:00
sfam b8e2bf6b7e Add pyfttt requirement for Maker IFTTT 2015-09-04 16:51:25 +01:00
sfam 1ffb4d9a55 Add ifttt.py to .coveragerc 2015-09-04 16:48:12 +01:00
sfam fd032cf6b7 support for trigger Maker IFTTT 2015-09-04 16:28:58 +01:00
Paulus Schoutsen 97e19908be Merge pull request #308 from nkgilley/actiontec
add support for home_interval variable to actiontec component
2015-09-02 12:51:53 -07:00
Nolan Gilley b9b751d234 fix for last_results 2015-09-02 12:00:20 -04:00
Nolan Gilley 5533618bd2 fix comments for home_interval 2015-09-02 11:48:36 -04:00
Nolan Gilley 5b643a8106 fixes for Paulus' comments. 2015-09-02 11:46:09 -04:00
Paulus Schoutsen aa779ff6da Merge pull request #309 from michaelarnauts/dev
Fix for aruba device tracker
2015-09-01 18:26:54 -07:00
Michaël Arnauts 5099fb7680 Don't try to parse other entries in client list since they can be empty and are not used anyway. 2015-09-01 21:13:39 +02:00
Nolan Gilley d2a13da930 pylint fix 2015-09-01 15:09:41 -04:00
Nolan Gilley 97076f1ff8 add support for home_interval variable 2015-09-01 14:43:14 -04:00
Paulus Schoutsen 3a5a94413b merge branch 'dev' 2015-09-01 08:50:56 -07:00
Paulus Schoutsen 03ceb667ba Hotfix for nmap -> v7.1 2015-09-01 08:50:45 -07:00
Paulus Schoutsen 40807f1ee0 Merge branch 'dev' 2015-09-01 01:56:18 -07:00
Paulus Schoutsen abb8958775 Setup.py fixes 2015-09-01 01:56:13 -07:00
Paulus Schoutsen ef141ef608 Add MANIFEST.in 2015-09-01 01:36:15 -07:00
Paulus Schoutsen 3ea91f917d Add MANIFEST.in 2015-09-01 01:36:00 -07:00
256 changed files with 14155 additions and 6222 deletions
+23 -3
View File
@@ -4,8 +4,6 @@ source = homeassistant
omit =
homeassistant/__main__.py
homeassistant/external/*
# omit pieces of code that rely on external devices being present
homeassistant/components/arduino.py
homeassistant/components/*/arduino.py
@@ -28,6 +26,10 @@ omit =
homeassistant/components/zwave.py
homeassistant/components/*/zwave.py
homeassistant/components/rfxtrx.py
homeassistant/components/*/rfxtrx.py
homeassistant/components/ifttt.py
homeassistant/components/browser.py
homeassistant/components/camera/*
homeassistant/components/device_tracker/actiontec.py
@@ -35,20 +37,29 @@ omit =
homeassistant/components/device_tracker/asuswrt.py
homeassistant/components/device_tracker/ddwrt.py
homeassistant/components/device_tracker/luci.py
homeassistant/components/device_tracker/ubus.py
homeassistant/components/device_tracker/netgear.py
homeassistant/components/device_tracker/nmap_tracker.py
homeassistant/components/device_tracker/thomson.py
homeassistant/components/device_tracker/tomato.py
homeassistant/components/device_tracker/tplink.py
homeassistant/components/device_tracker/snmp.py
homeassistant/components/discovery.py
homeassistant/components/downloader.py
homeassistant/components/keyboard.py
homeassistant/components/light/hue.py
homeassistant/components/light/limitlessled.py
homeassistant/components/light/blinksticklight.py
homeassistant/components/light/hyperion.py
homeassistant/components/media_player/cast.py
homeassistant/components/media_player/denon.py
homeassistant/components/media_player/firetv.py
homeassistant/components/media_player/itunes.py
homeassistant/components/media_player/kodi.py
homeassistant/components/media_player/mpd.py
homeassistant/components/media_player/plex.py
homeassistant/components/media_player/squeezebox.py
homeassistant/components/media_player/sonos.py
homeassistant/components/notify/file.py
homeassistant/components/notify/instapush.py
homeassistant/components/notify/nma.py
@@ -57,14 +68,19 @@ omit =
homeassistant/components/notify/slack.py
homeassistant/components/notify/smtp.py
homeassistant/components/notify/syslog.py
homeassistant/components/notify/telegram.py
homeassistant/components/notify/xmpp.py
homeassistant/components/sensor/arest.py
homeassistant/components/sensor/bitcoin.py
homeassistant/components/sensor/command_sensor.py
homeassistant/components/sensor/cpuspeed.py
homeassistant/components/sensor/dht.py
homeassistant/components/sensor/efergy.py
homeassistant/components/sensor/forecast.py
homeassistant/components/sensor/glances.py
homeassistant/components/sensor/mysensors.py
homeassistant/components/sensor/openweathermap.py
homeassistant/components/sensor/rfxtrx.py
homeassistant/components/sensor/rest.py
homeassistant/components/sensor/rpi_gpio.py
homeassistant/components/sensor/sabnzbd.py
homeassistant/components/sensor/swiss_public_transport.py
@@ -72,13 +88,17 @@ omit =
homeassistant/components/sensor/temper.py
homeassistant/components/sensor/time_date.py
homeassistant/components/sensor/transmission.py
homeassistant/components/sensor/worldclock.py
homeassistant/components/switch/arest.py
homeassistant/components/switch/command_switch.py
homeassistant/components/switch/edimax.py
homeassistant/components/switch/hikvisioncam.py
homeassistant/components/switch/rest.py
homeassistant/components/switch/rpi_gpio.py
homeassistant/components/switch/transmission.py
homeassistant/components/switch/wemo.py
homeassistant/components/thermostat/nest.py
homeassistant/components/thermostat/radiotherm.py
[report]
+3 -4
View File
@@ -15,10 +15,6 @@ tests/config/home-assistant.log
*.sublime-project
*.sublime-workspace
# Hide code validator output
pep8.txt
pylint.txt
# Hide some OS X stuff
.DS_Store
.AppleDouble
@@ -30,6 +26,9 @@ Icon
.idea
# pytest
.cache
# GITHUB Proposed Python stuff:
*.py[cod]
-9
View File
@@ -1,12 +1,3 @@
[submodule "homeassistant/external/noop"]
path = homeassistant/external/noop
url = https://github.com/balloob/noop.git
[submodule "homeassistant/external/vera"]
path = homeassistant/external/vera
url = https://github.com/jamespcole/home-assistant-vera-api.git
[submodule "homeassistant/external/nzbclients"]
path = homeassistant/external/nzbclients
url = https://github.com/jamespcole/home-assistant-nzb-clients.git
[submodule "homeassistant/components/frontend/www_static/home-assistant-polymer"]
path = homeassistant/components/frontend/www_static/home-assistant-polymer
url = https://github.com/balloob/home-assistant-polymer.git
+5 -7
View File
@@ -1,13 +1,11 @@
sudo: false
language: python
cache:
directories:
- $HOME/virtualenv/python3.4.2/
python:
- "3.4"
install:
- pip install -r requirements_all.txt
- pip install flake8 pylint coveralls
- script/bootstrap_server
script:
- flake8 homeassistant --exclude bower_components,external
- pylint homeassistant
- coverage run -m unittest discover tests
after_success:
- coveralls
- script/cibuild
+3 -3
View File
@@ -18,10 +18,10 @@ For help on building your component, please see the [developer documentation](ht
After you finish adding support for your device:
- Update the supported devices in the `README.md` file.
- Add any new dependencies to `requirements.txt`.
- Add any new dependencies to `requirements_all.txt`. There is no ordering right now, so just add it to the end.
- Update the `.coveragerc` file.
- Provide some documentation for [home-assistant.io](https://home-assistant.io/). The documentation is handled in a separate [git repository](https://github.com/balloob/home-assistant.io).
- Make sure all your code passes Pylint and flake8 (PEP8 and some more) validation. To generate reports, run `pylint homeassistant > pylint.txt` and `flake8 homeassistant --exclude bower_components,external > flake8.txt`.
- Provide some documentation for [home-assistant.io](https://home-assistant.io/). The documentation is handled in a separate [git repository](https://github.com/balloob/home-assistant.io). It's OK to add a docstring with configuration details to the file header.
- Make sure all your code passes ``pylint`` and ``flake8`` (PEP8 and some more) validation. To check your repository, run `./script/lint`.
- Create a Pull Request against the [**dev**](https://github.com/balloob/home-assistant/tree/dev) branch of Home Assistant.
- Check for comments and suggestions on your Pull Request and keep an eye on the [Travis output](https://travis-ci.org/balloob/home-assistant/).
+5 -6
View File
@@ -10,11 +10,10 @@ RUN apt-get update && \
apt-get install -y --no-install-recommends nmap net-tools && \
apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
# Open Z-Wave disabled because broken
#RUN apt-get update && \
# apt-get install -y cython3 libudev-dev && \
# apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \
# pip3 install cython && \
# scripts/build_python_openzwave
RUN apt-get update && \
apt-get install -y cython3 libudev-dev && \
apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \
pip3 install "cython<0.23" && \
script/build_python_openzwave
CMD [ "python", "-m", "homeassistant", "--config", "/config" ]
+5
View File
@@ -0,0 +1,5 @@
include README.md
include LICENSE
graft homeassistant
prune homeassistant/components/frontend/www_static/home-assistant-polymer
recursive-exclude * *.py[co]
+7 -5
View File
@@ -16,10 +16,12 @@ Check out [the website](https://home-assistant.io) for [a demo][demo], installat
Examples of devices it can interface it:
* Monitoring connected devices to a wireless router: [OpenWrt](https://openwrt.org/), [Tomato](http://www.polarcloud.com/tomato), [Netgear](http://netgear.com), [DD-WRT](http://www.dd-wrt.com/site/index), [TPLink](http://www.tp-link.us/), and [ASUSWRT](http://event.asus.com/2013/nw/ASUSWRT/)
* Monitoring connected devices to a wireless router: [OpenWrt](https://openwrt.org/), [Tomato](http://www.polarcloud.com/tomato), [Netgear](http://netgear.com), [DD-WRT](http://www.dd-wrt.com/site/index), [TPLink](http://www.tp-link.us/), [ASUSWRT](http://event.asus.com/2013/nw/ASUSWRT/) and any SNMP capable Linksys WAP/WRT
*
* [Philips Hue](http://meethue.com) lights, [WeMo](http://www.belkin.com/us/Products/home-automation/c/wemo-home-automation/) switches, [Edimax](http://www.edimax.com/) switches, [Efergy](https://efergy.com) energy monitoring, RFXtrx sensors, and [Tellstick](http://www.telldus.se/products/tellstick) devices and sensors
* [Google Chromecasts](http://www.google.com/intl/en/chrome/devices/chromecast), [Music Player Daemon](http://www.musicpd.org/), [Logitech Squeezebox](https://en.wikipedia.org/wiki/Squeezebox_%28network_music_player%29), and [Kodi (XBMC)](http://kodi.tv/)
* Support for [ISY994](https://www.universal-devices.com/residential/isy994i-series/) (Insteon and X10 devices), [Z-Wave](http://www.z-wave.com/), [Nest Thermostats](https://nest.com/), [Arduino](https://www.arduino.cc/), [Raspberry Pi](https://www.raspberrypi.org/), and [Modbus](http://www.modbus.org/)
* [Google Chromecasts](http://www.google.com/intl/en/chrome/devices/chromecast), [Music Player Daemon](http://www.musicpd.org/), [Logitech Squeezebox](https://en.wikipedia.org/wiki/Squeezebox_%28network_music_player%29), [Plex](https://plex.tv/), [Kodi (XBMC)](http://kodi.tv/), iTunes (by way of [itunes-api](https://github.com/maddox/itunes-api)), and Amazon Fire TV (by way of [python-firetv](https://github.com/happyleavesaoc/python-firetv))
* Support for [ISY994](https://www.universal-devices.com/residential/isy994i-series/) (Insteon and X10 devices), [Z-Wave](http://www.z-wave.com/), [Nest Thermostats](https://nest.com/), [RFXtrx](http://www.rfxcom.com/), [Arduino](https://www.arduino.cc/), [Raspberry Pi](https://www.raspberrypi.org/), and [Modbus](http://www.modbus.org/)
* Interaction with [IFTTT](https://ifttt.com/)
* Integrate data from the [Bitcoin](https://bitcoin.org) network, meteorological data from [OpenWeatherMap](http://openweathermap.org/) and [Forecast.io](https://forecast.io/), [Transmission](http://www.transmissionbt.com/), or [SABnzbd](http://sabnzbd.org).
* [See full list of supported devices](https://home-assistant.io/components/)
@@ -29,8 +31,8 @@ Built home automation on top of your devices:
* Turn on the lights when people get home after sun set
* Turn on lights slowly during sun set to compensate for less light
* Turn off all lights and devices when everybody leaves the house
* Offers a [REST API](https://home-assistant.io/developers/api.html) and can interface with MQTT for easy integration with other projects
* Allow sending notifications using [Instapush](https://instapush.im), [Notify My Android (NMA)](http://www.notifymyandroid.com/), [PushBullet](https://www.pushbullet.com/), [PushOver](https://pushover.net/), [Slack](https://slack.com/), and [Jabber (XMPP)](http://xmpp.org)
* Offers a [REST API](https://home-assistant.io/developers/api.html) and can interface with MQTT for easy integration with other projects like [OwnTracks](http://owntracks.org/)
* Allow sending notifications using [Instapush](https://instapush.im), [Notify My Android (NMA)](http://www.notifymyandroid.com/), [PushBullet](https://www.pushbullet.com/), [PushOver](https://pushover.net/), [Slack](https://slack.com/), [Telegram](https://telegram.org/), and [Jabber (XMPP)](http://xmpp.org)
The system is built modular so support for other devices or actions can be implemented easily. See also the [section on architecture](https://home-assistant.io/developers/architecture.html) and the [section on creating your own components](https://home-assistant.io/developers/creating_components.html).
+56 -19
View File
@@ -1,7 +1,9 @@
homeassistant:
# Omitted values in this section will be auto detected using freegeoip.net
# Location required to calculate the time the sun rises and sets
# Location required to calculate the time the sun rises and sets.
# Cooridinates are also used for location for weather related components.
# Google Maps can be used to determine more precise GPS cooridinates.
latitude: 32.87336
longitude: 117.22743
@@ -68,11 +70,18 @@ device_sun_light_trigger:
# A comma separated list of states that have to be tracked as a single group
# Grouped states should share the same type of states (ON/OFF or HOME/NOT_HOME)
# You can also have groups within groups.
group:
Home:
- group.living_room
- group.kitchen
living_room:
- light.Bowl
- light.Ceiling
- light.TV_back_light
kitchen:
- light.fan_bulb_1
- light.fan_bulb_2
children:
- device_tracker.child_1
- device_tracker.child_2
@@ -94,28 +103,39 @@ browser:
keyboard:
automation:
platform: state
alias: Sun starts shining
- alias: 'Rule 1 Light on in the evening'
trigger:
- platform: sun
event: sunset
offset: "-01:00:00"
- platform: state
entity_id: group.all_devices
state: home
condition:
- platform: state
entity_id: group.all_devices
state: home
- platform: time
after: "16:00:00"
before: "23:00:00"
action:
service: homeassistant.turn_on
entity_id: group.living_room
state_entity_id: sun.sun
# Next two are optional, omit to match all
state_from: below_horizon
state_to: above_horizon
- alias: 'Rule 2 - Away Mode'
execute_service: light.turn_off
service_entity_id: group.living_room
trigger:
- platform: state
entity_id: group.all_devices
state: 'not_home'
automation 2:
platform: time
alias: Beer o Clock
condition: use_trigger_values
action:
service: light.turn_off
entity_id: group.all_lights
time_hours: 16
time_minutes: 0
time_seconds: 0
execute_service: notify.notify
service_data:
message: It's 4, time for beer!
# Sensors need to be added into the configuration.yaml as sensor:, sensor 2:, sensor 3:, etc.
# Each sensor label should be unique or your sensors might not load correctly.
sensor:
platform: systemmonitor
@@ -135,6 +155,23 @@ sensor:
- type: 'process'
arg: 'octave-cli'
sensor 2:
platform: forecast
api_key: <register on Forecast.io for your PRIVATE API>
monitored_conditions:
- summary
- precip_type
- precip_intensity
- temperature
- dew_point
- wind_speed
- wind_bearing
- cloud_cover
- humidity
- pressure
- visibility
- ozone
script:
# Turns on the bedroom lights and then the living room lights 1 minute later
wakeup:
+1 -1
View File
@@ -12,7 +12,7 @@ Example component to target an entity_id to:
Configuration:
To use the Example custom component you will need to add the following to
your config/configuration.yaml
your configuration.yaml file.
example:
target: TARGET_ENTITY
+1 -3
View File
@@ -1,16 +1,14 @@
"""
custom_components.hello_world
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Implements the bare minimum that a component should implement.
Configuration:
To use the hello_word component you will need to add the following to your
config/configuration.yaml
configuration.yaml file.
hello_world:
"""
# The domain of your component. Should be equal to the name of your component
+1 -2
View File
@@ -1,7 +1,6 @@
"""
custom_components.mqtt_example
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Shows how to communicate with MQTT. Follows a topic on MQTT and updates the
state of an entity to the last message received on that topic.
@@ -12,7 +11,7 @@ example payload {"new_state": "some new state"}.
Configuration:
To use the mqtt_example component you will need to add the following to your
config/configuration.yaml
configuration.yaml file.
mqtt_example:
topic: home-assistant/mqtt_example
+81 -3
View File
@@ -77,6 +77,10 @@ def get_arguments():
'--open-ui',
action='store_true',
help='Open the webinterface in a browser')
parser.add_argument(
'--skip-pip',
action='store_true',
help='Skips pip install of required packages on startup')
parser.add_argument(
'-v', '--verbose',
action='store_true',
@@ -86,6 +90,23 @@ def get_arguments():
metavar='path_to_pid_file',
default=None,
help='Path to PID file useful for running as daemon')
parser.add_argument(
'--log-rotate-days',
type=int,
default=None,
help='Enables daily log rotation and keeps up to the specified days')
parser.add_argument(
'--install-osx',
action='store_true',
help='Installs as a service on OS X and loads on boot.')
parser.add_argument(
'--uninstall-osx',
action='store_true',
help='Uninstalls from OS X.')
parser.add_argument(
'--restart-osx',
action='store_true',
help='Restarts on OS X.')
if os.name != "nt":
parser.add_argument(
'--daemon',
@@ -143,6 +164,46 @@ def write_pid(pid_file):
sys.exit(1)
def install_osx():
""" Setup to run via launchd on OS X """
with os.popen('which hass') as inp:
hass_path = inp.read().strip()
with os.popen('whoami') as inp:
user = inp.read().strip()
cwd = os.path.dirname(__file__)
template_path = os.path.join(cwd, 'startup', 'launchd.plist')
with open(template_path, 'r', encoding='utf-8') as inp:
plist = inp.read()
plist = plist.replace("$HASS_PATH$", hass_path)
plist = plist.replace("$USER$", user)
path = os.path.expanduser("~/Library/LaunchAgents/org.homeassistant.plist")
try:
with open(path, 'w', encoding='utf-8') as outp:
outp.write(plist)
except IOError as err:
print('Unable to write to ' + path, err)
return
os.popen('launchctl load -w -F ' + path)
print("Home Assistant has been installed. \
Open it here: http://localhost:8123")
def uninstall_osx():
""" Unload from launchd on OS X """
path = os.path.expanduser("~/Library/LaunchAgents/org.homeassistant.plist")
os.popen('launchctl unload ' + path)
print("Home Assistant has been uninstalled.")
def main():
""" Starts Home Assistant. """
validate_python()
@@ -152,6 +213,18 @@ def main():
config_dir = os.path.join(os.getcwd(), args.config)
ensure_config_path(config_dir)
# os x launchd functions
if args.install_osx:
install_osx()
return
if args.uninstall_osx:
uninstall_osx()
return
if args.restart_osx:
uninstall_osx()
install_osx()
return
# daemon functions
if args.pid_file:
check_pid(args.pid_file)
@@ -161,15 +234,20 @@ def main():
write_pid(args.pid_file)
if args.demo_mode:
hass = bootstrap.from_config_dict({
config = {
'frontend': {},
'demo': {}
}, config_dir=config_dir, daemon=args.daemon, verbose=args.verbose)
}
hass = bootstrap.from_config_dict(
config, config_dir=config_dir, daemon=args.daemon,
verbose=args.verbose, skip_pip=args.skip_pip,
log_rotate_days=args.log_rotate_days)
else:
config_file = ensure_config_file(config_dir)
print('Config directory:', config_dir)
hass = bootstrap.from_config_file(
config_file, daemon=args.daemon, verbose=args.verbose)
config_file, daemon=args.daemon, verbose=args.verbose,
skip_pip=args.skip_pip, log_rotate_days=args.log_rotate_days)
if args.open_ui:
def open_browser(event):
+33 -15
View File
@@ -12,6 +12,7 @@ start by calling homeassistant.start_home_assistant(bus)
import os
import sys
import logging
import logging.handlers
from collections import defaultdict
import homeassistant.core as core
@@ -61,7 +62,7 @@ def setup_component(hass, domain, config=None):
def _handle_requirements(hass, component, name):
""" Installs requirements for component. """
if not hasattr(component, 'REQUIREMENTS'):
if hass.config.skip_pip or not hasattr(component, 'REQUIREMENTS'):
return True
for req in component.REQUIREMENTS:
@@ -122,6 +123,7 @@ def prepare_setup_platform(hass, config, domain, platform_name):
# Not found
if platform is None:
_LOGGER.error('Unable to find platform %s', platform_path)
return None
# Already loaded
@@ -151,7 +153,8 @@ def mount_local_lib_path(config_dir):
# 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, daemon=False):
verbose=False, daemon=False, skip_pip=False,
log_rotate_days=None):
"""
Tries to configure Home Assistant from a config dict.
@@ -167,7 +170,12 @@ def from_config_dict(config, hass=None, config_dir=None, enable_log=True,
process_ha_core_config(hass, config.get(core.DOMAIN, {}))
if enable_log:
enable_logging(hass, verbose, daemon)
enable_logging(hass, verbose, daemon, log_rotate_days)
hass.config.skip_pip = skip_pip
if skip_pip:
_LOGGER.warning('Skipping pip installation of required modules. '
'This may cause issues.')
_ensure_loader_prepared(hass)
@@ -178,8 +186,8 @@ def from_config_dict(config, hass=None, config_dir=None, enable_log=True,
dict, {key: value or {} for key, value in config.items()})
# Filter out the repeating and common config section [homeassistant]
components = (key for key in config.keys()
if ' ' not in key and key != core.DOMAIN)
components = set(key.split(' ')[0] for key in config.keys()
if key != core.DOMAIN)
if not core_components.setup(hass, config):
_LOGGER.error('Home Assistant core failed to initialize. '
@@ -196,7 +204,8 @@ def from_config_dict(config, hass=None, config_dir=None, enable_log=True,
return hass
def from_config_file(config_path, hass=None, verbose=False, daemon=False):
def from_config_file(config_path, hass=None, verbose=False, daemon=False,
skip_pip=True, log_rotate_days=None):
"""
Reads the configuration file and tries to start all the required
functionality. Will add functionality to 'hass' parameter if given,
@@ -210,14 +219,15 @@ def from_config_file(config_path, hass=None, verbose=False, daemon=False):
hass.config.config_dir = config_dir
mount_local_lib_path(config_dir)
enable_logging(hass, verbose, daemon)
enable_logging(hass, verbose, daemon, log_rotate_days)
config_dict = config_util.load_config_file(config_path)
return from_config_dict(config_dict, hass, enable_log=False)
return from_config_dict(config_dict, hass, enable_log=False,
skip_pip=skip_pip)
def enable_logging(hass, verbose=False, daemon=False):
def enable_logging(hass, verbose=False, daemon=False, log_rotate_days=None):
""" Setup the logging for home assistant. """
if not daemon:
logging.basicConfig(level=logging.INFO)
@@ -250,8 +260,12 @@ def enable_logging(hass, verbose=False, daemon=False):
if (err_path_exists and os.access(err_log_path, os.W_OK)) or \
(not err_path_exists and os.access(hass.config.config_dir, os.W_OK)):
err_handler = logging.FileHandler(
err_log_path, mode='w', delay=True)
if log_rotate_days:
err_handler = logging.handlers.TimedRotatingFileHandler(
err_log_path, when='midnight', backupCount=log_rotate_days)
else:
err_handler = logging.FileHandler(
err_log_path, mode='w', delay=True)
err_handler.setLevel(logging.INFO if verbose else logging.WARNING)
err_handler.setFormatter(
@@ -283,11 +297,15 @@ def process_ha_core_config(hass, config):
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')):
for key, attr, typ in ((CONF_LATITUDE, 'latitude', float),
(CONF_LONGITUDE, 'longitude', float),
(CONF_NAME, 'location_name', str)):
if key in config:
setattr(hac, attr, config[key])
try:
setattr(hac, attr, typ(config[key]))
except ValueError:
_LOGGER.error('Received invalid %s value for %s: %s',
typ.__name__, key, attr)
set_time_zone(config.get(CONF_TIME_ZONE))
@@ -0,0 +1,151 @@
"""
homeassistant.components.alarm_control_panel
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Component to interface with a alarm control panel.
"""
import logging
import os
from homeassistant.components import verisure
from homeassistant.const import (
ATTR_ENTITY_ID, SERVICE_ALARM_TRIGGER,
SERVICE_ALARM_DISARM, SERVICE_ALARM_ARM_HOME, SERVICE_ALARM_ARM_AWAY)
from homeassistant.config import load_yaml_config_file
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
DOMAIN = 'alarm_control_panel'
DEPENDENCIES = []
SCAN_INTERVAL = 30
ENTITY_ID_FORMAT = DOMAIN + '.{}'
# Maps discovered services to their platforms
DISCOVERY_PLATFORMS = {
verisure.DISCOVER_SENSORS: 'verisure'
}
SERVICE_TO_METHOD = {
SERVICE_ALARM_DISARM: 'alarm_disarm',
SERVICE_ALARM_ARM_HOME: 'alarm_arm_home',
SERVICE_ALARM_ARM_AWAY: 'alarm_arm_away',
SERVICE_ALARM_TRIGGER: 'alarm_trigger'
}
ATTR_CODE = 'code'
ATTR_CODE_FORMAT = 'code_format'
ATTR_TO_PROPERTY = [
ATTR_CODE,
ATTR_CODE_FORMAT
]
def setup(hass, config):
""" Track states and offer events for sensors. """
component = EntityComponent(
logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL,
DISCOVERY_PLATFORMS)
component.setup(config)
def alarm_service_handler(service):
""" Maps services to methods on Alarm. """
target_alarms = component.extract_from_service(service)
if ATTR_CODE not in service.data:
code = None
else:
code = service.data[ATTR_CODE]
method = SERVICE_TO_METHOD[service.service]
for alarm in target_alarms:
getattr(alarm, method)(code)
descriptions = load_yaml_config_file(
os.path.join(os.path.dirname(__file__), 'services.yaml'))
for service in SERVICE_TO_METHOD:
hass.services.register(DOMAIN, service, alarm_service_handler,
descriptions.get(service))
return True
def alarm_disarm(hass, code=None, entity_id=None):
""" Send the alarm the command for disarm. """
data = {}
if code:
data[ATTR_CODE] = code
if entity_id:
data[ATTR_ENTITY_ID] = entity_id
hass.services.call(DOMAIN, SERVICE_ALARM_DISARM, data)
def alarm_arm_home(hass, code=None, entity_id=None):
""" Send the alarm the command for arm home. """
data = {}
if code:
data[ATTR_CODE] = code
if entity_id:
data[ATTR_ENTITY_ID] = entity_id
hass.services.call(DOMAIN, SERVICE_ALARM_ARM_HOME, data)
def alarm_arm_away(hass, code=None, entity_id=None):
""" Send the alarm the command for arm away. """
data = {}
if code:
data[ATTR_CODE] = code
if entity_id:
data[ATTR_ENTITY_ID] = entity_id
hass.services.call(DOMAIN, SERVICE_ALARM_ARM_AWAY, data)
def alarm_trigger(hass, code=None, entity_id=None):
""" Send the alarm the command for trigger. """
data = {}
if code:
data[ATTR_CODE] = code
if entity_id:
data[ATTR_ENTITY_ID] = entity_id
hass.services.call(DOMAIN, SERVICE_ALARM_TRIGGER, data)
# pylint: disable=no-self-use
class AlarmControlPanel(Entity):
""" ABC for alarm control devices. """
@property
def code_format(self):
""" regex for code format or None if no code is required. """
return None
def alarm_disarm(self, code=None):
""" Send disarm command. """
raise NotImplementedError()
def alarm_arm_home(self, code=None):
""" Send arm home command. """
raise NotImplementedError()
def alarm_arm_away(self, code=None):
""" Send arm away command. """
raise NotImplementedError()
def alarm_trigger(self, code=None):
""" Send alarm trigger command. """
raise NotImplementedError()
@property
def state_attributes(self):
""" Return the state attributes. """
state_attr = {
ATTR_CODE_FORMAT: self.code_format,
}
return state_attr
@@ -0,0 +1,149 @@
"""
homeassistant.components.alarm_control_panel.manual
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for manual alarms.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.manual.html
"""
import logging
import datetime
import homeassistant.components.alarm_control_panel as alarm
from homeassistant.helpers.event import track_point_in_time
import homeassistant.util.dt as dt_util
from homeassistant.const import (
STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_AWAY,
STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED)
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = []
DEFAULT_ALARM_NAME = 'HA Alarm'
DEFAULT_PENDING_TIME = 60
DEFAULT_TRIGGER_TIME = 120
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the manual alarm platform. """
add_devices([ManualAlarm(
hass,
config.get('name', DEFAULT_ALARM_NAME),
config.get('code'),
config.get('pending_time', DEFAULT_PENDING_TIME),
config.get('trigger_time', DEFAULT_TRIGGER_TIME),
)])
# pylint: disable=too-many-arguments, too-many-instance-attributes
# pylint: disable=abstract-method
class ManualAlarm(alarm.AlarmControlPanel):
"""
Represents an alarm status.
When armed, will be pending for 'pending_time', after that armed.
When triggered, will be pending for 'trigger_time'. After that will be
triggered for 'trigger_time', after that we return to disarmed.
"""
def __init__(self, hass, name, code, pending_time, trigger_time):
self._state = STATE_ALARM_DISARMED
self._hass = hass
self._name = name
self._code = str(code) if code else None
self._pending_time = datetime.timedelta(seconds=pending_time)
self._trigger_time = datetime.timedelta(seconds=trigger_time)
self._state_ts = None
@property
def should_poll(self):
""" No polling needed. """
return False
@property
def name(self):
""" Returns the name of the device. """
return self._name
@property
def state(self):
""" Returns the state of the device. """
if self._state in (STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_AWAY) and \
self._pending_time and self._state_ts + self._pending_time > \
dt_util.utcnow():
return STATE_ALARM_PENDING
if self._state == STATE_ALARM_TRIGGERED and self._trigger_time:
if self._state_ts + self._pending_time > dt_util.utcnow():
return STATE_ALARM_PENDING
elif (self._state_ts + self._pending_time +
self._trigger_time) < dt_util.utcnow():
return STATE_ALARM_DISARMED
return self._state
@property
def code_format(self):
""" One or more characters. """
return None if self._code is None else '.+'
def alarm_disarm(self, code=None):
""" Send disarm command. """
if not self._validate_code(code, STATE_ALARM_DISARMED):
return
self._state = STATE_ALARM_DISARMED
self._state_ts = dt_util.utcnow()
self.update_ha_state()
def alarm_arm_home(self, code=None):
""" Send arm home command. """
if not self._validate_code(code, STATE_ALARM_ARMED_HOME):
return
self._state = STATE_ALARM_ARMED_HOME
self._state_ts = dt_util.utcnow()
self.update_ha_state()
if self._pending_time:
track_point_in_time(
self._hass, self.update_ha_state,
self._state_ts + self._pending_time)
def alarm_arm_away(self, code=None):
""" Send arm away command. """
if not self._validate_code(code, STATE_ALARM_ARMED_AWAY):
return
self._state = STATE_ALARM_ARMED_AWAY
self._state_ts = dt_util.utcnow()
self.update_ha_state()
if self._pending_time:
track_point_in_time(
self._hass, self.update_ha_state,
self._state_ts + self._pending_time)
def alarm_trigger(self, code=None):
""" Send alarm trigger command. No code needed. """
self._state = STATE_ALARM_TRIGGERED
self._state_ts = dt_util.utcnow()
self.update_ha_state()
if self._trigger_time:
track_point_in_time(
self._hass, self.update_ha_state,
self._state_ts + self._pending_time)
track_point_in_time(
self._hass, self.update_ha_state,
self._state_ts + self._pending_time + self._trigger_time)
def _validate_code(self, code, state):
""" Validate given code. """
check = self._code is None or code == self._code
if not check:
_LOGGER.warning('Invalid code given for %s', state)
return check
@@ -0,0 +1,127 @@
"""
homeassistant.components.alarm_control_panel.mqtt
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This platform enables the possibility to control a MQTT alarm.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.mqtt.html
"""
import logging
import homeassistant.components.mqtt as mqtt
import homeassistant.components.alarm_control_panel as alarm
from homeassistant.const import (
STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_AWAY,
STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, STATE_UNKNOWN)
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = "MQTT Alarm"
DEFAULT_QOS = 0
DEFAULT_PAYLOAD_DISARM = "DISARM"
DEFAULT_PAYLOAD_ARM_HOME = "ARM_HOME"
DEFAULT_PAYLOAD_ARM_AWAY = "ARM_AWAY"
DEPENDENCIES = ['mqtt']
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the MQTT platform. """
if config.get('state_topic') is None:
_LOGGER.error("Missing required variable: state_topic")
return False
if config.get('command_topic') is None:
_LOGGER.error("Missing required variable: command_topic")
return False
add_devices([MqttAlarm(
hass,
config.get('name', DEFAULT_NAME),
config.get('state_topic'),
config.get('command_topic'),
config.get('qos', DEFAULT_QOS),
config.get('payload_disarm', DEFAULT_PAYLOAD_DISARM),
config.get('payload_arm_home', DEFAULT_PAYLOAD_ARM_HOME),
config.get('payload_arm_away', DEFAULT_PAYLOAD_ARM_AWAY),
config.get('code'))])
# pylint: disable=too-many-arguments, too-many-instance-attributes
# pylint: disable=abstract-method
class MqttAlarm(alarm.AlarmControlPanel):
""" represents a MQTT alarm status within home assistant. """
def __init__(self, hass, name, state_topic, command_topic, qos,
payload_disarm, payload_arm_home, payload_arm_away, code):
self._state = STATE_UNKNOWN
self._hass = hass
self._name = name
self._state_topic = state_topic
self._command_topic = command_topic
self._qos = qos
self._payload_disarm = payload_disarm
self._payload_arm_home = payload_arm_home
self._payload_arm_away = payload_arm_away
self._code = str(code) if code else None
def message_received(topic, payload, qos):
""" A new MQTT message has been received. """
if payload not in (STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_AWAY, STATE_ALARM_PENDING,
STATE_ALARM_TRIGGERED):
_LOGGER.warning('Received unexpected payload: %s', payload)
return
self._state = payload
self.update_ha_state()
mqtt.subscribe(hass, self._state_topic, message_received, self._qos)
@property
def should_poll(self):
""" No polling needed """
return False
@property
def name(self):
""" Returns the name of the device. """
return self._name
@property
def state(self):
""" Returns the state of the device. """
return self._state
@property
def code_format(self):
""" One or more characters if code is defined """
return None if self._code is None else '.+'
def alarm_disarm(self, code=None):
""" Send disarm command. """
if not self._validate_code(code, 'disarming'):
return
mqtt.publish(self.hass, self._command_topic,
self._payload_disarm, self._qos)
def alarm_arm_home(self, code=None):
""" Send arm home command. """
if not self._validate_code(code, 'arming home'):
return
mqtt.publish(self.hass, self._command_topic,
self._payload_arm_home, self._qos)
def alarm_arm_away(self, code=None):
""" Send arm away command. """
if not self._validate_code(code, 'arming away'):
return
mqtt.publish(self.hass, self._command_topic,
self._payload_arm_away, self._qos)
def _validate_code(self, code, state):
""" Validate given code. """
check = self._code is None or code == self._code
if not check:
_LOGGER.warning('Wrong code entered for %s', state)
return check
@@ -0,0 +1,94 @@
"""
homeassistant.components.alarm_control_panel.verisure
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Interfaces with Verisure alarm control panel.
"""
import logging
import homeassistant.components.verisure as verisure
import homeassistant.components.alarm_control_panel as alarm
from homeassistant.const import (
STATE_UNKNOWN,
STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_AWAY)
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the Verisure platform. """
if not verisure.MY_PAGES:
_LOGGER.error('A connection has not been made to Verisure mypages.')
return False
alarms = []
alarms.extend([
VerisureAlarm(value)
for value in verisure.get_alarm_status().values()
if verisure.SHOW_ALARM
])
add_devices(alarms)
# pylint: disable=abstract-method
class VerisureAlarm(alarm.AlarmControlPanel):
""" Represents a Verisure alarm status. """
def __init__(self, alarm_status):
self._id = alarm_status.id
self._device = verisure.MY_PAGES.DEVICE_ALARM
self._state = STATE_UNKNOWN
@property
def name(self):
""" Returns the name of the device. """
return 'Alarm {}'.format(self._id)
@property
def state(self):
""" Returns the state of the device. """
return self._state
@property
def code_format(self):
""" Four digit code required. """
return '^\\d{4}$'
def update(self):
""" Update alarm status """
verisure.update()
if verisure.STATUS[self._device][self._id].status == 'unarmed':
self._state = STATE_ALARM_DISARMED
elif verisure.STATUS[self._device][self._id].status == 'armedhome':
self._state = STATE_ALARM_ARMED_HOME
elif verisure.STATUS[self._device][self._id].status == 'armedaway':
self._state = STATE_ALARM_ARMED_AWAY
elif verisure.STATUS[self._device][self._id].status != 'pending':
_LOGGER.error(
'Unknown alarm state %s',
verisure.STATUS[self._device][self._id].status)
def alarm_disarm(self, code=None):
""" Send disarm command. """
verisure.MY_PAGES.set_alarm_status(
code,
verisure.MY_PAGES.ALARM_DISARMED)
_LOGGER.warning('disarming')
def alarm_arm_home(self, code=None):
""" Send arm home command. """
verisure.MY_PAGES.set_alarm_status(
code,
verisure.MY_PAGES.ALARM_ARMED_HOME)
_LOGGER.warning('arming home')
def alarm_arm_away(self, code=None):
""" Send arm away command. """
verisure.MY_PAGES.set_alarm_status(
code,
verisure.MY_PAGES.ALARM_ARMED_AWAY)
_LOGGER.warning('arming away')
+9 -2
View File
@@ -1,8 +1,10 @@
"""
homeassistant.components.api
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides a Rest API for Home Assistant.
For more details about the RESTful API, please refer to the documentation at
https://home-assistant.io/developers/api.html
"""
import re
import logging
@@ -103,6 +105,10 @@ def _handle_get_api_stream(handler, path_match, data):
write_lock = threading.Lock()
block = threading.Event()
restrict = data.get('restrict')
if restrict:
restrict = restrict.split(',')
def write_message(payload):
""" Writes a message to the output. """
with write_lock:
@@ -118,7 +124,8 @@ def _handle_get_api_stream(handler, path_match, data):
""" Forwards events to the open request. """
nonlocal gracefully_closed
if block.is_set() or event.event_type == EVENT_TIME_CHANGED:
if block.is_set() or event.event_type == EVENT_TIME_CHANGED or \
restrict and event.event_type not in restrict:
return
elif event.event_type == EVENT_HOMEASSISTANT_STOP:
gracefully_closed = True
+2 -20
View File
@@ -4,26 +4,8 @@ components.arduino
Arduino component that connects to a directly attached Arduino board which
runs with the Firmata firmware.
Configuration:
To use the Arduino board you will need to add something like the following
to your config/configuration.yaml
arduino:
port: /dev/ttyACM0
Variables:
port
*Required
The port where is your board connected to your Home Assistant system.
If you are using an original Arduino the port will be named ttyACM*. The exact
number can be determined with 'ls /dev/ttyACM*' or check your 'dmesg'/
'journalctl -f' output. Keep in mind that Arduino clones are often using a
different name for the port (e.g. '/dev/ttyUSB*').
A word of caution: The Arduino is not storing states. This means that with
every initialization the pins are set to off/low.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/arduino.html
"""
import logging
+187 -38
View File
@@ -7,68 +7,217 @@ Allows to setup simple automation rules via the config file.
import logging
from homeassistant.bootstrap import prepare_setup_platform
from homeassistant.helpers import config_per_platform
from homeassistant.util import split_entity_id
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.const import ATTR_ENTITY_ID, CONF_PLATFORM
from homeassistant.components import logbook
DOMAIN = "automation"
DOMAIN = 'automation'
DEPENDENCIES = ["group"]
DEPENDENCIES = ['group']
CONF_ALIAS = "alias"
CONF_SERVICE = "execute_service"
CONF_SERVICE_ENTITY_ID = "service_entity_id"
CONF_SERVICE_DATA = "service_data"
CONF_ALIAS = 'alias'
CONF_SERVICE = 'service'
CONF_SERVICE_ENTITY_ID = 'entity_id'
CONF_SERVICE_DATA = 'data'
CONF_CONDITION = 'condition'
CONF_ACTION = 'action'
CONF_TRIGGER = 'trigger'
CONF_CONDITION_TYPE = 'condition_type'
CONDITION_USE_TRIGGER_VALUES = 'use_trigger_values'
CONDITION_TYPE_AND = 'and'
CONDITION_TYPE_OR = 'or'
DEFAULT_CONDITION_TYPE = CONDITION_TYPE_AND
_LOGGER = logging.getLogger(__name__)
def setup(hass, config):
""" Sets up automation. """
success = False
config_key = DOMAIN
found = 1
for p_type, p_config in config_per_platform(config, DOMAIN, _LOGGER):
platform = prepare_setup_platform(hass, config, DOMAIN, p_type)
while config_key in config:
# check for one block syntax
if isinstance(config[config_key], dict):
config_block = _migrate_old_config(config[config_key])
name = config_block.get(CONF_ALIAS, config_key)
_setup_automation(hass, config_block, name, config)
if platform is None:
_LOGGER.error("Unknown automation platform specified: %s", p_type)
continue
# check for multiple block syntax
elif isinstance(config[config_key], list):
for list_no, config_block in enumerate(config[config_key]):
name = config_block.get(CONF_ALIAS,
"{}, {}".format(config_key, list_no))
_setup_automation(hass, config_block, name, config)
if platform.register(hass, p_config, _get_action(hass, p_config)):
_LOGGER.info(
"Initialized %s rule %s", p_type, p_config.get(CONF_ALIAS, ""))
success = True
# any scalar value is incorrect
else:
_LOGGER.error(
"Error setting up rule %s", p_config.get(CONF_ALIAS, ""))
_LOGGER.error('Error in config in section %s.', config_key)
return success
found += 1
config_key = "{} {}".format(DOMAIN, found)
return True
def _get_action(hass, config):
def _setup_automation(hass, config_block, name, config):
""" Setup one instance of automation """
action = _get_action(hass, config_block.get(CONF_ACTION, {}), name)
if action is None:
return False
if CONF_CONDITION in config_block or CONF_CONDITION_TYPE in config_block:
action = _process_if(hass, config, config_block, action)
if action is None:
return False
_process_trigger(hass, config, config_block.get(CONF_TRIGGER, []), name,
action)
return True
def _get_action(hass, config, name):
""" Return an action based on a config. """
if CONF_SERVICE not in config:
_LOGGER.error('Error setting up %s, no action specified.', name)
return None
def action():
""" Action to be executed. """
_LOGGER.info("Executing rule %s", config.get(CONF_ALIAS, ""))
_LOGGER.info('Executing %s', name)
logbook.log_entry(hass, name, 'has been triggered', DOMAIN)
if CONF_SERVICE in config:
domain, service = split_entity_id(config[CONF_SERVICE])
domain, service = split_entity_id(config[CONF_SERVICE])
service_data = config.get(CONF_SERVICE_DATA, {})
service_data = config.get(CONF_SERVICE_DATA, {})
if not isinstance(service_data, dict):
_LOGGER.error("%s should be a dictionary", CONF_SERVICE_DATA)
service_data = {}
if not isinstance(service_data, dict):
_LOGGER.error("%s should be a dictionary", CONF_SERVICE_DATA)
service_data = {}
if CONF_SERVICE_ENTITY_ID in config:
try:
service_data[ATTR_ENTITY_ID] = \
config[CONF_SERVICE_ENTITY_ID].split(",")
except AttributeError:
service_data[ATTR_ENTITY_ID] = \
config[CONF_SERVICE_ENTITY_ID]
if CONF_SERVICE_ENTITY_ID in config:
try:
service_data[ATTR_ENTITY_ID] = \
config[CONF_SERVICE_ENTITY_ID].split(",")
except AttributeError:
service_data[ATTR_ENTITY_ID] = \
config[CONF_SERVICE_ENTITY_ID]
hass.services.call(domain, service, service_data)
hass.services.call(domain, service, service_data)
return action
def _migrate_old_config(config):
""" Migrate old config to new. """
if CONF_PLATFORM not in config:
return config
_LOGGER.warning(
'You are using an old configuration format. Please upgrade: '
'https://home-assistant.io/components/automation.html')
new_conf = {
CONF_TRIGGER: dict(config),
CONF_CONDITION: config.get('if', []),
CONF_ACTION: dict(config),
}
for cat, key, new_key in (('trigger', 'mqtt_topic', 'topic'),
('trigger', 'mqtt_payload', 'payload'),
('trigger', 'state_entity_id', 'entity_id'),
('trigger', 'state_before', 'before'),
('trigger', 'state_after', 'after'),
('trigger', 'state_to', 'to'),
('trigger', 'state_from', 'from'),
('trigger', 'state_hours', 'hours'),
('trigger', 'state_minutes', 'minutes'),
('trigger', 'state_seconds', 'seconds'),
('action', 'execute_service', 'service'),
('action', 'service_entity_id', 'entity_id'),
('action', 'service_data', 'data')):
if key in new_conf[cat]:
new_conf[cat][new_key] = new_conf[cat].pop(key)
return new_conf
def _process_if(hass, config, p_config, action):
""" Processes if checks. """
cond_type = p_config.get(CONF_CONDITION_TYPE,
DEFAULT_CONDITION_TYPE).lower()
if_configs = p_config.get(CONF_CONDITION)
use_trigger = if_configs == CONDITION_USE_TRIGGER_VALUES
if use_trigger:
if_configs = p_config[CONF_TRIGGER]
if isinstance(if_configs, dict):
if_configs = [if_configs]
checks = []
for if_config in if_configs:
platform = _resolve_platform('if_action', hass, config,
if_config.get(CONF_PLATFORM))
if platform is None:
continue
check = platform.if_action(hass, if_config)
# Invalid conditions are allowed if we base it on trigger
if check is None and not use_trigger:
return None
checks.append(check)
if cond_type == CONDITION_TYPE_AND:
def if_action():
""" AND all conditions. """
if all(check() for check in checks):
action()
else:
def if_action():
""" OR all conditions. """
if any(check() for check in checks):
action()
return if_action
def _process_trigger(hass, config, trigger_configs, name, action):
""" Setup triggers. """
if isinstance(trigger_configs, dict):
trigger_configs = [trigger_configs]
for conf in trigger_configs:
platform = _resolve_platform('trigger', hass, config,
conf.get(CONF_PLATFORM))
if platform is None:
continue
if platform.trigger(hass, conf, action):
_LOGGER.info("Initialized rule %s", name)
else:
_LOGGER.error("Error setting up rule %s", name)
def _resolve_platform(method, hass, config, platform):
""" Find automation platform. """
if platform is None:
return None
platform = prepare_setup_platform(hass, config, DOMAIN, platform)
if platform is None or not hasattr(platform, method):
_LOGGER.error("Unknown automation platform specified for %s: %s",
method, platform)
return None
return platform
+7 -4
View File
@@ -1,8 +1,10 @@
"""
homeassistant.components.automation.event
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Offers event listening automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation.html#event-trigger
"""
import logging
@@ -12,7 +14,7 @@ CONF_EVENT_DATA = "event_data"
_LOGGER = logging.getLogger(__name__)
def register(hass, config, action):
def trigger(hass, config, action):
""" Listen for events based on config. """
event_type = config.get(CONF_EVENT_TYPE)
@@ -20,11 +22,12 @@ def register(hass, config, action):
_LOGGER.error("Missing configuration key %s", CONF_EVENT_TYPE)
return False
event_data = config.get(CONF_EVENT_DATA, {})
event_data = config.get(CONF_EVENT_DATA)
def handle_event(event):
""" Listens for events and calls the action when data matches. """
if event_data == event.data:
if not event_data or all(val == event.data.get(key) for key, val
in event_data.items()):
action()
hass.bus.listen(event_type, handle_event)
+6 -4
View File
@@ -1,8 +1,10 @@
"""
homeassistant.components.automation.mqtt
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Offers MQTT listening automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation.html#mqtt-trigger
"""
import logging
@@ -10,11 +12,11 @@ import homeassistant.components.mqtt as mqtt
DEPENDENCIES = ['mqtt']
CONF_TOPIC = 'mqtt_topic'
CONF_PAYLOAD = 'mqtt_payload'
CONF_TOPIC = 'topic'
CONF_PAYLOAD = 'payload'
def register(hass, config, action):
def trigger(hass, config, action):
""" Listen for state changes based on `config`. """
topic = config.get(CONF_TOPIC)
payload = config.get(CONF_PAYLOAD)
@@ -0,0 +1,93 @@
"""
homeassistant.components.automation.numeric_state
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Offers numeric state listening automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation.html#numeric-state-trigger
"""
import logging
from homeassistant.helpers.event import track_state_change
CONF_ENTITY_ID = "entity_id"
CONF_BELOW = "below"
CONF_ABOVE = "above"
_LOGGER = logging.getLogger(__name__)
def trigger(hass, config, action):
""" Listen for state changes based on `config`. """
entity_id = config.get(CONF_ENTITY_ID)
if entity_id is None:
_LOGGER.error("Missing configuration key %s", CONF_ENTITY_ID)
return False
below = config.get(CONF_BELOW)
above = config.get(CONF_ABOVE)
if below is None and above is None:
_LOGGER.error("Missing configuration key."
" One of %s or %s is required",
CONF_BELOW, CONF_ABOVE)
return False
# pylint: disable=unused-argument
def state_automation_listener(entity, from_s, to_s):
""" Listens for state changes and calls action. """
# Fire action if we go from outside range into range
if _in_range(to_s.state, above, below) and \
(from_s is None or not _in_range(from_s.state, above, below)):
action()
track_state_change(
hass, entity_id, state_automation_listener)
return True
def if_action(hass, config):
""" Wraps action method with state based condition. """
entity_id = config.get(CONF_ENTITY_ID)
if entity_id is None:
_LOGGER.error("Missing configuration key %s", CONF_ENTITY_ID)
return None
below = config.get(CONF_BELOW)
above = config.get(CONF_ABOVE)
if below is None and above is None:
_LOGGER.error("Missing configuration key."
" One of %s or %s is required",
CONF_BELOW, CONF_ABOVE)
return None
def if_numeric_state():
""" Test numeric state condition. """
state = hass.states.get(entity_id)
return state is not None and _in_range(state.state, above, below)
return if_numeric_state
def _in_range(value, range_start, range_end):
""" Checks if value is inside the range """
try:
value = float(value)
except ValueError:
_LOGGER.warn("Missing value in numeric check")
return False
if range_start is not None and range_end is not None:
return float(range_start) <= value < float(range_end)
elif range_end is not None:
return value < float(range_end)
else:
return float(range_start) <= value
+35 -7
View File
@@ -1,8 +1,10 @@
"""
homeassistant.components.automation.state
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Offers state listening automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation.html#state-trigger
"""
import logging
@@ -10,22 +12,28 @@ from homeassistant.helpers.event import track_state_change
from homeassistant.const import MATCH_ALL
CONF_ENTITY_ID = "state_entity_id"
CONF_FROM = "state_from"
CONF_TO = "state_to"
CONF_ENTITY_ID = "entity_id"
CONF_FROM = "from"
CONF_TO = "to"
CONF_STATE = "state"
def register(hass, config, action):
def trigger(hass, config, action):
""" Listen for state changes based on `config`. """
entity_id = config.get(CONF_ENTITY_ID)
if entity_id is None:
logging.getLogger(__name__).error(
"Missing configuration key %s", CONF_ENTITY_ID)
"Missing trigger configuration key %s", CONF_ENTITY_ID)
return False
from_state = config.get(CONF_FROM, MATCH_ALL)
to_state = config.get(CONF_TO, MATCH_ALL)
to_state = config.get(CONF_TO) or config.get(CONF_STATE) or MATCH_ALL
if isinstance(from_state, bool) or isinstance(to_state, bool):
logging.getLogger(__name__).error(
'Config error. Surround to/from values with quotes.')
return False
def state_automation_listener(entity, from_s, to_s):
""" Listens for state changes and calls action. """
@@ -35,3 +43,23 @@ def register(hass, config, action):
hass, entity_id, state_automation_listener, from_state, to_state)
return True
def if_action(hass, config):
""" Wraps action method with state based condition. """
entity_id = config.get(CONF_ENTITY_ID)
state = config.get(CONF_STATE)
if entity_id is None or state is None:
logging.getLogger(__name__).error(
"Missing if-condition configuration key %s or %s", CONF_ENTITY_ID,
CONF_STATE)
return None
state = str(state)
def if_state():
""" Test if condition. """
return hass.states.is_state(entity_id, state)
return if_state
+105
View File
@@ -0,0 +1,105 @@
"""
homeassistant.components.automation.sun
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Offers sun based automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation.html#sun-trigger
"""
import logging
from datetime import timedelta
from homeassistant.components import sun
from homeassistant.helpers.event import track_point_in_utc_time
import homeassistant.util.dt as dt_util
DEPENDENCIES = ['sun']
CONF_OFFSET = 'offset'
CONF_EVENT = 'event'
EVENT_SUNSET = 'sunset'
EVENT_SUNRISE = 'sunrise'
_LOGGER = logging.getLogger(__name__)
def trigger(hass, config, action):
""" Listen for events based on config. """
event = config.get(CONF_EVENT)
if event is None:
_LOGGER.error("Missing configuration key %s", CONF_EVENT)
return False
event = event.lower()
if event not in (EVENT_SUNRISE, EVENT_SUNSET):
_LOGGER.error("Invalid value for %s: %s", CONF_EVENT, event)
return False
if CONF_OFFSET in config:
raw_offset = config.get(CONF_OFFSET)
negative_offset = False
if raw_offset.startswith('-'):
negative_offset = True
raw_offset = raw_offset[1:]
try:
(hour, minute, second) = [int(x) for x in raw_offset.split(':')]
except ValueError:
_LOGGER.error('Could not parse offset %s', raw_offset)
return False
offset = timedelta(hours=hour, minutes=minute, seconds=second)
if negative_offset:
offset *= -1
else:
offset = timedelta(0)
# Do something to call action
if event == EVENT_SUNRISE:
trigger_sunrise(hass, action, offset)
else:
trigger_sunset(hass, action, offset)
return True
def trigger_sunrise(hass, action, offset):
""" Trigger action at next sun rise. """
def next_rise():
""" Returns next sunrise. """
next_time = sun.next_rising_utc(hass) + offset
while next_time < dt_util.utcnow():
next_time = next_time + timedelta(days=1)
return next_time
def sunrise_automation_listener(now):
""" Called when it's time for action. """
track_point_in_utc_time(hass, sunrise_automation_listener, next_rise())
action()
track_point_in_utc_time(hass, sunrise_automation_listener, next_rise())
def trigger_sunset(hass, action, offset):
""" Trigger action at next sun set. """
def next_set():
""" Returns next sunrise. """
next_time = sun.next_setting_utc(hass) + offset
while next_time < dt_util.utcnow():
next_time = next_time + timedelta(days=1)
return next_time
def sunset_automation_listener(now):
""" Called when it's time for action. """
track_point_in_utc_time(hass, sunset_automation_listener, next_set())
action()
track_point_in_utc_time(hass, sunset_automation_listener, next_set())
+87 -8
View File
@@ -1,22 +1,46 @@
"""
homeassistant.components.automation.time
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Offers time listening automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation.html#time-trigger
"""
import logging
from homeassistant.util import convert
import homeassistant.util.dt as dt_util
from homeassistant.helpers.event import track_time_change
CONF_HOURS = "time_hours"
CONF_MINUTES = "time_minutes"
CONF_SECONDS = "time_seconds"
CONF_HOURS = "hours"
CONF_MINUTES = "minutes"
CONF_SECONDS = "seconds"
CONF_BEFORE = "before"
CONF_AFTER = "after"
CONF_WEEKDAY = "weekday"
WEEKDAYS = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']
_LOGGER = logging.getLogger(__name__)
def register(hass, config, action):
def trigger(hass, config, action):
""" Listen for state changes based on `config`. """
hours = convert(config.get(CONF_HOURS), int)
minutes = convert(config.get(CONF_MINUTES), int)
seconds = convert(config.get(CONF_SECONDS), int)
if CONF_AFTER in config:
after = dt_util.parse_time_str(config[CONF_AFTER])
if after is None:
_error_time(config[CONF_AFTER], CONF_AFTER)
return False
hours, minutes, seconds = after.hour, after.minute, after.second
elif (CONF_HOURS in config or CONF_MINUTES in config
or CONF_SECONDS in config):
hours = convert(config.get(CONF_HOURS), int)
minutes = convert(config.get(CONF_MINUTES), int)
seconds = convert(config.get(CONF_SECONDS), int)
else:
_LOGGER.error('One of %s, %s, %s OR %s needs to be specified',
CONF_HOURS, CONF_MINUTES, CONF_SECONDS, CONF_AFTER)
return False
def time_automation_listener(now):
""" Listens for time changes and calls action. """
@@ -26,3 +50,58 @@ def register(hass, config, action):
hour=hours, minute=minutes, second=seconds)
return True
def if_action(hass, config):
""" Wraps action method with time based condition. """
before = config.get(CONF_BEFORE)
after = config.get(CONF_AFTER)
weekday = config.get(CONF_WEEKDAY)
if before is None and after is None and weekday is None:
logging.getLogger(__name__).error(
"Missing if-condition configuration key %s, %s or %s",
CONF_BEFORE, CONF_AFTER, CONF_WEEKDAY)
return None
if before is not None:
before = dt_util.parse_time_str(before)
if before is None:
_error_time(before, CONF_BEFORE)
return None
if after is not None:
after = dt_util.parse_time_str(after)
if after is None:
_error_time(after, CONF_AFTER)
return None
def time_if():
""" Validate time based if-condition """
now = dt_util.now()
if before is not None and now > now.replace(hour=before.hour,
minute=before.minute):
return False
if after is not None and now < now.replace(hour=after.hour,
minute=after.minute):
return False
if weekday is not None:
now_weekday = WEEKDAYS[now.weekday()]
if isinstance(weekday, str) and weekday != now_weekday or \
now_weekday not in weekday:
return False
return True
return time_if
def _error_time(value, key):
""" Helper method to print error. """
_LOGGER.error(
"Received invalid value for '%s': %s", key, value)
if isinstance(value, int):
_LOGGER.error('Make sure you wrap time values in quotes')
@@ -0,0 +1,87 @@
"""
homeassistant.components.automation.zone
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Offers zone automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation.html#zone-trigger
"""
import logging
from homeassistant.components import zone
from homeassistant.helpers.event import track_state_change
from homeassistant.const import (
ATTR_GPS_ACCURACY, ATTR_LATITUDE, ATTR_LONGITUDE, MATCH_ALL)
CONF_ENTITY_ID = "entity_id"
CONF_ZONE = "zone"
CONF_EVENT = "event"
EVENT_ENTER = "enter"
EVENT_LEAVE = "leave"
DEFAULT_EVENT = EVENT_ENTER
def trigger(hass, config, action):
""" Listen for state changes based on `config`. """
entity_id = config.get(CONF_ENTITY_ID)
zone_entity_id = config.get(CONF_ZONE)
if entity_id is None or zone_entity_id is None:
logging.getLogger(__name__).error(
"Missing trigger configuration key %s or %s", CONF_ENTITY_ID,
CONF_ZONE)
return False
event = config.get(CONF_EVENT, DEFAULT_EVENT)
def zone_automation_listener(entity, from_s, to_s):
""" Listens for state changes and calls action. """
if from_s and None in (from_s.attributes.get(ATTR_LATITUDE),
from_s.attributes.get(ATTR_LONGITUDE)) or \
None in (to_s.attributes.get(ATTR_LATITUDE),
to_s.attributes.get(ATTR_LONGITUDE)):
return
from_match = _in_zone(hass, zone_entity_id, from_s) if from_s else None
to_match = _in_zone(hass, zone_entity_id, to_s)
if event == EVENT_ENTER and not from_match and to_match or \
event == EVENT_LEAVE and from_match and not to_match:
action()
track_state_change(
hass, entity_id, zone_automation_listener, MATCH_ALL, MATCH_ALL)
return True
def if_action(hass, config):
""" Wraps action method with zone based condition. """
entity_id = config.get(CONF_ENTITY_ID)
zone_entity_id = config.get(CONF_ZONE)
if entity_id is None or zone_entity_id is None:
logging.getLogger(__name__).error(
"Missing condition configuration key %s or %s", CONF_ENTITY_ID,
CONF_ZONE)
return False
def if_in_zone():
""" Test if condition. """
return _in_zone(hass, zone_entity_id, hass.states.get(entity_id))
return if_in_zone
def _in_zone(hass, zone_entity_id, state):
""" Check if state is in zone. """
if not state or None in (state.attributes.get(ATTR_LATITUDE),
state.attributes.get(ATTR_LONGITUDE)):
return False
zone_state = hass.states.get(zone_entity_id)
return zone_state and zone.in_zone(
zone_state, state.attributes.get(ATTR_LATITUDE),
state.attributes.get(ATTR_LONGITUDE),
state.attributes.get(ATTR_GPS_ACCURACY, 0))
+69
View File
@@ -0,0 +1,69 @@
"""
homeassistant.components.camera.foscam
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This component provides basic support for Foscam IP cameras.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.foscam.html
"""
import logging
from homeassistant.helpers import validate_config
from homeassistant.components.camera import DOMAIN
from homeassistant.components.camera import Camera
import requests
import re
_LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Adds a Foscam IP Camera. """
if not validate_config({DOMAIN: config},
{DOMAIN: ['username', 'password', 'ip']}, _LOGGER):
return None
add_devices_callback([FoscamCamera(config)])
# pylint: disable=too-many-instance-attributes
class FoscamCamera(Camera):
""" An implementation of a Foscam IP camera. """
def __init__(self, device_info):
super(FoscamCamera, self).__init__()
ip_address = device_info.get('ip')
port = device_info.get('port', 88)
self._base_url = 'http://' + ip_address + ':' + str(port) + '/'
self._username = device_info.get('username')
self._password = device_info.get('password')
self._snap_picture_url = self._base_url \
+ 'cgi-bin/CGIProxy.fcgi?cmd=snapPicture&usr=' \
+ self._username + '&pwd=' + self._password
self._name = device_info.get('name', 'Foscam Camera')
_LOGGER.info('Using the following URL for %s: %s',
self._name, self._snap_picture_url)
def camera_image(self):
""" Return a still image reponse from the camera. """
# send the request to snap a picture
response = requests.get(self._snap_picture_url)
# parse the response to find the image file name
pattern = re.compile('src="[.][.]/(.*[.]jpg)"')
filename = pattern.search(response.content.decode("utf-8")).group(1)
# send request for the image
response = requests.get(self._base_url + filename)
return response.content
@property
def name(self):
""" Return the name of this device. """
return self._name
+6 -44
View File
@@ -1,48 +1,10 @@
"""
homeassistant.components.camera.generic
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for IP Cameras.
This component provides basic support for IP cameras. For the basic support to
work you camera must support accessing a JPEG snapshot via a URL and you will
need to specify the "still_image_url" parameter which should be the location of
the JPEG image.
As part of the basic support the following features will be provided:
-MJPEG video streaming
-Saving a snapshot
-Recording(JPEG frame capture)
To use this component, add the following to your config/configuration.yaml:
camera:
platform: generic
name: Door Camera
username: YOUR_USERNAME
password: YOUR_PASSWORD
still_image_url: http://YOUR_CAMERA_IP_AND_PORT/image.jpg
VARIABLES:
These are the variables for the device_data array:
still_image_url
*Required
The URL your camera serves the image on.
Example: http://192.168.1.21:2112/
name
*Optional
This parameter allows you to override the name of your camera in homeassistant
username
*Optional
THe username for acessing your camera
password
*Optional
the password for accessing your camera
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.generic.html
"""
import logging
from requests.auth import HTTPBasicAuth
@@ -78,7 +40,7 @@ class GenericCamera(Camera):
self._still_image_url = device_info['still_image_url']
def camera_image(self):
""" Return a still image reponse from the camera """
""" Return a still image reponse from the camera. """
if self._username and self._password:
response = requests.get(
self._still_image_url,
@@ -90,5 +52,5 @@ class GenericCamera(Camera):
@property
def name(self):
""" Return the name of this device """
""" Return the name of this device. """
return self._name
+3 -2
View File
@@ -1,9 +1,10 @@
"""
homeassistant.components.conversation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides functionality to have conversations with Home Assistant.
This is more a proof of concept.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/conversation.html
"""
import logging
import re
+15 -5
View File
@@ -17,7 +17,7 @@ DOMAIN = "demo"
DEPENDENCIES = ['introduction', 'conversation']
COMPONENTS_WITH_DEMO_PLATFORM = [
'switch', 'light', 'thermostat', 'sensor', 'media_player', 'notify']
'switch', 'light', 'sensor', 'thermostat', 'media_player', 'notify']
def setup(hass, config):
@@ -33,10 +33,10 @@ def setup(hass, config):
# Setup sun
if not hass.config.latitude:
hass.config.latitude = '32.87336'
hass.config.latitude = 32.87336
if not hass.config.longitude:
hass.config.longitude = '117.22743'
hass.config.longitude = 117.22743
bootstrap.setup_component(hass, 'sun')
@@ -60,7 +60,15 @@ def setup(hass, config):
{'camera': {
'platform': 'generic',
'name': 'IP Camera',
'still_image_url': 'http://194.218.96.92/jpg/image.jpg',
'still_image_url': 'http://home-assistant.io/demo/webcam.jpg',
}})
# Setup alarm_control_panel
bootstrap.setup_component(
hass, 'alarm_control_panel',
{'alarm_control_panel': {
'platform': 'manual',
'name': 'Test Alarm',
}})
# Setup scripts
@@ -108,7 +116,9 @@ def setup(hass, config):
"http://graph.facebook.com/297400035/picture",
ATTR_FRIENDLY_NAME: 'Paulus'})
hass.states.set("device_tracker.anne_therese", "not_home",
{ATTR_FRIENDLY_NAME: 'Anne Therese'})
{ATTR_FRIENDLY_NAME: 'Anne Therese',
'latitude': hass.config.latitude + 0.002,
'longitude': hass.config.longitude + 0.002})
hass.states.set("group.all_devices", "home",
{
@@ -1,52 +1,90 @@
"""
homeassistant.components.tracker
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
homeassistant.components.device_tracker
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides functionality to keep track of devices.
device_tracker:
platform: netgear
# Optional
# How many seconds to wait after not seeing device to consider it not home
consider_home: 180
# Seconds between each scan
interval_seconds: 12
# New found devices auto found
track_new_devices: yes
# Maximum distance from home we consider people home
range_home: 100
"""
import logging
import threading
import os
# pylint: disable=too-many-instance-attributes, too-many-arguments
# pylint: disable=too-many-locals
import csv
from datetime import timedelta
import logging
import os
import threading
from homeassistant.helpers import validate_config
from homeassistant.helpers.entity import _OVERWRITE
from homeassistant.bootstrap import prepare_setup_platform
from homeassistant.components import discovery, group, zone
from homeassistant.config import load_yaml_config_file
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_per_platform
from homeassistant.helpers.entity import Entity
import homeassistant.util as util
import homeassistant.util.dt as dt_util
from homeassistant.bootstrap import prepare_setup_platform
from homeassistant.helpers.event import track_utc_time_change
from homeassistant.const import (
STATE_HOME, STATE_NOT_HOME, ATTR_ENTITY_PICTURE, ATTR_FRIENDLY_NAME,
CONF_PLATFORM, DEVICE_DEFAULT_NAME)
from homeassistant.components import group
ATTR_ENTITY_PICTURE, ATTR_GPS_ACCURACY, ATTR_LATITUDE, ATTR_LONGITUDE,
DEVICE_DEFAULT_NAME, STATE_HOME, STATE_NOT_HOME)
DOMAIN = "device_tracker"
DEPENDENCIES = []
SERVICE_DEVICE_TRACKER_RELOAD = "reload_devices_csv"
DEPENDENCIES = ['zone']
GROUP_NAME_ALL_DEVICES = 'all devices'
ENTITY_ID_ALL_DEVICES = group.ENTITY_ID_FORMAT.format('all_devices')
ENTITY_ID_FORMAT = DOMAIN + '.{}'
# After how much time do we consider a device not home if
# it does not show up on scans
TIME_DEVICE_NOT_FOUND = timedelta(minutes=3)
CSV_DEVICES = "known_devices.csv"
YAML_DEVICES = 'known_devices.yaml'
# Filename to save known devices to
KNOWN_DEVICES_FILE = "known_devices.csv"
CONF_TRACK_NEW = "track_new_devices"
DEFAULT_CONF_TRACK_NEW = True
CONF_SECONDS = "interval_seconds"
CONF_CONSIDER_HOME = 'consider_home'
DEFAULT_CONSIDER_HOME = 180 # seconds
DEFAULT_CONF_SECONDS = 12
CONF_SCAN_INTERVAL = "interval_seconds"
DEFAULT_SCAN_INTERVAL = 12
TRACK_NEW_DEVICES = "track_new_devices"
CONF_AWAY_HIDE = 'hide_if_away'
DEFAULT_AWAY_HIDE = False
CONF_HOME_RANGE = 'home_range'
DEFAULT_HOME_RANGE = 100
SERVICE_SEE = 'see'
ATTR_MAC = 'mac'
ATTR_DEV_ID = 'dev_id'
ATTR_HOST_NAME = 'host_name'
ATTR_LOCATION_NAME = 'location_name'
ATTR_GPS = 'gps'
ATTR_BATTERY = 'battery'
DISCOVERY_PLATFORMS = {
discovery.SERVICE_NETGEAR: 'netgear',
}
_LOGGER = logging.getLogger(__name__)
# pylint: disable=too-many-arguments
def is_on(hass, entity_id=None):
""" Returns if any or specified device is home. """
@@ -55,293 +93,356 @@ def is_on(hass, entity_id=None):
return hass.states.is_state(entity, STATE_HOME)
def see(hass, mac=None, dev_id=None, host_name=None, location_name=None,
gps=None, gps_accuracy=None, battery=None):
""" Call service to notify you see device. """
data = {key: value for key, value in
((ATTR_MAC, mac),
(ATTR_DEV_ID, dev_id),
(ATTR_HOST_NAME, host_name),
(ATTR_LOCATION_NAME, location_name),
(ATTR_GPS, gps)) if value is not None}
hass.services.call(DOMAIN, SERVICE_SEE, data)
def setup(hass, config):
""" Sets up the device tracker. """
""" Setup device tracker """
yaml_path = hass.config.path(YAML_DEVICES)
csv_path = hass.config.path(CSV_DEVICES)
if os.path.isfile(csv_path) and not os.path.isfile(yaml_path) and \
convert_csv_config(csv_path, yaml_path):
os.remove(csv_path)
if not validate_config(config, {DOMAIN: [CONF_PLATFORM]}, _LOGGER):
return False
conf = config.get(DOMAIN, {})
if isinstance(conf, list):
conf = conf[0]
consider_home = timedelta(
seconds=util.convert(conf.get(CONF_CONSIDER_HOME), int,
DEFAULT_CONSIDER_HOME))
track_new = util.convert(conf.get(CONF_TRACK_NEW), bool,
DEFAULT_CONF_TRACK_NEW)
home_range = util.convert(conf.get(CONF_HOME_RANGE), int,
DEFAULT_HOME_RANGE)
tracker_type = config[DOMAIN].get(CONF_PLATFORM)
devices = load_config(yaml_path, hass, consider_home, home_range)
tracker = DeviceTracker(hass, consider_home, track_new, home_range,
devices)
tracker_implementation = \
prepare_setup_platform(hass, config, DOMAIN, tracker_type)
if tracker_implementation is None:
_LOGGER.error("Unknown device_tracker type specified: %s.",
tracker_type)
return False
device_scanner = tracker_implementation.get_scanner(hass, config)
if device_scanner is None:
_LOGGER.error("Failed to initialize device scanner: %s",
tracker_type)
return False
seconds = util.convert(config[DOMAIN].get(CONF_SECONDS), int,
DEFAULT_CONF_SECONDS)
track_new_devices = config[DOMAIN].get(TRACK_NEW_DEVICES) or False
_LOGGER.info("Tracking new devices: %s", track_new_devices)
tracker = DeviceTracker(hass, device_scanner, seconds, track_new_devices)
# We only succeeded if we got to parse the known devices file
return not tracker.invalid_known_devices_file
class DeviceTracker(object):
""" Class that tracks which devices are home and which are not. """
def __init__(self, hass, device_scanner, seconds, track_new_devices):
self.hass = hass
self.device_scanner = device_scanner
self.lock = threading.Lock()
# Do we track new devices by default?
self.track_new_devices = track_new_devices
# Dictionary to keep track of known devices and devices we track
self.tracked = {}
self.untracked_devices = set()
# Did we encounter an invalid known devices file
self.invalid_known_devices_file = False
# Wrap it in a func instead of lambda so it can be identified in
# the bus by its __name__ attribute.
def update_device_state(now):
""" Triggers update of the device states. """
self.update_devices(now)
dev_group = group.Group(
hass, GROUP_NAME_ALL_DEVICES, user_defined=False)
def reload_known_devices_service(service):
""" Reload known devices file. """
self._read_known_devices_file()
self.update_devices(dt_util.utcnow())
dev_group.update_tracked_entity_ids(self.device_entity_ids)
reload_known_devices_service(None)
if self.invalid_known_devices_file:
return
seconds = range(0, 60, seconds)
_LOGGER.info("Device tracker interval second=%s", seconds)
track_utc_time_change(hass, update_device_state, second=seconds)
hass.services.register(DOMAIN,
SERVICE_DEVICE_TRACKER_RELOAD,
reload_known_devices_service)
@property
def device_entity_ids(self):
""" Returns a set containing all device entity ids
that are being tracked. """
return set(device['entity_id'] for device in self.tracked.values())
def _update_state(self, now, device, is_home):
""" Update the state of a device. """
dev_info = self.tracked[device]
if is_home:
# Update last seen if at home
dev_info['last_seen'] = now
else:
# State remains at home if it has been seen in the last
# TIME_DEVICE_NOT_FOUND
is_home = now - dev_info['last_seen'] < TIME_DEVICE_NOT_FOUND
state = STATE_HOME if is_home else STATE_NOT_HOME
# overwrite properties that have been set in the config file
attr = dict(dev_info['state_attr'])
attr.update(_OVERWRITE.get(dev_info['entity_id'], {}))
self.hass.states.set(
dev_info['entity_id'], state, attr)
def update_devices(self, now):
""" Update device states based on the found devices. """
if not self.lock.acquire(False):
def setup_platform(p_type, p_config, disc_info=None):
""" Setup a device tracker platform. """
platform = prepare_setup_platform(hass, config, DOMAIN, p_type)
if platform is None:
return
try:
found_devices = set(dev.upper() for dev in
self.device_scanner.scan_devices())
if hasattr(platform, 'get_scanner'):
scanner = platform.get_scanner(hass, {DOMAIN: p_config})
for device in self.tracked:
is_home = device in found_devices
if scanner is None:
_LOGGER.error('Error setting up platform %s', p_type)
return
self._update_state(now, device, is_home)
setup_scanner_platform(hass, p_config, scanner, tracker.see)
return
if is_home:
found_devices.remove(device)
if not platform.setup_scanner(hass, p_config, tracker.see):
_LOGGER.error('Error setting up platform %s', p_type)
except Exception: # pylint: disable=broad-except
_LOGGER.exception('Error setting up platform %s', p_type)
# Did we find any devices that we didn't know about yet?
new_devices = found_devices - self.untracked_devices
for p_type, p_config in \
config_per_platform(config, DOMAIN, _LOGGER):
setup_platform(p_type, p_config)
if new_devices:
if not self.track_new_devices:
self.untracked_devices.update(new_devices)
def device_tracker_discovered(service, info):
""" Called when a device tracker platform is discovered. """
setup_platform(DISCOVERY_PLATFORMS[service], {}, info)
self._update_known_devices_file(new_devices)
finally:
self.lock.release()
discovery.listen(hass, DISCOVERY_PLATFORMS.keys(),
device_tracker_discovered)
# pylint: disable=too-many-branches
def _read_known_devices_file(self):
""" Parse and process the known devices file. """
known_dev_path = self.hass.config.path(KNOWN_DEVICES_FILE)
def update_stale(now):
""" Clean up stale devices. """
tracker.update_stale(now)
track_utc_time_change(hass, update_stale, second=range(0, 60, 5))
# Return if no known devices file exists
if not os.path.isfile(known_dev_path):
tracker.setup_group()
def see_service(call):
""" Service to see a device. """
args = {key: value for key, value in call.data.items() if key in
(ATTR_MAC, ATTR_DEV_ID, ATTR_HOST_NAME, ATTR_LOCATION_NAME,
ATTR_GPS, ATTR_GPS_ACCURACY, ATTR_BATTERY)}
tracker.see(**args)
descriptions = load_yaml_config_file(
os.path.join(os.path.dirname(__file__), 'services.yaml'))
hass.services.register(DOMAIN, SERVICE_SEE, see_service,
descriptions.get(SERVICE_SEE))
return True
class DeviceTracker(object):
""" Track devices """
def __init__(self, hass, consider_home, track_new, home_range, devices):
self.hass = hass
self.devices = {dev.dev_id: dev for dev in devices}
self.mac_to_dev = {dev.mac: dev for dev in devices if dev.mac}
self.consider_home = consider_home
self.track_new = track_new
self.home_range = home_range
self.lock = threading.Lock()
for device in devices:
if device.track:
device.update_ha_state()
self.group = None
def see(self, mac=None, dev_id=None, host_name=None, location_name=None,
gps=None, gps_accuracy=None, battery=None):
""" Notify device tracker that you see a device. """
with self.lock:
if mac is None and dev_id is None:
raise HomeAssistantError('Neither mac or device id passed in')
elif mac is not None:
mac = mac.upper()
device = self.mac_to_dev.get(mac)
if not device:
dev_id = util.slugify(host_name or '') or util.slugify(mac)
else:
dev_id = str(dev_id).lower()
device = self.devices.get(dev_id)
if device:
device.seen(host_name, location_name, gps, gps_accuracy,
battery)
if device.track:
device.update_ha_state()
return
# If no device can be found, create it
device = Device(
self.hass, self.consider_home, self.home_range, self.track_new,
dev_id, mac, (host_name or dev_id).replace('_', ' '))
self.devices[dev_id] = device
if mac is not None:
self.mac_to_dev[mac] = device
device.seen(host_name, location_name, gps, gps_accuracy, battery)
if device.track:
device.update_ha_state()
# During init, we ignore the group
if self.group is not None:
self.group.update_tracked_entity_ids(
list(self.group.tracking) + [device.entity_id])
update_config(self.hass.config.path(YAML_DEVICES), dev_id, device)
def setup_group(self):
""" Initializes group for all tracked devices. """
entity_ids = (dev.entity_id for dev in self.devices.values()
if dev.track)
self.group = group.setup_group(
self.hass, GROUP_NAME_ALL_DEVICES, entity_ids, False)
def update_stale(self, now):
""" Update stale devices. """
with self.lock:
for device in self.devices.values():
if (device.track and device.last_update_home and
device.stale(now)):
device.update_ha_state(True)
class Device(Entity):
""" Tracked device. """
host_name = None
location_name = None
gps = None
gps_accuracy = 0
last_seen = None
battery = None
# Track if the last update of this device was HOME
last_update_home = False
_state = STATE_NOT_HOME
def __init__(self, hass, consider_home, home_range, track, dev_id, mac,
name=None, picture=None, away_hide=False):
self.hass = hass
self.entity_id = ENTITY_ID_FORMAT.format(dev_id)
# Timedelta object how long we consider a device home if it is not
# detected anymore.
self.consider_home = consider_home
# Distance in meters
self.home_range = home_range
# Device ID
self.dev_id = dev_id
self.mac = mac
# If we should track this device
self.track = track
# Configured name
self.config_name = name
# Configured picture
self.config_picture = picture
self.away_hide = away_hide
@property
def gps_home(self):
""" Return if device is within range of home. """
distance = max(
0, self.hass.config.distance(*self.gps) - self.gps_accuracy)
return self.gps is not None and distance <= self.home_range
@property
def name(self):
""" Returns the name of the entity. """
return self.config_name or self.host_name or DEVICE_DEFAULT_NAME
@property
def state(self):
""" State of the device. """
return self._state
@property
def state_attributes(self):
""" Device state attributes. """
attr = {}
if self.config_picture:
attr[ATTR_ENTITY_PICTURE] = self.config_picture
if self.gps:
attr[ATTR_LATITUDE] = self.gps[0]
attr[ATTR_LONGITUDE] = self.gps[1]
attr[ATTR_GPS_ACCURACY] = self.gps_accuracy
if self.battery:
attr[ATTR_BATTERY] = self.battery
return attr
@property
def hidden(self):
""" If device should be hidden. """
return self.away_hide and self.state != STATE_HOME
def seen(self, host_name=None, location_name=None, gps=None,
gps_accuracy=0, battery=None):
""" Mark the device as seen. """
self.last_seen = dt_util.utcnow()
self.host_name = host_name
self.location_name = location_name
self.gps_accuracy = gps_accuracy or 0
self.battery = battery
if gps is None:
self.gps = None
else:
try:
self.gps = tuple(float(val) for val in gps)
except ValueError:
_LOGGER.warning('Could not parse gps value for %s: %s',
self.dev_id, gps)
self.gps = None
self.update()
def stale(self, now=None):
""" Return if device state is stale. """
return self.last_seen and \
(now or dt_util.utcnow()) - self.last_seen > self.consider_home
def update(self):
""" Update state of entity. """
if not self.last_seen:
return
elif self.location_name:
self._state = self.location_name
elif self.gps is not None:
zone_state = zone.active_zone(self.hass, self.gps[0], self.gps[1],
self.gps_accuracy)
if zone_state is None:
self._state = STATE_NOT_HOME
elif zone_state.entity_id == zone.ENTITY_ID_HOME:
self._state = STATE_HOME
else:
self._state = zone_state.name
self.lock.acquire()
elif self.stale():
self._state = STATE_NOT_HOME
self.last_update_home = False
else:
self._state = STATE_HOME
self.last_update_home = True
self.untracked_devices.clear()
with open(known_dev_path) as inp:
def convert_csv_config(csv_path, yaml_path):
""" Convert CSV config file format to YAML. """
used_ids = set()
with open(csv_path) as inp:
for row in csv.DictReader(inp):
dev_id = util.ensure_unique_string(
(util.slugify(row['name']) or DEVICE_DEFAULT_NAME).lower(),
used_ids)
used_ids.add(dev_id)
device = Device(None, None, None, row['track'] == '1', dev_id,
row['device'], row['name'], row['picture'])
update_config(yaml_path, dev_id, device)
return True
# To track which devices need an entity_id assigned
need_entity_id = []
# All devices that are still in this set after we read the CSV file
# have been removed from the file and thus need to be cleaned up.
removed_devices = set(self.tracked.keys())
def load_config(path, hass, consider_home, home_range):
""" Load devices from YAML config file. """
if not os.path.isfile(path):
return []
return [
Device(hass, consider_home, home_range, device.get('track', False),
str(dev_id).lower(), str(device.get('mac')).upper(),
device.get('name'), device.get('picture'),
device.get(CONF_AWAY_HIDE, DEFAULT_AWAY_HIDE))
for dev_id, device in load_yaml_config_file(path).items()]
try:
for row in csv.DictReader(inp):
device = row['device'].upper()
if row['track'] == '1':
if device in self.tracked:
# Device exists
removed_devices.remove(device)
else:
# We found a new device
need_entity_id.append(device)
def setup_scanner_platform(hass, config, scanner, see_device):
""" Helper method to connect scanner-based platform to device tracker. """
interval = util.convert(config.get(CONF_SCAN_INTERVAL), int,
DEFAULT_SCAN_INTERVAL)
self._track_device(device, row['name'])
# Initial scan of each mac we also tell about host name for config
seen = set()
# Update state_attr with latest from file
state_attr = {
ATTR_FRIENDLY_NAME: row['name']
}
def device_tracker_scan(now):
""" Called when interval matches. """
for mac in scanner.scan_devices():
if mac in seen:
host_name = None
else:
host_name = scanner.get_device_name(mac)
seen.add(mac)
see_device(mac=mac, host_name=host_name)
if row['picture']:
state_attr[ATTR_ENTITY_PICTURE] = row['picture']
track_utc_time_change(hass, device_tracker_scan, second=range(0, 60,
interval))
self.tracked[device]['state_attr'] = state_attr
device_tracker_scan(None)
else:
self.untracked_devices.add(device)
# Remove existing devices that we no longer track
for device in removed_devices:
entity_id = self.tracked[device]['entity_id']
def update_config(path, dev_id, device):
""" Add device to YAML config file. """
with open(path, 'a') as out:
out.write('\n')
out.write('{}:\n'.format(device.dev_id))
_LOGGER.info("Removing entity %s", entity_id)
self.hass.states.remove(entity_id)
self.tracked.pop(device)
self._generate_entity_ids(need_entity_id)
if not self.tracked:
_LOGGER.warning(
"No devices to track. Please update %s.",
known_dev_path)
_LOGGER.info("Loaded devices from %s", known_dev_path)
except KeyError:
self.invalid_known_devices_file = True
_LOGGER.warning(
("Invalid known devices file: %s. "
"We won't update it with new found devices."),
known_dev_path)
finally:
self.lock.release()
def _update_known_devices_file(self, new_devices):
""" Add new devices to known devices file. """
if not self.invalid_known_devices_file:
known_dev_path = self.hass.config.path(KNOWN_DEVICES_FILE)
try:
# If file does not exist we will write the header too
is_new_file = not os.path.isfile(known_dev_path)
with open(known_dev_path, 'a') as outp:
_LOGGER.info("Found %d new devices, updating %s",
len(new_devices), known_dev_path)
writer = csv.writer(outp)
if is_new_file:
writer.writerow(("device", "name", "track", "picture"))
for device in new_devices:
# See if the device scanner knows the name
# else defaults to unknown device
name = self.device_scanner.get_device_name(device) or \
DEVICE_DEFAULT_NAME
track = 0
if self.track_new_devices:
self._track_device(device, name)
track = 1
writer.writerow((device, name, track, ""))
if self.track_new_devices:
self._generate_entity_ids(new_devices)
except IOError:
_LOGGER.exception("Error updating %s with %d new devices",
known_dev_path, len(new_devices))
def _track_device(self, device, name):
"""
Add a device to the list of tracked devices.
Does not generate the entity id yet.
"""
default_last_seen = dt_util.utcnow().replace(year=1990)
self.tracked[device] = {
'name': name,
'last_seen': default_last_seen,
'state_attr': {ATTR_FRIENDLY_NAME: name}
}
def _generate_entity_ids(self, need_entity_id):
""" Generate entity ids for a list of devices. """
# Setup entity_ids for the new devices
used_entity_ids = [info['entity_id'] for device, info
in self.tracked.items()
if device not in need_entity_id]
for device in need_entity_id:
name = self.tracked[device]['name']
entity_id = util.ensure_unique_string(
ENTITY_ID_FORMAT.format(util.slugify(name)),
used_entity_ids)
used_entity_ids.append(entity_id)
self.tracked[device]['entity_id'] = entity_id
for key, value in (('name', device.name), ('mac', device.mac),
('picture', device.config_picture),
('track', 'yes' if device.track else 'no'),
(CONF_AWAY_HIDE,
'yes' if device.away_hide else 'no')):
out.write(' {}: {}\n'.format(key, '' if value is None else value))
@@ -1,50 +1,31 @@
"""
homeassistant.components.device_tracker.actiontec
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Device tracker platform that supports scanning an Actiontec MI424WR
(Verizon FIOS) router for device presence.
This device tracker needs telnet to be enabled on the router.
Configuration:
To use the Actiontec tracker you will need to add something like the
following to your config/configuration.yaml
device_tracker:
platform: actiontec
host: YOUR_ROUTER_IP
username: YOUR_ADMIN_USERNAME
password: YOUR_ADMIN_PASSWORD
Variables:
host
*Required
The IP address of your router, e.g. 192.168.1.1.
username
*Required
The username of an user with administrative privileges, usually 'admin'.
password
*Required
The password for your given admin account.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.actiontec.html
"""
import logging
from datetime import timedelta
from collections import namedtuple
import re
import threading
import telnetlib
import homeassistant.util.dt as dt_util
from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD
from homeassistant.helpers import validate_config
from homeassistant.util import Throttle
from homeassistant.util import Throttle, convert
from homeassistant.components.device_tracker import DOMAIN
# Return cached results if last scan was less then this time ago
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
# Interval in minutes to exclude devices from a scan while they are home
CONF_HOME_INTERVAL = "home_interval"
_LOGGER = logging.getLogger(__name__)
_LEASES_REGEX = re.compile(
@@ -54,7 +35,7 @@ _LEASES_REGEX = re.compile(
# pylint: disable=unused-argument
def get_scanner(hass, config):
""" Validates config and returns a DD-WRT scanner. """
""" Validates config and returns an Actiontec scanner. """
if not validate_config(config,
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
_LOGGER):
@@ -64,59 +45,87 @@ def get_scanner(hass, config):
return scanner if scanner.success_init else None
Device = namedtuple("Device", ["mac", "ip", "last_update"])
class ActiontecDeviceScanner(object):
""" This class queries a an actiontec router
for connected devices. Adapted from DD-WRT scanner.
"""
This class queries a an actiontec router for connected devices.
Adapted from DD-WRT scanner.
"""
def __init__(self, config):
self.host = config[CONF_HOST]
self.username = config[CONF_USERNAME]
self.password = config[CONF_PASSWORD]
minutes = convert(config.get(CONF_HOME_INTERVAL), int, 0)
self.home_interval = timedelta(minutes=minutes)
self.lock = threading.Lock()
self.last_results = {}
self.last_results = []
# Test the router is accessible
data = self.get_actiontec_data()
self.success_init = data is not None
_LOGGER.info("actiontec scanner initialized")
if self.home_interval:
_LOGGER.info("home_interval set to: %s", self.home_interval)
def scan_devices(self):
""" Scans for new devices and return a
list containing found device ids. """
"""
Scans for new devices and return a list containing found device ids.
"""
self._update_info()
return [client['mac'] for client in self.last_results]
return [client.mac for client in self.last_results]
def get_device_name(self, device):
""" Returns the name of the given device or None if we don't know. """
if not self.last_results:
return None
for client in self.last_results:
if client['mac'] == device:
return client['ip']
if client.mac == device:
return client.ip
return None
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
""" Ensures the information from the Actiontec MI424WR router is up
to date. Returns boolean if scanning successful. """
"""
Ensures the information from the Actiontec MI424WR router is up
to date. Returns boolean if scanning successful.
"""
_LOGGER.info("Scanning")
if not self.success_init:
return False
with self.lock:
# _LOGGER.info("Checking ARP")
data = self.get_actiontec_data()
if not data:
exclude_targets = set()
exclude_target_list = []
now = dt_util.now()
if self.home_interval:
for host in self.last_results:
if host.last_update + self.home_interval > now:
exclude_targets.add(host)
if len(exclude_targets) > 0:
exclude_target_list = [t.ip for t in exclude_targets]
actiontec_data = self.get_actiontec_data()
if not actiontec_data:
return False
active_clients = [client for client in data.values()]
self.last_results = active_clients
self.last_results = []
for client in exclude_target_list:
if client in actiontec_data:
actiontec_data.pop(client)
for name, data in actiontec_data.items():
device = Device(data['mac'], name, now)
self.last_results.append(device)
self.last_results.extend(exclude_targets)
_LOGGER.info("actiontec scan successful")
return True
def get_actiontec_data(self):
""" Retrieve data from Actiontec MI424WR and return parsed result. """
""" Retrieve data from Actiontec MI424WR and return parsed result. """
try:
telnet = telnetlib.Telnet(self.host)
telnet.read_until(b'Username: ')
@@ -4,33 +4,8 @@ homeassistant.components.device_tracker.aruba
Device tracker platform that supports scanning a Aruba Access Point for device
presence.
This device tracker needs telnet to be enabled on the router.
Configuration:
To use the Aruba tracker you will need to add something like the following
to your config/configuration.yaml. You also need to enable Telnet in the
configuration pages.
device_tracker:
platform: aruba
host: YOUR_ACCESS_POINT_IP
username: YOUR_ADMIN_USERNAME
password: YOUR_ADMIN_PASSWORD
Variables:
host
*Required
The IP address of your router, e.g. 192.168.1.1.
username
*Required
The username of an user with administrative privileges, usually 'admin'.
password
*Required
The password for your given admin account.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.aruba.html
"""
import logging
from datetime import timedelta
@@ -51,15 +26,7 @@ _LOGGER = logging.getLogger(__name__)
_DEVICES_REGEX = re.compile(
r'(?P<name>([^\s]+))\s+' +
r'(?P<ip>([0-9]{1,3}[\.]){3}[0-9]{1,3})\s+' +
r'(?P<mac>(([0-9a-f]{2}[:-]){5}([0-9a-f]{2})))\s+' +
r'(?P<os>([^\s]+))\s+' +
r'(?P<network>([^\s]+))\s+' +
r'(?P<ap>([^\s]+))\s+' +
r'(?P<channel>([^\s]+))\s+' +
r'(?P<type>([^\s]+))\s+' +
r'(?P<role>([^\s]+))\s+' +
r'(?P<signal>([^\s]+))\s+' +
r'(?P<speed>([^\s]+))')
r'(?P<mac>(([0-9a-f]{2}[:-]){5}([0-9a-f]{2})))\s+')
# pylint: disable=unused-argument
@@ -91,8 +58,9 @@ class ArubaDeviceScanner(object):
self.success_init = data is not None
def scan_devices(self):
""" Scans for new devices and return a list containing found device
ids. """
"""
Scans for new devices and return a list containing found device IDs.
"""
self._update_info()
return [client['mac'] for client in self.last_results]
@@ -108,8 +76,10 @@ class ArubaDeviceScanner(object):
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
""" Ensures the information from the Aruba Access Point is up to date.
Returns boolean if scanning successful. """
"""
Ensures the information from the Aruba Access Point is up to date.
Returns boolean if scanning successful.
"""
if not self.success_init:
return False
@@ -122,8 +92,7 @@ class ArubaDeviceScanner(object):
return True
def get_aruba_data(self):
""" Retrieve data from Aruba Access Point and return parsed
result. """
""" Retrieve data from Aruba Access Point and return parsed result. """
try:
telnet = telnetlib.Telnet(self.host)
telnet.read_until(b'User: ')
@@ -9,7 +9,7 @@ This device tracker needs telnet to be enabled on the router.
Configuration:
To use the ASUSWRT tracker you will need to add something like the following
to your config/configuration.yaml
to your configuration.yaml file.
device_tracker:
platform: asuswrt
@@ -63,7 +63,7 @@ _IP_NEIGH_REGEX = re.compile(
# pylint: disable=unused-argument
def get_scanner(hass, config):
""" Validates config and returns a DD-WRT scanner. """
""" Validates config and returns an ASUS-WRT scanner. """
if not validate_config(config,
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
_LOGGER):
@@ -75,7 +75,8 @@ def get_scanner(hass, config):
class AsusWrtDeviceScanner(object):
""" This class queries a router running ASUSWRT firmware
"""
This class queries a router running ASUSWRT firmware
for connected devices. Adapted from DD-WRT scanner.
"""
@@ -93,8 +94,9 @@ class AsusWrtDeviceScanner(object):
self.success_init = data is not None
def scan_devices(self):
""" Scans for new devices and return a
list containing found device ids. """
"""
Scans for new devices and return a list containing found device IDs.
"""
self._update_info()
return [client['mac'] for client in self.last_results]
@@ -110,8 +112,10 @@ class AsusWrtDeviceScanner(object):
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
""" Ensures the information from the ASUSWRT router is up to date.
Returns boolean if scanning successful. """
"""
Ensures the information from the ASUSWRT router is up to date.
Returns boolean if scanning successful.
"""
if not self.success_init:
return False
@@ -129,7 +133,7 @@ class AsusWrtDeviceScanner(object):
return True
def get_asuswrt_data(self):
""" Retrieve data from ASUSWRT and return parsed result. """
""" Retrieve data from ASUSWRT and return parsed result. """
try:
telnet = telnetlib.Telnet(self.host)
telnet.read_until(b'login: ')
@@ -153,15 +157,24 @@ class AsusWrtDeviceScanner(object):
devices = {}
for lease in leases_result:
match = _LEASES_REGEX.search(lease.decode('utf-8'))
# For leases where the client doesn't set a hostname, ensure
# it is blank and not '*', which breaks the entity_id down
# the line
if match:
host = match.group('host')
if host == '*':
host = ''
devices[match.group('ip')] = {
'host': host,
'status': '',
'ip': match.group('ip'),
'mac': match.group('mac').upper(),
'host': match.group('host'),
'status': ''
}
for neighbor in neighbors:
match = _IP_NEIGH_REGEX.search(neighbor.decode('utf-8'))
if match.group('ip') in devices:
if match and match.group('ip') in devices:
devices[match.group('ip')]['status'] = match.group('status')
return devices
@@ -1,34 +1,11 @@
"""
homeassistant.components.device_tracker.ddwrt
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Device tracker platform that supports scanning a DD-WRT router for device
presence.
Configuration:
To use the DD-WRT tracker you will need to add something like the following
to your config/configuration.yaml
device_tracker:
platform: ddwrt
host: YOUR_ROUTER_IP
username: YOUR_ADMIN_USERNAME
password: YOUR_ADMIN_PASSWORD
Variables:
host
*Required
The IP address of your router, e.g. 192.168.1.1.
username
*Required
The username of an user with administrative privileges, usually 'admin'.
password
*Required
The password for your given admin account.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.ddwrt.html
"""
import logging
from datetime import timedelta
@@ -47,6 +24,7 @@ MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__)
_DDWRT_DATA_REGEX = re.compile(r'\{(\w+)::([^\}]*)\}')
_MAC_REGEX = re.compile(r'(([0-9A-Fa-f]{1,2}\:){5}[0-9A-Fa-f]{1,2})')
# pylint: disable=unused-argument
@@ -64,7 +42,8 @@ def get_scanner(hass, config):
# pylint: disable=too-many-instance-attributes
class DdWrtDeviceScanner(object):
""" This class queries a wireless router running DD-WRT firmware
"""
This class queries a wireless router running DD-WRT firmware
for connected devices. Adapted from Tomato scanner.
"""
@@ -77,7 +56,7 @@ class DdWrtDeviceScanner(object):
self.last_results = {}
self.mac2name = None
self.mac2name = {}
# Test the router is accessible
url = 'http://{}/Status_Wireless.live.asp'.format(self.host)
@@ -85,8 +64,9 @@ class DdWrtDeviceScanner(object):
self.success_init = data is not None
def scan_devices(self):
""" Scans for new devices and return a
list containing found device ids. """
"""
Scans for new devices and return a list containing found device ids.
"""
self._update_info()
@@ -97,35 +77,40 @@ class DdWrtDeviceScanner(object):
with self.lock:
# if not initialised and not already scanned and not found
if self.mac2name is None or device not in self.mac2name:
if device not in self.mac2name:
url = 'http://{}/Status_Lan.live.asp'.format(self.host)
data = self.get_ddwrt_data(url)
if not data:
return
return None
dhcp_leases = data.get('dhcp_leases', None)
if dhcp_leases:
# remove leading and trailing single quotes
cleaned_str = dhcp_leases.strip().strip('"')
elements = cleaned_str.split('","')
num_clients = int(len(elements)/5)
self.mac2name = {}
for idx in range(0, num_clients):
# this is stupid but the data is a single array
# every 5 elements represents one hosts, the MAC
# is the third element and the name is the first
mac_index = (idx * 5) + 2
if mac_index < len(elements):
mac = elements[mac_index]
self.mac2name[mac] = elements[idx * 5]
return self.mac2name.get(device, None)
if not dhcp_leases:
return None
# remove leading and trailing single quotes
cleaned_str = dhcp_leases.strip().strip('"')
elements = cleaned_str.split('","')
num_clients = int(len(elements)/5)
self.mac2name = {}
for idx in range(0, num_clients):
# this is stupid but the data is a single array
# every 5 elements represents one hosts, the MAC
# is the third element and the name is the first
mac_index = (idx * 5) + 2
if mac_index < len(elements):
mac = elements[mac_index]
self.mac2name[mac] = elements[idx * 5]
return self.mac2name.get(device)
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
""" Ensures the information from the DD-WRT router is up to date.
Returns boolean if scanning successful. """
"""
Ensures the information from the DD-WRT router is up to date.
Returns boolean if scanning successful.
"""
if not self.success_init:
return False
@@ -138,32 +123,28 @@ class DdWrtDeviceScanner(object):
if not data:
return False
if data:
self.last_results = []
active_clients = data.get('active_wireless', None)
if active_clients:
# This is really lame, instead of using JSON the DD-WRT UI
# uses its own data format for some reason and then
# regex's out values so I guess I have to do the same,
# LAME!!!
self.last_results = []
# remove leading and trailing single quotes
clean_str = active_clients.strip().strip("'")
elements = clean_str.split("','")
active_clients = data.get('active_wireless', None)
if not active_clients:
return False
num_clients = int(len(elements)/9)
for idx in range(0, num_clients):
# get every 9th element which is the MAC address
index = idx * 9
if index < len(elements):
self.last_results.append(elements[index])
# This is really lame, instead of using JSON the DD-WRT UI
# uses its own data format for some reason and then
# regex's out values so I guess I have to do the same,
# LAME!!!
return True
# remove leading and trailing single quotes
clean_str = active_clients.strip().strip("'")
elements = clean_str.split("','")
return False
self.last_results.extend(item for item in elements
if _MAC_REGEX.match(item))
return True
def get_ddwrt_data(self, url):
""" Retrieve data from DD-WRT and return parsed result. """
""" Retrieve data from DD-WRT and return parsed result. """
try:
response = requests.get(
url,
@@ -0,0 +1,50 @@
"""
homeassistant.components.device_tracker.demo
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Demo platform for the device tracker.
device_tracker:
platform: demo
"""
import random
from homeassistant.components.device_tracker import DOMAIN
def setup_scanner(hass, config, see):
""" Set up a demo tracker. """
def offset():
""" Return random offset. """
return (random.randrange(500, 2000)) / 2e5 * random.choice((-1, 1))
def random_see(dev_id, name):
""" Randomize a sighting. """
see(
dev_id=dev_id,
host_name=name,
gps=(hass.config.latitude + offset(),
hass.config.longitude + offset()),
gps_accuracy=random.randrange(50, 150),
battery=random.randrange(10, 90)
)
def observe(call=None):
""" Observe three entities. """
random_see('demo_paulus', 'Paulus')
random_see('demo_anne_therese', 'Anne Therese')
observe()
see(
dev_id='demo_home_boy',
host_name='Home Boy',
gps=[hass.config.latitude - 0.00002, hass.config.longitude + 0.00002],
gps_accuracy=20,
battery=53
)
hass.services.register(DOMAIN, 'demo', observe)
return True
@@ -0,0 +1,71 @@
"""
homeassistant.components.device_tracker.geofancy
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Geofancy platform for the device tracker.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.geofancy.html
"""
from homeassistant.const import (
HTTP_UNPROCESSABLE_ENTITY, HTTP_INTERNAL_SERVER_ERROR)
DEPENDENCIES = ['http']
_SEE = 0
URL_API_GEOFANCY_ENDPOINT = "/api/geofancy"
def setup_scanner(hass, config, see):
""" Set up an endpoint for the Geofancy app. """
# Use a global variable to keep setup_scanner compact when using a callback
global _SEE
_SEE = see
# POST would be semantically better, but that currently does not work
# since Geofancy sends the data as key1=value1&key2=value2
# in the request body, while Home Assistant expects json there.
hass.http.register_path(
'GET', URL_API_GEOFANCY_ENDPOINT, _handle_get_api_geofancy)
return True
def _handle_get_api_geofancy(handler, path_match, data):
""" Geofancy message received. """
if not isinstance(data, dict):
handler.write_json_message(
"Error while parsing Geofancy message.",
HTTP_INTERNAL_SERVER_ERROR)
return
if 'latitude' not in data or 'longitude' not in data:
handler.write_json_message(
"Location not specified.",
HTTP_UNPROCESSABLE_ENTITY)
return
if 'device' not in data or 'id' not in data:
handler.write_json_message(
"Device id or location id not specified.",
HTTP_UNPROCESSABLE_ENTITY)
return
try:
gps_coords = (float(data['latitude']), float(data['longitude']))
except ValueError:
# If invalid latitude / longitude format
handler.write_json_message(
"Invalid latitude / longitude format.",
HTTP_UNPROCESSABLE_ENTITY)
return
# entity id's in Home Assistant must be alphanumerical
device_uuid = data['device']
device_entity_id = device_uuid.replace('-', '')
_SEE(dev_id=device_entity_id, gps=gps_coords, location_name=data['id'])
handler.write_json_message("Geofancy message processed")
+12 -35
View File
@@ -1,38 +1,11 @@
"""
homeassistant.components.device_tracker.luci
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Device tracker platform that supports scanning a OpenWRT router for device
presence.
It's required that the luci RPC package is installed on the OpenWRT router:
# opkg install luci-mod-rpc
Configuration:
To use the Luci tracker you will need to add something like the following
to your config/configuration.yaml
device_tracker:
platform: luci
host: YOUR_ROUTER_IP
username: YOUR_ADMIN_USERNAME
password: YOUR_ADMIN_PASSWORD
Variables:
host
*Required
The IP address of your router, e.g. 192.168.1.1.
username
*Required
The username of an user with administrative privileges, usually 'admin'.
password
*Required
The password for your given admin account.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.luci.html
"""
import logging
import json
@@ -66,7 +39,8 @@ def get_scanner(hass, config):
# pylint: disable=too-many-instance-attributes
class LuciDeviceScanner(object):
""" This class queries a wireless router running OpenWrt firmware
"""
This class queries a wireless router running OpenWrt firmware
for connected devices. Adapted from Tomato scanner.
# opkg install luci-mod-rpc
@@ -95,8 +69,9 @@ class LuciDeviceScanner(object):
self.success_init = self.token is not None
def scan_devices(self):
""" Scans for new devices and return a
list containing found device ids. """
"""
Scans for new devices and return a list containing found device ids.
"""
self._update_info()
@@ -124,8 +99,10 @@ class LuciDeviceScanner(object):
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
""" Ensures the information from the Luci router is up to date.
Returns boolean if scanning successful. """
"""
Ensures the information from the Luci router is up to date.
Returns boolean if scanning successful.
"""
if not self.success_init:
return False
@@ -179,6 +156,6 @@ def _req_json_rpc(url, method, *args, **kwargs):
def _get_token(host, username, password):
""" Get authentication token for the given host+username+password """
""" Get authentication token for the given host+username+password. """
url = 'http://{}/cgi-bin/luci/rpc/auth'.format(host)
return _req_json_rpc(url, 'login', username, password)
@@ -0,0 +1,43 @@
"""
homeassistant.components.device_tracker.mqtt
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
MQTT platform for the device tracker.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.mqtt.html
"""
import logging
from homeassistant import util
import homeassistant.components.mqtt as mqtt
DEPENDENCIES = ['mqtt']
CONF_QOS = 'qos'
CONF_DEVICES = 'devices'
DEFAULT_QOS = 0
_LOGGER = logging.getLogger(__name__)
def setup_scanner(hass, config, see):
""" Set up a MQTT tracker. """
devices = config.get(CONF_DEVICES)
qos = util.convert(config.get(CONF_QOS), int, DEFAULT_QOS)
if not isinstance(devices, dict):
_LOGGER.error('Expected %s to be a dict, found %s', CONF_DEVICES,
devices)
return False
dev_id_lookup = {}
def device_tracker_message_received(topic, payload, qos):
""" MQTT message received. """
see(dev_id=dev_id_lookup[topic], location_name=payload)
for dev_id, topic in devices.items():
dev_id_lookup[topic] = dev_id
mqtt.subscribe(hass, topic, device_tracker_message_received, qos)
return True
@@ -1,34 +1,11 @@
"""
homeassistant.components.device_tracker.netgear
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Device tracker platform that supports scanning a Netgear router for device
presence.
Configuration:
To use the Netgear tracker you will need to add something like the following
to your config/configuration.yaml
device_tracker:
platform: netgear
host: YOUR_ROUTER_IP
username: YOUR_ADMIN_USERNAME
password: YOUR_ADMIN_PASSWORD
Variables:
host
*Required
The IP address of your router, e.g. 192.168.1.1.
username
*Required
The username of an user with administrative privileges, usually 'admin'.
password
*Required
The password for your given admin account.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.netgear.html
"""
import logging
from datetime import timedelta
@@ -71,7 +48,6 @@ class NetgearDeviceScanner(object):
self.lock = threading.Lock()
if host is None:
print("BIER")
self._api = pynetgear.Netgear()
elif username is None:
self._api = pynetgear.Netgear(password, host)
@@ -90,8 +66,9 @@ class NetgearDeviceScanner(object):
_LOGGER.error("Failed to Login")
def scan_devices(self):
""" Scans for new devices and return a
list containing found device ids. """
"""
Scans for new devices and return a list containing found device ids.
"""
self._update_info()
return (device.mac for device in self.last_results)
@@ -106,8 +83,10 @@ class NetgearDeviceScanner(object):
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
""" Retrieves latest information from the Netgear router.
Returns boolean if scanning successful. """
"""
Retrieves latest information from the Netgear router.
Returns boolean if scanning successful.
"""
if not self.success_init:
return
@@ -1,29 +1,10 @@
"""
homeassistant.components.device_tracker.nmap
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Device tracker platform that supports scanning a network with nmap.
Configuration:
To use the nmap tracker you will need to add something like the following
to your config/configuration.yaml
device_tracker:
platform: nmap_tracker
hosts: 192.168.1.1/24
Variables:
hosts
*Required
The IP addresses to scan in the network-prefix notation (192.168.1.1/24) or
the range notation (192.168.1.1-255).
home_interval
*Optional
Number of minutes it will not scan devices that it found in previous results.
This is to save battery.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.nmap_scanner.html
"""
import logging
from datetime import timedelta
@@ -45,7 +26,7 @@ _LOGGER = logging.getLogger(__name__)
# interval in minutes to exclude devices from a scan while they are home
CONF_HOME_INTERVAL = "home_interval"
REQUIREMENTS = ['python-nmap==0.4.1']
REQUIREMENTS = ['python-nmap==0.4.3']
def get_scanner(hass, config):
@@ -74,7 +55,7 @@ def _arp(ip_address):
class NmapDeviceScanner(object):
""" This class scans for devices using nmap """
""" This class scans for devices using nmap. """
def __init__(self, config):
self.last_results = []
@@ -87,8 +68,9 @@ class NmapDeviceScanner(object):
_LOGGER.info("nmap scanner initialized")
def scan_devices(self):
""" Scans for new devices and return a
list containing found device ids. """
"""
Scans for new devices and return a list containing found device ids.
"""
self._update_info()
@@ -107,23 +89,28 @@ class NmapDeviceScanner(object):
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
""" Scans the network for devices.
Returns boolean if scanning successful. """
"""
Scans the network for devices.
Returns boolean if scanning successful.
"""
_LOGGER.info("Scanning")
from nmap import PortScanner, PortScannerError
scanner = PortScanner()
options = "-sP --host-timeout 5"
exclude_targets = set()
options = "-F --host-timeout 5"
if self.home_interval:
now = dt_util.now()
for host in self.last_results:
if host.last_update + self.home_interval > now:
exclude_targets.add(host)
if len(exclude_targets) > 0:
target_list = [t.ip for t in exclude_targets]
options += " --exclude {}".format(",".join(target_list))
boundary = dt_util.now() - self.home_interval
last_results = [device for device in self.last_results
if device.last_update > boundary]
if last_results:
# Pylint is confused here.
# pylint: disable=no-member
options += " --exclude {}".format(",".join(device.ip for device
in last_results))
else:
last_results = []
try:
result = scanner.scan(hosts=self.hosts, arguments=options)
@@ -131,18 +118,17 @@ class NmapDeviceScanner(object):
return False
now = dt_util.now()
self.last_results = []
for ipv4, info in result['scan'].items():
if info['status']['state'] != 'up':
continue
name = info['hostnames'][0] if info['hostnames'] else ipv4
name = info['hostnames'][0]['name'] if info['hostnames'] else ipv4
# Mac address only returned if nmap ran as root
mac = info['addresses'].get('mac') or _arp(ipv4)
if mac is None:
continue
device = Device(mac.upper(), name, ipv4, now)
self.last_results.append(device)
self.last_results.extend(exclude_targets)
last_results.append(Device(mac.upper(), name, ipv4, now))
self.last_results = last_results
_LOGGER.info("nmap scan successful")
return True
@@ -0,0 +1,53 @@
"""
homeassistant.components.device_tracker.owntracks
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
OwnTracks platform for the device tracker.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.owntracks.html
"""
import json
import logging
import homeassistant.components.mqtt as mqtt
DEPENDENCIES = ['mqtt']
LOCATION_TOPIC = 'owntracks/+/+'
def setup_scanner(hass, config, see):
""" Set up a OwnTracksks tracker. """
def owntracks_location_update(topic, payload, qos):
""" MQTT message received. """
# Docs on available data:
# http://owntracks.org/booklet/tech/json/#_typelocation
try:
data = json.loads(payload)
except ValueError:
# If invalid JSON
logging.getLogger(__name__).error(
'Unable to parse payload as JSON: %s', payload)
return
if not isinstance(data, dict) or data.get('_type') != 'location':
return
parts = topic.split('/')
kwargs = {
'dev_id': '{}_{}'.format(parts[1], parts[2]),
'host_name': parts[1],
'gps': (data['lat'], data['lon']),
}
if 'acc' in data:
kwargs['gps_accuracy'] = data['acc']
if 'batt' in data:
kwargs['battery'] = data['batt']
see(**kwargs)
mqtt.subscribe(hass, LOCATION_TOPIC, owntracks_location_update, 1)
return True
@@ -0,0 +1,119 @@
"""
homeassistant.components.device_tracker.snmp
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Device tracker platform that supports fetching WiFi associations
through SNMP.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.snmp.html
"""
import logging
from datetime import timedelta
import threading
import binascii
from homeassistant.const import CONF_HOST
from homeassistant.helpers import validate_config
from homeassistant.util import Throttle
from homeassistant.components.device_tracker import DOMAIN
# Return cached results if last scan was less then this time ago
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['pysnmp==4.2.5']
CONF_COMMUNITY = "community"
CONF_BASEOID = "baseoid"
# pylint: disable=unused-argument
def get_scanner(hass, config):
""" Validates config and returns an snmp scanner """
if not validate_config(config,
{DOMAIN: [CONF_HOST, CONF_COMMUNITY, CONF_BASEOID]},
_LOGGER):
return None
scanner = SnmpScanner(config[DOMAIN])
return scanner if scanner.success_init else None
class SnmpScanner(object):
"""
This class queries any SNMP capable Acces Point for connected devices.
"""
def __init__(self, config):
self.host = config[CONF_HOST]
self.community = config[CONF_COMMUNITY]
self.baseoid = config[CONF_BASEOID]
self.lock = threading.Lock()
self.last_results = []
# Test the router is accessible
data = self.get_snmp_data()
self.success_init = data is not None
def scan_devices(self):
"""
Scans for new devices and return a list containing found device IDs.
"""
self._update_info()
return [client['mac'] for client in self.last_results]
# Supressing no-self-use warning
# pylint: disable=R0201
def get_device_name(self, device):
""" Returns the name of the given device or None if we don't know. """
# We have no names
return None
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
"""
Ensures the information from the WAP is up to date.
Returns boolean if scanning successful.
"""
if not self.success_init:
return False
with self.lock:
data = self.get_snmp_data()
if not data:
return False
self.last_results = data
return True
def get_snmp_data(self):
""" Fetch mac addresses from WAP via SNMP. """
from pysnmp.entity.rfc3413.oneliner import cmdgen
devices = []
snmp = cmdgen.CommandGenerator()
errindication, errstatus, errindex, restable = snmp.nextCmd(
cmdgen.CommunityData(self.community),
cmdgen.UdpTransportTarget((self.host, 161)),
cmdgen.MibVariable(self.baseoid)
)
if errindication:
_LOGGER.error("SNMPLIB error: %s", errindication)
return
if errstatus:
_LOGGER.error('SNMP error: %s at %s', errstatus.prettyPrint(),
errindex and restable[-1][int(errindex)-1]
or '?')
return
for resrow in restable:
for _, val in resrow:
mac = binascii.hexlify(val.asOctets()).decode('utf-8')
mac = ':'.join([mac[i:i+2] for i in range(0, len(mac), 2)])
devices.append({'mac': mac})
return devices
@@ -4,32 +4,8 @@ homeassistant.components.device_tracker.thomson
Device tracker platform that supports scanning a THOMSON router for device
presence.
This device tracker needs telnet to be enabled on the router.
Configuration:
To use the THOMSON tracker you will need to add something like the following
to your config/configuration.yaml
device_tracker:
platform: thomson
host: YOUR_ROUTER_IP
username: YOUR_ADMIN_USERNAME
password: YOUR_ADMIN_PASSWORD
Variables:
host
*Required
The IP address of your router, e.g. 192.168.1.1.
username
*Required
The username of an user with administrative privileges, usually 'admin'.
password
*Required
The password for your given admin account.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.thomson.html
"""
import logging
from datetime import timedelta
@@ -71,7 +47,8 @@ def get_scanner(hass, config):
class ThomsonDeviceScanner(object):
""" This class queries a router running THOMSON firmware
"""
This class queries a router running THOMSON firmware
for connected devices. Adapted from ASUSWRT scanner.
"""
@@ -107,8 +84,10 @@ class ThomsonDeviceScanner(object):
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
""" Ensures the information from the THOMSON router is up to date.
Returns boolean if scanning successful. """
"""
Ensures the information from the THOMSON router is up to date.
Returns boolean if scanning successful.
"""
if not self.success_init:
return False
@@ -125,7 +104,7 @@ class ThomsonDeviceScanner(object):
return True
def get_thomson_data(self):
""" Retrieve data from THOMSON and return parsed result. """
""" Retrieve data from THOMSON and return parsed result. """
try:
telnet = telnetlib.Telnet(self.host)
telnet.read_until(b'Username : ')
@@ -1,40 +1,11 @@
"""
homeassistant.components.device_tracker.tomato
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Device tracker platform that supports scanning a Tomato router for device
presence.
Configuration:
To use the Tomato tracker you will need to add something like the following
to your config/configuration.yaml
device_tracker:
platform: tomato
host: YOUR_ROUTER_IP
username: YOUR_ADMIN_USERNAME
password: YOUR_ADMIN_PASSWORD
http_id: ABCDEFG
Variables:
host
*Required
The IP address of your router, e.g. 192.168.1.1.
username
*Required
The username of an user with administrative privileges, usually 'admin'.
password
*Required
The password for your given admin account.
http_id
*Required
The value can be obtained by logging in to the Tomato admin interface and
search for http_id in the page source code.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.tomato.html
"""
import logging
import json
+146 -45
View File
@@ -1,35 +1,11 @@
"""
homeassistant.components.device_tracker.tplink
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Device tracker platform that supports scanning a TP-Link router for device
presence.
Configuration:
To use the TP-Link tracker you will need to add something like the following
to your config/configuration.yaml
device_tracker:
platform: tplink
host: YOUR_ROUTER_IP
username: YOUR_ADMIN_USERNAME
password: YOUR_ADMIN_PASSWORD
Variables:
host
*Required
The IP address of your router, e.g. 192.168.1.1.
username
*Required
The username of an user with administrative privileges, usually 'admin'.
password
*Required
The password for your given admin account.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.tplink.html
"""
import base64
import logging
@@ -56,16 +32,20 @@ def get_scanner(hass, config):
_LOGGER):
return None
scanner = Tplink2DeviceScanner(config[DOMAIN])
scanner = Tplink3DeviceScanner(config[DOMAIN])
if not scanner.success_init:
scanner = TplinkDeviceScanner(config[DOMAIN])
scanner = Tplink2DeviceScanner(config[DOMAIN])
if not scanner.success_init:
scanner = TplinkDeviceScanner(config[DOMAIN])
return scanner if scanner.success_init else None
class TplinkDeviceScanner(object):
""" This class queries a wireless router running TP-Link firmware
"""
This class queries a wireless router running TP-Link firmware
for connected devices.
"""
@@ -85,8 +65,9 @@ class TplinkDeviceScanner(object):
self.success_init = self._update_info()
def scan_devices(self):
""" Scans for new devices and return a
list containing found device ids. """
"""
Scans for new devices and return a list containing found device ids.
"""
self._update_info()
@@ -94,15 +75,18 @@ class TplinkDeviceScanner(object):
# pylint: disable=no-self-use
def get_device_name(self, device):
""" The TP-Link firmware doesn't save the name of the wireless
device. """
"""
The TP-Link firmware doesn't save the name of the wireless device.
"""
return None
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
""" Ensures the information from the TP-Link router is up to date.
Returns boolean if scanning successful. """
"""
Ensures the information from the TP-Link router is up to date.
Returns boolean if scanning successful.
"""
with self.lock:
_LOGGER.info("Loading wireless clients...")
@@ -122,33 +106,38 @@ class TplinkDeviceScanner(object):
class Tplink2DeviceScanner(TplinkDeviceScanner):
""" This class queries a wireless router running newer version of TP-Link
"""
This class queries a wireless router running newer version of TP-Link
firmware for connected devices.
"""
def scan_devices(self):
""" Scans for new devices and return a
list containing found device ids. """
"""
Scans for new devices and return a list containing found device ids.
"""
self._update_info()
return self.last_results.keys()
# pylint: disable=no-self-use
def get_device_name(self, device):
""" The TP-Link firmware doesn't save the name of the wireless
device. """
"""
The TP-Link firmware doesn't save the name of the wireless device.
"""
return self.last_results.get(device)
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
""" Ensures the information from the TP-Link router is up to date.
Returns boolean if scanning successful. """
"""
Ensures the information from the TP-Link router is up to date.
Returns boolean if scanning successful.
"""
with self.lock:
_LOGGER.info("Loading wireless clients...")
url = 'http://{}/data/map_access_wireless_client_grid.json'\
url = 'http://{}/data/map_access_wireless_client_grid.json' \
.format(self.host)
referer = 'http://{}'.format(self.host)
@@ -158,7 +147,7 @@ class Tplink2DeviceScanner(TplinkDeviceScanner):
b64_encoded_username_password = base64.b64encode(
username_password.encode('ascii')
).decode('ascii')
cookie = 'Authorization=Basic {}'\
cookie = 'Authorization=Basic {}' \
.format(b64_encoded_username_password)
response = requests.post(url, headers={'referer': referer,
@@ -175,7 +164,119 @@ class Tplink2DeviceScanner(TplinkDeviceScanner):
self.last_results = {
device['mac_addr'].replace('-', ':'): device['name']
for device in result
}
}
return True
return False
class Tplink3DeviceScanner(TplinkDeviceScanner):
"""
This class queries the Archer C9 router running version 150811 or higher
of TP-Link firmware for connected devices.
"""
def __init__(self, config):
self.stok = ''
self.sysauth = ''
super(Tplink3DeviceScanner, self).__init__(config)
def scan_devices(self):
"""
Scans for new devices and return a list containing found device ids.
"""
self._update_info()
return self.last_results.keys()
# pylint: disable=no-self-use
def get_device_name(self, device):
"""
The TP-Link firmware doesn't save the name of the wireless device.
We are forced to use the MAC address as name here.
"""
return self.last_results.get(device)
def _get_auth_tokens(self):
"""
Retrieves auth tokens from the router.
"""
_LOGGER.info("Retrieving auth tokens...")
url = 'http://{}/cgi-bin/luci/;stok=/login?form=login' \
.format(self.host)
referer = 'http://{}/webpages/login.html'.format(self.host)
# if possible implement rsa encryption of password here
response = requests.post(url,
params={'operation': 'login',
'username': self.username,
'password': self.password},
headers={'referer': referer})
try:
self.stok = response.json().get('data').get('stok')
_LOGGER.info(self.stok)
regex_result = re.search('sysauth=(.*);',
response.headers['set-cookie'])
self.sysauth = regex_result.group(1)
_LOGGER.info(self.sysauth)
return True
except ValueError:
_LOGGER.error("Couldn't fetch auth tokens!")
return False
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
"""
Ensures the information from the TP-Link router is up to date.
Returns boolean if scanning successful.
"""
with self.lock:
if (self.stok == '') or (self.sysauth == ''):
self._get_auth_tokens()
_LOGGER.info("Loading wireless clients...")
url = 'http://{}/cgi-bin/luci/;stok={}/admin/wireless?form=statistics' \
.format(self.host, self.stok)
referer = 'http://{}/webpages/index.html'.format(self.host)
response = requests.post(url,
params={'operation': 'load'},
headers={'referer': referer},
cookies={'sysauth': self.sysauth})
try:
json_response = response.json()
if json_response.get('success'):
result = response.json().get('data')
else:
if json_response.get('errorcode') == 'timeout':
_LOGGER.info("Token timed out. "
"Relogging on next scan.")
self.stok = ''
self.sysauth = ''
return False
else:
_LOGGER.error("An unknown error happened "
"while fetching data.")
return False
except ValueError:
_LOGGER.error("Router didn't respond with JSON. "
"Check if credentials are correct.")
return False
if result:
self.last_results = {
device['mac'].replace('-', ':'): device['mac']
for device in result
}
return True
return False
@@ -0,0 +1,173 @@
"""
homeassistant.components.device_tracker.ubus
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Device tracker platform that supports scanning a OpenWRT router for device
presence.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.ubus.html
"""
import logging
import json
from datetime import timedelta
import re
import threading
import requests
from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD
from homeassistant.helpers import validate_config
from homeassistant.util import Throttle
from homeassistant.components.device_tracker import DOMAIN
# Return cached results if last scan was less then this time ago
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__)
def get_scanner(hass, config):
""" Validates config and returns a Luci scanner. """
if not validate_config(config,
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
_LOGGER):
return None
scanner = UbusDeviceScanner(config[DOMAIN])
return scanner if scanner.success_init else None
# pylint: disable=too-many-instance-attributes
class UbusDeviceScanner(object):
"""
This class queries a wireless router running OpenWrt firmware
for connected devices. Adapted from Tomato scanner.
Configure your routers' ubus ACL based on following instructions:
http://wiki.openwrt.org/doc/techref/ubus
Read only access will be fine.
To use this class you have to install rpcd-mod-file package
in your OpenWrt router:
opkg install rpcd-mod-file
"""
def __init__(self, config):
host = config[CONF_HOST]
username, password = config[CONF_USERNAME], config[CONF_PASSWORD]
self.parse_api_pattern = re.compile(r"(?P<param>\w*) = (?P<value>.*);")
self.lock = threading.Lock()
self.last_results = {}
self.url = 'http://{}/ubus'.format(host)
self.session_id = _get_session_id(self.url, username, password)
self.hostapd = []
self.leasefile = None
self.mac2name = None
self.success_init = self.session_id is not None
def scan_devices(self):
"""
Scans for new devices and return a list containing found device ids.
"""
self._update_info()
return self.last_results
def get_device_name(self, device):
""" Returns the name of the given device or None if we don't know. """
with self.lock:
if self.leasefile is None:
result = _req_json_rpc(self.url, self.session_id,
'call', 'uci', 'get',
config="dhcp", type="dnsmasq")
if result:
values = result["values"].values()
self.leasefile = next(iter(values))["leasefile"]
else:
return
if self.mac2name is None:
result = _req_json_rpc(self.url, self.session_id,
'call', 'file', 'read',
path=self.leasefile)
if result:
self.mac2name = dict()
for line in result["data"].splitlines():
hosts = line.split(" ")
self.mac2name[hosts[1].upper()] = hosts[3]
else:
# Error, handled in the _req_json_rpc
return
return self.mac2name.get(device.upper(), None)
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
"""
Ensures the information from the Luci router is up to date.
Returns boolean if scanning successful.
"""
if not self.success_init:
return False
with self.lock:
_LOGGER.info("Checking ARP")
if not self.hostapd:
hostapd = _req_json_rpc(self.url, self.session_id,
'list', 'hostapd.*', '')
self.hostapd.extend(hostapd.keys())
self.last_results = []
results = 0
for hostapd in self.hostapd:
result = _req_json_rpc(self.url, self.session_id,
'call', hostapd, 'get_clients')
if result:
results = results + 1
self.last_results.extend(result['clients'].keys())
return bool(results)
def _req_json_rpc(url, session_id, rpcmethod, subsystem, method, **params):
""" Perform one JSON RPC operation. """
data = json.dumps({"jsonrpc": "2.0",
"id": 1,
"method": rpcmethod,
"params": [session_id,
subsystem,
method,
params]})
try:
res = requests.post(url, data=data, timeout=5)
except requests.exceptions.Timeout:
return
if res.status_code == 200:
response = res.json()
if rpcmethod == "call":
return response["result"][1]
else:
return response["result"]
def _get_session_id(url, username, password):
""" Get authentication token for the given host+username+password. """
res = _req_json_rpc(url, "00000000000000000000000000000000", 'call',
'session', 'login', username=username,
password=password)
return res["ubus_rpc_session"]
+6 -10
View File
@@ -19,22 +19,24 @@ from homeassistant.const import (
DOMAIN = "discovery"
DEPENDENCIES = []
REQUIREMENTS = ['netdisco==0.3']
REQUIREMENTS = ['netdisco==0.5.1']
SCAN_INTERVAL = 300 # seconds
# Next 3 lines for now a mirror from netdisco.const
# Should setup a mapping netdisco.const -> own constants
SERVICE_WEMO = 'belkin_wemo'
SERVICE_HUE = 'philips_hue'
SERVICE_CAST = 'google_cast'
SERVICE_NETGEAR = 'netgear_router'
SERVICE_SONOS = 'sonos'
SERVICE_PLEX = 'plex_mediaserver'
SERVICE_HANDLERS = {
SERVICE_WEMO: "switch",
SERVICE_CAST: "media_player",
SERVICE_HUE: "light",
SERVICE_NETGEAR: 'device_tracker',
SERVICE_SONOS: 'media_player',
SERVICE_PLEX: 'media_player',
}
@@ -79,13 +81,6 @@ def setup(hass, config):
if not component:
return
# Hack - fix when device_tracker supports discovery
if service == SERVICE_NETGEAR:
bootstrap.setup_component(hass, component, {
'device_tracker': {'platform': 'netgear'}
})
return
# This component cannot be setup.
if not bootstrap.setup_component(hass, component, config):
return
@@ -95,6 +90,7 @@ def setup(hass, config):
ATTR_DISCOVERED: info
})
# pylint: disable=unused-argument
def start_discovery(event):
""" Start discovering. """
netdisco = DiscoveryService(SCAN_INTERVAL)
+7 -1
View File
@@ -1,8 +1,10 @@
"""
homeassistant.components.downloader
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides functionality to download files.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/downloader.html
"""
import os
import logging
@@ -42,6 +44,10 @@ def setup(hass, config):
download_path = config[DOMAIN][CONF_DOWNLOAD_DIR]
# If path is relative, we assume relative to HASS config dir
if not os.path.isabs(download_path):
download_path = hass.config.path(download_path)
if not os.path.isdir(download_path):
logger.error(
+19 -2
View File
@@ -11,6 +11,7 @@ import logging
from . import version
import homeassistant.util as util
from homeassistant.const import URL_ROOT, HTTP_OK
from homeassistant.config import get_default_config_dir
DOMAIN = 'frontend'
DEPENDENCIES = ['api']
@@ -19,9 +20,9 @@ INDEX_PATH = os.path.join(os.path.dirname(__file__), 'index.html.template')
_LOGGER = logging.getLogger(__name__)
FRONTEND_URLS = [
URL_ROOT, '/logbook', '/history', '/devService', '/devState', '/devEvent']
URL_ROOT, '/logbook', '/history', '/map', '/devService', '/devState',
'/devEvent']
STATES_URL = re.compile(r'/states(/([a-zA-Z\._\-0-9/]+)|)')
@@ -43,6 +44,9 @@ def setup(hass, config):
hass.http.register_path(
'HEAD', re.compile(r'/static/(?P<file>[a-zA-Z\._\-0-9/]+)'),
_handle_get_static, False)
hass.http.register_path(
'GET', re.compile(r'/local/(?P<file>[a-zA-Z\._\-0-9/]+)'),
_handle_get_local, False)
return True
@@ -83,3 +87,16 @@ def _handle_get_static(handler, path_match, data):
path = os.path.join(os.path.dirname(__file__), 'www_static', req_file)
handler.write_file(path)
def _handle_get_local(handler, path_match, data):
"""
Returns a static file from the hass.config.path/www for the frontend.
"""
req_file = util.sanitize_path(path_match.group('file'))
path = os.path.join(get_default_config_dir(), 'www', req_file)
if not os.path.isfile(path):
return False
handler.write_file(path)
+1 -1
View File
@@ -1,2 +1,2 @@
""" DO NOT MODIFY. Auto-generated by build_frontend script """
VERSION = "35ecb5457a9ff0f4142c2605b53eb843"
VERSION = "beb922c55bb26ea576581b453f6d7c04"
File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 797 B

File diff suppressed because one or more lines are too long
+7 -4
View File
@@ -1,10 +1,11 @@
"""
homeassistant.components.group
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides functionality to group devices that can be turned on or off.
"""
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/group.html
"""
import homeassistant.core as ha
from homeassistant.helpers import generate_entity_id
from homeassistant.helpers.event import track_state_change
@@ -12,7 +13,8 @@ from homeassistant.helpers.entity import Entity
import homeassistant.util as util
from homeassistant.const import (
ATTR_ENTITY_ID, STATE_ON, STATE_OFF,
STATE_HOME, STATE_NOT_HOME, STATE_UNKNOWN)
STATE_HOME, STATE_NOT_HOME, STATE_OPEN, STATE_CLOSED,
STATE_UNKNOWN)
DOMAIN = "group"
DEPENDENCIES = []
@@ -22,7 +24,8 @@ ENTITY_ID_FORMAT = DOMAIN + ".{}"
ATTR_AUTO = "auto"
# List of ON/OFF state tuples for groupable states
_GROUP_TYPES = [(STATE_ON, STATE_OFF), (STATE_HOME, STATE_NOT_HOME)]
_GROUP_TYPES = [(STATE_ON, STATE_OFF), (STATE_HOME, STATE_NOT_HOME),
(STATE_OPEN, STATE_CLOSED)]
def _get_group_on_off(state):
+3 -3
View File
@@ -1,8 +1,10 @@
"""
homeassistant.components.history
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provide pre-made queries on top of the recorder component.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/history.html
"""
import re
from datetime import timedelta
@@ -147,8 +149,6 @@ def _api_history_period(handler, path_match, data):
end_time = start_time + one_day
print("Fetchign", start_time, end_time)
entity_id = data.get('filter_entity_id')
handler.write_json(
+12 -72
View File
@@ -1,76 +1,11 @@
"""
homeassistant.components.httpinterface
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
homeassistant.components.http
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This module provides an API and a HTTP interface for debug purposes.
By default it will run on port 8123.
All API calls have to be accompanied by an 'api_password' parameter and will
return JSON. If successful calls will return status code 200 or 201.
Other status codes that can occur are:
- 400 (Bad Request)
- 401 (Unauthorized)
- 404 (Not Found)
- 405 (Method not allowed)
The api supports the following actions:
/api - GET
Returns message if API is up and running.
Example result:
{
"message": "API running."
}
/api/states - GET
Returns a list of entities for which a state is available
Example result:
[
{ .. state object .. },
{ .. state object .. }
]
/api/states/<entity_id> - GET
Returns the current state from an entity
Example result:
{
"attributes": {
"next_rising": "07:04:15 29-10-2013",
"next_setting": "18:00:31 29-10-2013"
},
"entity_id": "weather.sun",
"last_changed": "23:24:33 28-10-2013",
"state": "below_horizon"
}
/api/states/<entity_id> - POST
Updates the current state of an entity. Returns status code 201 if successful
with location header of updated resource and as body the new state.
parameter: new_state - string
optional parameter: attributes - JSON encoded object
Example result:
{
"attributes": {
"next_rising": "07:04:15 29-10-2013",
"next_setting": "18:00:31 29-10-2013"
},
"entity_id": "weather.sun",
"last_changed": "23:24:33 28-10-2013",
"state": "below_horizon"
}
/api/events/<event_type> - POST
Fires an event with event_type
optional parameter: event_data - JSON encoded object
Example result:
{
"message": "Event download_file fired."
}
For more details about the RESTful API, please refer to the documentation at
https://home-assistant.io/developers/api.html
"""
import json
import threading
import logging
@@ -205,7 +140,7 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer):
self.serve_forever()
def register_path(self, method, url, callback, require_auth=True):
""" Registers a path wit the server. """
""" Registers a path with the server. """
self.paths.append((method, url, callback, require_auth))
def log_message(self, fmt, *args):
@@ -232,7 +167,12 @@ class RequestHandler(SimpleHTTPRequestHandler):
def log_message(self, fmt, *arguments):
""" Redirect built-in log to HA logging """
_LOGGER.info(fmt, *arguments)
if self.server.no_password_set:
_LOGGER.info(fmt, *arguments)
else:
_LOGGER.info(
fmt, *(arg.replace(self.server.api_password, '*******')
if isinstance(arg, str) else arg for arg in arguments))
def _handle_request(self, method): # pylint: disable=too-many-branches
""" Does some common checks and calls appropriate method. """
@@ -487,7 +427,7 @@ class ServerSession:
return self._expiry < date_util.utcnow()
class SessionStore:
class SessionStore(object):
""" Responsible for storing and retrieving http sessions """
def __init__(self, enabled=True):
""" Set up the session store """
+66
View File
@@ -0,0 +1,66 @@
"""
homeassistant.components.ifttt
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This component enable you to trigger Maker IFTTT recipes.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/ifttt.html
"""
import logging
import requests
from homeassistant.helpers import validate_config
_LOGGER = logging.getLogger(__name__)
DOMAIN = "ifttt"
SERVICE_TRIGGER = 'trigger'
ATTR_EVENT = 'event'
ATTR_VALUE1 = 'value1'
ATTR_VALUE2 = 'value2'
ATTR_VALUE3 = 'value3'
DEPENDENCIES = []
REQUIREMENTS = ['pyfttt==0.3']
def trigger(hass, event, value1=None, value2=None, value3=None):
""" Trigger a Maker IFTTT recipe """
data = {
ATTR_EVENT: event,
ATTR_VALUE1: value1,
ATTR_VALUE2: value2,
ATTR_VALUE3: value3,
}
hass.services.call(DOMAIN, SERVICE_TRIGGER, data)
def setup(hass, config):
""" Setup the ifttt service component """
if not validate_config(config, {DOMAIN: ['key']}, _LOGGER):
return False
key = config[DOMAIN]['key']
def trigger_service(call):
""" Handle ifttt trigger service calls. """
event = call.data.get(ATTR_EVENT)
value1 = call.data.get(ATTR_VALUE1)
value2 = call.data.get(ATTR_VALUE2)
value3 = call.data.get(ATTR_VALUE3)
if event is None:
return
try:
import pyfttt as pyfttt
pyfttt.send_event(key, event, value1, value2, value3)
except requests.exceptions.RequestException:
_LOGGER.exception("Error communicating with IFTTT")
hass.services.register(DOMAIN, SERVICE_TRIGGER, trigger_service)
return True
+3 -1
View File
@@ -1,8 +1,10 @@
"""
homeassistant.components.introduction
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Component that will help guide the user taking its first steps.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/introduction.html
"""
import logging
+3 -1
View File
@@ -1,8 +1,10 @@
"""
homeassistant.components.keyboard
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides functionality to emulate keyboard presses on host machine.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/keyboard.html
"""
import logging
+13 -10
View File
@@ -52,14 +52,14 @@ import logging
import os
import csv
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity import ToggleEntity
import homeassistant.util as util
import homeassistant.util.color as color_util
from homeassistant.components import group, discovery, wink, isy994
from homeassistant.config import load_yaml_config_file
from homeassistant.const import (
STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID)
from homeassistant.components import group, discovery, wink, isy994
from homeassistant.helpers.entity import ToggleEntity
from homeassistant.helpers.entity_component import EntityComponent
import homeassistant.util as util
import homeassistant.util.color as color_util
DOMAIN = "light"
@@ -246,6 +246,7 @@ def setup(hass, config):
rgb_color = dat.get(ATTR_RGB_COLOR)
if len(rgb_color) == 3:
params[ATTR_RGB_COLOR] = [int(val) for val in rgb_color]
params[ATTR_XY_COLOR] = \
color_util.color_RGB_to_xy(int(rgb_color[0]),
int(rgb_color[1]),
@@ -275,11 +276,13 @@ def setup(hass, config):
light.update_ha_state(True)
# Listen for light on and light off service calls
hass.services.register(DOMAIN, SERVICE_TURN_ON,
handle_light_service)
descriptions = load_yaml_config_file(
os.path.join(os.path.dirname(__file__), 'services.yaml'))
hass.services.register(DOMAIN, SERVICE_TURN_ON, handle_light_service,
descriptions.get(SERVICE_TURN_ON))
hass.services.register(DOMAIN, SERVICE_TURN_OFF,
handle_light_service)
hass.services.register(DOMAIN, SERVICE_TURN_OFF, handle_light_service,
descriptions.get(SERVICE_TURN_OFF))
return True
@@ -0,0 +1,76 @@
"""
homeassistant.components.light.blinksticklight
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for Blinkstick lights.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.blinksticklight.html
"""
import logging
from blinkstick import blinkstick
from homeassistant.components.light import (Light, ATTR_RGB_COLOR)
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ["blinkstick==1.1.7"]
DEPENDENCIES = []
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Add device specified by serial number. """
stick = blinkstick.find_by_serial(config['serial'])
add_devices_callback([BlinkStickLight(stick, config['name'])])
class BlinkStickLight(Light):
""" Represents a BlinkStick light. """
def __init__(self, stick, name):
self._stick = stick
self._name = name
self._serial = stick.get_serial()
self._rgb_color = stick.get_color()
@property
def should_poll(self):
""" Polling needed. """
return True
@property
def name(self):
""" The name of the light. """
return self._name
@property
def rgb_color(self):
""" Read back the color of the light. """
return self._rgb_color
@property
def is_on(self):
""" Check whether any of the LEDs colors are non-zero. """
return sum(self._rgb_color) > 0
def update(self):
""" Read back the device state """
self._rgb_color = self._stick.get_color()
def turn_on(self, **kwargs):
""" Turn the device on. """
if ATTR_RGB_COLOR in kwargs:
self._rgb_color = kwargs[ATTR_RGB_COLOR]
else:
self._rgb_color = [255, 255, 255]
self._stick.set_color(red=self._rgb_color[0],
green=self._rgb_color[1],
blue=self._rgb_color[2])
def turn_off(self, **kwargs):
""" Turn the device off """
self._stick.turn_off()
+2
View File
@@ -2,6 +2,8 @@
homeassistant.components.light.hue
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for Hue lights.
https://home-assistant.io/components/light.hue.html
"""
import logging
import socket
+125
View File
@@ -0,0 +1,125 @@
"""
homeassistant.components.light.hyperion
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for Hyperion remotes.
https://home-assistant.io/components/light.hyperion.html
"""
import logging
import socket
import json
from homeassistant.const import CONF_HOST
from homeassistant.components.light import (Light, ATTR_RGB_COLOR)
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = []
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Sets up a Hyperion server remote """
host = config.get(CONF_HOST, None)
port = config.get("port", 19444)
device = Hyperion(host, port)
if device.setup():
add_devices_callback([device])
return True
else:
return False
class Hyperion(Light):
""" Represents a Hyperion remote """
def __init__(self, host, port):
self._host = host
self._port = port
self._name = host
self._is_available = True
self._rgb_color = [255, 255, 255]
@property
def name(self):
""" Return the hostname of the server. """
return self._name
@property
def rgb_color(self):
""" Last RGB color value set. """
return self._rgb_color
@property
def is_on(self):
""" True if the device is online. """
return self._is_available
def turn_on(self, **kwargs):
""" Turn the lights on. """
if self._is_available:
if ATTR_RGB_COLOR in kwargs:
self._rgb_color = kwargs[ATTR_RGB_COLOR]
self.json_request({"command": "color", "priority": 128,
"color": self._rgb_color})
def turn_off(self, **kwargs):
""" Disconnect the remote. """
self.json_request({"command": "clearall"})
def update(self):
""" Ping the remote. """
# just see if the remote port is open
self._is_available = self.json_request()
def setup(self):
""" Get the hostname of the remote. """
response = self.json_request({"command": "serverinfo"})
if response:
self._name = response["info"]["hostname"]
return True
return False
def json_request(self, request=None, wait_for_response=False):
""" Communicate with the json server. """
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(5)
try:
sock.connect((self._host, self._port))
except OSError:
sock.close()
return False
if not request:
# no communication needed, simple presence detection returns True
sock.close()
return True
sock.send(bytearray(json.dumps(request) + "\n", "utf-8"))
try:
buf = sock.recv(4096)
except socket.timeout:
# something is wrong, assume it's offline
sock.close()
return False
# read until a newline or timeout
buffering = True
while buffering:
if "\n" in str(buf, "utf-8"):
response = str(buf, "utf-8").split("\n")[0]
buffering = False
else:
try:
more = sock.recv(4096)
except socket.timeout:
more = None
if not more:
buffering = False
response = str(buf, "utf-8")
else:
buf += more
sock.close()
return json.loads(response)
+78 -43
View File
@@ -12,19 +12,7 @@ Support for LimitlessLED bulbs, also known as...
- dekolight
- iLight
Configuration:
To use limitlessled you will need to add the following to your
config/configuration.yaml.
light:
platform: limitlessled
host: 192.168.1.10
group_1_name: Living Room
group_2_name: Bedroom
group_3_name: Office
group_4_name: Kitchen
https://home-assistant.io/components/light.limitlessled.html
"""
import logging
@@ -34,19 +22,30 @@ from homeassistant.components.light import (Light, ATTR_BRIGHTNESS,
from homeassistant.util.color import color_RGB_to_xy
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['ledcontroller==1.0.7']
REQUIREMENTS = ['ledcontroller==1.1.0']
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Gets the LimitlessLED lights. """
import ledcontroller
led = ledcontroller.LedController(config['host'])
# Handle old configuration format:
bridges = config.get('bridges', [config])
for bridge_id, bridge in enumerate(bridges):
bridge['id'] = bridge_id
pool = ledcontroller.LedControllerPool([x['host'] for x in bridges])
lights = []
for i in range(1, 5):
if 'group_%d_name' % (i) in config:
lights.append(LimitlessLED(led, i, config['group_%d_name' % (i)]))
for bridge in bridges:
for i in range(1, 5):
name_key = 'group_%d_name' % i
if name_key in bridge:
group_type = bridge.get('group_%d_type' % i, 'rgbw')
lights.append(LimitlessLED.factory(pool, bridge['id'], i,
bridge[name_key],
group_type))
add_devices_callback(lights)
@@ -54,15 +53,57 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
class LimitlessLED(Light):
""" Represents a LimitlessLED light """
def __init__(self, led, group, name):
self.led = led
@staticmethod
def factory(pool, controller_id, group, name, group_type):
''' Construct a Limitless LED of the appropriate type '''
if group_type == 'white':
return WhiteLimitlessLED(pool, controller_id, group, name)
elif group_type == 'rgbw':
return RGBWLimitlessLED(pool, controller_id, group, name)
# pylint: disable=too-many-arguments
def __init__(self, pool, controller_id, group, name, group_type):
self.pool = pool
self.controller_id = controller_id
self.group = group
self.pool.execute(self.controller_id, "set_group_type", self.group,
group_type)
# LimitlessLEDs don't report state, we have track it ourselves.
self.led.off(self.group)
self.pool.execute(self.controller_id, "off", self.group)
self._name = name or DEVICE_DEFAULT_NAME
self._state = False
@property
def should_poll(self):
""" No polling needed. """
return False
@property
def name(self):
""" Returns the name of the device if any. """
return self._name
@property
def is_on(self):
""" True if device is on. """
return self._state
def turn_off(self, **kwargs):
""" Turn the device off. """
self._state = False
self.pool.execute(self.controller_id, "off", self.group)
self.update_ha_state()
class RGBWLimitlessLED(LimitlessLED):
""" Represents a RGBW LimitlessLED light """
def __init__(self, pool, controller_id, group, name):
super().__init__(pool, controller_id, group, name, 'rgbw')
self._brightness = 100
self._xy_color = color_RGB_to_xy(255, 255, 255)
@@ -88,16 +129,6 @@ class LimitlessLED(Light):
((0xE6, 0xE6, 0xFA), 'lavendar'),
]]
@property
def should_poll(self):
""" No polling needed for a demo light. """
return False
@property
def name(self):
""" Returns the name of the device if any. """
return self._name
@property
def brightness(self):
return self._brightness
@@ -107,7 +138,7 @@ class LimitlessLED(Light):
return self._xy_color
def _xy_to_led_color(self, xy_color):
""" Convert an XY color to the closest LedController color string """
""" Convert an XY color to the closest LedController color string. """
def abs_dist_squared(p_0, p_1):
""" Returns the absolute value of the squared distance """
return abs((p_0[0] - p_1[0])**2 + (p_0[1] - p_1[1])**2)
@@ -118,11 +149,6 @@ class LimitlessLED(Light):
# First candidate in the sorted list is closest to desired color:
return sorted(candidates)[0][1]
@property
def is_on(self):
""" True if device is on. """
return self._state
def turn_on(self, **kwargs):
""" Turn the device on. """
self._state = True
@@ -133,12 +159,21 @@ class LimitlessLED(Light):
if ATTR_XY_COLOR in kwargs:
self._xy_color = kwargs[ATTR_XY_COLOR]
self.led.set_color(self._xy_to_led_color(self._xy_color), self.group)
self.led.set_brightness(self._brightness / 255.0, self.group)
self.pool.execute(self.controller_id, "set_color",
self._xy_to_led_color(self._xy_color), self.group)
self.pool.execute(self.controller_id, "set_brightness",
self._brightness / 255.0, self.group)
self.update_ha_state()
def turn_off(self, **kwargs):
""" Turn the device off. """
self._state = False
self.led.off(self.group)
class WhiteLimitlessLED(LimitlessLED):
""" Represents a White LimitlessLED light """
def __init__(self, pool, controller_id, group, name):
super().__init__(pool, controller_id, group, name, 'white')
def turn_on(self, **kwargs):
""" Turn the device on. """
self._state = True
self.pool.execute(self.controller_id, "on", self.group)
self.update_ha_state()
+112
View File
@@ -0,0 +1,112 @@
"""
homeassistant.components.light.rfxtrx
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for RFXtrx lights.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.rfxtrx.html
"""
import logging
import homeassistant.components.rfxtrx as rfxtrx
import RFXtrx as rfxtrxmod
from homeassistant.components.light import Light
from homeassistant.util import slugify
DEPENDENCIES = ['rfxtrx']
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Setup the RFXtrx platform. """
lights = []
devices = config.get('devices', None)
if devices:
for entity_id, entity_info in devices.items():
if entity_id not in rfxtrx.RFX_DEVICES:
_LOGGER.info("Add %s rfxtrx.light", entity_info['name'])
rfxobject = rfxtrx.get_rfx_object(entity_info['packetid'])
new_light = RfxtrxLight(entity_info['name'], rfxobject, False)
rfxtrx.RFX_DEVICES[entity_id] = new_light
lights.append(new_light)
add_devices_callback(lights)
def light_update(event):
""" Callback for light updates from the RFXtrx gateway. """
if not isinstance(event.device, rfxtrxmod.LightingDevice):
return
# Add entity if not exist and the automatic_add is True
entity_id = slugify(event.device.id_string.lower())
if entity_id not in rfxtrx.RFX_DEVICES:
automatic_add = config.get('automatic_add', False)
if not automatic_add:
return
_LOGGER.info(
"Automatic add %s rfxtrx.light (Class: %s Sub: %s)",
entity_id,
event.device.__class__.__name__,
event.device.subtype
)
pkt_id = "".join("{0:02x}".format(x) for x in event.data)
entity_name = "%s : %s" % (entity_id, pkt_id)
new_light = RfxtrxLight(entity_name, event, False)
rfxtrx.RFX_DEVICES[entity_id] = new_light
add_devices_callback([new_light])
# Check if entity exists or previously added automatically
if entity_id in rfxtrx.RFX_DEVICES:
if event.values['Command'] == 'On'\
or event.values['Command'] == 'Off':
if event.values['Command'] == 'On':
rfxtrx.RFX_DEVICES[entity_id].turn_on()
else:
rfxtrx.RFX_DEVICES[entity_id].turn_off()
# Subscribe to main rfxtrx events
if light_update not in rfxtrx.RECEIVED_EVT_SUBSCRIBERS:
rfxtrx.RECEIVED_EVT_SUBSCRIBERS.append(light_update)
class RfxtrxLight(Light):
""" Provides a RFXtrx light. """
def __init__(self, name, event, state):
self._name = name
self._event = event
self._state = state
@property
def should_poll(self):
""" No polling needed for a light. """
return False
@property
def name(self):
""" Returns the name of the light if any. """
return self._name
@property
def is_on(self):
""" True if light is on. """
return self._state
def turn_on(self, **kwargs):
""" Turn the light on. """
if hasattr(self, '_event') and self._event:
self._event.device.send_on(rfxtrx.RFXOBJECT.transport)
self._state = True
self.update_ha_state()
def turn_off(self, **kwargs):
""" Turn the light off. """
if hasattr(self, '_event') and self._event:
self._event.device.send_off(rfxtrx.RFXOBJECT.transport)
self._state = False
self.update_ha_state()
@@ -0,0 +1,52 @@
# Describes the format for available light services
turn_on:
description: Turn a light on
fields:
entity_id:
description: Name(s) of entities to turn on
example: 'light.kitchen'
transition:
description: Duration in seconds it takes to get to next state
example: 60
rgb_color:
description: Color for the light in RGB-format
example: '[255, 100, 100]'
xy_color:
description: Color for the light in XY-format
example: '[0.52, 0.43]'
brightness:
description: Number between 0..255 indicating brightness
example: 120
profile:
description: Name of a light profile to use
example: relax
flash:
description: If the light should flash
values:
- short
- long
effect:
description: Light effect
values:
- colorloop
turn_off:
description: Turn a light off
fields:
entity_id:
description: Name(s) of entities to turn off
example: 'light.kitchen'
transition:
description: Duration in seconds it takes to get to next state
example: 60
+43 -12
View File
@@ -2,16 +2,21 @@
homeassistant.components.light.tellstick
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for Tellstick lights.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.tellstick.html
"""
import logging
# pylint: disable=no-name-in-module, import-error
from homeassistant.components.light import Light, ATTR_BRIGHTNESS
from homeassistant.const import ATTR_FRIENDLY_NAME
from homeassistant.const import (EVENT_HOMEASSISTANT_STOP,
ATTR_FRIENDLY_NAME)
import tellcore.constants as tellcore_constants
REQUIREMENTS = ['tellcore-py==1.0.4']
from tellcore.library import DirectCallbackDispatcher
REQUIREMENTS = ['tellcore-py==1.1.2']
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Find and return Tellstick lights. """
@@ -22,13 +27,32 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"Failed to import tellcore")
return []
core = telldus.TelldusCore()
core = telldus.TelldusCore(callback_dispatcher=DirectCallbackDispatcher())
switches_and_lights = core.devices()
lights = []
for switch in switches_and_lights:
if switch.methods(tellcore_constants.TELLSTICK_DIM):
lights.append(TellstickLight(switch))
def _device_event_callback(id_, method, data, cid):
""" Called from the TelldusCore library to update one device """
for light_device in lights:
if light_device.tellstick_device.id == id_:
# Execute the update in another thread
light_device.update_ha_state(True)
break
callback_id = core.register_device_event(_device_event_callback)
def unload_telldus_lib(event):
""" Un-register the callback bindings """
if callback_id is not None:
core.unregister_callback(callback_id)
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, unload_telldus_lib)
add_devices_callback(lights)
@@ -40,15 +64,15 @@ class TellstickLight(Light):
tellcore_constants.TELLSTICK_UP |
tellcore_constants.TELLSTICK_DOWN)
def __init__(self, tellstick):
self.tellstick = tellstick
self.state_attr = {ATTR_FRIENDLY_NAME: tellstick.name}
def __init__(self, tellstick_device):
self.tellstick_device = tellstick_device
self.state_attr = {ATTR_FRIENDLY_NAME: tellstick_device.name}
self._brightness = 0
@property
def name(self):
""" Returns the name of the switch if any. """
return self.tellstick.name
return self.tellstick_device.name
@property
def is_on(self):
@@ -62,8 +86,9 @@ class TellstickLight(Light):
def turn_off(self, **kwargs):
""" Turns the switch off. """
self.tellstick.turn_off()
self.tellstick_device.turn_off()
self._brightness = 0
self.update_ha_state()
def turn_on(self, **kwargs):
""" Turns the switch on. """
@@ -74,11 +99,12 @@ class TellstickLight(Light):
else:
self._brightness = brightness
self.tellstick.dim(self._brightness)
self.tellstick_device.dim(self._brightness)
self.update_ha_state()
def update(self):
""" Update state of the light. """
last_command = self.tellstick.last_sent_command(
last_command = self.tellstick_device.last_sent_command(
self.last_sent_command_mask)
if last_command == tellcore_constants.TELLSTICK_TURNON:
@@ -88,6 +114,11 @@ class TellstickLight(Light):
elif (last_command == tellcore_constants.TELLSTICK_DIM or
last_command == tellcore_constants.TELLSTICK_UP or
last_command == tellcore_constants.TELLSTICK_DOWN):
last_sent_value = self.tellstick.last_sent_value()
last_sent_value = self.tellstick_device.last_sent_value()
if last_sent_value is not None:
self._brightness = last_sent_value
@property
def should_poll(self):
""" Tells Home Assistant not to poll this entity. """
return False
+8 -48
View File
@@ -1,59 +1,18 @@
"""
homeassistant.components.light.vera
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for Vera lights.
Support for Vera lights. This component is useful if you wish for switches
connected to your Vera controller to appear as lights in Home Assistant.
All switches will be added as a light unless you exclude them in the config.
Configuration:
To use the Vera lights you will need to add something like the following to
your config/configuration.yaml.
light:
platform: vera
vera_controller_url: http://YOUR_VERA_IP:3480/
device_data:
12:
name: My awesome switch
exclude: true
13:
name: Another switch
Variables:
vera_controller_url
*Required
This is the base URL of your vera controller including the port number if not
running on 80. Example: http://192.168.1.21:3480/
device_data
*Optional
This contains an array additional device info for your Vera devices. It is not
required and if not specified all lights configured in your Vera controller
will be added with default values. You should use the id of your vera device
as the key for the device within device_data.
These are the variables for the device_data array:
name
*Optional
This parameter allows you to override the name of your Vera device in the HA
interface, if not specified the value configured for the device in your Vera
will be used.
exclude
*Optional
This parameter allows you to exclude the specified device from Home Assistant,
it should be set to "true" if you want this device excluded.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.vera.html
"""
import logging
from requests.exceptions import RequestException
from homeassistant.components.switch.vera import VeraSwitch
# pylint: disable=no-name-in-module, import-error
import homeassistant.external.vera.vera as veraApi
REQUIREMENTS = ['https://github.com/balloob/home-assistant-vera-api/archive/'
'a8f823066ead6c7da6fb5e7abaf16fef62e63364.zip'
'#python-vera==0.1']
_LOGGER = logging.getLogger(__name__)
@@ -61,6 +20,7 @@ _LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Find and return Vera lights. """
import pyvera as veraApi
base_url = config.get('vera_controller_url')
if not base_url:
+6 -2
View File
@@ -2,6 +2,9 @@
homeassistant.components.light.wink
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for Wink lights.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.wink.html
"""
import logging
@@ -9,8 +12,9 @@ from homeassistant.components.light import ATTR_BRIGHTNESS
from homeassistant.components.wink import WinkToggleDevice
from homeassistant.const import CONF_ACCESS_TOKEN
REQUIREMENTS = ['https://github.com/balloob/python-wink/archive/' +
'c2b700e8ca866159566ecf5e644d9c297f69f257.zip']
REQUIREMENTS = ['https://github.com/balloob/python-wink/archive/'
'c2b700e8ca866159566ecf5e644d9c297f69f257.zip'
'#python-wink==0.1']
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
+56 -10
View File
@@ -1,8 +1,10 @@
"""
homeassistant.components.logbook
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Parses events and generates a human log.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/logbook.html
"""
from datetime import timedelta
from itertools import groupby
@@ -10,11 +12,12 @@ import re
from homeassistant.core import State, DOMAIN as HA_DOMAIN
from homeassistant.const import (
EVENT_STATE_CHANGED, STATE_HOME, STATE_ON, STATE_OFF,
EVENT_STATE_CHANGED, STATE_NOT_HOME, STATE_ON, STATE_OFF,
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, HTTP_BAD_REQUEST)
from homeassistant import util
import homeassistant.util.dt as dt_util
import homeassistant.components.recorder as recorder
import homeassistant.components.sun as sun
from homeassistant.components import recorder, sun
DOMAIN = "logbook"
DEPENDENCIES = ['recorder', 'http']
@@ -25,8 +28,29 @@ QUERY_EVENTS_BETWEEN = """
SELECT * FROM events WHERE time_fired > ? AND time_fired < ?
"""
EVENT_LOGBOOK_ENTRY = 'LOGBOOK_ENTRY'
GROUP_BY_MINUTES = 15
ATTR_NAME = 'name'
ATTR_MESSAGE = 'message'
ATTR_DOMAIN = 'domain'
ATTR_ENTITY_ID = 'entity_id'
def log_entry(hass, name, message, domain=None, entity_id=None):
""" Adds an entry to the logbook. """
data = {
ATTR_NAME: name,
ATTR_MESSAGE: message
}
if domain is not None:
data[ATTR_DOMAIN] = domain
if entity_id is not None:
data[ATTR_ENTITY_ID] = entity_id
hass.bus.fire(EVENT_LOGBOOK_ENTRY, data)
def setup(hass, config):
""" Listens for download events to download files. """
@@ -110,7 +134,10 @@ def humanify(events):
# Process events
for event in events_batch:
if event.event_type == EVENT_STATE_CHANGED:
entity_id = event.data['entity_id']
entity_id = event.data.get('entity_id')
if entity_id is None:
continue
if entity_id.startswith('sensor.'):
last_sensor_event[entity_id] = event
@@ -137,10 +164,12 @@ def humanify(events):
to_state = State.from_dict(event.data.get('new_state'))
# if last_changed == last_updated only attributes have changed
# we do not report on that yet.
# if last_changed != last_updated only attributes have changed
# we do not report on that yet. Also filter auto groups.
if not to_state or \
to_state.last_changed != to_state.last_updated:
to_state.last_changed != to_state.last_updated or \
to_state.domain == 'group' and \
to_state.attributes.get('auto', False):
continue
domain = to_state.domain
@@ -175,14 +204,31 @@ def humanify(events):
event.time_fired, "Home Assistant", action,
domain=HA_DOMAIN)
elif event.event_type == EVENT_LOGBOOK_ENTRY:
domain = event.data.get(ATTR_DOMAIN)
entity_id = event.data.get(ATTR_ENTITY_ID)
if domain is None and entity_id is not None:
try:
domain = util.split_entity_id(str(entity_id))[0]
except IndexError:
pass
yield Entry(
event.time_fired, event.data.get(ATTR_NAME),
event.data.get(ATTR_MESSAGE), domain,
entity_id)
def _entry_message_from_state(domain, state):
""" Convert a state to a message for the logbook. """
# We pass domain in so we don't have to split entity_id again
# pylint: disable=too-many-return-statements
if domain == 'device_tracker':
return '{} home'.format(
'arrived' if state.state == STATE_HOME else 'left')
if state.state == STATE_NOT_HOME:
return 'is away'
else:
return 'is at {}'.format(state.state)
elif domain == 'sun':
if state.state == sun.STATE_ABOVE_HORIZON:
@@ -5,8 +5,10 @@ homeassistant.components.media_player
Component to interface with various media players.
"""
import logging
import os
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
from homeassistant.const import (
@@ -19,15 +21,18 @@ from homeassistant.const import (
DOMAIN = 'media_player'
DEPENDENCIES = []
SCAN_INTERVAL = 30
SCAN_INTERVAL = 10
ENTITY_ID_FORMAT = DOMAIN + '.{}'
DISCOVERY_PLATFORMS = {
discovery.SERVICE_CAST: 'cast',
discovery.SERVICE_SONOS: 'sonos',
discovery.SERVICE_PLEX: 'plex',
}
SERVICE_YOUTUBE_VIDEO = 'play_youtube_video'
SERVICE_PLAY_MEDIA = 'play_media'
ATTR_MEDIA_VOLUME_LEVEL = 'volume_level'
ATTR_MEDIA_VOLUME_MUTED = 'is_volume_muted'
@@ -43,6 +48,8 @@ ATTR_MEDIA_TRACK = 'media_track'
ATTR_MEDIA_SERIES_TITLE = 'media_series_title'
ATTR_MEDIA_SEASON = 'media_season'
ATTR_MEDIA_EPISODE = 'media_episode'
ATTR_MEDIA_CHANNEL = 'media_channel'
ATTR_MEDIA_PLAYLIST = 'media_playlist'
ATTR_APP_ID = 'app_id'
ATTR_APP_NAME = 'app_name'
ATTR_SUPPORTED_MEDIA_COMMANDS = 'supported_media_commands'
@@ -50,6 +57,9 @@ ATTR_SUPPORTED_MEDIA_COMMANDS = 'supported_media_commands'
MEDIA_TYPE_MUSIC = 'music'
MEDIA_TYPE_TVSHOW = 'tvshow'
MEDIA_TYPE_VIDEO = 'movie'
MEDIA_TYPE_EPISODE = 'episode'
MEDIA_TYPE_CHANNEL = 'channel'
MEDIA_TYPE_PLAYLIST = 'playlist'
SUPPORT_PAUSE = 1
SUPPORT_SEEK = 2
@@ -60,6 +70,7 @@ SUPPORT_NEXT_TRACK = 32
SUPPORT_YOUTUBE = 64
SUPPORT_TURN_ON = 128
SUPPORT_TURN_OFF = 256
SUPPORT_PLAY_MEDIA = 512
YOUTUBE_COVER_URL_FORMAT = 'https://img.youtube.com/vi/{}/1.jpg'
@@ -73,6 +84,7 @@ SERVICE_TO_METHOD = {
SERVICE_MEDIA_PAUSE: 'media_pause',
SERVICE_MEDIA_NEXT_TRACK: 'media_next_track',
SERVICE_MEDIA_PREVIOUS_TRACK: 'media_previous_track',
SERVICE_PLAY_MEDIA: 'play_media',
}
ATTR_TO_PROPERTY = [
@@ -89,6 +101,8 @@ ATTR_TO_PROPERTY = [
ATTR_MEDIA_SERIES_TITLE,
ATTR_MEDIA_SEASON,
ATTR_MEDIA_EPISODE,
ATTR_MEDIA_CHANNEL,
ATTR_MEDIA_PLAYLIST,
ATTR_APP_ID,
ATTR_APP_NAME,
ATTR_SUPPORTED_MEDIA_COMMANDS,
@@ -177,6 +191,16 @@ def media_previous_track(hass, entity_id=None):
hass.services.call(DOMAIN, SERVICE_MEDIA_PREVIOUS_TRACK, data)
def play_media(hass, media_type, media_id, entity_id=None):
""" Send the media player the command for playing media. """
data = {"media_type": media_type, "media_id": media_id}
if entity_id:
data[ATTR_ENTITY_ID] = entity_id
hass.services.call(DOMAIN, SERVICE_PLAY_MEDIA, data)
def setup(hass, config):
""" Track states and offer events for media_players. """
component = EntityComponent(
@@ -185,6 +209,9 @@ def setup(hass, config):
component.setup(config)
descriptions = load_yaml_config_file(
os.path.join(os.path.dirname(__file__), 'services.yaml'))
def media_player_service_handler(service):
""" Maps services to methods on MediaPlayerDevice. """
target_players = component.extract_from_service(service)
@@ -198,7 +225,8 @@ def setup(hass, config):
player.update_ha_state(True)
for service in SERVICE_TO_METHOD:
hass.services.register(DOMAIN, service, media_player_service_handler)
hass.services.register(DOMAIN, service, media_player_service_handler,
descriptions.get(service))
def volume_set_service(service):
""" Set specified volume on the media player. """
@@ -215,7 +243,8 @@ def setup(hass, config):
if player.should_poll:
player.update_ha_state(True)
hass.services.register(DOMAIN, SERVICE_VOLUME_SET, volume_set_service)
hass.services.register(DOMAIN, SERVICE_VOLUME_SET, volume_set_service,
descriptions.get(SERVICE_VOLUME_SET))
def volume_mute_service(service):
""" Mute (true) or unmute (false) the media player. """
@@ -232,7 +261,8 @@ def setup(hass, config):
if player.should_poll:
player.update_ha_state(True)
hass.services.register(DOMAIN, SERVICE_VOLUME_MUTE, volume_mute_service)
hass.services.register(DOMAIN, SERVICE_VOLUME_MUTE, volume_mute_service,
descriptions.get(SERVICE_VOLUME_MUTE))
def media_seek_service(service):
""" Seek to a position. """
@@ -249,7 +279,8 @@ def setup(hass, config):
if player.should_poll:
player.update_ha_state(True)
hass.services.register(DOMAIN, SERVICE_MEDIA_SEEK, media_seek_service)
hass.services.register(DOMAIN, SERVICE_MEDIA_SEEK, media_seek_service,
descriptions.get(SERVICE_MEDIA_SEEK))
def play_youtube_video_service(service, media_id=None):
""" Plays specified media_id on the media player. """
@@ -265,16 +296,40 @@ def setup(hass, config):
if player.should_poll:
player.update_ha_state(True)
def play_media_service(service):
""" Plays specified media_id on the media player. """
media_type = service.data.get('media_type')
media_id = service.data.get('media_id')
if media_type is None:
return
if media_id is None:
return
for player in component.extract_from_service(service):
player.play_media(media_type, media_id)
if player.should_poll:
player.update_ha_state(True)
hass.services.register(
DOMAIN, "start_fireplace",
lambda service: play_youtube_video_service(service, "eyU3bRy2x44"))
lambda service: play_youtube_video_service(service, "eyU3bRy2x44"),
descriptions.get('start_fireplace'))
hass.services.register(
DOMAIN, "start_epic_sax",
lambda service: play_youtube_video_service(service, "kxopViU98Xo"))
lambda service: play_youtube_video_service(service, "kxopViU98Xo"),
descriptions.get('start_epic_sax'))
hass.services.register(
DOMAIN, SERVICE_YOUTUBE_VIDEO, play_youtube_video_service)
DOMAIN, SERVICE_YOUTUBE_VIDEO, play_youtube_video_service,
descriptions.get(SERVICE_YOUTUBE_VIDEO))
hass.services.register(
DOMAIN, SERVICE_PLAY_MEDIA, play_media_service,
descriptions.get(SERVICE_PLAY_MEDIA))
return True
@@ -360,6 +415,16 @@ class MediaPlayerDevice(Entity):
""" Episode of current playing media. (TV Show only) """
return None
@property
def media_channel(self):
""" Channel currently playing. """
return None
@property
def media_playlist(self):
""" Title of Playlist currently playing. """
return None
@property
def app_id(self):
""" ID of the current running app. """
@@ -420,6 +485,10 @@ class MediaPlayerDevice(Entity):
""" Plays a YouTube media. """
raise NotImplementedError()
def play_media(self, media_type, media_id):
""" Plays a piece of media. """
raise NotImplementedError()
# No need to overwrite these.
@property
def support_pause(self):
@@ -456,6 +525,11 @@ class MediaPlayerDevice(Entity):
""" Boolean if YouTube is supported. """
return bool(self.supported_media_commands & SUPPORT_YOUTUBE)
@property
def support_play_media(self):
""" Boolean if play media command supported. """
return bool(self.supported_media_commands & SUPPORT_PLAY_MEDIA)
def volume_up(self):
""" volume_up media player. """
if self.volume_level < 1:
@@ -483,7 +557,7 @@ class MediaPlayerDevice(Entity):
else:
state_attr = {
attr: getattr(self, attr) for attr
in ATTR_TO_PROPERTY if getattr(self, attr)
in ATTR_TO_PROPERTY if getattr(self, attr) is not None
}
if self.media_image_url:
@@ -1,10 +1,10 @@
"""
homeassistant.components.media_player.chromecast
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides functionality to interact with Cast devices on the network.
WARNING: This platform is currently not working due to a changed Cast API
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.cast.html
"""
import logging
@@ -76,6 +76,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class CastDevice(MediaPlayerDevice):
""" Represents a Cast device on the network. """
# pylint: disable=abstract-method
# pylint: disable=too-many-public-methods
def __init__(self, host):
@@ -1,9 +1,7 @@
"""
homeassistant.components.media_player.demo
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Demo implementation of the media player.
"""
from homeassistant.const import (
STATE_PLAYING, STATE_PAUSED, STATE_OFF)
@@ -0,0 +1,172 @@
"""
homeassistant.components.media_player.denon
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides an interface to Denon Network Receivers.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.denon.html
"""
import telnetlib
import logging
from homeassistant.components.media_player import (
MediaPlayerDevice, SUPPORT_PAUSE, SUPPORT_VOLUME_SET,
SUPPORT_VOLUME_MUTE, SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK,
SUPPORT_TURN_ON, SUPPORT_TURN_OFF,
DOMAIN)
from homeassistant.const import (
CONF_HOST, STATE_OFF, STATE_ON, STATE_UNKNOWN)
_LOGGER = logging.getLogger(__name__)
SUPPORT_DENON = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | \
SUPPORT_TURN_ON | SUPPORT_TURN_OFF
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the Denon platform. """
if not config.get(CONF_HOST):
_LOGGER.error(
"Missing required configuration items in %s: %s",
DOMAIN,
CONF_HOST)
return False
denon = DenonDevice(
config.get("name", "Music station"),
config.get("host")
)
if denon.update():
add_devices([denon])
return True
else:
return False
class DenonDevice(MediaPlayerDevice):
""" Represents a Denon device. """
# pylint: disable=too-many-public-methods
def __init__(self, name, host):
self._name = name
self._host = host
self._pwstate = "PWSTANDBY"
self._volume = 0
self._muted = False
self._mediasource = ""
@classmethod
def telnet_request(cls, telnet, command):
""" Executes `command` and returns the response. """
telnet.write(command.encode("ASCII") + b"\r")
return telnet.read_until(b"\r", timeout=0.2).decode("ASCII").strip()
def telnet_command(self, command):
""" Establishes a telnet connection and sends `command`. """
telnet = telnetlib.Telnet(self._host)
telnet.write(command.encode("ASCII") + b"\r")
telnet.read_very_eager() # skip response
telnet.close()
def update(self):
try:
telnet = telnetlib.Telnet(self._host)
except ConnectionRefusedError:
return False
self._pwstate = self.telnet_request(telnet, "PW?")
# PW? sends also SISTATUS, which is not interesting
telnet.read_until(b"\r", timeout=0.2)
volume_str = self.telnet_request(telnet, "MV?")[len("MV"):]
self._volume = int(volume_str) / 60
self._muted = (self.telnet_request(telnet, "MU?") == "MUON")
self._mediasource = self.telnet_request(telnet, "SI?")[len("SI"):]
telnet.close()
return True
@property
def name(self):
""" Returns the name of the device. """
return self._name
@property
def state(self):
""" Returns the state of the device. """
if self._pwstate == "PWSTANDBY":
return STATE_OFF
if self._pwstate == "PWON":
return STATE_ON
return STATE_UNKNOWN
@property
def volume_level(self):
""" Volume level of the media player (0..1). """
return self._volume
@property
def is_volume_muted(self):
""" Boolean if volume is currently muted. """
return self._muted
@property
def media_title(self):
""" Current media source. """
return self._mediasource
@property
def supported_media_commands(self):
""" Flags of media commands that are supported. """
return SUPPORT_DENON
def turn_off(self):
""" turn_off media player. """
self.telnet_command("PWSTANDBY")
def volume_up(self):
""" volume_up media player. """
self.telnet_command("MVUP")
def volume_down(self):
""" volume_down media player. """
self.telnet_command("MVDOWN")
def set_volume_level(self, volume):
""" set volume level, range 0..1. """
# 60dB max
self.telnet_command("MV" + str(round(volume * 60)).zfill(2))
def mute_volume(self, mute):
""" mute (true) or unmute (false) media player. """
self.telnet_command("MU" + ("ON" if mute else "OFF"))
def media_play_pause(self):
""" media_play_pause media player. """
raise NotImplementedError()
def media_play(self):
""" media_play media player. """
self.telnet_command("NS9A")
def media_pause(self):
""" media_pause media player. """
self.telnet_command("NS9B")
def media_next_track(self):
""" Send next track command. """
self.telnet_command("NS9D")
def media_previous_track(self):
self.telnet_command("NS9E")
def media_seek(self, position):
raise NotImplementedError()
def turn_on(self):
""" turn the media player on. """
self.telnet_command("PWON")
@@ -0,0 +1,190 @@
"""
homeassistant.components.media_player.firetv
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides functionality to interact with FireTV devices.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.firetv.html
"""
import logging
import requests
from homeassistant.const import (
STATE_PLAYING, STATE_PAUSED, STATE_IDLE, STATE_OFF,
STATE_UNKNOWN, STATE_STANDBY)
from homeassistant.components.media_player import (
MediaPlayerDevice,
SUPPORT_PAUSE, SUPPORT_VOLUME_SET,
SUPPORT_TURN_ON, SUPPORT_TURN_OFF,
SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK)
SUPPORT_FIRETV = SUPPORT_PAUSE | \
SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_PREVIOUS_TRACK | \
SUPPORT_NEXT_TRACK | SUPPORT_VOLUME_SET
DOMAIN = 'firetv'
DEVICE_LIST_URL = 'http://{0}/devices/list'
DEVICE_STATE_URL = 'http://{0}/devices/state/{1}'
DEVICE_ACTION_URL = 'http://{0}/devices/action/{1}/{2}'
_LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the FireTV platform. """
host = config.get('host', 'localhost:5556')
device_id = config.get('device', 'default')
try:
response = requests.get(DEVICE_LIST_URL.format(host)).json()
if device_id in response['devices'].keys():
add_devices([
FireTVDevice(
host,
device_id,
config.get('name', 'Amazon Fire TV')
)
])
_LOGGER.info(
'Device %s accessible and ready for control', device_id)
else:
_LOGGER.warn(
'Device %s is not registered with firetv-server', device_id)
except requests.exceptions.RequestException:
_LOGGER.error('Could not connect to firetv-server at %s', host)
class FireTV(object):
""" firetv-server client.
Should a native Python 3 ADB module become available, python-firetv can
support Python 3, it can be added as a dependency, and this class can be
dispensed of.
For now, it acts as a client to the firetv-server HTTP server (which must
be running via Python 2).
"""
def __init__(self, host, device_id):
self.host = host
self.device_id = device_id
@property
def state(self):
""" Get the device state. An exception means UNKNOWN state. """
try:
response = requests.get(
DEVICE_STATE_URL.format(
self.host,
self.device_id
)
).json()
return response.get('state', STATE_UNKNOWN)
except requests.exceptions.RequestException:
_LOGGER.error(
'Could not retrieve device state for %s', self.device_id)
return STATE_UNKNOWN
def action(self, action_id):
""" Perform an action on the device. """
try:
requests.get(
DEVICE_ACTION_URL.format(
self.host,
self.device_id,
action_id
)
)
except requests.exceptions.RequestException:
_LOGGER.error(
'Action request for %s was not accepted for device %s',
action_id, self.device_id)
class FireTVDevice(MediaPlayerDevice):
""" Represents an Amazon Fire TV device on the network. """
def __init__(self, host, device, name):
self._firetv = FireTV(host, device)
self._name = name
self._state = STATE_UNKNOWN
@property
def name(self):
""" Get the device name. """
return self._name
@property
def should_poll(self):
""" Device should be polled. """
return True
@property
def supported_media_commands(self):
""" Flags of media commands that are supported. """
return SUPPORT_FIRETV
@property
def state(self):
""" State of the player. """
return self._state
def update(self):
""" Update device state. """
self._state = {
'idle': STATE_IDLE,
'off': STATE_OFF,
'play': STATE_PLAYING,
'pause': STATE_PAUSED,
'standby': STATE_STANDBY,
'disconnected': STATE_UNKNOWN,
}.get(self._firetv.state, STATE_UNKNOWN)
def turn_on(self):
""" Turns on the device. """
self._firetv.action('turn_on')
def turn_off(self):
""" Turns off the device. """
self._firetv.action('turn_off')
def media_play(self):
""" Send play command. """
self._firetv.action('media_play')
def media_pause(self):
""" Send pause command. """
self._firetv.action('media_pause')
def media_play_pause(self):
""" Send play/pause command. """
self._firetv.action('media_play_pause')
def volume_up(self):
""" Send volume up command. """
self._firetv.action('volume_up')
def volume_down(self):
""" Send volume down command. """
self._firetv.action('volume_down')
def media_previous_track(self):
""" Send previous track command (results in rewind). """
self._firetv.action('media_previous')
def media_next_track(self):
""" Send next track command (results in fast-forward). """
self._firetv.action('media_next')
def media_seek(self, position):
raise NotImplementedError()
def mute_volume(self, mute):
raise NotImplementedError()
def play_youtube(self, media_id):
raise NotImplementedError()
def set_volume_level(self, volume):
raise NotImplementedError()
@@ -0,0 +1,443 @@
"""
homeassistant.components.media_player.itunes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides an interface to iTunes API.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.itunes.html
"""
import logging
from homeassistant.components.media_player import (
MediaPlayerDevice, MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, SUPPORT_PAUSE,
SUPPORT_SEEK, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_MUTE,
SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK, SUPPORT_TURN_ON,
SUPPORT_TURN_OFF, SUPPORT_PLAY_MEDIA,
ATTR_ENTITY_PICTURE, ATTR_SUPPORTED_MEDIA_COMMANDS)
from homeassistant.const import (
STATE_IDLE, STATE_PLAYING, STATE_PAUSED, STATE_OFF, STATE_ON)
import requests
_LOGGER = logging.getLogger(__name__)
SUPPORT_ITUNES = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_SEEK | \
SUPPORT_PLAY_MEDIA
SUPPORT_AIRPLAY = SUPPORT_VOLUME_SET | SUPPORT_TURN_ON | SUPPORT_TURN_OFF
DOMAIN = 'itunes'
class Itunes(object):
""" itunes-api client. """
def __init__(self, host, port):
self.host = host
self.port = port
@property
def _base_url(self):
""" Returns the base url for endpoints. """
return self.host + ":" + str(self.port)
def _request(self, method, path, params=None):
""" Makes the actual request and returns the parsed response. """
url = self._base_url + path
try:
if method == 'GET':
response = requests.get(url)
elif method == "POST":
response = requests.put(url, params)
elif method == "PUT":
response = requests.put(url, params)
elif method == "DELETE":
response = requests.delete(url)
return response.json()
except requests.exceptions.HTTPError:
return {'player_state': 'error'}
except requests.exceptions.RequestException:
return {'player_state': 'offline'}
def _command(self, named_command):
""" Makes a request for a controlling command. """
return self._request('PUT', '/' + named_command)
def now_playing(self):
""" Returns the current state. """
return self._request('GET', '/now_playing')
def set_volume(self, level):
""" Sets the volume and returns the current state, level 0-100. """
return self._request('PUT', '/volume', {'level': level})
def set_muted(self, muted):
""" Mutes and returns the current state, muted True or False. """
return self._request('PUT', '/mute', {'muted': muted})
def play(self):
""" Sets playback to play and returns the current state. """
return self._command('play')
def pause(self):
""" Sets playback to paused and returns the current state. """
return self._command('pause')
def next(self):
""" Skips to the next track and returns the current state. """
return self._command('next')
def previous(self):
""" Skips back and returns the current state. """
return self._command('previous')
def play_playlist(self, playlist_id_or_name):
""" Sets a playlist to be current and returns the current state. """
response = self._request('GET', '/playlists')
playlists = response.get('playlists', [])
found_playlists = \
[playlist for playlist in playlists if
(playlist_id_or_name in [playlist["name"], playlist["id"]])]
if len(found_playlists) > 0:
playlist = found_playlists[0]
path = '/playlists/' + playlist['id'] + '/play'
return self._request('PUT', path)
def artwork_url(self):
""" Returns a URL of the current track's album art. """
return self._base_url + '/artwork'
def airplay_devices(self):
""" Returns a list of AirPlay devices. """
return self._request('GET', '/airplay_devices')
def airplay_device(self, device_id):
""" Returns an AirPlay device. """
return self._request('GET', '/airplay_devices/' + device_id)
def toggle_airplay_device(self, device_id, toggle):
""" Toggles airplay device on or off, id, toggle True or False. """
command = 'on' if toggle else 'off'
path = '/airplay_devices/' + device_id + '/' + command
return self._request('PUT', path)
def set_volume_airplay_device(self, device_id, level):
""" Sets volume, returns current state of device, id,level 0-100. """
path = '/airplay_devices/' + device_id + '/volume'
return self._request('PUT', path, {'level': level})
# pylint: disable=unused-argument, abstract-method
# pylint: disable=too-many-instance-attributes
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the itunes platform. """
add_devices([
ItunesDevice(
config.get('name', 'iTunes'),
config.get('host'),
config.get('port'),
add_devices
)
])
class ItunesDevice(MediaPlayerDevice):
""" Represents a iTunes-API instance. """
# pylint: disable=too-many-public-methods
def __init__(self, name, host, port, add_devices):
self._name = name
self._host = host
self._port = port
self._add_devices = add_devices
self.client = Itunes(self._host, self._port)
self.current_volume = None
self.muted = None
self.current_title = None
self.current_album = None
self.current_artist = None
self.current_playlist = None
self.content_id = None
self.player_state = None
self.airplay_devices = {}
self.update()
def update_state(self, state_hash):
""" Update all the state properties with the passed in dictionary. """
self.player_state = state_hash.get('player_state', None)
self.current_volume = state_hash.get('volume', 0)
self.muted = state_hash.get('muted', None)
self.current_title = state_hash.get('name', None)
self.current_album = state_hash.get('album', None)
self.current_artist = state_hash.get('artist', None)
self.current_playlist = state_hash.get('playlist', None)
self.content_id = state_hash.get('id', None)
@property
def name(self):
""" Returns the name of the device. """
return self._name
@property
def state(self):
""" Returns the state of the device. """
if self.player_state == 'offline' or self.player_state is None:
return 'offline'
if self.player_state == 'error':
return 'error'
if self.player_state == 'stopped':
return STATE_IDLE
if self.player_state == 'paused':
return STATE_PAUSED
else:
return STATE_PLAYING
def update(self):
""" Retrieve latest state. """
now_playing = self.client.now_playing()
self.update_state(now_playing)
found_devices = self.client.airplay_devices()
found_devices = found_devices.get('airplay_devices', [])
new_devices = []
for device_data in found_devices:
device_id = device_data.get('id')
if self.airplay_devices.get(device_id):
# update it
airplay_device = self.airplay_devices.get(device_id)
airplay_device.update_state(device_data)
else:
# add it
airplay_device = AirPlayDevice(device_id, self.client)
airplay_device.update_state(device_data)
self.airplay_devices[device_id] = airplay_device
new_devices.append(airplay_device)
if new_devices:
self._add_devices(new_devices)
@property
def is_volume_muted(self):
""" Boolean if volume is currently muted. """
return self.muted
@property
def volume_level(self):
""" Volume level of the media player (0..1). """
return self.current_volume/100.0
@property
def media_content_id(self):
""" Content ID of current playing media. """
return self.content_id
@property
def media_content_type(self):
""" Content type of current playing media. """
return MEDIA_TYPE_MUSIC
@property
def media_image_url(self):
""" Image url of current playing media. """
if self.player_state in (STATE_PLAYING, STATE_IDLE, STATE_PAUSED) and \
self.current_title is not None:
return self.client.artwork_url()
else:
return 'https://cloud.githubusercontent.com/assets/260/9829355' \
'/33fab972-58cf-11e5-8ea2-2ca74bdaae40.png'
@property
def media_title(self):
""" Title of current playing media. """
return self.current_title
@property
def media_artist(self):
""" Artist of current playing media. (Music track only) """
return self.current_artist
@property
def media_album_name(self):
""" Album of current playing media. (Music track only) """
return self.current_album
@property
def media_playlist(self):
""" Title of the currently playing playlist. """
return self.current_playlist
@property
def supported_media_commands(self):
""" Flags of media commands that are supported. """
return SUPPORT_ITUNES
def set_volume_level(self, volume):
""" set volume level, range 0..1. """
response = self.client.set_volume(int(volume * 100))
self.update_state(response)
def mute_volume(self, mute):
""" mute (true) or unmute (false) media player. """
response = self.client.set_muted(mute)
self.update_state(response)
def media_play(self):
""" media_play media player. """
response = self.client.play()
self.update_state(response)
def media_pause(self):
""" media_pause media player. """
response = self.client.pause()
self.update_state(response)
def media_next_track(self):
""" media_next media player. """
response = self.client.next()
self.update_state(response)
def media_previous_track(self):
""" media_previous media player. """
response = self.client.previous()
self.update_state(response)
def play_media(self, media_type, media_id):
""" play_media media player. """
if media_type == MEDIA_TYPE_PLAYLIST:
response = self.client.play_playlist(media_id)
self.update_state(response)
class AirPlayDevice(MediaPlayerDevice):
""" Represents an AirPlay device via an iTunes-API instance. """
# pylint: disable=too-many-public-methods
def __init__(self, device_id, client):
self._id = device_id
self.client = client
self.device_name = "AirPlay"
self.kind = None
self.active = False
self.selected = False
self.volume = 0
self.supports_audio = False
self.supports_video = False
self.player_state = None
def update_state(self, state_hash):
""" Update all the state properties with the passed in dictionary. """
if 'player_state' in state_hash:
self.player_state = state_hash.get('player_state', None)
if 'name' in state_hash:
name = state_hash.get('name', '')
self.device_name = (name + ' AirTunes Speaker').strip()
if 'kind' in state_hash:
self.kind = state_hash.get('kind', None)
if 'active' in state_hash:
self.active = state_hash.get('active', None)
if 'selected' in state_hash:
self.selected = state_hash.get('selected', None)
if 'sound_volume' in state_hash:
self.volume = state_hash.get('sound_volume', 0)
if 'supports_audio' in state_hash:
self.supports_audio = state_hash.get('supports_audio', None)
if 'supports_video' in state_hash:
self.supports_video = state_hash.get('supports_video', None)
@property
def name(self):
""" Returns the name of the device. """
return self.device_name
@property
def state(self):
""" Returns the state of the device. """
if self.selected is True:
return STATE_ON
else:
return STATE_OFF
def update(self):
""" Retrieve latest state. """
@property
def volume_level(self):
return float(self.volume)/100.0
@property
def media_content_type(self):
return MEDIA_TYPE_MUSIC
@property
def supported_media_commands(self):
""" Flags of media commands that are supported. """
return SUPPORT_AIRPLAY
@property
def device_state_attributes(self):
""" Return the state attributes. """
state_attr = {}
state_attr[ATTR_SUPPORTED_MEDIA_COMMANDS] = SUPPORT_AIRPLAY
if self.state == STATE_OFF:
state_attr[ATTR_ENTITY_PICTURE] = \
('https://cloud.githubusercontent.com/assets/260/9833073'
'/6eb5c906-5958-11e5-9b4a-472cdf36be16.png')
else:
state_attr[ATTR_ENTITY_PICTURE] = \
('https://cloud.githubusercontent.com/assets/260/9833072'
'/6eb13cce-5958-11e5-996f-e2aaefbc9a24.png')
return state_attr
def set_volume_level(self, volume):
""" set volume level, range 0..1. """
volume = int(volume * 100)
response = self.client.set_volume_airplay_device(self._id, volume)
self.update_state(response)
def turn_on(self):
""" Select AirPlay. """
self.update_state({"selected": True})
self.update_ha_state()
response = self.client.toggle_airplay_device(self._id, True)
self.update_state(response)
def turn_off(self):
""" Deselect AirPlay. """
self.update_state({"selected": False})
self.update_ha_state()
response = self.client.toggle_airplay_device(self._id, False)
self.update_state(response)
+7 -33
View File
@@ -3,35 +3,8 @@ homeassistant.components.media_player.kodi
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides an interface to the XBMC/Kodi JSON-RPC API
Configuration:
To use the Kodi you will need to add something like the following to
your config/configuration.yaml.
media_player:
platform: kodi
name: Kodi
url: http://192.168.0.123/jsonrpc
user: kodi
password: my_secure_password
Variables:
name
*Optional
The name of the device.
url
*Required
The URL of the XBMC/Kodi JSON-RPC API. Example: http://192.168.0.123/jsonrpc
user
*Optional
The XBMC/Kodi HTTP username.
password
*Optional
The XBMC/Kodi HTTP password.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.kodi.html
"""
import urllib
import logging
@@ -74,7 +47,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
def _get_image_url(kodi_url):
""" Helper function that parses the thumbnail URLs used by Kodi """
""" Helper function that parses the thumbnail URLs used by Kodi. """
url_components = urllib.parse.urlparse(kodi_url)
if url_components.scheme == 'image':
@@ -107,6 +80,7 @@ class KodiDevice(MediaPlayerDevice):
try:
return self._server.Player.GetActivePlayers()
except jsonrpc_requests.jsonrpc.TransportError:
_LOGGER.exception('Unable to fetch kodi data')
return None
@property
@@ -166,7 +140,7 @@ class KodiDevice(MediaPlayerDevice):
def media_content_id(self):
""" Content ID of current playing media. """
if self._item is not None:
return self._item['uniqueid']
return self._item.get('uniqueid', None)
@property
def media_content_type(self):
@@ -235,7 +209,7 @@ class KodiDevice(MediaPlayerDevice):
self.update_ha_state()
def _set_play_state(self, state):
""" Helper method for play/pause/toggle """
""" Helper method for play/pause/toggle. """
players = self._get_players()
if len(players) != 0:
@@ -256,7 +230,7 @@ class KodiDevice(MediaPlayerDevice):
self._set_play_state(False)
def _goto(self, direction):
""" Helper method used for previous/next track """
""" Helper method used for previous/next track. """
players = self._get_players()
if len(players) != 0:
+2 -30
View File
@@ -1,38 +1,10 @@
"""
homeassistant.components.media_player.mpd
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides functionality to interact with a Music Player Daemon.
Configuration:
To use MPD you will need to add something like the following to your
config/configuration.yaml
media_player:
platform: mpd
server: 127.0.0.1
port: 6600
location: bedroom
password: superSecretPassword123
Variables:
server
*Required
IP address of the Music Player Daemon. Example: 192.168.1.32
port
*Optional
Port of the Music Player Daemon, defaults to 6600. Example: 6600
location
*Optional
Location of your Music Player Daemon.
password
*Optional
Password for your Music Player Daemon.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.mpd.html
"""
import logging
import socket
@@ -0,0 +1,318 @@
"""
homeassistant.components.media_player.plex
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides an interface to the Plex API.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.plex.html
"""
import os
import json
import logging
from datetime import timedelta
from urllib.parse import urlparse
from homeassistant.loader import get_component
import homeassistant.util as util
from homeassistant.components.media_player import (
MediaPlayerDevice, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK,
SUPPORT_NEXT_TRACK, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO)
from homeassistant.const import (
DEVICE_DEFAULT_NAME, STATE_IDLE, STATE_PLAYING,
STATE_PAUSED, STATE_OFF, STATE_UNKNOWN)
REQUIREMENTS = ['plexapi==1.1.0']
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(seconds=1)
PLEX_CONFIG_FILE = 'plex.conf'
# Map ip to request id for configuring
_CONFIGURING = {}
_LOGGER = logging.getLogger(__name__)
SUPPORT_PLEX = SUPPORT_PAUSE | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK
def config_from_file(filename, config=None):
''' Small configuration file management function'''
if config:
# We're writing configuration
try:
with open(filename, 'w') as fdesc:
fdesc.write(json.dumps(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 IOError as error:
_LOGGER.error('Reading config file failed: %s', error)
# This won't work yet
return False
else:
return {}
# pylint: disable=abstract-method, unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Sets up the plex platform. """
config = config_from_file(hass.config.path(PLEX_CONFIG_FILE))
if len(config):
# Setup a configured PlexServer
host, token = config.popitem()
token = token['token']
# Via discovery
elif discovery_info is not None:
# Parse discovery data
host = urlparse(discovery_info[1]).netloc
_LOGGER.info('Discovered PLEX server: %s', host)
if host in _CONFIGURING:
return
token = None
else:
return
setup_plexserver(host, token, hass, add_devices_callback)
# pylint: disable=too-many-branches
def setup_plexserver(host, token, hass, add_devices_callback):
''' Setup a plexserver based on host parameter'''
import plexapi.server
import plexapi.exceptions
try:
plexserver = plexapi.server.PlexServer('http://%s' % host, token)
except (plexapi.exceptions.BadRequest,
plexapi.exceptions.Unauthorized,
plexapi.exceptions.NotFound) as error:
_LOGGER.info(error)
# No token or wrong token
request_configuration(host, hass, add_devices_callback)
return
# 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(PLEX_CONFIG_FILE),
{host: {'token': token}}):
_LOGGER.error('failed to save config file')
_LOGGER.info('Connected to: htts://%s', host)
plex_clients = {}
plex_sessions = {}
@util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS)
def update_devices():
""" Updates the devices objects. """
try:
devices = plexserver.clients()
except plexapi.exceptions.BadRequest:
_LOGGER.exception("Error listing plex devices")
return
new_plex_clients = []
for device in devices:
# For now, let's allow all deviceClass types
if device.deviceClass in ['badClient']:
continue
if device.machineIdentifier not in plex_clients:
new_client = PlexClient(device, plex_sessions, update_devices,
update_sessions)
plex_clients[device.machineIdentifier] = new_client
new_plex_clients.append(new_client)
else:
plex_clients[device.machineIdentifier].set_device(device)
if new_plex_clients:
add_devices_callback(new_plex_clients)
@util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS)
def update_sessions():
""" Updates the sessions objects. """
try:
sessions = plexserver.sessions()
except plexapi.exceptions.BadRequest:
_LOGGER.exception("Error listing plex sessions")
return
plex_sessions.clear()
for session in sessions:
plex_sessions[session.player.machineIdentifier] = session
update_devices()
update_sessions()
def request_configuration(host, hass, add_devices_callback):
""" Request configuration steps from the user. """
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 plex_configuration_callback(data):
""" Actions to do when our configuration callback is called. """
setup_plexserver(host, data.get('token'), hass, add_devices_callback)
_CONFIGURING[host] = configurator.request_config(
hass, "Plex Media Server", plex_configuration_callback,
description=('Enter the X-Plex-Token'),
description_image="/static/images/config_plex_mediaserver.png",
submit_caption="Confirm",
fields=[{'id': 'token', 'name': 'X-Plex-Token', 'type': ''}]
)
class PlexClient(MediaPlayerDevice):
""" Represents a Plex device. """
# pylint: disable=too-many-public-methods, attribute-defined-outside-init
def __init__(self, device, plex_sessions, update_devices, update_sessions):
self.plex_sessions = plex_sessions
self.update_devices = update_devices
self.update_sessions = update_sessions
self.set_device(device)
def set_device(self, device):
""" Sets the device property. """
self.device = device
@property
def unique_id(self):
""" Returns the id of this plex client """
return "{}.{}".format(
self.__class__, self.device.machineIdentifier or self.device.name)
@property
def name(self):
""" Returns the name of the device. """
return self.device.name or DEVICE_DEFAULT_NAME
@property
def session(self):
""" Returns the session, if any. """
if self.device.machineIdentifier not in self.plex_sessions:
return None
return self.plex_sessions[self.device.machineIdentifier]
@property
def state(self):
""" Returns the state of the device. """
if self.session:
state = self.session.player.state
if state == 'playing':
return STATE_PLAYING
elif state == 'paused':
return STATE_PAUSED
# This is nasty. Need to find a way to determine alive
elif self.device:
return STATE_IDLE
else:
return STATE_OFF
return STATE_UNKNOWN
def update(self):
self.update_devices(no_throttle=True)
self.update_sessions(no_throttle=True)
@property
def media_content_id(self):
""" Content ID of current playing media. """
if self.session is not None:
return self.session.ratingKey
@property
def media_content_type(self):
""" Content type of current playing media. """
if self.session is None:
return None
media_type = self.session.type
if media_type == 'episode':
return MEDIA_TYPE_TVSHOW
elif media_type == 'movie':
return MEDIA_TYPE_VIDEO
return None
@property
def media_duration(self):
""" Duration of current playing media in seconds. """
if self.session is not None:
return self.session.duration
@property
def media_image_url(self):
""" Image url of current playing media. """
if self.session is not None:
return self.session.thumbUrl
@property
def media_title(self):
""" Title of current playing media. """
# find a string we can use as a title
if self.session is not None:
return self.session.title
@property
def media_season(self):
""" Season of curent playing media (TV Show only). """
from plexapi.video import Show
if isinstance(self.session, Show):
return self.session.seasons()[0].index
@property
def media_series_title(self):
""" Series title of current playing media (TV Show only). """
from plexapi.video import Show
if isinstance(self.session, Show):
return self.session.grandparentTitle
@property
def media_episode(self):
""" Episode of current playing media (TV Show only). """
from plexapi.video import Show
if isinstance(self.session, Show):
return self.session.index
@property
def supported_media_commands(self):
""" Flags of media commands that are supported. """
return SUPPORT_PLEX
def media_play(self):
""" media_play media player. """
self.device.play()
def media_pause(self):
""" media_pause media player. """
self.device.pause()
def media_next_track(self):
""" Send next track command. """
self.device.skipNext()
def media_previous_track(self):
""" Send previous track command. """
self.device.skipPrevious()
@@ -0,0 +1,199 @@
"""
homeassistant.components.media_player.sonos
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides an interface to Sonos players (via SoCo)
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.sonos.html
"""
import logging
import datetime
from homeassistant.components.media_player import (
MediaPlayerDevice, SUPPORT_PAUSE, SUPPORT_SEEK, SUPPORT_VOLUME_SET,
SUPPORT_VOLUME_MUTE, SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK,
MEDIA_TYPE_MUSIC)
from homeassistant.const import (
STATE_IDLE, STATE_PLAYING, STATE_PAUSED, STATE_UNKNOWN)
REQUIREMENTS = ['SoCo==0.11.1']
_LOGGER = logging.getLogger(__name__)
# The soco library is excessively chatty when it comes to logging and
# causes a LOT of spam in the logs due to making a http connection to each
# speaker every 10 seconds. Quiet it down a bit to just actual problems.
_SOCO_LOGGER = logging.getLogger('soco')
_SOCO_LOGGER.setLevel(logging.ERROR)
_REQUESTS_LOGGER = logging.getLogger('requests')
_REQUESTS_LOGGER.setLevel(logging.ERROR)
SUPPORT_SONOS = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE |\
SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_SEEK
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the Sonos platform. """
import soco
players = soco.discover()
if not players:
_LOGGER.warning('No Sonos speakers found. Disabling: %s', __name__)
return False
add_devices(SonosDevice(hass, p) for p in players)
_LOGGER.info('Added %s Sonos speakers', len(players))
return True
# pylint: disable=too-many-instance-attributes, too-many-public-methods
# pylint: disable=abstract-method
class SonosDevice(MediaPlayerDevice):
""" Represents a Sonos device. """
# pylint: disable=too-many-arguments
def __init__(self, hass, player):
self.hass = hass
super(SonosDevice, self).__init__()
self._player = player
self.update()
@property
def should_poll(self):
return True
def update_sonos(self, now):
""" Updates state, called by track_utc_time_change. """
self.update_ha_state(True)
@property
def name(self):
""" Returns the name of the device. """
return self._name
@property
def unique_id(self):
""" Returns a unique id. """
return "{}.{}".format(self.__class__, self._player.uid)
@property
def state(self):
""" Returns the state of the device. """
if self._status == 'PAUSED_PLAYBACK':
return STATE_PAUSED
if self._status == 'PLAYING':
return STATE_PLAYING
if self._status == 'STOPPED':
return STATE_IDLE
return STATE_UNKNOWN
def update(self):
""" Retrieve latest state. """
self._name = self._player.get_speaker_info()['zone_name'].replace(
' (R)', '').replace(' (L)', '')
self._status = self._player.get_current_transport_info().get(
'current_transport_state')
self._trackinfo = self._player.get_current_track_info()
@property
def volume_level(self):
""" Volume level of the media player (0..1). """
return self._player.volume / 100.0
@property
def is_volume_muted(self):
return self._player.mute
@property
def media_content_id(self):
""" Content ID of current playing media. """
return self._trackinfo.get('title', None)
@property
def media_content_type(self):
""" Content type of current playing media. """
return MEDIA_TYPE_MUSIC
@property
def media_duration(self):
""" Duration of current playing media in seconds. """
dur = self._trackinfo.get('duration', '0:00')
# If the speaker is playing from the "line-in" source, getting
# track metadata can return NOT_IMPLEMENTED, which breaks the
# volume logic below
if dur == 'NOT_IMPLEMENTED':
return None
return sum(60 ** x[0] * int(x[1]) for x in
enumerate(reversed(dur.split(':'))))
@property
def media_image_url(self):
""" Image url of current playing media. """
if 'album_art' in self._trackinfo:
return self._trackinfo['album_art']
@property
def media_title(self):
""" Title of current playing media. """
if 'artist' in self._trackinfo and 'title' in self._trackinfo:
return '{artist} - {title}'.format(
artist=self._trackinfo['artist'],
title=self._trackinfo['title']
)
if 'title' in self._status:
return self._trackinfo['title']
@property
def supported_media_commands(self):
""" Flags of media commands that are supported. """
return SUPPORT_SONOS
def turn_off(self):
""" Turn off media player. """
self._player.pause()
def volume_up(self):
""" Volume up media player. """
self._player.volume += 1
def volume_down(self):
""" Volume down media player. """
self._player.volume -= 1
def set_volume_level(self, volume):
""" Set volume level, range 0..1. """
self._player.volume = str(int(volume * 100))
def mute_volume(self, mute):
""" Mute (true) or unmute (false) media player. """
self._player.mute = mute
def media_play(self):
""" Send paly command. """
self._player.play()
def media_pause(self):
""" Send pause command. """
self._player.pause()
def media_next_track(self):
""" Send next track command. """
self._player.next()
def media_previous_track(self):
""" Send next track command. """
self._player.previous()
def media_seek(self, position):
""" Send seek command. """
self._player.seek(str(datetime.timedelta(seconds=int(position))))
def turn_on(self):
""" Turn the media player on. """
self._player.play()
@@ -1,39 +1,11 @@
"""
homeassistant.components.media_player.squeezebox
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides an interface to the Logitech SqueezeBox API
Configuration:
To use SqueezeBox add something like this to your configuration:
media_player:
platform: squeezebox
host: 192.168.1.21
port: 9090
username: user
password: password
Variables:
host
*Required
The host name or address of the Logitech Media Server
port
*Optional
Telnet port to Logitech Media Server, default 9090
usermame
*Optional
Username, if password protection is enabled
password
*Optional
Password, if password protection is enabled
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.squeezebox.html
"""
import logging
import telnetlib
import urllib.parse
@@ -91,7 +63,7 @@ class LogitechMediaServer(object):
self.init_success = True if self.http_port else False
def _get_http_port(self):
""" Get http port from media server, it is used to get cover art """
""" Get http port from media server, it is used to get cover art. """
http_port = None
try:
http_port = self.query('pref', 'httpport', '?')
@@ -111,7 +83,7 @@ class LogitechMediaServer(object):
return
def create_players(self):
""" Create a list of SqueezeBoxDevices connected to the LMS """
""" Create a list of SqueezeBoxDevices connected to the LMS. """
players = []
count = self.query('player', 'count', '?')
for index in range(0, int(count)):
@@ -121,7 +93,7 @@ class LogitechMediaServer(object):
return players
def query(self, *parameters):
""" Send request and await response from server """
""" Send request and await response from server. """
telnet = telnetlib.Telnet(self.host, self.port)
if self._username and self._password:
telnet.write('login {username} {password}\n'.format(
@@ -138,7 +110,7 @@ class LogitechMediaServer(object):
return urllib.parse.unquote(response)
def get_player_status(self, player):
""" Get ithe status of a player """
""" Get ithe status of a player. """
# (title) : Song title
# Requested Information
# a (artist): Artist name 'artist'
@@ -195,7 +167,7 @@ class SqueezeBoxDevice(MediaPlayerDevice):
def update(self):
""" Retrieve latest state. """
self._status = self._lms.get_player_status(self._name)
self._status = self._lms.get_player_status(self._id)
@property
def volume_level(self):
@@ -291,7 +263,7 @@ class SqueezeBoxDevice(MediaPlayerDevice):
def media_pause(self):
""" media_pause media player. """
self._lms.query(self._id, 'pause', '0')
self._lms.query(self._id, 'pause', '1')
self.update_ha_state()
def media_next_track(self):
+5 -29
View File
@@ -1,34 +1,10 @@
"""
homeassistant.components.modbus
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Modbus component, using pymodbus (python3 branch)
Configuration:
To use the Forecast sensor you will need to add something like the
following to your config/configuration.yaml
Configuration:
To use the Modbus component you will need to add something like the following
to your config/configuration.yaml
#Modbus TCP
modbus:
type: tcp
host: 127.0.0.1
port: 2020
#Modbus RTU
modbus:
type: serial
method: rtu
port: /dev/ttyUSB0
baudrate: 9600
stopbits: 1
bytesize: 8
parity: N
Modbus component, using pymodbus (python3 branch).
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/modbus.html
"""
import logging
@@ -38,8 +14,8 @@ from homeassistant.const import (EVENT_HOMEASSISTANT_START,
DOMAIN = "modbus"
DEPENDENCIES = []
REQUIREMENTS = ['https://github.com/bashwork/pymodbus/archive/' +
'd7fc4f1cc975631e0a9011390e8017f64b612661.zip']
REQUIREMENTS = ['https://github.com/bashwork/pymodbus/archive/'
'd7fc4f1cc975631e0a9011390e8017f64b612661.zip#pymodbus==1.2.0']
# Type of network
MEDIUM = "type"
@@ -1,49 +1,13 @@
"""
homeassistant.components.mqtt
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
MQTT component, using paho-mqtt. This component needs a MQTT broker like
Mosquitto or Mosca. The Eclipse Foundation is running a public MQTT server
at iot.eclipse.org. If you prefer to use that one, keep in mind to adjust
the topic/client ID and that your messages are public.
MQTT component, using paho-mqtt.
Configuration:
To use MQTT you will need to add something like the following to your
config/configuration.yaml.
mqtt:
broker: 127.0.0.1
Or, if you want more options:
mqtt:
broker: 127.0.0.1
port: 1883
client_id: home-assistant-1
keepalive: 60
username: your_username
password: your_secret_password
Variables:
broker
*Required
This is the IP address of your MQTT broker, e.g. 192.168.1.32.
port
*Optional
The network port to connect to. Default is 1883.
client_id
*Optional
Client ID that Home Assistant will use. Has to be unique on the server.
Default is a random generated one.
keepalive
*Optional
The keep alive in seconds for this client. Default is 60.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/mqtt.html
"""
import logging
import os
import socket
from homeassistant.exceptions import HomeAssistantError
@@ -74,22 +38,25 @@ CONF_CLIENT_ID = 'client_id'
CONF_KEEPALIVE = 'keepalive'
CONF_USERNAME = 'username'
CONF_PASSWORD = 'password'
CONF_CERTIFICATE = 'certificate'
ATTR_QOS = 'qos'
ATTR_TOPIC = 'topic'
ATTR_PAYLOAD = 'payload'
ATTR_QOS = 'qos'
def publish(hass, topic, payload):
def publish(hass, topic, payload, qos=None):
""" Send an MQTT message. """
data = {
ATTR_TOPIC: topic,
ATTR_PAYLOAD: payload,
}
if qos is not None:
data[ATTR_QOS] = qos
hass.services.call(DOMAIN, SERVICE_PUBLISH, data)
def subscribe(hass, topic, callback, qos=0):
def subscribe(hass, topic, callback, qos=DEFAULT_QOS):
""" Subscribe to a topic. """
def mqtt_topic_subscriber(event):
""" Match subscribed MQTT topic. """
@@ -117,11 +84,18 @@ def setup(hass, config):
keepalive = util.convert(conf.get(CONF_KEEPALIVE), int, DEFAULT_KEEPALIVE)
username = util.convert(conf.get(CONF_USERNAME), str)
password = util.convert(conf.get(CONF_PASSWORD), str)
certificate = util.convert(conf.get(CONF_CERTIFICATE), str)
# For cloudmqtt.com, secured connection, auto fill in certificate
if certificate is None and 19999 < port < 30000 and \
broker.endswith('.cloudmqtt.com'):
certificate = os.path.join(os.path.dirname(__file__),
'addtrustexternalcaroot.crt')
global MQTT_CLIENT
try:
MQTT_CLIENT = MQTT(hass, broker, port, client_id, keepalive, username,
password)
password, certificate)
except socket.error:
_LOGGER.exception("Can't connect to the broker. "
"Please check your settings and the broker "
@@ -141,9 +115,10 @@ def setup(hass, config):
""" Handle MQTT publish service calls. """
msg_topic = call.data.get(ATTR_TOPIC)
payload = call.data.get(ATTR_PAYLOAD)
qos = call.data.get(ATTR_QOS, DEFAULT_QOS)
if msg_topic is None or payload is None:
return
MQTT_CLIENT.publish(msg_topic, payload)
MQTT_CLIENT.publish(msg_topic, payload, qos)
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_mqtt)
@@ -158,7 +133,7 @@ def setup(hass, config):
class MQTT(object): # pragma: no cover
""" Implements messaging service for MQTT. """
def __init__(self, hass, broker, port, client_id, keepalive, username,
password):
password, certificate):
import paho.mqtt.client as mqtt
self.hass = hass
@@ -169,17 +144,21 @@ class MQTT(object): # pragma: no cover
self._mqttc = mqtt.Client()
else:
self._mqttc = mqtt.Client(client_id)
if username is not None:
self._mqttc.username_pw_set(username, password)
if certificate is not None:
self._mqttc.tls_set(certificate)
self._mqttc.on_subscribe = self._mqtt_on_subscribe
self._mqttc.on_unsubscribe = self._mqtt_on_unsubscribe
self._mqttc.on_connect = self._mqtt_on_connect
self._mqttc.on_message = self._mqtt_on_message
self._mqttc.connect(broker, port, keepalive)
def publish(self, topic, payload):
def publish(self, topic, payload, qos):
""" Publish a MQTT message. """
self._mqttc.publish(topic, payload)
self._mqttc.publish(topic, payload, qos)
def unsubscribe(self, topic):
""" Unsubscribe from topic. """
@@ -206,6 +185,17 @@ class MQTT(object): # pragma: no cover
def _mqtt_on_connect(self, mqttc, obj, flags, result_code):
""" On connect, resubscribe to all topics we were subscribed to. """
if result_code != 0:
_LOGGER.error('Unable to connect to the MQTT broker: %s', {
1: 'Incorrect protocol version',
2: 'Invalid client identifier',
3: 'Server unavailable',
4: 'Bad username or password',
5: 'Not authorised'
}.get(result_code))
self._mqttc.disconnect()
return
old_topics = self.topics
self._progress = {}
self.topics = {}
@@ -0,0 +1,25 @@
-----BEGIN CERTIFICATE-----
MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU
MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs
IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290
MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux
FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h
bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v
dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt
H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9
uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX
mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX
a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN
E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0
WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD
VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0
Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU
cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx
IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN
AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH
YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5
6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC
Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX
c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a
mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ=
-----END CERTIFICATE-----
+7 -1
View File
@@ -6,7 +6,9 @@ Provides functionality to notify people.
"""
from functools import partial
import logging
import os
from homeassistant.config import load_yaml_config_file
from homeassistant.loader import get_component
from homeassistant.helpers import config_per_platform
@@ -36,6 +38,9 @@ def setup(hass, config):
""" Sets up notify services. """
success = False
descriptions = load_yaml_config_file(
os.path.join(os.path.dirname(__file__), 'services.yaml'))
for platform, p_config in config_per_platform(config, DOMAIN, _LOGGER):
# get platform
notify_implementation = get_component(
@@ -69,7 +74,8 @@ def setup(hass, config):
# register service
service_call_handler = partial(notify_message, notify_service)
service_notify = p_config.get(CONF_NAME, SERVICE_NOTIFY)
hass.services.register(DOMAIN, service_notify, service_call_handler)
hass.services.register(DOMAIN, service_notify, service_call_handler,
descriptions.get(service_notify))
success = True
return success
+2 -21
View File
@@ -1,29 +1,10 @@
"""
homeassistant.components.notify.file
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
File notification service.
Configuration:
To use the File notifier you will need to add something like the following
to your config/configuration.yaml
notify:
platform: file
filename: FILENAME
timestamp: 1 or 0
Variables:
filename
*Required
Name of the file to use. The file will be created if it doesn't exist and saved
in your config/ folder.
timestamp
*Required
Add a timestamp to the entry, valid entries are 1 or 0.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/notify.file.html
"""
import logging
import os
+2 -48
View File
@@ -1,56 +1,10 @@
"""
homeassistant.components.notify.instapush
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Instapush notification service.
Configuration:
To use the Instapush notifier you will need to add something like the following
to your config/configuration.yaml
notify:
platform: instapush
api_key: YOUR_APP_KEY
app_secret: YOUR_APP_SECRET
event: YOUR_EVENT
tracker: YOUR_TRACKER
VARIABLES:
api_key
*Required
To retrieve this value log into your account at https://instapush.im and go
to 'APPS', choose an app, and check 'Basic Info'.
app_secret
*Required
To get this value log into your account at https://instapush.im and go to
'APPS'. The 'Application ID' can be found under 'Basic Info'.
event
*Required
To retrieve a valid event log into your account at https://instapush.im and go
to 'APPS'. If you have no events to use with Home Assistant, create one event
for your app.
tracker
*Required
To retrieve the tracker value log into your account at https://instapush.im and
go to 'APPS', choose the app, and check the event entries.
Example usage of Instapush if you have an event 'notification' and a tracker
'home-assistant'.
curl -X POST \
-H "x-instapush-appid: YOUR_APP_KEY" \
-H "x-instapush-appsecret: YOUR_APP_SECRET" \
-H "Content-Type: application/json" \
-d '{"event":"notification","trackers":{"home-assistant":"Switch 1"}}' \
https://api.instapush.im/v1/post
Details for the API : https://instapush.im/developer/rest
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/notify.instapush.html
"""
import logging
import json
+2 -19
View File
@@ -1,27 +1,10 @@
"""
homeassistant.components.notify.nma
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
NMA (Notify My Android) notification service.
Configuration:
To use the NMA notifier you will need to add something like the following
to your config/configuration.yaml
notify:
platform: nma
api_key: YOUR_API_KEY
VARIABLES:
api_key
*Required
Enter the API key for NMA. Go to https://www.notifymyandroid.com and create a
new API key to use with Home Assistant.
Details for the API : https://www.notifymyandroid.com/api.jsp
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/notify.nma.html
"""
import logging
import xml.etree.ElementTree as ET

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