Compare commits

...

300 Commits

Author SHA1 Message Date
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
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
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
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
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
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
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
Paulus Schoutsen dd71e4fdd1 Record in logbook when automation triggered 2015-09-14 00:02:33 -07: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 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 0509b478e9 Bump version 0.7.2 2015-09-09 19:37:44 -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
177 changed files with 5780 additions and 1773 deletions
+8 -2
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,7 @@ omit =
homeassistant/components/zwave.py
homeassistant/components/*/zwave.py
homeassistant/components/ifttt.py
homeassistant/components/browser.py
homeassistant/components/camera/*
homeassistant/components/device_tracker/actiontec.py
@@ -46,9 +45,12 @@ omit =
homeassistant/components/light/hue.py
homeassistant/components/light/limitlessled.py
homeassistant/components/media_player/cast.py
homeassistant/components/media_player/denon.py
homeassistant/components/media_player/itunes.py
homeassistant/components/media_player/kodi.py
homeassistant/components/media_player/mpd.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
@@ -58,10 +60,13 @@ omit =
homeassistant/components/notify/smtp.py
homeassistant/components/notify/syslog.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/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
@@ -72,6 +77,7 @@ omit =
homeassistant/components/sensor/temper.py
homeassistant/components/sensor/time_date.py
homeassistant/components/sensor/transmission.py
homeassistant/components/switch/arest.py
homeassistant/components/switch/command_switch.py
homeassistant/components/switch/edimax.py
homeassistant/components/switch/hikvisioncam.py
+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
+2 -7
View File
@@ -3,11 +3,6 @@ language: python
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
+1
View File
@@ -0,0 +1 @@
recursive-exclude tests *
+1 -1
View File
@@ -18,7 +18,7 @@ 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/)
* [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/)
* [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), [Kodi (XBMC)](http://kodi.tv/), and iTunes (by way of [itunes-api](https://github.com/maddox/itunes-api))
* 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/)
* 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/)
+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):
+23 -9
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)
@@ -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(
@@ -0,0 +1,108 @@
"""
homeassistant.components.alarm_control_panel
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Component to interface with a alarm control panel.
"""
import logging
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.components import verisure
from homeassistant.const import (
ATTR_ENTITY_ID,
SERVICE_ALARM_DISARM, SERVICE_ALARM_ARM_HOME, SERVICE_ALARM_ARM_AWAY)
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',
}
ATTR_CODE = 'code'
ATTR_TO_PROPERTY = [
ATTR_CODE,
]
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:
return
code = service.data[ATTR_CODE]
method = SERVICE_TO_METHOD[service.service]
for alarm in target_alarms:
getattr(alarm, method)(code)
for service in SERVICE_TO_METHOD:
hass.services.register(DOMAIN, service, alarm_service_handler)
return True
def alarm_disarm(hass, code, entity_id=None):
""" Send the alarm the command for disarm. """
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, entity_id=None):
""" Send the alarm the command for arm home. """
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, entity_id=None):
""" Send the alarm the command for arm away. """
data = {ATTR_CODE: code}
if entity_id:
data[ATTR_ENTITY_ID] = entity_id
hass.services.call(DOMAIN, SERVICE_ALARM_ARM_AWAY, data)
class AlarmControlPanel(Entity):
""" ABC for alarm control devices. """
def alarm_disarm(self, code):
""" Send disarm command. """
raise NotImplementedError()
def alarm_arm_home(self, code):
""" Send arm home command. """
raise NotImplementedError()
def alarm_arm_away(self, code):
""" Send arm away command. """
raise NotImplementedError()
@@ -0,0 +1,88 @@
"""
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)
class VerisureAlarm(alarm.AlarmControlPanel):
""" represents a Verisure alarm status within home assistant. """
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
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):
""" Send disarm command. """
verisure.MY_PAGES.set_alarm_status(
code,
verisure.MY_PAGES.ALARM_DISARMED)
_LOGGER.warning('disarming')
def alarm_arm_home(self, code):
""" 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):
""" Send arm away command. """
verisure.MY_PAGES.set_alarm_status(
code,
verisure.MY_PAGES.ALARM_ARMED_AWAY)
_LOGGER.warning('arming away')
+1 -1
View File
@@ -7,7 +7,7 @@ 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
to your configuration.yaml file.
arduino:
port: /dev/ttyACM0
+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
+4 -3
View File
@@ -12,7 +12,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 +20,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)
+3 -3
View File
@@ -10,11 +10,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,91 @@
"""
homeassistant.components.automation.numeric_state
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Offers numeric state listening automation rules.
"""
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
+27 -6
View File
@@ -10,22 +10,23 @@ 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
def state_automation_listener(entity, from_s, to_s):
""" Listens for state changes and calls action. """
@@ -35,3 +36,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
+103
View File
@@ -0,0 +1,103 @@
"""
homeassistant.components.automation.sun
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Offers sun based automation rules.
"""
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())
+84 -7
View File
@@ -4,19 +4,41 @@ homeassistant.components.automation.time
Offers time listening automation rules.
"""
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 +48,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')
+105
View File
@@ -0,0 +1,105 @@
"""
homeassistant.components.camera.foscam
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This component provides basic support for Foscam IP cameras.
As part of the basic support the following features will be provided:
-MJPEG video streaming
To use this component, add the following to your configuration.yaml file.
camera:
platform: foscam
name: Door Camera
ip: 192.168.0.123
port: 88
username: YOUR_USERNAME
password: YOUR_PASSWORD
Variables:
ip
*Required
The IP address of your Foscam device.
username
*Required
The username of a visitor or operator of your camera. Oddly admin accounts
don't seem to have access to take snapshots.
password
*Required
The password for accessing your camera.
name
*Optional
This parameter allows you to override the name of your camera in homeassistant.
port
*Optional
The port that the camera is running on. The default is 88.
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
+14 -17
View File
@@ -1,4 +1,6 @@
"""
homeassistant.components.camera.generic
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for IP Cameras.
This component provides basic support for IP cameras. For the basic support to
@@ -7,11 +9,11 @@ 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)
- MJPEG video streaming
- Saving a snapshot
- Recording(JPEG frame capture)
To use this component, add the following to your config/configuration.yaml:
To use this component, add the following to your configuration.yaml file.
camera:
platform: generic
@@ -20,29 +22,24 @@ camera:
password: YOUR_PASSWORD
still_image_url: http://YOUR_CAMERA_IP_AND_PORT/image.jpg
VARIABLES:
These are the variables for the device_data array:
Variables:
still_image_url
*Required
The URL your camera serves the image on.
Example: http://192.168.1.21:2112/
The URL your camera serves the image on, eg. http://192.168.1.21:2112/
name
*Optional
This parameter allows you to override the name of your camera in homeassistant
This parameter allows you to override the name of your camera in Home
Assistant.
username
*Optional
THe username for acessing your camera
The username for accessing your camera.
password
*Optional
the password for accessing your camera
The password for accessing your camera.
"""
import logging
from requests.auth import HTTPBasicAuth
@@ -78,7 +75,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 +87,5 @@ class GenericCamera(Camera):
@property
def name(self):
""" Return the name of this device """
""" Return the name of this device. """
return self._name
+1 -1
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):
@@ -1,52 +1,82 @@
"""
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
"""
import logging
import threading
import os
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
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, DEVICE_DEFAULT_NAME, STATE_HOME, STATE_NOT_HOME)
DOMAIN = "device_tracker"
DEPENDENCIES = []
SERVICE_DEVICE_TRACKER_RELOAD = "reload_devices_csv"
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_CONF_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
SERVICE_SEE = 'see'
ATTR_LATITUDE = 'latitude'
ATTR_LONGITUDE = 'longitude'
ATTR_MAC = 'mac'
ATTR_DEV_ID = 'dev_id'
ATTR_HOST_NAME = 'host_name'
ATTR_LOCATION_NAME = 'location_name'
ATTR_GPS = 'gps'
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 +85,310 @@ 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):
""" 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, {})
consider_home = util.convert(conf.get(CONF_CONSIDER_HOME), int,
DEFAULT_CONF_CONSIDER_HOME)
track_new = util.convert(conf.get(CONF_TRACK_NEW), bool,
DEFAULT_CONF_TRACK_NEW)
tracker_type = config[DOMAIN].get(CONF_PLATFORM)
devices = load_config(yaml_path, hass, timedelta(seconds=consider_home))
tracker = DeviceTracker(hass, consider_home, track_new, 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)}
tracker.see(**args)
hass.services.register(DOMAIN, SERVICE_SEE, see_service)
return True
class DeviceTracker(object):
""" Track devices """
def __init__(self, hass, consider_home, track_new, 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 = timedelta(seconds=consider_home)
self.track_new = track_new
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):
""" 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)
if device.track:
device.update_ha_state()
return
# If no device can be found, create it
device = Device(
self.hass, self.consider_home, 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)
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. """
# pylint: disable=too-many-instance-attributes, too-many-arguments
host_name = None
location_name = None
gps = None
last_seen = 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, 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
# 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 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],
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):
""" Mark the device as seen. """
self.last_seen = dt_util.utcnow()
self.host_name = host_name
self.location_name = location_name
self.gps = gps
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.stale():
self._state = STATE_NOT_HOME
self.last_update_home = False
else:
self._state = STATE_HOME
self.last_update_home = True
self.lock.acquire()
self.untracked_devices.clear()
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, row['track'] == '1', dev_id,
row['device'], row['name'], row['picture'])
update_config(yaml_path, dev_id, device)
return True
with open(known_dev_path) as inp:
# To track which devices need an entity_id assigned
need_entity_id = []
def load_config(path, hass, consider_home):
""" Load devices from YAML config file. """
if not os.path.isfile(path):
return []
return [
Device(hass, consider_home, 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()]
# 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())
try:
for row in csv.DictReader(inp):
device = row['device'].upper()
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)
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)
# Initial scan of each mac we also tell about host name for config
seen = set()
self._track_device(device, 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)
# Update state_attr with latest from file
state_attr = {
ATTR_FRIENDLY_NAME: row['name']
}
track_utc_time_change(hass, device_tracker_scan, second=range(0, 60,
interval))
if row['picture']:
state_attr[ATTR_ENTITY_PICTURE] = row['picture']
device_tracker_scan(None)
self.tracked[device]['state_attr'] = state_attr
else:
self.untracked_devices.add(device)
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))
# Remove existing devices that we no longer track
for device in removed_devices:
entity_id = self.tracked[device]['entity_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,6 +1,6 @@
"""
homeassistant.components.device_tracker.actiontec
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Device tracker platform that supports scanning an Actiontec MI424WR
(Verizon FIOS) router for device presence.
@@ -9,13 +9,16 @@ 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
following to your configuration.yaml file. If you experience disconnects
you can modify the home_interval variable.
device_tracker:
platform: actiontec
host: YOUR_ROUTER_IP
username: YOUR_ADMIN_USERNAME
password: YOUR_ADMIN_PASSWORD
# optional:
home_interval: 10
Variables:
@@ -30,21 +33,32 @@ The username of an user with administrative privileges, usually 'admin'.
password
*Required
The password for your given admin account.
home_interval
*Optional
If the home_interval is set then the component will not let a device
be AWAY if it has been HOME in the last home_interval minutes. This is
in addition to the 3 minute wait built into the device_tracker component.
"""
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 +68,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 +78,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: ')
@@ -9,8 +9,8 @@ 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.
to your configuration.yaml file. You also need to enable Telnet in the
configuration page of your router.
device_tracker:
platform: aruba
@@ -51,15 +51,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 +83,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 +101,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 +117,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,11 +157,19 @@ 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
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:
@@ -1,14 +1,13 @@
"""
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
to your configuration.yaml file.
device_tracker:
platform: ddwrt
@@ -64,7 +63,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.
"""
@@ -85,8 +85,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()
@@ -124,8 +125,10 @@ class DdWrtDeviceScanner(object):
@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
@@ -163,7 +166,7 @@ class DdWrtDeviceScanner(object):
return False
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,
@@ -1,18 +1,16 @@
"""
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
to your configuration.yaml file.
device_tracker:
platform: luci
@@ -66,7 +64,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 +94,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 +124,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 +181,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,48 @@
"""
homeassistant.components.device_tracker.mqtt
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
MQTT platform for the device tracker.
device_tracker:
platform: mqtt
qos: 1
devices:
paulus_oneplus: /location/paulus
annetherese_n4: /location/annetherese
"""
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,14 +1,13 @@
"""
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
to your configuration.yaml file.
device_tracker:
platform: netgear
@@ -71,7 +70,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 +88,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 +105,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,13 +1,12 @@
"""
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
to your configuration.yaml file.
device_tracker:
platform: nmap_tracker
@@ -45,7 +44,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 +73,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 +86,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,14 +107,16 @@ 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"
options = "-F --host-timeout 5"
exclude_targets = set()
if self.home_interval:
now = dt_util.now()
@@ -9,7 +9,7 @@ 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
to your configuration.yaml file.
device_tracker:
platform: thomson
@@ -71,7 +71,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 +108,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 +128,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,14 +1,13 @@
"""
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
to your configuration.yaml file.
device_tracker:
platform: tomato
@@ -1,14 +1,13 @@
"""
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
to your configuration.yaml file.
device_tracker:
platform: tplink
@@ -29,7 +28,6 @@ The username of an user with administrative privileges, usually 'admin'.
password
*Required
The password for your given admin account.
"""
import base64
import logging
@@ -65,7 +63,8 @@ def get_scanner(hass, config):
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 +84,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 +94,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,28 +125,33 @@ 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...")
+3 -10
View File
@@ -19,22 +19,22 @@ from homeassistant.const import (
DOMAIN = "discovery"
DEPENDENCIES = []
REQUIREMENTS = ['netdisco==0.3']
REQUIREMENTS = ['netdisco==0.4']
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_HANDLERS = {
SERVICE_WEMO: "switch",
SERVICE_CAST: "media_player",
SERVICE_HUE: "light",
SERVICE_NETGEAR: 'device_tracker',
SERVICE_SONOS: 'media_player',
}
@@ -79,13 +79,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
+1 -1
View File
@@ -1,2 +1,2 @@
""" DO NOT MODIFY. Auto-generated by build_frontend script """
VERSION = "35ecb5457a9ff0f4142c2605b53eb843"
VERSION = "5f35285bc502e3f69f564240fee04baa"
File diff suppressed because one or more lines are too long
-2
View File
@@ -147,8 +147,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(
+2 -2
View File
@@ -205,7 +205,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):
@@ -487,7 +487,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 """
+79
View File
@@ -0,0 +1,79 @@
"""
homeassistant.components.ifttt
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This component enable you to trigger Maker IFTTT recipes.
Check https://ifttt.com/maker for details.
Configuration:
To use Maker IFTTT you will need to add something like the following to your
config/configuration.yaml.
ifttt:
key: xxxxx-x-xxxxxxxxxxxxx
Variables:
key
*Required
Your api key
"""
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
@@ -15,7 +15,7 @@ Support for LimitlessLED bulbs, also known as...
Configuration:
To use limitlessled you will need to add the following to your
config/configuration.yaml.
configuration.yaml file.
light:
platform: limitlessled
@@ -24,7 +24,6 @@ light:
group_2_name: Bedroom
group_3_name: Office
group_4_name: Kitchen
"""
import logging
@@ -107,7 +106,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)
+14 -12
View File
@@ -1,7 +1,6 @@
"""
homeassistant.components.light.vera
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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.
@@ -9,17 +8,17 @@ 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.
your configuration.yaml file.
light:
platform: vera
vera_controller_url: http://YOUR_VERA_IP:3480/
device_data:
12:
name: My awesome switch
exclude: true
13:
name: Another switch
platform: vera
vera_controller_url: http://YOUR_VERA_IP:3480/
device_data:
12:
name: My awesome switch
exclude: true
13:
name: Another switch
Variables:
@@ -52,8 +51,10 @@ it should be set to "true" if you want this device excluded.
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 +62,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:
+3 -2
View File
@@ -9,8 +9,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):
+42 -3
View File
@@ -12,9 +12,10 @@ from homeassistant.core import State, DOMAIN as HA_DOMAIN
from homeassistant.const import (
EVENT_STATE_CHANGED, STATE_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 +26,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 +132,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
@@ -175,6 +200,20 @@ 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. """
@@ -19,12 +19,13 @@ 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',
}
SERVICE_YOUTUBE_VIDEO = 'play_youtube_video'
@@ -483,7 +484,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:
+16 -2
View File
@@ -1,10 +1,24 @@
"""
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
WARNING: This platform is currently not working due to a changed Cast API.
Configuration:
To use the chromecast integration you will need to add something like the
following to your configuration.yaml file.
media_player:
platform: chromecast
host: 192.168.1.9
Variables:
host
*Optional
Use only if you don't want to scan for devices.
"""
import logging
@@ -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,191 @@
"""
homeassistant.components.media_player.denon
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides an interface to Denon Network Receivers.
Developed for a Denon DRA-N5, see
http://www.denon.co.uk/chg/product/compactsystems/networkmusicsystems/ceolpiccolo
A few notes:
- As long as this module is active and connected, the receiver does
not seem to accept additional telnet connections.
- Be careful with the volume. 50% or even 100% are very loud.
- To be able to wake up the receiver, activate the "remote" setting
in the receiver's settings.
- Play and pause are supported, toggling is not possible.
- Seeking cannot be implemented as the UI sends absolute positions.
Only seeking via simulated button presses is possible.
Configuration:
To use your Denon you will need to add something like the following to
your config/configuration.yaml:
media_player:
platform: denon
name: Music station
host: 192.168.0.123
Variables:
host
*Required
The ip of the player. Example: 192.168.0.123
name
*Optional
The name of the device.
"""
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
add_devices([
DenonDevice(
config.get('name', 'Music station'),
config.get('host'))
])
return True
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._telnet = telnetlib.Telnet(self._host)
def query(self, message):
""" Send request and await response from server """
try:
# unspecified command, should be ignored
self._telnet.write("?".encode('UTF-8') + b'\r')
except (EOFError, BrokenPipeError, ConnectionResetError):
self._telnet.open(self._host)
self._telnet.read_very_eager() # skip what is not requested
self._telnet.write(message.encode('ASCII') + b'\r')
# timeout 200ms, defined by protocol
resp = self._telnet.read_until(b'\r', timeout=0.2)\
.decode('UTF-8').strip()
if message == "PW?":
# workaround; PW? sends also SISTATUS
self._telnet.read_until(b'\r', timeout=0.2)
return resp
@property
def name(self):
""" Returns the name of the device. """
return self._name
@property
def state(self):
""" Returns the state of the device. """
pwstate = self.query('PW?')
if pwstate == "PWSTANDBY":
return STATE_OFF
if pwstate == "PWON":
return STATE_ON
return STATE_UNKNOWN
@property
def volume_level(self):
""" Volume level of the media player (0..1). """
return int(self.query('MV?')[len('MV'):]) / 60
@property
def is_volume_muted(self):
""" Boolean if volume is currently muted. """
return self.query('MU?') == "MUON"
@property
def media_title(self):
""" Current media source. """
return self.query('SI?')[len('SI'):]
@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.query('PWSTANDBY')
def volume_up(self):
""" volume_up media player. """
self.query('MVUP')
def volume_down(self):
""" volume_down media player. """
self.query('MVDOWN')
def set_volume_level(self, volume):
""" set volume level, range 0..1. """
# 60dB max
self.query('MV' + str(round(volume * 60)).zfill(2))
def mute_volume(self, mute):
""" mute (true) or unmute (false) media player. """
self.query('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.query('NS9A')
def media_pause(self):
""" media_pause media player. """
self.query('NS9B')
def media_next_track(self):
""" Send next track command. """
self.query('NS9D')
def media_previous_track(self):
self.query('NS9E')
def media_seek(self, position):
raise NotImplementedError()
def turn_on(self):
""" turn the media player on. """
self.query('PWON')
@@ -0,0 +1,445 @@
"""
homeassistant.components.media_player.itunes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides an interface to iTunes-API (https://github.com/maddox/itunes-api)
The iTunes media player will allow you to control your iTunes instance. You
can play/pause/next/previous/mute, adjust volume, etc.
In addition to controlling iTunes, your available AirPlay endpoints will be
added as media players as well. You can then individually address them append
turn them on, turn them off, or adjust their volume.
Configuration:
To use iTunes you will need to add something like the following to
your configuration.yaml file.
media_player:
platform: itunes
name: iTunes
host: http://192.168.1.16
port: 8181
Variables:
name
*Optional
The name of the device.
url
*Required
URL of your running version of iTunes-API. Example: http://192.168.1.50:8181
"""
import logging
from homeassistant.components.media_player import (
MediaPlayerDevice, MEDIA_TYPE_MUSIC, SUPPORT_PAUSE, SUPPORT_SEEK,
SUPPORT_VOLUME_SET, SUPPORT_VOLUME_MUTE, SUPPORT_PREVIOUS_TRACK,
SUPPORT_NEXT_TRACK, SUPPORT_TURN_ON, SUPPORT_TURN_OFF,
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_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 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
# pylint: disable=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 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)
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)
@@ -6,7 +6,7 @@ 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.
your configuration.yaml file.
media_player:
platform: kodi
@@ -74,7 +74,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 +107,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
@@ -235,7 +236,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 +257,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:
+1 -2
View File
@@ -1,13 +1,12 @@
"""
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
configuration.yaml file.
media_player:
platform: mpd
@@ -0,0 +1,206 @@
"""
homeassistant.components.media_player.sonos
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides an interface to Sonos players (via SoCo)
Configuration:
To use SoCo, add something like this to your configuration:
media_player:
platform: sonos
"""
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
# pylint: disable=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):
""" media_play media player. """
self._player.play()
def media_pause(self):
""" media_pause media player. """
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,12 +1,12 @@
"""
homeassistant.components.media_player.squeezebox
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides an interface to the Logitech SqueezeBox API
Configuration:
To use SqueezeBox add something like this to your configuration:
To use SqueezeBox add something something like the following to your
configuration.yaml file.
media_player:
platform: squeezebox
@@ -19,19 +19,19 @@ Variables:
host
*Required
The host name or address of the Logitech Media Server
The host name or address of the Logitech Media Server.
port
*Optional
Telnet port to Logitech Media Server, default 9090
Telnet port to Logitech Media Server, default 9090.
usermame
*Optional
Username, if password protection is enabled
Username, if password protection is enabled.
password
*Optional
Password, if password protection is enabled
Password, if password protection is enabled.
"""
import logging
@@ -91,7 +91,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 +111,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 +121,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 +138,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 +195,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):
+4 -9
View File
@@ -1,17 +1,12 @@
"""
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
Modbus component, using pymodbus (python3 branch).
Configuration:
To use the Modbus component you will need to add something like the following
to your config/configuration.yaml
to your configuration.yaml file.
#Modbus TCP
modbus:
@@ -38,8 +33,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"
+9 -6
View File
@@ -75,21 +75,23 @@ CONF_KEEPALIVE = 'keepalive'
CONF_USERNAME = 'username'
CONF_PASSWORD = 'password'
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. """
@@ -141,9 +143,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)
@@ -177,9 +180,9 @@ class MQTT(object): # pragma: no cover
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. """
+1 -2
View File
@@ -1,13 +1,12 @@
"""
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
to your configuration.yaml file.
notify:
platform: file
+2 -4
View File
@@ -1,13 +1,12 @@
"""
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
to your configuration.yaml file.
notify:
platform: instapush
@@ -16,7 +15,7 @@ notify:
event: YOUR_EVENT
tracker: YOUR_TRACKER
VARIABLES:
Variables:
api_key
*Required
@@ -50,7 +49,6 @@ curl -X POST \
https://api.instapush.im/v1/post
Details for the API : https://instapush.im/developer/rest
"""
import logging
import json
+2 -4
View File
@@ -1,19 +1,18 @@
"""
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
to your configuration.yaml file.
notify:
platform: nma
api_key: YOUR_API_KEY
VARIABLES:
Variables:
api_key
*Required
@@ -21,7 +20,6 @@ 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
"""
import logging
import xml.etree.ElementTree as ET
@@ -1,13 +1,12 @@
"""
homeassistant.components.notify.pushbullet
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
PushBullet platform for notify component.
Configuration:
To use the PushBullet notifier you will need to add something like the
following to your config/configuration.yaml
following to your configuration.yaml file.
notify:
platform: pushbullet
@@ -32,7 +31,7 @@ REQUIREMENTS = ['pushbullet.py==0.7.1']
def get_service(hass, config):
""" Get the pushbullet notification service. """
""" Get the PushBullet notification service. """
if not validate_config(config,
{DOMAIN: [CONF_API_KEY]},
+4 -6
View File
@@ -1,18 +1,17 @@
"""
homeassistant.components.notify.pushover
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Pushover platform for notify component.
Configuration:
To use the Pushover notifier you will need to add something like the following
to your config/configuration.yaml
to your configuration.yaml file.
notify:
platform: pushover
api_key: ABCDEFGHJKLMNOPQRSTUVXYZ
user_key: ABCDEFGHJKLMNOPQRSTUVXYZ
platform: pushover
api_key: ABCDEFGHJKLMNOPQRSTUVXYZ
user_key: ABCDEFGHJKLMNOPQRSTUVXYZ
Variables:
@@ -33,7 +32,6 @@ https://home-assistant.io/images/favicon-192x192.png
user_key
*Required
To retrieve this value log into your account at https://pushover.net
"""
import logging
+1 -1
View File
@@ -6,7 +6,7 @@ Slack platform for notify component.
Configuration:
To use the Slack notifier you will need to add something like the following
to your config/configuration.yaml
to your configuration.yaml file.
notify:
platform: slack
+2 -3
View File
@@ -1,13 +1,12 @@
"""
homeassistant.components.notify.mail
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Mail notification service.
Mail (SMTP) notification service.
Configuration:
To use the Mail notifier you will need to add something like the following
to your config/configuration.yaml
to your configuration.yaml file.
notify:
platform: mail
+1 -2
View File
@@ -1,13 +1,12 @@
"""
homeassistant.components.notify.syslog
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Syslog notification service.
Configuration:
To use the Syslog notifier you will need to add something like the following
to your config/configuration.yaml
to your configuration.yaml file.
notify:
platform: syslog
+1 -1
View File
@@ -6,7 +6,7 @@ Jabber (XMPP) notification service.
Configuration:
To use the Jabber notifier you will need to add something like the following
to your config/configuration.yaml
to your configuration.yaml file.
notify:
platform: xmpp
+1 -1
View File
@@ -256,7 +256,7 @@ class Recorder(threading.Thread):
""" Query the database. """
try:
with self.conn, self.lock:
_LOGGER.info("Running query %s", sql_query)
_LOGGER.debug("Running query %s", sql_query)
cur = self.conn.cursor()
@@ -1,137 +0,0 @@
"""
homeassistant.components.scheduler
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A component that will act as a scheduler and perform actions based
on the events in the schedule.
It will read a json object from schedule.json in the config dir
and create a schedule based on it.
Each schedule is a JSON with the keys id, name, description,
entity_ids, and events.
- days is an array with the weekday number (monday=0) that the schedule
is active
- entity_ids an array with entity ids that the events in the schedule should
effect (can also be groups)
- events is an array of objects that describe the different events that is
supported. Read in the events descriptions for more information.
"""
import logging
import json
from homeassistant import bootstrap
from homeassistant.loader import get_component
from homeassistant.const import ATTR_ENTITY_ID
DOMAIN = 'scheduler'
DEPENDENCIES = []
_LOGGER = logging.getLogger(__name__)
_SCHEDULE_FILE = 'schedule.json'
def setup(hass, config):
""" Create the schedules. """
def setup_listener(schedule, event_data):
""" Creates the event listener based on event_data. """
event_type = event_data['type']
component = event_type
# if the event isn't part of a component
if event_type in ['time']:
component = 'scheduler.{}'.format(event_type)
elif not bootstrap.setup_component(hass, component, config):
_LOGGER.warn("Could setup event listener for %s", component)
return None
return get_component(component).create_event_listener(schedule,
event_data)
def setup_schedule(schedule_data):
""" Setup a schedule based on the description. """
schedule = Schedule(schedule_data['id'],
name=schedule_data['name'],
description=schedule_data['description'],
entity_ids=schedule_data['entity_ids'],
days=schedule_data['days'])
for event_data in schedule_data['events']:
event_listener = setup_listener(schedule, event_data)
if event_listener:
schedule.add_event_listener(event_listener)
schedule.schedule(hass)
return True
with open(hass.config.path(_SCHEDULE_FILE)) as schedule_file:
schedule_descriptions = json.load(schedule_file)
for schedule_description in schedule_descriptions:
if not setup_schedule(schedule_description):
return False
return True
class Schedule(object):
""" A Schedule """
# pylint: disable=too-many-arguments
def __init__(self, schedule_id, name=None, description=None,
entity_ids=None, days=None):
self.schedule_id = schedule_id
self.name = name
self.description = description
self.entity_ids = entity_ids or []
self.days = days or [0, 1, 2, 3, 4, 5, 6]
self.__event_listeners = []
def add_event_listener(self, event_listener):
""" Add a event to the schedule. """
self.__event_listeners.append(event_listener)
def schedule(self, hass):
""" Schedule all the events in the schedule. """
for event in self.__event_listeners:
event.schedule(hass)
class EventListener(object):
""" The base EventListener class that the schedule uses. """
def __init__(self, schedule):
self.my_schedule = schedule
def schedule(self, hass):
""" Schedule the event """
pass
def execute(self, hass):
""" execute the event """
pass
# pylint: disable=too-few-public-methods
class ServiceEventListener(EventListener):
""" A EventListener that calls a service when executed. """
def __init__(self, schdule, service):
EventListener.__init__(self, schdule)
(self.domain, self.service) = service.split('.')
def execute(self, hass):
""" Call the service. """
data = {ATTR_ENTITY_ID: self.my_schedule.entity_ids}
hass.services.call(self.domain, self.service, data)
# Reschedule for next day
self.schedule(hass)
@@ -1,70 +0,0 @@
"""
homeassistant.components.scheduler.time
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
An event in the scheduler component that will call the service
every specified day at the time specified.
A time event need to have the type 'time', which service to call and at
which time.
{
"type": "time",
"service": "switch.turn_off",
"time": "22:00:00"
}
"""
from datetime import timedelta
import logging
import homeassistant.util.dt as dt_util
from homeassistant.helpers.event import track_point_in_time
from homeassistant.components.scheduler import ServiceEventListener
_LOGGER = logging.getLogger(__name__)
def create_event_listener(schedule, event_listener_data):
""" Create a TimeEvent based on the description. """
service = event_listener_data['service']
(hour, minute, second) = [int(x) for x in
event_listener_data['time'].split(':')]
return TimeEventListener(schedule, service, hour, minute, second)
# pylint: disable=too-few-public-methods
class TimeEventListener(ServiceEventListener):
""" The time event that the scheduler uses. """
# pylint: disable=too-many-arguments
def __init__(self, schedule, service, hour, minute, second):
ServiceEventListener.__init__(self, schedule, service)
self.hour = hour
self.minute = minute
self.second = second
def schedule(self, hass):
""" Schedule this event so that it will be called. """
next_time = dt_util.now().replace(
hour=self.hour, minute=self.minute, second=self.second)
# Calculate the next time the event should be executed.
# That is the next day that the schedule is configured to run
while next_time < dt_util.now() or \
next_time.weekday() not in self.my_schedule.days:
next_time = next_time + timedelta(days=1)
# pylint: disable=unused-argument
def execute(now):
""" Call the execute method """
self.execute(hass)
track_point_in_time(hass, execute, next_time)
_LOGGER.info(
'TimeEventListener scheduled for %s, will call service %s.%s',
next_time, self.domain, self.service)
+22 -3
View File
@@ -1,7 +1,7 @@
"""
homeassistant.components.script
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
entity_id
Scripts are a sequence of actions that can be triggered manually
by the user or automatically based upon automation events, etc.
"""
@@ -22,7 +22,10 @@ CONF_ALIAS = "alias"
CONF_SERVICE = "execute_service"
CONF_SERVICE_DATA = "service_data"
CONF_SEQUENCE = "sequence"
CONF_EVENT = "event"
CONF_EVENT_DATA = "event_data"
CONF_DELAY = "delay"
ATTR_ENTITY_ID = "entity_id"
_LOGGER = logging.getLogger(__name__)
@@ -41,15 +44,22 @@ def setup(hass, config):
hass.services.register(DOMAIN, name, script)
scripts.append(script)
def _get_entities(service):
""" Make sure that we always get a list of entities """
if isinstance(service.data[ATTR_ENTITY_ID], list):
return service.data[ATTR_ENTITY_ID]
else:
return [service.data[ATTR_ENTITY_ID]]
def turn_on(service):
""" Calls a script. """
for entity_id in service.data['entity_id']:
for entity_id in _get_entities(service):
domain, service = split_entity_id(entity_id)
hass.services.call(domain, service, {})
def turn_off(service):
""" Cancels a script. """
for entity_id in service.data['entity_id']:
for entity_id in _get_entities(service):
for script in scripts:
if script.entity_id == entity_id:
script.cancel()
@@ -109,6 +119,8 @@ class Script(object):
for action in self.actions:
if CONF_SERVICE in action:
self._call_service(action)
elif CONF_EVENT in action:
self._fire_event(action)
elif CONF_DELAY in action:
delay = timedelta(**action[CONF_DELAY])
point_in_time = date_util.now() + delay
@@ -140,3 +152,10 @@ class Script(object):
domain, service = split_entity_id(action[CONF_SERVICE])
data = action.get(CONF_SERVICE_DATA, {})
self.hass.services.call(domain, service, data)
def _fire_event(self, action):
""" Fires an event. """
self.last_action = action.get(CONF_ALIAS, action[CONF_EVENT])
_LOGGER.info("Executing script %s step %s", self.alias,
self.last_action)
self.hass.bus.fire(action[CONF_EVENT], action.get(CONF_EVENT_DATA))
+4 -1
View File
@@ -6,6 +6,9 @@ supported.
Configuration:
To use the arduino sensor you will need to add something like the following
to your configuration.yaml file.
sensor:
platform: arduino
pins:
@@ -46,7 +49,7 @@ _LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the Arduino platform. """
# Verify that Arduino board is present
# Verify that the Arduino board is present
if arduino.BOARD is None:
_LOGGER.error('A connection has not been made to the Arduino board.')
return False
+150
View File
@@ -0,0 +1,150 @@
"""
homeassistant.components.sensor.arest
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The arest sensor will consume an exposed aREST API of a device.
Configuration:
To use the arest sensor you will need to add something like the following
to your configuration.yaml file.
sensor:
platform: arest
resource: http://IP_ADDRESS
monitored_variables:
- name: temperature
unit: '°C'
- name: humidity
unit: '%'
Variables:
resource:
*Required
IP address of the device that is exposing an aREST API.
These are the variables for the monitored_variables array:
name
*Required
The name of the variable you wish to monitor.
unit
*Optional
Defines the units of measurement of the sensor, if any.
Details for the API: http://arest.io
Format of a default JSON response by aREST:
{
"variables":{
"temperature":21,
"humidity":89
},
"id":"device008",
"name":"Bedroom",
"connected":true
}
"""
import logging
from requests import get, exceptions
from datetime import timedelta
from homeassistant.util import Throttle
from homeassistant.helpers.entity import Entity
_LOGGER = logging.getLogger(__name__)
# Return cached results if last scan was less then this time ago
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60)
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Get the aREST sensor. """
resource = config.get('resource', None)
try:
response = get(resource, timeout=10)
except exceptions.MissingSchema:
_LOGGER.error("Missing resource or schema in configuration. "
"Add http:// to your URL.")
return False
except exceptions.ConnectionError:
_LOGGER.error("No route to device. "
"Please check the IP address in the configuration file.")
return False
rest = ArestData(resource)
dev = []
for variable in config['monitored_variables']:
if 'unit' not in variable:
variable['unit'] = ' '
if variable['name'] not in response.json()['variables']:
_LOGGER.error('Variable: "%s" does not exist', variable['name'])
else:
dev.append(ArestSensor(rest,
response.json()['name'],
variable['name'],
variable['unit']))
add_devices(dev)
class ArestSensor(Entity):
""" Implements an aREST sensor. """
def __init__(self, rest, location, variable, unit_of_measurement):
self.rest = rest
self._name = '{} {}'.format(location.title(), variable.title())
self._variable = variable
self._state = 'n/a'
self._unit_of_measurement = unit_of_measurement
self.update()
@property
def name(self):
""" The name of the sensor. """
return self._name
@property
def unit_of_measurement(self):
""" Unit the value is expressed in. """
return self._unit_of_measurement
@property
def state(self):
""" Returns the state of the device. """
return self._state
def update(self):
""" Gets the latest data from aREST API and updates the state. """
self.rest.update()
values = self.rest.data
if 'error' in values:
self._state = values['error']
else:
self._state = values[self._variable]
# pylint: disable=too-few-public-methods
class ArestData(object):
""" Class for handling the data retrieval. """
def __init__(self, resource):
self.resource = resource
self.data = dict()
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
""" Gets the latest data from aREST device. """
try:
response = get(self.resource, timeout=10)
if 'error' in self.data:
del self.data['error']
self.data = response.json()['variables']
except exceptions.ConnectionError:
_LOGGER.error("No route to device. Is device offline?")
self.data['error'] = 'n/a'
+1 -2
View File
@@ -1,7 +1,6 @@
"""
homeassistant.components.sensor.bitcoin
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Bitcoin information service that uses blockchain.info and its online wallet.
Configuration:
@@ -12,7 +11,7 @@ check 'Enable Api Access'. You will get an email message from blockchain.info
where you must authorize the API access.
To use the Bitcoin sensor you will need to add something like the following
to your config/configuration.yaml
to your configuration.yaml file.
sensor:
platform: bitcoin
@@ -0,0 +1,139 @@
"""
homeassistant.components.sensor.command_sensor
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Allows to configure custom shell commands to turn a value for a sensor.
Configuration:
To use the command_line sensor you will need to add something like the
following to your configuration.yaml file.
sensor:
platform: command_sensor
name: "Command sensor"
command: sensor_command
unit_of_measurement: "°C"
correction_factor: 0.0001
decimal_places: 0
Variables:
name
*Optional
Name of the command sensor.
command
*Required
The action to take to get the value.
unit_of_measurement
*Optional
Defines the units of measurement of the sensor, if any.
correction_factor
*Optional
A float value to do some basic calculations.
decimal_places
*Optional
Number of decimal places of the value. Default is 0.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.command_sensor.html
"""
import logging
import subprocess
from datetime import timedelta
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = "Command Sensor"
# Return cached results if last scan was less then this time ago
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60)
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Add the Command Sensor. """
if config.get('command') is None:
_LOGGER.error('Missing required variable: "command"')
return False
data = CommandSensorData(config.get('command'))
add_devices_callback([CommandSensor(
data,
config.get('name', DEFAULT_NAME),
config.get('unit_of_measurement'),
config.get('correction_factor', 1.0),
config.get('decimal_places', 0)
)])
# pylint: disable=too-many-arguments
class CommandSensor(Entity):
""" Represents a sensor that is returning a value of a shell commands. """
def __init__(self, data, name, unit_of_measurement, corr_factor,
decimal_places):
self.data = data
self._name = name
self._state = False
self._unit_of_measurement = unit_of_measurement
self._corr_factor = float(corr_factor)
self._decimal_places = decimal_places
self.update()
@property
def name(self):
""" The name of the sensor. """
return self._name
@property
def unit_of_measurement(self):
""" Unit the value is expressed in. """
return self._unit_of_measurement
@property
def state(self):
""" Returns the state of the device. """
return self._state
def update(self):
""" Gets the latest data and updates the state. """
self.data.update()
value = self.data.value
try:
if value is not None:
if self._corr_factor is not None:
self._state = round((float(value) * self._corr_factor),
self._decimal_places)
else:
self._state = value
except ValueError:
self._state = value
# pylint: disable=too-few-public-methods
class CommandSensorData(object):
""" Class for handling the data retrieval. """
def __init__(self, command):
self.command = command
self.value = None
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
""" Gets the latest data with a shell command. """
_LOGGER.info('Running command: %s', self.command)
try:
return_value = subprocess.check_output(self.command.split())
self.value = return_value.strip().decode('utf-8')
except subprocess.CalledProcessError:
_LOGGER.error('Command failed: %s', self.command)
+1 -3
View File
@@ -1,9 +1,7 @@
"""
homeassistant.components.sensor.demo
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Demo platform that has two fake sensors.
Demo platform that has a couple of fake sensors.
"""
from homeassistant.helpers.entity import Entity
from homeassistant.const import TEMP_CELCIUS, ATTR_BATTERY_LEVEL
+7 -7
View File
@@ -1,6 +1,6 @@
"""
homeassistant.components.sensor.dht
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Adafruit DHT temperature and humidity sensor.
You need a Python3 compatible version of the Adafruit_Python_DHT library
(e.g. https://github.com/mala-zaba/Adafruit_Python_DHT,
@@ -10,8 +10,8 @@ as root.
Configuration:
To use the Adafruit DHT sensor you will need to
add something like the following to your config/configuration.yaml:
To use the Adafruit DHT sensor you will need to add something like the
following to your configuration.yaml file.
sensor:
platform: dht
@@ -44,8 +44,10 @@ from homeassistant.const import TEMP_FAHRENHEIT
from homeassistant.helpers.entity import Entity
# update this requirement to upstream as soon as it supports python3
REQUIREMENTS = ['http://github.com/mala-zaba/Adafruit_Python_DHT/archive/' +
'4101340de8d2457dd194bca1e8d11cbfc237e919.zip']
REQUIREMENTS = ['http://github.com/mala-zaba/Adafruit_Python_DHT/archive/'
'4101340de8d2457dd194bca1e8d11cbfc237e919.zip'
'#Adafruit_DHT==1.1.0']
_LOGGER = logging.getLogger(__name__)
SENSOR_TYPES = {
'temperature': ['Temperature', ''],
@@ -102,7 +104,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
# pylint: disable=too-few-public-methods
class DHTSensor(Entity):
""" Implements an DHT sensor. """
def __init__(self, dht_client, sensor_type, temp_unit):
@@ -144,7 +145,6 @@ class DHTSensor(Entity):
class DHTClient(object):
""" Gets the latest data from the DHT sensor. """
def __init__(self, adafruit_dht, sensor, pin):
+3 -3
View File
@@ -7,7 +7,7 @@ Monitors home energy use as measured by an efergy engage hub using its
Configuration:
To use the efergy sensor you will need to add something like the following
to your config/configuration.yaml
to your configuration.yaml file.
sensor:
platform: efergy
@@ -61,7 +61,7 @@ SENSOR_TYPES = {
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the efergy sensor. """
""" Sets up the Efergy sensor. """
app_token = config.get("app_token")
if not app_token:
_LOGGER.error(
@@ -118,7 +118,7 @@ class EfergySensor(Entity):
return self._unit_of_measurement
def update(self):
""" Gets the efergy monitor data from the web service """
""" Gets the Efergy monitor data from the web service. """
if self.type == 'instant_readings':
url_string = _RESOURCE + 'getInstant?token=' + self.app_token
response = get(url_string)
+5 -7
View File
@@ -1,12 +1,12 @@
"""
homeassistant.components.sensor.forecast
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Forecast.io service.
Forecast.io weather service.
Configuration:
To use the Forecast sensor you will need to add something like the
following to your config/configuration.yaml
following to your configuration.yaml file.
sensor:
platform: forecast
@@ -37,11 +37,9 @@ monitored_conditions
*Required
An array specifying the conditions to monitor.
These are the variables for the monitored_conditions array:
type
monitored_conditions
*Required
The condition you wish to monitor, see the configuration example above for a
Conditions to monitor. See the configuration example above for a
list of all available conditions to monitor.
Details for the API : https://developer.forecast.io/docs/v2
@@ -73,7 +71,7 @@ SENSOR_TYPES = {
'humidity': ['Humidity', '%'],
'pressure': ['Pressure', 'mBar'],
'visibility': ['Visibility', 'km'],
'ozone': ['Ozone', ''],
'ozone': ['Ozone', 'DU'],
}
# Return cached results if last scan was less then this time ago
+204
View File
@@ -0,0 +1,204 @@
"""
homeassistant.components.sensor.glances
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Gathers system information of hosts which running glances.
Configuration:
To use the glances sensor you will need to add something like the following
to your configuration.yaml file.
sensor:
platform: glances
name: Glances sensor
host: IP_ADDRESS
port: 61208
resources:
- 'disk_use_percent'
- 'disk_use'
- 'disk_free'
- 'memory_use_percent'
- 'memory_use'
- 'memory_free'
- 'swap_use_percent'
- 'swap_use'
- 'swap_free'
- 'processor_load'
- 'process_running'
- 'process_total'
- 'process_thread'
- 'process_sleeping'
Variables:
name
*Optional
The name of the sensor. Default is 'Glances Sensor'.
host
*Required
The IP address of your host, e.g. 192.168.1.32.
port
*Optional
The network port to connect to. Default is 61208.
resources
*Required
Resources to monitor on the host. See the configuration example above for a
list of all available conditions to monitor.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.glances.html
"""
import logging
import requests
from datetime import timedelta
from homeassistant.util import Throttle
from homeassistant.helpers.entity import Entity
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'Glances Sensor'
_RESOURCE = '/api/2/all'
SENSOR_TYPES = {
'disk_use_percent': ['Disk Use', '%'],
'disk_use': ['Disk Use', 'GiB'],
'disk_free': ['Disk Free', 'GiB'],
'memory_use_percent': ['RAM Use', '%'],
'memory_use': ['RAM Use', 'MiB'],
'memory_free': ['RAM Free', 'MiB'],
'swap_use_percent': ['Swap Use', '%'],
'swap_use': ['Swap Use', 'GiB'],
'swap_free': ['Swap Free', 'GiB'],
'processor_load': ['CPU Load', ''],
'process_running': ['Running', ''],
'process_total': ['Total', ''],
'process_thread': ['Thread', ''],
'process_sleeping': ['Sleeping', '']
}
_LOGGER = logging.getLogger(__name__)
# Return cached results if last scan was less then this time ago
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60)
# pylint: disable=unused-variable
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Setup the Glances sensor. """
if not config.get('host'):
_LOGGER.error('"host:" is missing your configuration')
return False
host = config.get('host')
port = config.get('port', 61208)
url = 'http://{}:{}{}'.format(host, port, _RESOURCE)
try:
response = requests.get(url, timeout=10)
if not response.ok:
_LOGGER.error('Response status is "%s"', response.status_code)
return False
except requests.exceptions.MissingSchema:
_LOGGER.error('Missing resource or schema in configuration. '
'Please heck our details in the configuration file.')
return False
except requests.exceptions.ConnectionError:
_LOGGER.error('No route to resource/endpoint. '
'Please check the details in the configuration file.')
return False
rest = GlancesData(url)
dev = []
for resource in config['resources']:
if resource not in SENSOR_TYPES:
_LOGGER.error('Sensor type: "%s" does not exist', resource)
else:
dev.append(GlancesSensor(rest, resource))
add_devices(dev)
class GlancesSensor(Entity):
""" Implements a REST sensor. """
def __init__(self, rest, sensor_type):
self.rest = rest
self._name = SENSOR_TYPES[sensor_type][0]
self.type = sensor_type
self._state = None
self._unit_of_measurement = SENSOR_TYPES[sensor_type][1]
self.update()
@property
def name(self):
""" The name of the sensor. """
return self._name
@property
def unit_of_measurement(self):
""" Unit the value is expressed in. """
return self._unit_of_measurement
@property
def state(self):
""" Returns the state of the device. """
return self._state
# pylint: disable=too-many-branches
def update(self):
""" Gets the latest data from REST API and updates the state. """
self.rest.update()
value = self.rest.data
if value is not None:
if self.type == 'disk_use_percent':
self._state = value['fs'][0]['percent']
elif self.type == 'disk_use':
self._state = round(value['fs'][0]['used'] / 1024**3, 1)
elif self.type == 'disk_free':
self._state = round(value['fs'][0]['free'] / 1024**3, 1)
elif self.type == 'memory_use_percent':
self._state = value['mem']['percent']
elif self.type == 'memory_use':
self._state = round(value['mem']['used'] / 1024**2, 1)
elif self.type == 'memory_free':
self._state = round(value['mem']['free'] / 1024**2, 1)
elif self.type == 'swap_use_percent':
self._state = value['memswap']['percent']
elif self.type == 'swap_use':
self._state = round(value['memswap']['used'] / 1024**3, 1)
elif self.type == 'swap_free':
self._state = round(value['memswap']['free'] / 1024**3, 1)
elif self.type == 'processor_load':
self._state = value['load']['min15']
elif self.type == 'process_running':
self._state = value['processcount']['running']
elif self.type == 'process_total':
self._state = value['processcount']['total']
elif self.type == 'process_thread':
self._state = value['processcount']['thread']
elif self.type == 'process_sleeping':
self._state = value['processcount']['sleeping']
# pylint: disable=too-few-public-methods
class GlancesData(object):
""" Class for handling the data retrieval. """
def __init__(self, resource):
self.resource = resource
self.data = dict()
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
""" Gets the latest data from REST service. """
try:
response = requests.get(self.resource, timeout=10)
self.data = response.json()
except requests.exceptions.ConnectionError:
_LOGGER.error("No route to host/endpoint.")
self.data = None
@@ -1,7 +1,6 @@
"""
homeassistant.components.sensor.isy994
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for ISY994 sensors.
"""
import logging
+8 -6
View File
@@ -4,8 +4,9 @@ homeassistant.components.modbus
Support for Modbus sensors.
Configuration:
To use the Modbus sensors you will need to add something like the following to
your config/configuration.yaml
your configuration.yaml file.
sensor:
platform: modbus
@@ -47,7 +48,6 @@ Note:
- Each named register will create an integer sensor.
- Each named bit will create a boolean sensor.
"""
import logging
import homeassistant.components.modbus as modbus
@@ -61,7 +61,7 @@ DEPENDENCIES = ['modbus']
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Read config and create Modbus devices """
""" Read config and create Modbus devices. """
sensors = []
slave = config.get("slave", None)
if modbus.TYPE == "serial" and not slave:
@@ -97,7 +97,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class ModbusSensor(Entity):
# pylint: disable=too-many-arguments
""" Represents a Modbus Sensor """
""" Represents a Modbus Sensor. """
def __init__(self, name, slave, register, bit=None, unit=None, coil=False):
self._name = name
@@ -113,8 +113,10 @@ class ModbusSensor(Entity):
@property
def should_poll(self):
""" We should poll, because slaves are not allowed to
initiate communication on Modbus networks"""
"""
We should poll, because slaves are not allowed to
initiate communication on Modbus networks.
"""
return True
@property
+11 -2
View File
@@ -13,6 +13,7 @@ sensor:
platform: mqtt
name: "MQTT Sensor"
state_topic: "home/bedroom/temperature"
qos: 0
unit_of_measurement: "ºC"
Variables:
@@ -25,6 +26,10 @@ state_topic
*Required
The MQTT topic subscribed to receive sensor values.
qos
*Optional
The maximum QoS level of the state topic. Default is 0.
unit_of_measurement
*Optional
Defines the units of measurement of the sensor, if any.
@@ -38,6 +43,7 @@ import homeassistant.components.mqtt as mqtt
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = "MQTT Sensor"
DEFAULT_QOS = 0
DEPENDENCIES = ['mqtt']
@@ -54,16 +60,19 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
hass,
config.get('name', DEFAULT_NAME),
config.get('state_topic'),
config.get('qos', DEFAULT_QOS),
config.get('unit_of_measurement'))])
# pylint: disable=too-many-arguments, too-many-instance-attributes
class MqttSensor(Entity):
""" Represents a sensor that can be updated using MQTT """
def __init__(self, hass, name, state_topic, unit_of_measurement):
def __init__(self, hass, name, state_topic, qos, unit_of_measurement):
self._state = "-"
self._hass = hass
self._name = name
self._state_topic = state_topic
self._qos = qos
self._unit_of_measurement = unit_of_measurement
def message_received(topic, payload, qos):
@@ -71,7 +80,7 @@ class MqttSensor(Entity):
self._state = payload
self.update_ha_state()
mqtt.subscribe(hass, self._state_topic, message_received)
mqtt.subscribe(hass, self._state_topic, message_received, self._qos)
@property
def should_poll(self):
+4 -4
View File
@@ -1,13 +1,12 @@
"""
homeassistant.components.sensor.mysensors
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for MySensors sensors.
Configuration:
To use the MySensors sensor you will need to add something like the
following to your config/configuration.yaml
following to your configuration.yaml file.
sensor:
platform: mysensors
@@ -36,8 +35,9 @@ ATTR_NODE_ID = "node_id"
ATTR_CHILD_ID = "child_id"
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['https://github.com/theolind/pymysensors/archive/' +
'35b87d880147a34107da0d40cb815d75e6cb4af7.zip']
REQUIREMENTS = ['https://github.com/theolind/pymysensors/archive/'
'35b87d880147a34107da0d40cb815d75e6cb4af7.zip'
'#pymysensors==0.2']
def setup_platform(hass, config, add_devices, discovery_info=None):
@@ -6,7 +6,7 @@ OpenWeatherMap (OWM) service.
Configuration:
To use the OpenWeatherMap sensor you will need to add something like the
following to your config/configuration.yaml
following to your configuration.yaml file.
sensor:
platform: openweathermap
@@ -33,7 +33,7 @@ forecast
Enables the forecast. The default is to display the current conditions.
monitored_conditions
*Optional
*Required
Conditions to monitor. See the configuration example above for a
list of all available conditions to monitor.
@@ -91,7 +91,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
_LOGGER.error(
"Connection error "
"Please check your settings for OpenWeatherMap.")
return None
return False
data = WeatherData(owm, forecast, hass.config.latitude,
hass.config.longitude)
+4 -4
View File
@@ -4,10 +4,9 @@ homeassistant.components.sensor.rfxtrx
Shows sensor values from RFXtrx sensors.
Configuration:
To use the rfxtrx sensors you will need to add something like the following to
your config/configuration.yaml
Example:
To use the rfxtrx sensors you will need to add something like the following to
your configuration.yaml file.
sensor:
platform: rfxtrx
@@ -27,7 +26,7 @@ from homeassistant.const import (TEMP_CELCIUS)
from homeassistant.helpers.entity import Entity
REQUIREMENTS = ['https://github.com/Danielhiversen/pyRFXtrx/archive/' +
'ec7a1aaddf8270db6e5da1c13d58c1547effd7cf.zip']
'ec7a1aaddf8270db6e5da1c13d58c1547effd7cf.zip#RFXtrx==0.15']
DATA_TYPES = OrderedDict([
('Temperature', TEMP_CELCIUS),
@@ -102,4 +101,5 @@ class RfxtrxSensor(Entity):
@property
def unit_of_measurement(self):
""" Unit this state is expressed in. """
return self._unit_of_measurement
+4 -5
View File
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
"""
homeassistant.components.sensor.rpi_gpio
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Allows to configure a binary state sensor using RPi GPIO.
Note: To use RPi GPIO, Home Assistant must be run as root.
@@ -35,11 +35,10 @@ The time in milliseconds for port debouncing. Default is 50ms.
ports
*Required
An array specifying the GPIO ports to use and the name to use in the frontend.
"""
import logging
from homeassistant.helpers.entity import Entity
try:
import RPi.GPIO as GPIO
except ImportError:
@@ -119,12 +118,12 @@ class RPiGPIOSensor(Entity):
@property
def should_poll(self):
""" No polling needed """
""" No polling needed. """
return False
@property
def name(self):
""" The name of the sensor """
""" The name of the sensor. """
return self._name
@property
+12 -10
View File
@@ -1,13 +1,12 @@
"""
homeassistant.components.sensor.sabnzbd
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Monitors SABnzbd NZB client API
Configuration:
To use the SABnzbd sensor you will need to add something like the following to
your config/configuration.yaml
your configuration.yaml file.
sensor:
platform: sabnzbd
@@ -27,11 +26,11 @@ Variables:
base_url
*Required
This is the base URL of your SABnzbd instance including the port number if not
running on 80. Example: http://192.168.1.32:8124/
running on 80, e.g. http://192.168.1.32:8124/
name
*Optional
The name to use when displaying this SABnzbd instance
The name to use when displaying this SABnzbd instance.
monitored_variables
*Required
@@ -44,17 +43,17 @@ type
The variable you wish to monitor, see the configuration example above for a
list of all available variables.
"""
from homeassistant.util import Throttle
from datetime import timedelta
from homeassistant.helpers.entity import Entity
# pylint: disable=no-name-in-module, import-error
from homeassistant.external.nzbclients.sabnzbd import SabnzbdApi
from homeassistant.external.nzbclients.sabnzbd import SabnzbdApiException
import logging
REQUIREMENTS = ['https://github.com/jamespcole/home-assistant-nzb-clients/'
'archive/616cad59154092599278661af17e2a9f2cf5e2a9.zip'
'#python-sabnzbd==0.1']
SENSOR_TYPES = {
'current_status': ['Status', ''],
'speed': ['Speed', 'MB/s'],
@@ -71,7 +70,9 @@ _THROTTLED_REFRESH = None
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the sensors. """
""" Sets up the SABnzbd sensors. """
from pysabnzbd import SabnzbdApi, SabnzbdApiException
api_key = config.get("api_key")
base_url = config.get("base_url")
name = config.get("name", "SABnzbd")
@@ -105,7 +106,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class SabnzbdSensor(Entity):
""" A Sabnzbd sensor """
""" Represents an SABnzbd sensor. """
def __init__(self, sensor_type, sabnzb_client, client_name):
self._name = SENSOR_TYPES[sensor_type][0]
@@ -132,6 +133,7 @@ class SabnzbdSensor(Entity):
def refresh_sabnzbd_data(self):
""" Calls the throttled SABnzbd refresh method. """
if _THROTTLED_REFRESH is not None:
from pysabnzbd import SabnzbdApiException
try:
_THROTTLED_REFRESH()
except SabnzbdApiException:
@@ -1,14 +1,13 @@
"""
homeassistant.components.sensor.swiss_public_transport
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The Swiss public transport sensor will give you the next two departure times
from a given location to another one. This sensor is limited to Switzerland.
Configuration:
To use the Swiss public transport sensor you will need to add something like
the following to your config/configuration.yaml
the following to your configuration.yaml file.
sensor:
platform: swiss_public_transport
@@ -32,7 +31,7 @@ Details for the API : http://transport.opendata.ch
"""
import logging
from datetime import timedelta
from requests import get
import requests
from homeassistant.util import Throttle
import homeassistant.util.dt as dt_util
@@ -54,7 +53,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
try:
for location in [config.get('from', None), config.get('to', None)]:
# transport.opendata.ch doesn't play nice with requests.Session
result = get(_RESOURCE + 'locations?query=%s' % location)
result = requests.get(_RESOURCE + 'locations?query=%s' % location,
timeout=10)
journey.append(result.json()['stations'][0]['name'])
except KeyError:
_LOGGER.exception(
@@ -110,14 +110,14 @@ class PublicTransportData(object):
def update(self):
""" Gets the latest data from opendata.ch. """
response = get(
response = requests.get(
_RESOURCE +
'connections?' +
'from=' + self.start + '&' +
'to=' + self.destination + '&' +
'fields[]=connections/from/departureTimestamp/&' +
'fields[]=connections/')
'fields[]=connections/',
timeout=10)
connections = response.json()['connections'][:2]
try:
@@ -1,13 +1,12 @@
"""
homeassistant.components.sensor.systemmonitor
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Shows system monitor values such as: disk, memory and processor use
Shows system monitor values such as: disk, memory, and processor use.
Configuration:
To use the System monitor sensor you will need to add something like the
following to your config/configuration.yaml
following to your configuration.yaml file.
sensor:
platform: systemmonitor
@@ -60,7 +59,6 @@ arg
Additional details for the type, eg. path, binary name, etc.
"""
import logging
import psutil
import homeassistant.util.dt as dt_util
from homeassistant.helpers.entity import Entity
@@ -121,7 +119,7 @@ class SystemMonitorSensor(Entity):
@property
def name(self):
return self._name
return self._name.rstrip()
@property
def state(self):
@@ -134,6 +132,7 @@ class SystemMonitorSensor(Entity):
# pylint: disable=too-many-branches
def update(self):
import psutil
if self.type == 'disk_use_percent':
self._state = psutil.disk_usage(self.argument).percent
elif self.type == 'disk_use':
+1 -2
View File
@@ -1,8 +1,7 @@
"""
homeassistant.components.sensor.tellstick
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Shows sensor values from tellstick sensors.
Shows sensor values from Tellstick sensors.
Possible config keys:
+5 -5
View File
@@ -4,10 +4,9 @@ homeassistant.components.sensor.temper
Support for getting temperature from TEMPer devices.
Configuration:
To use the temper sensors you will need to add something like the following to
your config/configuration.yaml
Example:
To use the temper sensors you will need to add something like the following to
your configuration.yaml file.
sensor:
platform: temper
@@ -18,8 +17,9 @@ from homeassistant.const import CONF_NAME, DEVICE_DEFAULT_NAME
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['https://github.com/rkabadi/temper-python/archive/' +
'3dbdaf2d87b8db9a3cd6e5585fc704537dd2d09b.zip']
REQUIREMENTS = ['https://github.com/rkabadi/temper-python/archive/'
'3dbdaf2d87b8db9a3cd6e5585fc704537dd2d09b.zip'
'#temperusb==1.2.3']
# pylint: disable=unused-argument
+1 -2
View File
@@ -1,13 +1,12 @@
"""
homeassistant.components.sensor.time_date
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Date and Time service.
Configuration:
To use the Date and Time sensor you will need to add something like the
following to your config/configuration.yaml
following to your configuration.yaml file.
sensor:
platform: time_date
+21 -29
View File
@@ -1,31 +1,30 @@
"""
homeassistant.components.sensor.transmission
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Monitors Transmission BitTorrent client API
Monitors Transmission BitTorrent client API.
Configuration:
To use the Transmission sensor you will need to add something like the
following to your config/configuration.yaml
following to your configuration.yaml file.
sensor:
platform: transmission
name: Transmission
host: 192.168.1.26
port: 9091
username: YOUR_USERNAME
password: YOUR_PASSWORD
monitored_variables:
- type: 'current_status'
- type: 'download_speed'
- type: 'upload_speed'
platform: transmission
name: Transmission
host: 192.168.1.26
port: 9091
username: YOUR_USERNAME
password: YOUR_PASSWORD
monitored_variables:
- 'current_status'
- 'download_speed'
- 'upload_speed'
Variables:
host
*Required
This is the IP address of your Transmission daemon. Example: 192.168.1.32
This is the IP address of your Transmission daemon, e.g. 192.168.1.32
port
*Optional
@@ -33,11 +32,11 @@ The port your Transmission daemon uses, defaults to 9091. Example: 8080
username
*Required
Your Transmission username
Your Transmission username.
password
*Required
Your Transmission password
Your Transmission password.
name
*Optional
@@ -45,16 +44,9 @@ The name to use when displaying this Transmission instance.
monitored_variables
*Required
An array specifying the variables to monitor.
These are the variables for the monitored_variables array:
type
*Required
The variable you wish to monitor, see the configuration example above for a
list of all available variables.
Variables to monitor. See the configuration example above for a
list of all available variables to monitor.
"""
from homeassistant.util import Throttle
from datetime import timedelta
from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD
@@ -81,7 +73,7 @@ _THROTTLED_REFRESH = None
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the sensors. """
""" Sets up the Transmission sensors. """
host = config.get(CONF_HOST)
username = config.get(CONF_USERNAME, None)
password = config.get(CONF_PASSWORD, None)
@@ -110,11 +102,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
dev = []
for variable in config['monitored_variables']:
if variable['type'] not in SENSOR_TYPES:
_LOGGER.error('Sensor type: "%s" does not exist', variable['type'])
if variable not in SENSOR_TYPES:
_LOGGER.error('Sensor type: "%s" does not exist', variable)
else:
dev.append(TransmissionSensor(
variable['type'], transmission_api, name))
variable, transmission_api, name))
add_devices(dev)
+11 -12
View File
@@ -1,13 +1,12 @@
"""
homeassistant.components.sensor.vera
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for Vera sensors.
Configuration:
To use the Vera sensors you will need to add something like the following to
your config/configuration.yaml
your configuration.yaml file.
sensor:
platform: vera
@@ -24,8 +23,7 @@ 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/
running on 80, e.g. http://192.168.1.21:3480/
device_data
@@ -33,7 +31,7 @@ device_data
This contains an array additional device info for your Vera devices. It is not
required and if not specified all sensors 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
as the key for the device within device_data.
These are the variables for the device_data array:
@@ -41,14 +39,12 @@ 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
will be used.
exclude
*Optional
This parameter allows you to exclude the specified device from homeassistant,
it should be set to "true" if you want this device excluded
This parameter allows you to exclude the specified device from Home Assistant,
it should be set to "true" if you want this device excluded.
"""
import logging
from requests.exceptions import RequestException
@@ -58,8 +54,10 @@ from homeassistant.helpers.entity import Entity
from homeassistant.const import (
ATTR_BATTERY_LEVEL, ATTR_TRIPPED, ATTR_ARMED, ATTR_LAST_TRIP_TIME,
TEMP_CELCIUS, TEMP_FAHRENHEIT)
# 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__)
@@ -67,6 +65,7 @@ _LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument
def get_devices(hass, config):
""" Find and return Vera Sensors. """
import pyvera as veraApi
base_url = config.get('vera_controller_url')
if not base_url:
@@ -36,12 +36,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
hasattr(value, 'humidity') and value.humidity
])
sensors.extend([
VerisureAlarm(value)
for value in verisure.get_alarm_status().values()
if verisure.SHOW_ALARM
])
add_devices(sensors)
@@ -103,25 +97,3 @@ class VerisureHygrometer(Entity):
def update(self):
''' update sensor '''
verisure.update()
class VerisureAlarm(Entity):
""" represents a Verisure alarm status within home assistant. """
def __init__(self, alarm_status):
self._id = alarm_status.id
self._device = verisure.MY_PAGES.DEVICE_ALARM
@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 verisure.STATUS[self._device][self._id].label
def update(self):
''' update sensor '''
verisure.update()
+3 -2
View File
@@ -8,8 +8,9 @@ import logging
from homeassistant.helpers.entity import Entity
from homeassistant.const import CONF_ACCESS_TOKEN, STATE_OPEN, STATE_CLOSED
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, discovery_info=None):
+1 -95
View File
@@ -25,10 +25,8 @@ import urllib
import homeassistant.util as util
import homeassistant.util.dt as dt_util
from homeassistant.helpers.event import (
track_point_in_utc_time, track_point_in_time)
from homeassistant.helpers.event import track_point_in_utc_time
from homeassistant.helpers.entity import Entity
from homeassistant.components.scheduler import ServiceEventListener
DEPENDENCIES = []
REQUIREMENTS = ['astral==0.8.1']
@@ -214,95 +212,3 @@ class Sun(Entity):
track_point_in_utc_time(
self.hass, self.point_in_time_listener,
self.next_change + timedelta(seconds=1))
def create_event_listener(schedule, event_listener_data):
""" Create a sun event listener based on the description. """
negative_offset = False
service = event_listener_data['service']
offset_str = event_listener_data['offset']
event = event_listener_data['event']
if offset_str.startswith('-'):
negative_offset = True
offset_str = offset_str[1:]
(hour, minute, second) = [int(x) for x in offset_str.split(':')]
offset = timedelta(hours=hour, minutes=minute, seconds=second)
if event == 'sunset':
return SunsetEventListener(schedule, service, offset, negative_offset)
return SunriseEventListener(schedule, service, offset, negative_offset)
# pylint: disable=too-few-public-methods
class SunEventListener(ServiceEventListener):
""" This is the base class for sun event listeners. """
def __init__(self, schedule, service, offset, negative_offset):
ServiceEventListener.__init__(self, schedule, service)
self.offset = offset
self.negative_offset = negative_offset
def __get_next_time(self, next_event):
"""
Returns when the next time the service should be called.
Taking into account the offset and which days the event should execute.
"""
if self.negative_offset:
next_time = next_event - self.offset
else:
next_time = next_event + self.offset
while next_time < dt_util.now() or \
next_time.weekday() not in self.my_schedule.days:
next_time = next_time + timedelta(days=1)
return next_time
def schedule_next_event(self, hass, next_event):
""" Schedule the event. """
next_time = self.__get_next_time(next_event)
# pylint: disable=unused-argument
def execute(now):
""" Call the execute method. """
self.execute(hass)
track_point_in_time(hass, execute, next_time)
return next_time
# pylint: disable=too-few-public-methods
class SunsetEventListener(SunEventListener):
""" This class is used the call a service when the sun sets. """
def schedule(self, hass):
""" Schedule the event """
next_setting_dt = next_setting(hass)
next_time_dt = self.schedule_next_event(hass, next_setting_dt)
_LOGGER.info(
'SunsetEventListener scheduled for %s, will call service %s.%s',
next_time_dt, self.domain, self.service)
# pylint: disable=too-few-public-methods
class SunriseEventListener(SunEventListener):
""" This class is used the call a service when the sun rises. """
def schedule(self, hass):
""" Schedule the event. """
next_rising_dt = next_rising(hass)
next_time_dt = self.schedule_next_event(hass, next_rising_dt)
_LOGGER.info(
'SunriseEventListener scheduled for %s, will call service %s.%s',
next_time_dt, self.domain, self.service)
+5 -2
View File
@@ -1,11 +1,14 @@
"""
homeassistant.components.switch.arduino
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for switching Arduino pins on and off. So fare only digital pins are
Support for switching Arduino pins on and off. So far only digital pins are
supported.
Configuration:
To use the arduino switch you will need to add something like the following
to your configuration.yaml file.
switch:
platform: arduino
pins:
@@ -62,7 +65,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class ArduinoSwitch(SwitchDevice):
""" Represents an Arduino Switch. """
""" Represents an Arduino switch. """
def __init__(self, name, pin, pin_type):
self._pin = pin
self._name = name or DEVICE_DEFAULT_NAME
+123
View File
@@ -0,0 +1,123 @@
"""
homeassistant.components.switch.arest
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The arest switch can control the digital pins of a device running with the
aREST RESTful framework for Arduino, the ESP8266, and the Raspberry Pi.
Only tested with Arduino boards so far.
Configuration:
To use the arest switch you will need to add something like the following
to your configuration.yaml file.
sensor:
platform: arest
resource: http://IP_ADDRESS
pins:
11:
name: Fan Office
12:
name: Light Desk
Variables:
resource:
*Required
IP address of the device that is exposing an aREST API.
pins:
The number of the digital pin to switch.
These are the variables for the pins array:
name
*Required
The name for the pin that will be used in the frontend.
Details for the API: http://arest.io
"""
import logging
from requests import get, exceptions
from homeassistant.components.switch import SwitchDevice
from homeassistant.const import DEVICE_DEFAULT_NAME
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Get the aREST switches. """
resource = config.get('resource', None)
try:
response = get(resource, timeout=10)
except exceptions.MissingSchema:
_LOGGER.error("Missing resource or schema in configuration. "
"Add http:// to your URL.")
return False
except exceptions.ConnectionError:
_LOGGER.error("No route to device. "
"Please check the IP address in the configuration file.")
return False
dev = []
pins = config.get('pins')
for pinnum, pin in pins.items():
dev.append(ArestSwitch(resource,
response.json()['name'],
pin.get('name'),
pinnum))
add_devices(dev)
class ArestSwitch(SwitchDevice):
""" Implements an aREST switch. """
def __init__(self, resource, location, name, pin):
self._resource = resource
self._name = '{} {}'.format(location.title(), name.title()) \
or DEVICE_DEFAULT_NAME
self._pin = pin
self._state = None
request = get('{}/mode/{}/o'.format(self._resource, self._pin),
timeout=10)
if request.status_code is not 200:
_LOGGER.error("Can't set mode. Is device offline?")
@property
def name(self):
""" The name of the switch. """
return self._name
@property
def is_on(self):
""" True if device is on. """
return self._state
def turn_on(self, **kwargs):
""" Turn the device on. """
request = get('{}/digital/{}/1'.format(self._resource, self._pin),
timeout=10)
if request.status_code == 200:
self._state = True
else:
_LOGGER.error("Can't turn on pin %s at %s. Is device offline?",
self._resource, self._pin)
def turn_off(self, **kwargs):
""" Turn the device off. """
request = get('{}/digital/{}/0'.format(self._resource, self._pin),
timeout=10)
if request.status_code == 200:
self._state = False
else:
_LOGGER.error("Can't turn off pin %s at %s. Is device offline?",
self._resource, self._pin)
def update(self):
""" Gets the latest data from aREST API and updates the state. """
request = get('{}/digital/{}'.format(self._resource, self._pin),
timeout=10)
self._state = request.json()['return_value'] != 0

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