Compare commits

...

285 Commits

Author SHA1 Message Date
Paulus Schoutsen 763a9ce8c6 Merge pull request #1594 from balloob/dev
0.16
2016-03-26 01:02:12 -07:00
Paulus Schoutsen e53adf003c Version bump to 0.16 2016-03-26 01:01:46 -07:00
Paulus Schoutsen 3df946aa9e Merge pull request #1598 from aoakeson/dev
Yamaha Receiver Support
2016-03-26 00:05:05 -07:00
Andrew 2285a6761c Initial Yamaha Receiver Implementation
Text Update

Additional additions and better support for volume, and mute.  Cleanup

Added rxv to requirements_all

Added yamaha.py to .coveragerc

Made uppercase, and removed tabs

Added requirements variable

Added doc string for lint

Removed global variable, and simplified state as per balloobs suggestion

Refactored the component with balloobs suggestions

-Added import in the method
- Only get receiver information on init
- A bit of cleanup

Remove up and down volume

Uneeded as this is handled by set volume instead

Fixed a lint build error

More lint fixes

Removed unused imports

Lint Fixes

Simplified if statement

Minor refactoring since the init calls update.

Fixed lint error

Just variable naming change

Added support for an optional name for the receiver.

Better error handling, a bit of refactoring based on balloobs suggestions

Fixed lint error.

Another lint error fix

Changed raise to return

Disable pylint error handling

Pylint broad exception

Made exception handling in the setup platform instead of the constructor.

Lint error fix

Refactored the way devices are found.  This allows for multiple receivers
2016-03-26 00:47:00 -06:00
Paulus Schoutsen b85753d5da Merge pull request #1613 from robbiet480/media_player_services_yaml
Fill out services.yaml for media_player
2016-03-25 23:45:31 -07:00
Robbie Trencheny ce4933d637 Fill out services.yaml for media_player 2016-03-25 23:38:10 -07:00
Paulus Schoutsen de6fc771cb Merge pull request #1607 from fabaff/sensor-class-rest
Add support for sensor classes
2016-03-25 23:27:21 -07:00
Paulus Schoutsen 7f5109e8f3 Merge pull request #1612 from robbiet480/sonos_play_media
Sonos play_media Support
2016-03-25 23:12:29 -07:00
Robbie Trencheny de68be06dd Misspelling fix 2016-03-25 22:57:28 -07:00
Robbie Trencheny 950cc9e618 Add play_media support to Sonos 2016-03-25 22:57:14 -07:00
Paulus Schoutsen a3d505f45e Merge pull request #1610 from robbiet480/gntp
Add GNTP notifier
2016-03-25 22:09:13 -07:00
Paulus Schoutsen befdecc3b0 Merge pull request #1602 from robbiet480/uber-sensor
Uber sensor
2016-03-25 21:51:03 -07:00
Paulus Schoutsen b92a51c1c3 Merge pull request #1603 from balloob/chore/template-race-condition
Clean up template platforms
2016-03-25 21:48:36 -07:00
Robbie Trencheny 20f8935b86 Clean up a lot of pep8 and syntax errors. 2016-03-25 19:32:49 -07:00
Robbie Trencheny 652c666d14 Add GNTP notifier 2016-03-25 18:39:08 -07:00
Fabian Affolter 9157bd38e8 Update docstrings 2016-03-25 20:35:38 +01:00
Fabian Affolter f0970f4104 Add support for sensor classes 2016-03-25 18:34:58 +01:00
Paulus Schoutsen 41f205e09d Clean up template platforms 2016-03-24 23:22:17 -07:00
Robbie Trencheny 72c40ab826 Add Uber requirements to requirements_all.txt 2016-03-24 22:30:42 -07:00
Robbie Trencheny 01b216f6cb Block uber.py tests 2016-03-24 22:30:22 -07:00
Robbie Trencheny 5b18ea4237 Fix all pylint, flake8 and pydocstyle issues 2016-03-24 22:30:10 -07:00
Paulus Schoutsen 70ce179224 Merge pull request #1551 from w1ll1am23/command_line_assumed
Added assumed state to command_line switch
2016-03-24 21:54:36 -07:00
Robbie Trencheny 0b9699fd4b Uber sensor 2016-03-24 19:48:10 -07:00
Fabian Affolter 8797ea8f37 Merge pull request #1600 from Bart274/pyiclo
upgrade pyicloud version
2016-03-24 17:31:15 +01:00
Bart274 dd691a4684 upgrade pyicloud version 2016-03-24 17:22:44 +01:00
Fabian Affolter b6a4257753 Merge pull request #1599 from fabaff/forecast.io
Upgrade to python-forecastio 1.3.4
2016-03-24 17:10:56 +01:00
Fabian Affolter 40e17da415 Upgrade to python-forecastio 1.3.4 2016-03-24 16:42:25 +01:00
Fabian Affolter fef682b192 Revert "Upgrade pysnmp to 4.3.2"
This reverts commit 38e6f8fdab.
2016-03-24 16:23:57 +01:00
Fabian Affolter 369d234bda Remove wallet 2016-03-24 12:38:18 +01:00
Paulus Schoutsen 5085e337e5 Update frontend 2016-03-24 00:48:21 -07:00
Fabian Affolter 10cb8c0799 Upgrade python-telegram-bot to 3.4 2016-03-24 08:07:59 +01:00
Fabian Affolter 565ae8d30f Upgrade python-mpd2 to 0.5.5 2016-03-24 08:07:06 +01:00
Fabian Affolter 2c770164f2 Upgrade blockchain to 1.3.1 2016-03-24 08:00:05 +01:00
Fabian Affolter 38e6f8fdab Upgrade pysnmp to 4.3.2 2016-03-23 23:57:32 +01:00
Fabian Affolter a0be348f3a Upgrade psutil to 4.1.0 2016-03-23 22:44:05 +01:00
Fabian Affolter f7943d9448 Upgrade python-nmap to 0.6.0 (attempt to fix #1592) 2016-03-23 22:25:35 +01:00
Paulus Schoutsen 890930ea32 Merge pull request #1596 from jaharkes/lms-avoid-discovery-error
Assume we only run one Logitech Media server on a host.
2016-03-23 14:14:50 -07:00
Jan Harkes 2d91dce6d0 Assume we only run one Logitech Media server on a host.
Because the LMS discovery mechanism uses the SlimProto protocol to discover the
presence of a Logitech Media server which operates on port 3483/udp and
3483/tcp. But HA uses a different 'CLI' protocol that is typically on port
9090/tcp to query player state.

However the CLI port number is configurable and if someone runs the CLI on a
different port, and has the server configured in configuration.yaml, we get an
error in the logs when we try to connect to 9090/tcp when we find the server
through discovery. Because of the way local slim player discover the server
using SlimProto we can be fairly certain only a single server will run on a
given IP address so if one is already configured with a user defined port, we
should ignore the discovered one that assumes the default port.
2016-03-23 14:47:29 -04:00
Paulus Schoutsen 154d184247 Merge pull request #1593 from balloob/nest-sensor-fix
Nest sensor - remove broken sensor types
2016-03-23 00:26:07 -07:00
Paulus Schoutsen 015527aa5f Nest sensor - remove broken sensor types 2016-03-22 08:45:44 -07:00
Paulus Schoutsen 5bd004ee38 Merge pull request #1526 from tilutza/dev
Arduino switch: add support for default stat and negate port function…
2016-03-22 08:40:39 -07:00
Paulus Schoutsen 8fe0740b7f Update PULL_REQUEST_TEMPLATE.md 2016-03-22 08:39:41 -07:00
Paulus Schoutsen c22e106d44 Update PULL_REQUEST_TEMPLATE.md 2016-03-22 08:39:24 -07:00
Paulus Schoutsen 4792b9f40c Merge pull request #1583 from Cinntax/pulseaudio-loopback-switch
Pulseaudio loopback switch
2016-03-22 08:30:17 -07:00
Fabian Affolter 0490514d9e Merge pull request #1585 from jaharkes/wemo
Bump pywemo to new version.
2016-03-21 23:50:00 +01:00
Per Sandström fcfa6f2691 Merge pull request #1586 from persandstrom/vsure_ver_bump
bump vsure version
2016-03-21 20:48:56 +01:00
Per Sandström ca788b6e4d bump vsure verisure 2016-03-21 20:39:03 +01:00
Jan Harkes fd7d6a9d53 Bump pywemo to new version. 2016-03-21 10:05:06 -04:00
Dan Cinnamon 6ee3ca8264 Creation of a new platform for the existing switch component. 2016-03-20 23:10:57 -05:00
dtila a8761e1ef8 Add support for having default state on arduino switch and negate functionality
Arduino switch: add support for default stat and negate port functionality

Travis changes

Arduino switch: add support for default stat and negate port functionality
Because Travis was configured to have maxiimum 5 arguments allowed in functions, I changed the function constructor signature too ... Paul, 6 parameters for a function is really ok

Arduino Switch: add default state config and negate functionality
2016-03-20 12:52:06 +02:00
Paulus Schoutsen 557dae7ab3 Fix typo in light docs 2016-03-19 19:44:20 -07:00
Paulus Schoutsen 454688cc17 Merge pull request #1579 from seedzero/dev
ZwaveDimmer turn_on brightness changed from float to integer
2016-03-19 19:33:45 -07:00
seedzero 59dc8da365 ZwaveDimmer turn_on brightness changed from float to integer 2016-03-20 13:14:59 +11:00
Fabian Affolter bb658412c4 Revert "Allow encrypted passwords"
This reverts commit 505b3b198e.
2016-03-19 22:19:08 +01:00
Fabian Affolter 505b3b198e Allow encrypted passwords 2016-03-19 22:15:23 +01:00
Martin Hjelmare 0bf13dbf26 Merge pull request #1573 from MartinHjelmare/service-warnings
Log error for servicecall without required data
2016-03-19 08:45:40 +01:00
MartinHjelmare 4e4b24fcff Log error for servicecall without required data
* Log error for services called without required attributes, in
	media_player, notify and thermostat platforms.
* Add fan property and methods in thermostat demo component.
* Add tests for notify file and thermostat demo component.
* Increase coverage of tests for media_player, notify and thermostat
	platforms.
* Fix some PEP issues, but not all. Tests still have old linting
	errors.
2016-03-19 08:06:57 +01:00
Paulus Schoutsen 72fdd94b29 Merge pull request #1576 from joopert/kodi_log_once
log "unable to fetch kodi data" only once
2016-03-18 15:37:52 -07:00
joopert c6f66de16e log "unable to fetch kodi data" only once 2016-03-18 18:29:50 +01:00
Paulus Schoutsen 41021e63d0 Merge pull request #1574 from joopert/dev
wake on lan platform
2016-03-18 07:21:10 -07:00
joopert 0bbcc81285 wake on lan platform 2016-03-18 15:01:53 +01:00
Paulus Schoutsen 302f32eacd Merge pull request #1553 from florianholzapfel/notify-messagebird
provide sms notifications via messagebird
2016-03-17 22:02:08 -07:00
Paulus Schoutsen c093a2bf54 Merge pull request #1564 from balloob/fix_template_race_condition
Fix remaining race condition in template components.
2016-03-17 21:18:38 -07:00
Paulus Schoutsen 792954adc9 Merge pull request #1566 from jaharkes/squeezebox-discovery
Add discovery for squeezebox (logitech media) servers.
2016-03-17 21:12:13 -07:00
Paulus Schoutsen bb5c80b8f5 Merge pull request #1567 from jaharkes/wemo-color-lights
Update to new pywemo bridge API
2016-03-17 21:11:02 -07:00
Paulus Schoutsen 3f3bfbbb94 Merge pull request #1572 from balloob/update-netdisco
Update netdisco to 0.5.5
2016-03-17 21:08:48 -07:00
Paulus Schoutsen a1f8466754 Merge pull request #1570 from w1ll1am23/wink_availability
Added available method to all wink components
2016-03-17 21:00:42 -07:00
Paulus Schoutsen 8a38ba5954 Update netdisco to 0.5.5 2016-03-17 20:57:58 -07:00
William Scanlon 208a7c9e60 Added available method to all wink components 2016-03-17 15:57:26 -04:00
Jan Harkes 27aba5c834 Update to new pywemo bridge API
Support RGBW and tunable white lights.
2016-03-17 12:27:06 -04:00
William Scanlon a64726e321 Added assumed state to command_line switch 2016-03-17 10:41:26 -04:00
Jan Harkes c2204433bd Add discovery for squeezebox (logitech media) servers. 2016-03-17 09:38:56 -04:00
Greg Dowling fa15d86c2b Merge pull request #1562 from balloob/bump_pywemo_version
Bump pywemo version.
2016-03-17 12:17:42 +00:00
pavoni 4e7160139e Fix race condition in template components. 2016-03-17 12:10:19 +00:00
pavoni 47d5c4f437 Bump pywemo version. 2016-03-17 11:40:25 +00:00
pavoni 179c6efff7 Bump pywemo version. 2016-03-16 23:25:00 +00:00
Florian Holzapfel 6e32d99174 provide update message_bird.py to include the suggested changes 2016-03-16 10:34:06 +01:00
Greg Dowling fd27e4fc7d Merge pull request #1550 from balloob/restructure_vera
Refactor vera into a platform.
2016-03-16 09:28:27 +00:00
pavoni 9fc73fa644 Refactor vera into a platform. 2016-03-16 09:03:56 +00:00
Paulus Schoutsen ba0cbab6cb Merge pull request #1552 from balloob/hue-allow-unreachable
Hue Lights - Allow Unreachable Lights
2016-03-15 18:27:49 -07:00
Paulus Schoutsen 83778a0f55 Merge pull request #1558 from fabaff/login
Log failed login attempts
2016-03-15 18:26:26 -07:00
Fabian Affolter b29eff5ef1 UPdate log messages 2016-03-15 23:52:55 +01:00
Fabian Affolter c865ff852d Log failed login attempts 2016-03-15 23:40:24 +01:00
Fabian Affolter 1f7e0936fa Add missing comments 2016-03-15 19:22:21 +01:00
Florian Holzapfel a855a9bfcd provide sms notifications via messagebird 2016-03-15 18:09:19 +01:00
Jon Maddox 9fe17423ea pass allow_unreachable for callback 2016-03-15 10:37:47 -04:00
Jon Maddox b7b34b280f just pass them in 2016-03-15 10:27:56 -04:00
Jon Maddox 971fe8bc7b style 2016-03-15 10:27:46 -04:00
Jon Maddox 95d5c456d7 ignore unreachable state if opted in 2016-03-15 10:15:29 -04:00
Jon Maddox dd33831cb1 pass allow_unreachable all the way down the line 2016-03-15 10:15:10 -04:00
Jon Maddox 5abff70a87 read in allow_unreachable config value 2016-03-15 10:13:57 -04:00
Paulus Schoutsen d28116f2bf Update frontend 2016-03-14 23:55:52 -07:00
Paulus Schoutsen cde05b91ce Better netgear logging 2016-03-14 21:12:42 -07:00
Paulus Schoutsen 663e4d57d9 Merge pull request #1546 from persandstrom/input_slider
input slider
2016-03-14 20:26:34 -07:00
Paulus Schoutsen cf285e0a0a Merge pull request #1548 from bradsk88/wink063
Upgrading python-wink to 0.6.3
2016-03-14 20:16:55 -07:00
Paulus Schoutsen dd607ea84a Merge pull request #1540 from balloob/slowtests_rfxtrx
Fixed close connection issue with rfxtrx device and update rfxtrx lib
2016-03-14 19:09:15 -07:00
Paulus Schoutsen b4458b7f52 Merge pull request #1538 from balloob/script-allow-template-service
Remove support for old deprecated script call service config
2016-03-14 19:05:34 -07:00
bradsk88 8a6cc49438 Upgrading python-wink to 0.6.3
This corrects a bug where multi-sensors' internal states were rendered null when downloading state updates from the Wink API.
2016-03-14 19:16:54 -06:00
Per Sandström 2bdad928d0 input slider 2016-03-14 22:03:30 +01:00
Paulus Schoutsen 3e3d1ae9de Merge pull request #1542 from balloob/fix_race_condition
Handle Wemo startup race condition.
2016-03-14 07:47:50 -07:00
Paulus Schoutsen fb3586c18f Merge pull request #1541 from balloob/catch_template_exceptions
Catch template automation exception common during startup.
2016-03-14 07:43:59 -07:00
pavoni fe2adff017 Handle startup race condition. 2016-03-14 10:29:12 +00:00
pavoni e5c8dd03e1 Catch exception common during startup. 2016-03-14 10:10:38 +00:00
Daniel d6344d6492 Fixed close connection issue with rfxtrx device and update rfxtrx lib 2016-03-14 08:33:59 +01:00
Paulus Schoutsen 399fda079f Merge pull request #1539 from srcLurker/zwave-heal
Enable openzwave's network heal and soft reset.
2016-03-13 22:42:49 -07:00
Paulus Schoutsen 6bc3e0bb58 Merge pull request #1530 from balloob/content-length
Add content-length header to http resonses
2016-03-13 22:32:59 -07:00
Paulus Schoutsen 54b60c6564 Merge pull request #1531 from balloob/cleanup-automation
Remove support for old deprecated automation config
2016-03-13 22:32:48 -07:00
Paulus Schoutsen 61b387cd0b Script: fix template service calls and remove old config support 2016-03-13 22:29:36 -07:00
Charles Spirakis b7044a79b2 Enable openzwave's network heal and soft reset.
Makes Zwave's network heal and the controller's soft reset
commands available as service calls. They can be used
in automation to help keep the zwave netwrok healthy.

For example:

automation:
 alias: At 2:35am, heal problematic zwave network routes
 trigger:
   platform: time
   after: '2:35:00'
 action:
   service: zwave.heal
2016-03-13 21:48:07 -07:00
Paulus Schoutsen 368668784a Merge pull request #1534 from balloob/allow_entry_passive_zones
Allow entry into passive zones.
2016-03-13 15:58:42 -07:00
Paulus Schoutsen 7099030cd7 Merge pull request #1529 from stefan-jonasson/tellstick-rewrite
Rewrite of the tellstick module. It now uses a common base for all sh…
2016-03-13 15:56:54 -07:00
Stefan Jonasson eb9ed5ccfe Rewrite of the tellstick module. It now uses a common base for all shared functionality.
The rewrite addresses a problem with the tellstick hardware dropping
commands when too many simultaneous calls is being made from HA. Also fixes a bug when the dim level was changed externally.
This breaks previous configurations.

The new config for tellstick is

```yaml
tellstick:
  signal_repetitions: X
```
Lights and Switches are detected automatically.
Sensors work like before because they do not share any functionality with the other devices and they also needs a complete other configuration.
2016-03-13 19:28:42 +01:00
pavoni fc455a1047 Allow entry into passive zones. 2016-03-13 18:01:29 +00:00
Paulus Schoutsen 956f6056f9 Remove support for old deprecated automation config 2016-03-13 00:00:05 -08:00
Paulus Schoutsen f8d2da2ace Add content-length header to http resonses 2016-03-12 23:41:00 -08:00
Paulus Schoutsen 7d23a5f7e4 Merge pull request #1496 from balloob/mqtt-server
Add MQTT server component
2016-03-12 21:53:06 -08:00
Paulus Schoutsen 8386bda4e4 MQTT: Start embedded server if no config given 2016-03-12 21:42:47 -08:00
Paulus Schoutsen 13d7f742a7 Add thread names 2016-03-12 16:54:31 -08:00
Paulus Schoutsen b67964274b Version bump to 0.16.0.dev0 2016-03-12 11:29:42 -08:00
Paulus Schoutsen 6797365d4f Merge pull request #1502 from balloob/dev
0.15
2016-03-12 11:29:30 -08:00
Paulus Schoutsen 2410942fd0 Version bump to 0.15 2016-03-12 11:23:43 -08:00
Paulus Schoutsen 2a75c6fcf6 Update frontend build scripts for pep257 2016-03-12 10:52:48 -08:00
Paulus Schoutsen 159e287959 Update frontend 2016-03-12 10:42:07 -08:00
Paulus Schoutsen a01111ae56 Merge pull request #1457 from fabaff/sensor-classes
Add support for sensor classes
2016-03-11 22:50:32 -08:00
Paulus Schoutsen 4599fd6dae Update .coveragerc 2016-03-11 22:47:39 -08:00
Paulus Schoutsen ddc139b853 Merge pull request #1510 from persandstrom/automation_templating
Use templates in service calls
2016-03-11 21:49:47 -08:00
Paulus Schoutsen 2b6f0586b4 Merge pull request #1524 from turbokongen/dev
Workaround for no off motion sensor: Wenzhou tkd slim sensor 3/4 in 1…
2016-03-11 21:45:40 -08:00
Paulus Schoutsen 9904667a80 Merge pull request #1520 from balloob/mqtt-fixes
Type checks for MQTT config
2016-03-11 21:45:17 -08:00
Paulus Schoutsen 097ded73b1 Merge pull request #1519 from sander76/dev
changed powerview version requirement to solve error in installing
2016-03-11 21:44:11 -08:00
Paulus Schoutsen 5b2093ebc1 Merge pull request #1523 from Bart274/patch-1
add gps_accuracy and battery information to the see function
2016-03-11 16:21:26 -08:00
Fabian Affolter 1fa2f23864 Revert "Upgrade limitlessled to 1.1.0"
This reverts commit 2e2537de2a.
2016-03-11 23:37:32 +01:00
Fabian Affolter 2e2537de2a Upgrade limitlessled to 1.1.0 2016-03-11 23:27:18 +01:00
root 1f8b66511d Workaround for no off motion sensor: Wenzhou tkd slim sensor 3/4 in 1 sensor 2016-03-11 20:51:40 +01:00
Bart274 312d0320c6 Update __init__.py 2016-03-11 14:20:08 +01:00
Bart274 284a1fd08e add gps_accuracy and battery information to the see function
the see function from device_tracker doesn't pass the gps_accuracy and the battery information to the see service, so this information gets lost.
2016-03-11 14:10:04 +01:00
sander 1fbf7afc6a changed the powerview version 2016-03-11 06:44:04 +01:00
Paulus Schoutsen 025713ee41 Type checks for MQTT config 2016-03-10 21:42:17 -08:00
Per Sandström 81e5a852f0 Use templates in service calls 2016-03-10 21:36:05 +01:00
Fabian Affolter 4637a83c29 Add sensor_class to MQTT binary sensor 2016-03-10 20:51:58 +01:00
Fabian Affolter ce82bd7515 Fix formatting 2016-03-10 19:17:36 +01:00
Greg Dowling 534308e461 Merge pull request #1473 from balloob/add_wemo_motion
Add Wemo Motion device as a binary sensor.
2016-03-10 18:13:04 +00:00
pavoni 84bdba28b3 Add Wemo Motion 2016-03-10 17:58:24 +00:00
Fabian Affolter 8af7858415 Merge pull request #1513 from balloob/pep257
Enable Pep257 checking on CI
2016-03-10 09:29:04 +01:00
Paulus Schoutsen 56ef16e858 Add pydocstyle to requirements_test.txt 2016-03-09 23:39:38 -08:00
Paulus Schoutsen b9856a2af5 Fix final pep257 issues 2016-03-09 23:34:38 -08:00
Paulus Schoutsen 14a9b9fe3f Add pep257 checking 2016-03-09 23:34:28 -08:00
Paulus Schoutsen 47c4f66886 Merge pull request #1485 from MartinHjelmare/refactor-scene-reproduce_state
Refactor reproduce_state for scene component
2016-03-09 15:29:10 -08:00
Fabian Affolter 0d9b8a0d06 Fix docstring 2016-03-09 23:49:54 +01:00
MartinHjelmare c56701baaf Refactor reproduce_state for scene component
* Add tests to reach full coverage for helpers/state.py.
* Refactor reproduce_state function in helpers/state.py. Add two dicts,
	as global constants, service_attributes and service_to_state. Use
	these in combination with the dict of services per domain from
	ServiceRegistry, to find the correct service to use in a scene state
	change.
* Use break statement in for loop, to break if service was selected
	to update state, in preference to update state attributes, ie state
	update takes precedence.
* Add ATTR_CODE and ATTR_CODE_FORMAT in const. Import these in
	alarm_control_panel and lock platforms instead of making duplicate
	constants in multiple modules.
* Use ATTR_MEDIA_CONTENT_TYPE and ATTR_MEDIA_CONTENT_ID in media_player
	platform in SERVICE_PLAY_MEDIA and play_media methods, instead of
	'media_type' and 'media_id'.
* Fix PEP257 in modified files.
2016-03-09 18:52:05 +01:00
Fabian Affolter f22a40c3e8 Fix PEP257 issues 2016-03-09 11:15:04 +01:00
Fabian Affolter 986c9c55bf Merge branch 'pep257-tests' into dev 2016-03-09 10:26:49 +01:00
Fabian Affolter 9838697d2b Fix PEP257 issues 2016-03-09 10:25:50 +01:00
Per Sandström d1256d4889 Merge pull request #1508 from persandstrom/verisure_typo_thermometers
verisure configuration value typo fix
2016-03-08 21:42:43 +01:00
Fabian Affolter d6eab03a61 Remove parenthesis 2016-03-08 18:05:01 +01:00
Fabian Affolter b534244e40 Fix PEEP257 issues 2016-03-08 17:55:57 +01:00
Fabian Affolter d784610c52 Merge branch 'pep257-sensor' into dev 2016-03-08 16:47:18 +01:00
Fabian Affolter 8bff97083c Fix PEPE257 issues 2016-03-08 16:46:34 +01:00
Paulus Schoutsen 3e4412d375 Merge pull request #1507 from sander76/dev
change module name to correct brand name "douglas" to "hunterdouglas"
2016-03-08 07:20:43 -08:00
Per Sandström 567727fb3b verisure configuration value typo fix 2016-03-08 14:10:47 +01:00
Fabian Affolter 49ebc6d0b0 Fix PEP257 issues 2016-03-08 13:35:39 +01:00
sander fd5e2d1321 change module name to correct brand name "douglas" to "hunterdouglas" 2016-03-08 11:59:46 +01:00
Fabian Affolter 652f059d6a Fix PEP257 issues 2016-03-08 11:46:32 +01:00
Fabian Affolter cf7c2c492a Fix PEP257 issues 2016-03-08 10:34:33 +01:00
Fabian Affolter 7f1e181c9b Merge branch 'pep257-utils' into dev 2016-03-08 08:34:50 +01:00
Fabian Affolter 08233da8a7 Merge branch 'pep257-thermostats' into dev 2016-03-08 08:11:38 +01:00
Fabian Affolter 5423252a52 Merge branch 'pep257-scene-mqtt' into dev 2016-03-08 08:11:30 +01:00
Fabian Affolter 036cfe4ef6 Merge branch 'pep257-lock' into dev 2016-03-08 08:11:21 +01:00
Fabian Affolter db6212dae3 Merge branch 'pep257-light' into dev 2016-03-08 08:11:12 +01:00
Fabian Affolter 391c6a358a Merge branch 'pep257-garage' into dev 2016-03-08 08:10:25 +01:00
Fabian Affolter 29a651dd92 Merge branch 'pep257-device-tracker' into dev 2016-03-08 08:08:35 +01:00
Paulus Schoutsen 20dd782d9e Rename powerview to douglas_powerview 2016-03-07 22:43:38 -08:00
Paulus Schoutsen 9d76c58bd4 Merge pull request #1490 from sander76/powerview
Powerview
2016-03-07 22:42:20 -08:00
Paulus Schoutsen c21918233b Merge pull request #1506 from fabaff/pep257-camera
PEP257 - Camera
2016-03-07 17:10:21 -08:00
Paulus Schoutsen 87008f23a1 Merge pull request #1505 from fabaff/pep257-binary-sensor
PEP257 - Binary sensor
2016-03-07 17:09:59 -08:00
Paulus Schoutsen ba0a25aef6 Merge pull request #1503 from fabaff/pep257-automation
PEP257 - Automation
2016-03-07 17:09:11 -08:00
Paulus Schoutsen ee728345f6 Merge pull request #1501 from fabaff/pep257-alarm
PEP257 - Alarm control panel
2016-03-07 17:08:39 -08:00
Paulus Schoutsen 2db873322d Update frontend 2016-03-07 15:17:51 -08:00
Paulus Schoutsen 8ecced5fe6 Merge branch 'master' into dev
Conflicts:
	homeassistant/components/wemo.py
	homeassistant/const.py
2016-03-07 15:15:57 -08:00
Fabian Affolter 897b5c668f Fix PEP257 issues 2016-03-08 00:06:04 +01:00
Fabian Affolter 4f536ac63d Fix PEP257 issues 2016-03-07 23:39:52 +01:00
Fabian Affolter 876978d64a Fix PEP257 issues 2016-03-07 23:20:48 +01:00
Fabian Affolter 91731c7234 Fix PEP257 issues 2016-03-07 23:01:34 +01:00
Fabian Affolter 1b8b2acb51 Fix PEP257 issues 2016-03-07 22:50:56 +01:00
Fabian Affolter 095dd70391 Fix PEP257 issues 2016-03-07 22:44:35 +01:00
Fabian Affolter 6879e39d6c Fix PEP257 issues 2016-03-07 22:13:18 +01:00
Fabian Affolter 7e8e91ef3c Fix PEP257 issues 2016-03-07 22:08:21 +01:00
Fabian Affolter ac43d23bd7 Fix PEP257 issues 2016-03-07 21:22:21 +01:00
Fabian Affolter cc7a4d545e Fix PEP257 issues 2016-03-07 21:18:53 +01:00
sander 40be9f4e0c Powerview hub implementation 2016-03-07 21:07:38 +01:00
Fabian Affolter cd5b5c55e2 Fix PEP257 issues 2016-03-07 20:29:54 +01:00
Fabian Affolter 3a4250133a Fix PEP257 issues 2016-03-07 20:21:43 +01:00
Fabian Affolter 7035af6634 Fix PEP257 issues 2016-03-07 20:21:08 +01:00
Fabian Affolter fb7bd1bfe1 Fix PEP257 issues 2016-03-07 20:20:07 +01:00
Fabian Affolter f6bc1a4575 Revert change (FIXME) 2016-03-07 20:18:48 +01:00
Fabian Affolter 5ba9ac6465 Fix PEP257 issues 2016-03-07 20:18:48 +01:00
Paulus Schoutsen cfd65e48cb Merge pull request #1495 from balloob/mqtt-light
MQTT Light: fix brightness issues
2016-03-07 10:20:36 -08:00
Fabian Affolter b8a40457ee Update docstrings to match PEP257 2016-03-07 18:50:30 +01:00
Fabian Affolter 7ff9aecd4e Update docstrings to match PEP257 2016-03-07 18:50:30 +01:00
Fabian Affolter 032f06e015 Modify docstrings to match PEP257 2016-03-07 18:50:30 +01:00
Fabian Affolter 6ac9210919 Modify docstrings to match PEP257 2016-03-07 18:50:30 +01:00
Paulus Schoutsen b47f1c3a96 MQTT Light: fix brightness issues 2016-03-07 08:54:08 -08:00
Paulus Schoutsen 5222c19b4c Fix TP-Link get auth token 2016-03-07 08:29:05 -08:00
Fabian Affolter 1e97d31711 Modify docstrings to match PEP257 2016-03-07 16:45:21 +01:00
Paulus Schoutsen 18f48191d9 Merge pull request #1482 from balloob/history-significant-states
History: update significant states
2016-03-06 21:45:48 -08:00
Paulus Schoutsen abc7243bc8 Merge pull request #1484 from balloob/device-tracker-remove-old-conf-format
Device tracker remove old conf format
2016-03-06 21:41:59 -08:00
Paulus Schoutsen 4c64be5cfa Merge pull request #1488 from balloob/template_component_fix
Fix noisy error on template component startup.
2016-03-06 13:47:42 -08:00
Paulus Schoutsen 199375bd25 Merge pull request #1489 from balloob/zone_fix
Force zone params to be float
2016-03-06 13:47:11 -08:00
Paulus Schoutsen 69bbfe5372 Merge pull request #1491 from persandstrom/verisure_disable_password_lock
After maintenance false password errors are expected
2016-03-06 13:43:07 -08:00
Per Sandström d2c94da938 After maintenance false password errors are expected 2016-03-06 22:09:15 +01:00
pavoni 4e3c8a8697 Fix noisy error on startup. Make callbacks code consistent. 2016-03-06 19:19:07 +00:00
pavoni d75d08e97c Force float for zone parameters. 2016-03-06 19:07:30 +00:00
Paulus Schoutsen cd30f2de0d Device Sun Light Trigger: Clean up 2016-03-05 20:15:33 -08:00
Paulus Schoutsen bdad69307a Group: fix thread safety 2016-03-05 19:55:05 -08:00
Paulus Schoutsen b16c17fae5 Merge pull request #1480 from balloob/fix-slow-tests
Fix slow tests
2016-03-05 19:05:47 -08:00
Paulus Schoutsen 91ee889527 Merge pull request #1434 from nunofgs/enhancement/add-archer-c7-support
Add ArcherC7 150427 support
2016-03-05 18:48:59 -08:00
Nuno Sousa 7b45001879 Add ArcherC7 150427 support 2016-03-05 23:15:48 +00:00
Paulus Schoutsen fb46eff5f8 Device tracker: remove support for old config 2016-03-05 11:43:23 -08:00
Paulus Schoutsen 9701be9e9c History: ignore scripts that we cannot cancel 2016-03-05 10:28:48 -08:00
Paulus Schoutsen 605a572c86 History: Ignore insignificant domains 2016-03-05 09:49:04 -08:00
Paulus Schoutsen 1159360281 Skip rfxtrx tests because of speed reasons 2016-03-05 09:28:47 -08:00
Paulus Schoutsen 41214fd082 TCP test to work offline 2016-03-05 09:27:22 -08:00
Paulus Schoutsen d30fb7f04a Merge pull request #1476 from stefan-jonasson/assumed-state
Add assumed state for Tellstick devices.
2016-03-05 01:41:55 -08:00
Stefan Jonasson e57eca517b Add assumed state for Tellstick devices.
Tellstick device states are always assumed so this should be set to true.
2016-03-05 10:24:22 +01:00
Paulus Schoutsen 931dc27300 Merge pull request #1465 from bestlibre/influxdb_blacklist
Possibility to blacklist events for influxdb
2016-03-05 00:57:26 -08:00
bestlibre d7094b996a Use global variable and merge two if in one 2016-03-04 23:32:24 +01:00
Greg Dowling 9654f6b4e4 Merge pull request #1474 from HydrelioxGitHub/Fix-Owntracks-GPS-Accuracy
Add GPS accuracy check for a location update or an event update
2016-03-04 20:07:37 +00:00
Hydreliox c04555d7b1 Add GPS accuracy check for a location update or an event update
Add tests related to this
Great thanks to @pavoni for his support on this fix
2016-03-04 20:19:50 +01:00
Paulus Schoutsen e755731588 Merge pull request #1471 from balloob/cast-update
Update pychromecast 0.7.2
2016-03-03 22:30:43 -08:00
Paulus Schoutsen a687a3decb Update pychromecast 0.7.2 2016-03-03 21:59:27 -08:00
Paulus Schoutsen 88c834fdd3 Merge pull request #1466 from balloob/tests_rfxtrx_device
Tests rfxtrx device
2016-03-03 09:11:50 -08:00
Daniel Hoyer Iversen feb1a15489 update rfxtrx lib to ver 0.5 2016-03-03 17:47:50 +01:00
bestlibre 4d5e9f2931 Possibility to blacklist events
Possibility to blacklist some events based on the entity_id
2016-03-03 17:02:41 +01:00
Paulus Schoutsen 1e3199eb8c Merge pull request #1458 from jaharkes/track-wemos-by-serial
Track wemos by serial
2016-03-02 23:10:13 -08:00
Paulus Schoutsen 17493660df Merge pull request #1463 from kk7ds/snmp-track-macless-things
Fix SNMP device_tracker results without MAC addresses
2016-03-02 14:58:31 -08:00
Dan Smith e59d6b7da0 Fix SNMP device_tracker results without MAC addresses
@llauren reported that his router returns empty MAC addresses for things
on different subnets, which causes heartache for the HA snmp device tracker.
2016-03-02 11:43:08 -08:00
Daniel 2fd0b28c53 Added tests for RFxtrx device, updated rfxtrx to ver 0.5, fixed bug in setting brightness for rfxtrx light 2016-03-02 20:36:41 +01:00
Jan Harkes bbd3a43585 Update netdisco to 0.5.4 2016-03-01 23:25:18 -05:00
Dan Smith a02840379d Track WeMo devices by serial number
This makes us track WeMo devices by serial number instead of URL. Per
@jaharkes' suggestion, tracking via URL could potentially be a problem
if a device changes to another IP and then appears to be discovered again.
Since we can't track based on mac (for static-configured devices), we
can use the serial number exposed to make sure we're always looking at
a list of unique devices.
2016-03-01 10:49:38 -08:00
Paulus Schoutsen d641cd5eef Merge pull request #1452 from balloob/rfxtrx_tests
Added tests for rfxtrx sensor
2016-03-01 08:07:21 -08:00
Daniel 6c518c74ec Added tests for rfxtrx sensor 2016-03-01 10:14:02 +01:00
Paulus Schoutsen fcbeafc6db Update Dockerfile: link Z-Wave config 2016-02-29 23:15:14 -08:00
Paulus Schoutsen 4370f5170b Merge pull request #1374 from sander76/scene_with_platforms
scene with platforms
2016-02-29 23:00:03 -08:00
Paulus Schoutsen 49c6484f72 Merge pull request #1433 from fabaff/mqtt-lock
Add MQTT lock support
2016-02-29 22:58:55 -08:00
Paulus Schoutsen ec642cbeff Merge pull request #1450 from kk7ds/mfi-tls-verify
Give mFi options to control TLS usage
2016-02-29 19:18:06 -08:00
Martin Hjelmare 2823d8f0d8 Merge pull request #1451 from MartinHjelmare/mysensors-assumed-state
Add assumed_state to mysensors switch and light
2016-03-01 03:37:40 +01:00
MartinHjelmare bfa579f8b2 Add assumed_state to mysensors switch and light
* Return true for assumed_state if optimistic config setting is true.
* Fix PEP257
2016-03-01 03:09:55 +01:00
Dan Smith be04ebf9ed Give mFi options to control TLS usage
The default configuration of the mFi controller generates self-signed
certificates which are valid for short periods of time, and which are
regnerated on start or as needed. This makes requests mad. Since most
people will be using the self-signed certificates anyway, add options
to let them choose non-TLS, or unverified connections if they want/need.

This bumps the mficlient requirement to 0.3.0 for proper handling of
verify=False.
2016-02-29 17:37:41 -08:00
Paulus Schoutsen 87e23e7d73 Merge pull request #1440 from stefan-jonasson/fix-manufacturer-id
Fixed the z-wave sensor workarounds for empty manufacturer ids
2016-02-29 08:01:41 -08:00
Stefan Jonasson c586111c8c Fixed the z-wave sensor workarounds for empty manufacturer ids 2016-02-29 11:05:11 +01:00
Paulus Schoutsen e92c77a113 Update frontend 2016-02-28 21:39:42 -08:00
Paulus Schoutsen 2d2b26ff1a Update Dockerfile 2016-02-28 21:08:11 -08:00
Paulus Schoutsen eed5b1e41b Merge pull request #1437 from balloob/update-docker-zwave
Docker + Z-Wave <3
2016-02-28 20:47:49 -08:00
Paulus Schoutsen 029094e549 Docker + Z-Wave <3 2016-02-28 20:46:16 -08:00
Paulus Schoutsen 28d80e6e2d Merge pull request #1436 from partofthething/zwave-unfork
updated build script for unforking of python-openzwave
2016-02-28 20:24:56 -08:00
Paulus Schoutsen 5449d53e07 Update netdisco to 0.5.3 2016-02-28 15:32:11 -08:00
ntouran 112b85877f updated build script for unforking of python-openzwave 2016-02-28 12:40:17 -08:00
sander 0f6ec9b7ac Added your suggestions.
Looking at your code suggestion below I am not sure exactly how other people might want to put in lists. (But I am missing a more general overview of the code)

 ```
if not isinstance(scene_config,list):
  scene_config=[scene_config]
```

But it is there !
And changed "config" to "states" !
2016-02-28 21:00:51 +01:00
Fabian Affolter a82931022c Add MQTT lock support 2016-02-28 18:25:28 +01:00
Paulus Schoutsen 1974e2cfbe Merge pull request #1430 from ggruner/dev
Add support for NetAtmo rain gauge
2016-02-28 09:19:41 -08:00
Paulus Schoutsen e0ba33300a Merge pull request #1431 from kk7ds/fix-wemo-fix
Gracefully handle having no wemo config section
2016-02-28 08:20:04 -08:00
Dan Smith 566c143ee3 Gracefully handle having no wemo config section
I broke this with my fix, where I assumed that we'd always have a wemo
config section if we're running the wemo code. However, if we're fully
auto-detected, we might not.
2016-02-28 07:58:44 -08:00
Fabian Affolter 014b11c61d Upgrade to psutil 4.0.0 2016-02-28 16:34:06 +01:00
Fabian Affolter b70a455a13 Upgrade py-cpuinfo to 0.2.3 2016-02-28 16:16:03 +01:00
Fabian Affolter 9da8679dbd Add link to docs 2016-02-28 15:35:20 +01:00
Gregor Gruener c14ca9983c Add support for NetAtmo rain gauge
Example Config:
...
modules:
    Bedroom:
      - temperature
    LivingRoom:
      - temperature
    Outside:
      - temperature
    Garden:
      - rain
      - sum_rain_1
      - sum_rain_24

The three new parameters stand for:
- rain # Estimated rainfall for today in "mm"
- sum_rain_1 # Rainfall in the last hour in "mm"
- sum_rain_24 # Rainfall in "mm" from 00:00am - 23:59pm
2016-02-28 12:24:45 +01:00
Fabian Affolter 9c4fe6e7fe Modify docstrings to match PEP257 2016-02-28 12:03:47 +01:00
sander a8edcfd315 removed logger.
I guess I should add some error checking, but I'd like you to okay this code first.
2016-02-28 10:29:21 +01:00
sander 0193454064 removed logger.
I guess I should add some error checking, but I'd like you to okay this code first.
2016-02-28 10:28:34 +01:00
sander 94633b3795 1. added platform per scene entry.
2. changed homeassistant scene setup_platform method to work with 1.
2016-02-28 10:24:02 +01:00
Paulus Schoutsen 49982ac83c Update PULL_REQUEST_TEMPLATE.md 2016-02-28 00:59:28 -08:00
Paulus Schoutsen 72940da874 Update frontend 2016-02-28 00:42:10 -08:00
Paulus Schoutsen 5acf995d71 Merge pull request #1429 from balloob/fix-script-can_cancel
Fix incorrectly setting can_cancel on scripts
2016-02-28 00:38:54 -08:00
Paulus Schoutsen f623a332cf Fix incorrectly setting can_cancel on scripts 2016-02-27 23:11:51 -08:00
Paulus Schoutsen f5dd41e019 Merge pull request #1425 from kk7ds/fix-static-wemos
Fix static configured wemo devices
2016-02-27 21:02:57 -08:00
Paulus Schoutsen 97edc39d0a Merge pull request #1426 from shaftoe/python_constant
Move hardcoded required Python version into homeassistant.const
2016-02-27 21:00:28 -08:00
Dan Smith 2b4be33a3d Fix wemo known device tracking
The wemo component was tracking devices by mac to avoid adding duplicates,
but this means we'd never be able to load more than one static wemo, since
they all have mac=None.

This changes the code to track by url, which has to be unique per wemo
anyway, and which we also have for even static wemos.
2016-02-27 20:56:02 -08:00
Alexander Fortin ac69db8133 Move hardcoded required Python version into homeassistant.const 2016-02-28 05:41:03 +01:00
Dan Smith 5a126736e4 Fix static configured wemo devices
The new wemo code was pulling 'static' from the global config instead of
the wemo component config.
2016-02-27 20:11:32 -08:00
Martin Hjelmare 8e9c557a2c Merge pull request #1423 from MartinHjelmare/fix-mysensors-sensor-types
Fix mysensors sensor types
2016-02-28 02:16:32 +01:00
MartinHjelmare d12dfc33a5 Fix mysensors sensor types
* Remove sprinkler and water leak from sensor types. These two are
  supported by binary sensor.
2016-02-28 01:58:22 +01:00
Paulus Schoutsen 1be90081ef Version bump to 0.15.0.dev0 2016-02-27 16:22:21 -08:00
sander 019af42e94 removed unnecessary properties. 2016-02-23 09:42:34 +01:00
sander 88e7967a7d - removed update method
- removed failing tests from test_scene
2016-02-22 22:01:05 +01:00
sander e37c232bf6 flake8 correction 2016-02-22 20:05:42 +01:00
sander fb449cbc82 first commit 2016-02-22 19:53:55 +01:00
452 changed files with 10175 additions and 7242 deletions
+12 -5
View File
@@ -26,11 +26,13 @@ omit =
homeassistant/components/modbus.py
homeassistant/components/*/modbus.py
homeassistant/components/tellstick.py
homeassistant/components/*/tellstick.py
homeassistant/components/tellduslive.py
homeassistant/components/*/tellduslive.py
homeassistant/components/vera.py
homeassistant/components/*/vera.py
homeassistant/components/ecobee.py
@@ -51,9 +53,6 @@ omit =
homeassistant/components/zwave.py
homeassistant/components/*/zwave.py
homeassistant/components/rfxtrx.py
homeassistant/components/*/rfxtrx.py
homeassistant/components/mysensors.py
homeassistant/components/*/mysensors.py
@@ -108,9 +107,12 @@ omit =
homeassistant/components/media_player/snapcast.py
homeassistant/components/media_player/sonos.py
homeassistant/components/media_player/squeezebox.py
homeassistant/components/media_player/yamaha.py
homeassistant/components/notify/free_mobile.py
homeassistant/components/notify/googlevoice.py
homeassistant/components/notify/gntp.py
homeassistant/components/notify/instapush.py
homeassistant/components/notify/message_bird.py
homeassistant/components/notify/nma.py
homeassistant/components/notify/pushbullet.py
homeassistant/components/notify/pushetta.py
@@ -123,6 +125,7 @@ omit =
homeassistant/components/notify/telegram.py
homeassistant/components/notify/twitter.py
homeassistant/components/notify/xmpp.py
homeassistant/components/scene/hunterdouglas_powerview.py
homeassistant/components/sensor/arest.py
homeassistant/components/sensor/bitcoin.py
homeassistant/components/sensor/cpuspeed.py
@@ -139,8 +142,8 @@ omit =
homeassistant/components/sensor/openweathermap.py
homeassistant/components/sensor/rest.py
homeassistant/components/sensor/sabnzbd.py
homeassistant/components/sensor/steam_online.py
homeassistant/components/sensor/speedtest.py
homeassistant/components/sensor/steam_online.py
homeassistant/components/sensor/swiss_public_transport.py
homeassistant/components/sensor/systemmonitor.py
homeassistant/components/sensor/temper.py
@@ -148,20 +151,24 @@ omit =
homeassistant/components/sensor/torque.py
homeassistant/components/sensor/transmission.py
homeassistant/components/sensor/twitch.py
homeassistant/components/sensor/uber.py
homeassistant/components/sensor/worldclock.py
homeassistant/components/switch/arest.py
homeassistant/components/switch/edimax.py
homeassistant/components/switch/dlink.py
homeassistant/components/switch/edimax.py
homeassistant/components/switch/hikvisioncam.py
homeassistant/components/switch/mystrom.py
homeassistant/components/switch/orvibo.py
homeassistant/components/switch/pulseaudio_loopback.py
homeassistant/components/switch/rest.py
homeassistant/components/switch/transmission.py
homeassistant/components/switch/wake_on_lan.py
homeassistant/components/thermostat/heatmiser.py
homeassistant/components/thermostat/homematic.py
homeassistant/components/thermostat/proliphix.py
homeassistant/components/thermostat/radiotherm.py
[report]
# Regexes for lines to exclude from consideration
exclude_lines =
+10 -10
View File
@@ -2,6 +2,7 @@
**Related issue (if applicable):** #
**Example entry for `configuration.yaml` (if applicable):**
```yaml
@@ -9,17 +10,16 @@
**Checklist:**
- [ ] Local tests with `tox` ran successfully.
- [ ] No CI failures. **Your PR cannot be merged unless CI is green!**
- [ ] [Fork is up to date][fork] and was rebased on the `dev` branch before creating the PR.
- If code communicates with devices:
- [ ] 3rd party library/libraries for communication is/are added as dependencies via the `REQUIREMENTS` variable ([example][ex-requir]).
- [ ] 3rd party dependencies are imported inside functions that use them ([example][ex-import]).
- [ ] `requirements_all.txt` is up-to-date, `script/gen_requirements_all.py` ran and the updated file is included in the PR.
If code communicates with devices:
- [ ] Local tests with `tox` run successfully. **Your PR cannot be merged unless tests pass**
- [ ] New dependencies have been added to the `REQUIREMENTS` variable ([example][ex-requir]).
- [ ] New dependencies are only imported inside functions that use them ([example][ex-import]).
- [ ] New dependencies have been added to `requirements_all.txt` by running `script/gen_requirements_all.py`.
- [ ] New files were added to `.coveragerc`.
- If the code does not depend on external Python module:
- [ ] Tests to verify that the code works are included.
- [ ] [Commits will be squashed][squash] when the PR is ready to be merged.
If the code does not interact with devices:
- [ ] Local tests with `tox` run successfully. **Your PR cannot be merged unless tests pass**
- [ ] Tests have been added to verify that the new code works.
[fork]: http://stackoverflow.com/a/7244456
[squash]: https://github.com/ginatrapani/todo.txt-android/wiki/Squash-All-Commits-Related-to-a-Single-Issue-into-a-Single-Commit
+4 -4
View File
@@ -71,10 +71,10 @@ When you are done with development and ready to commit your changes, run `build_
To test your code before submission, used the `tox` tool.
```shell
> pip install -U tox
> tox
```
```bash
> pip install -U tox
> tox
```
This will run unit tests against python 3.4 and 3.5 (if both are available locally), as well as run a set of tests which validate `pep8` and `pylint` style of the code.
+5 -7
View File
@@ -6,19 +6,17 @@ VOLUME /config
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
RUN pip3 install --no-cache-dir colorlog
RUN pip3 install --no-cache-dir colorlog cython
# For the nmap tracker
RUN apt-get update && \
apt-get install -y --no-install-recommends nmap net-tools && \
apt-get install -y --no-install-recommends nmap net-tools cython3 libudev-dev sudo && \
apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
COPY script/build_python_openzwave script/build_python_openzwave
RUN apt-get update && \
apt-get install -y cython3 libudev-dev && \
apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \
pip3 install "cython<0.23" && \
script/build_python_openzwave
RUN script/build_python_openzwave && \
mkdir -p /usr/local/share/python-openzwave && \
ln -sf /usr/src/app/build/python-openzwave/openzwave/config /usr/local/share/python-openzwave/config
COPY requirements_all.txt requirements_all.txt
RUN pip3 install --no-cache-dir -r requirements_all.txt
+7 -5
View File
@@ -101,9 +101,10 @@ def track_devices(hass, entity_id, old_state, new_state):
@track_time_change(hour=7, minute=0, second=0)
def wake_up(hass, now):
"""
Turn it on in the morning (7 AM) if there are people home and
it is not already on.
"""Turn light on in the morning.
Turn the light on at 7 AM if there are people home and it is not already
on.
"""
if not TARGET_ID:
return
@@ -126,8 +127,9 @@ def all_lights_off(hass, entity_id, old_state, new_state):
@service(DOMAIN, SERVICE_FLASH)
def flash_service(hass, call):
"""
Service that will turn the target off for 10 seconds if on and vice versa.
"""Service that will toggle the target.
Set the light to off for 10 seconds if on and vice versa.
"""
if not TARGET_ID:
return
-1
View File
@@ -20,7 +20,6 @@ DEPENDENCIES = []
def setup(hass, config):
"""Setup our skeleton component."""
# States are in the format DOMAIN.OBJECT_ID.
hass.states.set('hello_world.Hello_World', 'Works!')
+1
View File
@@ -0,0 +1 @@
"""Init file for Home Assistant."""
+33 -27
View File
@@ -1,4 +1,4 @@
""" Starts home assistant. """
"""Starts home assistant."""
from __future__ import print_function
import argparse
@@ -12,21 +12,26 @@ from multiprocessing import Process
import homeassistant.config as config_util
from homeassistant import bootstrap
from homeassistant.const import (
EVENT_HOMEASSISTANT_START, RESTART_EXIT_CODE, __version__)
__version__,
EVENT_HOMEASSISTANT_START,
REQUIRED_PYTHON_VER,
RESTART_EXIT_CODE,
)
def validate_python():
""" Validate we're running the right Python version. """
"""Validate we're running the right Python version."""
major, minor = sys.version_info[:2]
req_major, req_minor = REQUIRED_PYTHON_VER
if major < 3 or (major == 3 and minor < 4):
print("Home Assistant requires atleast Python 3.4")
if major < req_major or (major == req_major and minor < req_minor):
print("Home Assistant requires at least Python {}.{}".format(
req_major, req_minor))
sys.exit(1)
def ensure_config_path(config_dir):
""" Validates configuration directory. """
"""Validate the configuration directory."""
lib_dir = os.path.join(config_dir, 'lib')
# Test if configuration directory exists
@@ -54,7 +59,7 @@ def ensure_config_path(config_dir):
def ensure_config_file(config_dir):
""" Ensure configuration file exists. """
"""Ensure configuration file exists."""
config_path = config_util.ensure_config_exists(config_dir)
if config_path is None:
@@ -65,7 +70,7 @@ def ensure_config_file(config_dir):
def get_arguments():
""" Get parsed passed in arguments. """
"""Get parsed passed in arguments."""
parser = argparse.ArgumentParser(
description="Home Assistant: Observe, Control, Automate.")
parser.add_argument('--version', action='version', version=__version__)
@@ -130,25 +135,25 @@ def get_arguments():
def daemonize():
""" Move current process to daemon process """
# create first fork
"""Move current process to daemon process."""
# Create first fork
pid = os.fork()
if pid > 0:
sys.exit(0)
# decouple fork
# Decouple fork
os.setsid()
os.umask(0)
# create second fork
# Create second fork
pid = os.fork()
if pid > 0:
sys.exit(0)
def check_pid(pid_file):
""" Check that HA is not already running """
# check pid file
"""Check that HA is not already running."""
# Check pid file
try:
pid = int(open(pid_file, 'r').readline())
except IOError:
@@ -165,7 +170,7 @@ def check_pid(pid_file):
def write_pid(pid_file):
""" Create PID File """
"""Create a PID File."""
pid = os.getpid()
try:
open(pid_file, 'w').write(str(pid))
@@ -175,7 +180,7 @@ def write_pid(pid_file):
def install_osx():
""" Setup to run via launchd on OS X """
"""Setup to run via launchd on OS X."""
with os.popen('which hass') as inp:
hass_path = inp.read().strip()
@@ -207,7 +212,7 @@ def install_osx():
def uninstall_osx():
""" Unload from launchd on OS X """
"""Unload from launchd on OS X."""
path = os.path.expanduser("~/Library/LaunchAgents/org.homeassistant.plist")
os.popen('launchctl unload ' + path)
@@ -215,9 +220,10 @@ def uninstall_osx():
def setup_and_run_hass(config_dir, args, top_process=False):
"""
Setup HASS and run. Block until stopped. Will assume it is running in a
subprocess unless top_process is set to true.
"""Setup HASS and run.
Block until stopped. Will assume it is running in a subprocess unless
top_process is set to true.
"""
if args.demo_mode:
config = {
@@ -237,7 +243,7 @@ def setup_and_run_hass(config_dir, args, top_process=False):
if args.open_ui:
def open_browser(event):
""" Open the webinterface in a browser. """
"""Open the webinterface in a browser."""
if hass.config.api is not None:
import webbrowser
webbrowser.open(hass.config.api.base_url)
@@ -253,12 +259,12 @@ def setup_and_run_hass(config_dir, args, top_process=False):
def run_hass_process(hass_proc):
""" Runs a child hass process. Returns True if it should be restarted. """
"""Run a child hass process. Returns True if it should be restarted."""
requested_stop = threading.Event()
hass_proc.daemon = True
def request_stop(*args):
""" request hass stop, *args is for signal handler callback """
"""Request hass stop, *args is for signal handler callback."""
requested_stop.set()
hass_proc.terminate()
@@ -283,7 +289,7 @@ def run_hass_process(hass_proc):
def main():
""" Starts Home Assistant. """
"""Start Home Assistant."""
validate_python()
args = get_arguments()
@@ -291,7 +297,7 @@ def main():
config_dir = os.path.join(os.getcwd(), args.config)
ensure_config_path(config_dir)
# os x launchd functions
# OS X launchd functions
if args.install_osx:
install_osx()
return 0
@@ -305,7 +311,7 @@ def main():
install_osx()
return 0
# daemon functions
# Daemon functions
if args.pid_file:
check_pid(args.pid_file)
if args.daemon:
+14 -16
View File
@@ -34,8 +34,7 @@ ERROR_LOG_FILENAME = 'home-assistant.log'
def setup_component(hass, domain, config=None):
""" Setup a component and all its dependencies. """
"""Setup a component and all its dependencies."""
if domain in hass.config.components:
return True
@@ -58,7 +57,7 @@ def setup_component(hass, domain, config=None):
def _handle_requirements(hass, component, name):
""" Installs requirements for component. """
"""Install the requirements for a component."""
if hass.config.skip_pip or not hasattr(component, 'REQUIREMENTS'):
return True
@@ -126,7 +125,7 @@ def _setup_component(hass, domain, config):
def prepare_setup_platform(hass, config, domain, platform_name):
""" Loads a platform and makes sure dependencies are setup. """
"""Load a platform and makes sure dependencies are setup."""
_ensure_loader_prepared(hass)
platform_path = PLATFORM_FORMAT.format(domain, platform_name)
@@ -158,7 +157,7 @@ def prepare_setup_platform(hass, config, domain, platform_name):
def mount_local_lib_path(config_dir):
""" Add local library to Python Path """
"""Add local library to Python Path."""
sys.path.insert(0, os.path.join(config_dir, 'lib'))
@@ -166,8 +165,7 @@ def mount_local_lib_path(config_dir):
def from_config_dict(config, hass=None, config_dir=None, enable_log=True,
verbose=False, daemon=False, skip_pip=False,
log_rotate_days=None):
"""
Tries to configure Home Assistant from a config dict.
"""Try to configure Home Assistant from a config dict.
Dynamically loads required components and its dependencies.
"""
@@ -209,7 +207,7 @@ def from_config_dict(config, hass=None, config_dir=None, enable_log=True,
_LOGGER.info('Home Assistant core initialized')
# give event decorators access to HASS
# Give event decorators access to HASS
event_decorators.HASS = hass
service.HASS = hass
@@ -222,9 +220,9 @@ def from_config_dict(config, hass=None, config_dir=None, enable_log=True,
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,
"""Read the configuration file and try to start all the functionality.
Will add functionality to 'hass' parameter if given,
instantiates a new Home Assistant object if 'hass' is not given.
"""
if hass is None:
@@ -244,7 +242,7 @@ def from_config_file(config_path, hass=None, verbose=False, daemon=False,
def enable_logging(hass, verbose=False, daemon=False, log_rotate_days=None):
""" Setup the logging for home assistant. """
"""Setup the logging."""
if not daemon:
logging.basicConfig(level=logging.INFO)
fmt = ("%(log_color)s%(asctime)s %(levelname)s (%(threadName)s) "
@@ -297,7 +295,7 @@ def enable_logging(hass, verbose=False, daemon=False, log_rotate_days=None):
def process_ha_config_upgrade(hass):
""" Upgrade config if necessary. """
"""Upgrade config if necessary."""
version_path = hass.config.path('.HA_VERSION')
try:
@@ -322,11 +320,11 @@ def process_ha_config_upgrade(hass):
def process_ha_core_config(hass, config):
""" Processes the [homeassistant] section from the config. """
"""Process the [homeassistant] section from the config."""
hac = hass.config
def set_time_zone(time_zone_str):
""" Helper method to set time zone in HA. """
"""Helper method to set time zone."""
if time_zone_str is None:
return
@@ -397,6 +395,6 @@ def process_ha_core_config(hass, config):
def _ensure_loader_prepared(hass):
""" Ensure Home Assistant loader is prepared. """
"""Ensure Home Assistant loader is prepared."""
if not loader.PREPARED:
loader.prepare(hass)
+13 -17
View File
@@ -1,16 +1,11 @@
"""
homeassistant.components
~~~~~~~~~~~~~~~~~~~~~~~~
This package contains components that can be plugged into Home Assistant.
Component design guidelines:
Each component defines a constant DOMAIN that is equal to its filename.
Each component that tracks states should create state entity names in the
format "<DOMAIN>.<OBJECT_ID>".
Each component should publish services only under its own domain.
- Each component defines a constant DOMAIN that is equal to its filename.
- Each component that tracks states should create state entity names in the
format "<DOMAIN>.<OBJECT_ID>".
- Each component should publish services only under its own domain.
"""
import itertools as it
import logging
@@ -26,8 +21,10 @@ _LOGGER = logging.getLogger(__name__)
def is_on(hass, entity_id=None):
""" Loads up the module to call the is_on method.
If there is no entity id given we will check all. """
"""Load up the module to call the is_on method.
If there is no entity id given we will check all.
"""
if entity_id:
group = get_component('group')
@@ -53,7 +50,7 @@ def is_on(hass, entity_id=None):
def turn_on(hass, entity_id=None, **service_data):
""" Turns specified entity on if possible. """
"""Turn specified entity on if possible."""
if entity_id is not None:
service_data[ATTR_ENTITY_ID] = entity_id
@@ -61,7 +58,7 @@ def turn_on(hass, entity_id=None, **service_data):
def turn_off(hass, entity_id=None, **service_data):
""" Turns specified entity off. """
"""Turn specified entity off."""
if entity_id is not None:
service_data[ATTR_ENTITY_ID] = entity_id
@@ -69,7 +66,7 @@ def turn_off(hass, entity_id=None, **service_data):
def toggle(hass, entity_id=None, **service_data):
""" Toggles specified entity. """
"""Toggle specified entity."""
if entity_id is not None:
service_data[ATTR_ENTITY_ID] = entity_id
@@ -77,10 +74,9 @@ def toggle(hass, entity_id=None, **service_data):
def setup(hass, config):
""" Setup general services related to homeassistant. """
"""Setup general services related to Home Assistant."""
def handle_turn_service(service):
""" Method to handle calls to homeassistant.turn_on/off. """
"""Method to handle calls to homeassistant.turn_on/off."""
entity_ids = extract_entity_ids(hass, service)
# Generic turn on/off method requires entity id
@@ -1,14 +1,15 @@
"""
homeassistant.components.alarm_control_panel
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Component to interface with a alarm control panel.
Component to interface with an alarm control panel.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel/
"""
import logging
import os
from homeassistant.components import verisure
from homeassistant.const import (
ATTR_ENTITY_ID, SERVICE_ALARM_TRIGGER,
ATTR_CODE, ATTR_CODE_FORMAT, ATTR_ENTITY_ID, SERVICE_ALARM_TRIGGER,
SERVICE_ALARM_DISARM, SERVICE_ALARM_ARM_HOME, SERVICE_ALARM_ARM_AWAY)
from homeassistant.config import load_yaml_config_file
from homeassistant.helpers.entity import Entity
@@ -31,9 +32,6 @@ SERVICE_TO_METHOD = {
SERVICE_ALARM_TRIGGER: 'alarm_trigger'
}
ATTR_CODE = 'code'
ATTR_CODE_FORMAT = 'code_format'
ATTR_TO_PROPERTY = [
ATTR_CODE,
ATTR_CODE_FORMAT
@@ -41,7 +39,7 @@ ATTR_TO_PROPERTY = [
def setup(hass, config):
""" Track states and offer events for sensors. """
"""Track states and offer events for sensors."""
component = EntityComponent(
logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL,
DISCOVERY_PLATFORMS)
@@ -49,7 +47,7 @@ def setup(hass, config):
component.setup(config)
def alarm_service_handler(service):
""" Maps services to methods on Alarm. """
"""Map services to methods on Alarm."""
target_alarms = component.extract_from_service(service)
if ATTR_CODE not in service.data:
@@ -75,7 +73,7 @@ def setup(hass, config):
def alarm_disarm(hass, code=None, entity_id=None):
""" Send the alarm the command for disarm. """
"""Send the alarm the command for disarm."""
data = {}
if code:
data[ATTR_CODE] = code
@@ -86,7 +84,7 @@ def alarm_disarm(hass, code=None, entity_id=None):
def alarm_arm_home(hass, code=None, entity_id=None):
""" Send the alarm the command for arm home. """
"""Send the alarm the command for arm home."""
data = {}
if code:
data[ATTR_CODE] = code
@@ -97,7 +95,7 @@ def alarm_arm_home(hass, code=None, entity_id=None):
def alarm_arm_away(hass, code=None, entity_id=None):
""" Send the alarm the command for arm away. """
"""Send the alarm the command for arm away."""
data = {}
if code:
data[ATTR_CODE] = code
@@ -108,7 +106,7 @@ def alarm_arm_away(hass, code=None, entity_id=None):
def alarm_trigger(hass, code=None, entity_id=None):
""" Send the alarm the command for trigger. """
"""Send the alarm the command for trigger."""
data = {}
if code:
data[ATTR_CODE] = code
@@ -120,33 +118,33 @@ def alarm_trigger(hass, code=None, entity_id=None):
# pylint: disable=no-self-use
class AlarmControlPanel(Entity):
""" ABC for alarm control devices. """
"""An abstract class for alarm control devices."""
@property
def code_format(self):
""" regex for code format or None if no code is required. """
"""Regex for code format or None if no code is required."""
return None
def alarm_disarm(self, code=None):
""" Send disarm command. """
"""Send disarm command."""
raise NotImplementedError()
def alarm_arm_home(self, code=None):
""" Send arm home command. """
"""Send arm home command."""
raise NotImplementedError()
def alarm_arm_away(self, code=None):
""" Send arm away command. """
"""Send arm away command."""
raise NotImplementedError()
def alarm_trigger(self, code=None):
""" Send alarm trigger command. """
"""Send alarm trigger command."""
raise NotImplementedError()
@property
def state_attributes(self):
""" Return the state attributes. """
"""Return the state attributes."""
state_attr = {
ATTR_CODE_FORMAT: self.code_format,
}
}
return state_attr
@@ -1,7 +1,5 @@
"""
homeassistant.components.alarm_control_panel.alarmdotcom
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Interfaces with Verisure alarm control panel.
Interfaces with Alarm.com alarm control panels.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.alarmdotcom/
@@ -23,8 +21,7 @@ DEFAULT_NAME = 'Alarm.com'
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Setup an Alarm.com control panel. """
"""Setup an Alarm.com control panel."""
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
@@ -42,9 +39,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
# pylint: disable=too-many-arguments, too-many-instance-attributes
# pylint: disable=abstract-method
class AlarmDotCom(alarm.AlarmControlPanel):
""" Represents a Alarm.com status. """
"""Represent an Alarm.com status."""
def __init__(self, hass, name, code, username, password):
"""Initialize the Alarm.com status."""
from pyalarmdotcom.pyalarmdotcom import Alarmdotcom
self._alarm = Alarmdotcom(username, password, timeout=10)
self._hass = hass
@@ -55,22 +53,22 @@ class AlarmDotCom(alarm.AlarmControlPanel):
@property
def should_poll(self):
""" No polling needed. """
"""No polling needed."""
return True
@property
def name(self):
""" Returns the name of the device. """
"""Return the name of the alarm."""
return self._name
@property
def code_format(self):
""" One or more characters if code is defined. """
"""One or more characters if code is defined."""
return None if self._code is None else '.+'
@property
def state(self):
""" Returns the state of the device. """
"""Return the state of the device."""
if self._alarm.state == 'Disarmed':
return STATE_ALARM_DISARMED
elif self._alarm.state == 'Armed Stay':
@@ -81,7 +79,7 @@ class AlarmDotCom(alarm.AlarmControlPanel):
return STATE_UNKNOWN
def alarm_disarm(self, code=None):
""" Send disarm command. """
"""Send disarm command."""
if not self._validate_code(code, 'arming home'):
return
from pyalarmdotcom.pyalarmdotcom import Alarmdotcom
@@ -90,7 +88,7 @@ class AlarmDotCom(alarm.AlarmControlPanel):
_alarm.disarm()
def alarm_arm_home(self, code=None):
""" Send arm home command. """
"""Send arm home command."""
if not self._validate_code(code, 'arming home'):
return
from pyalarmdotcom.pyalarmdotcom import Alarmdotcom
@@ -99,7 +97,7 @@ class AlarmDotCom(alarm.AlarmControlPanel):
_alarm.arm_stay()
def alarm_arm_away(self, code=None):
""" Send arm away command. """
"""Send arm away command."""
if not self._validate_code(code, 'arming home'):
return
from pyalarmdotcom.pyalarmdotcom import Alarmdotcom
@@ -108,7 +106,7 @@ class AlarmDotCom(alarm.AlarmControlPanel):
_alarm.arm_away()
def _validate_code(self, code, state):
""" Validate given code. """
"""Validate given code."""
check = self._code is None or code == self._code
if not check:
_LOGGER.warning('Wrong code entered for %s', state)
@@ -1,6 +1,4 @@
"""
homeassistant.components.alarm_control_panel.manual
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for manual alarms.
For more details about this platform, please refer to the documentation at
@@ -24,8 +22,7 @@ DEFAULT_TRIGGER_TIME = 120
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the manual alarm platform. """
"""Setup the manual alarm platform."""
add_devices([ManualAlarm(
hass,
config.get('name', DEFAULT_ALARM_NAME),
@@ -47,6 +44,7 @@ class ManualAlarm(alarm.AlarmControlPanel):
"""
def __init__(self, hass, name, code, pending_time, trigger_time):
"""Initalize the manual alarm panel."""
self._state = STATE_ALARM_DISARMED
self._hass = hass
self._name = name
@@ -57,17 +55,17 @@ class ManualAlarm(alarm.AlarmControlPanel):
@property
def should_poll(self):
""" No polling needed. """
"""No polling needed."""
return False
@property
def name(self):
""" Returns the name of the device. """
"""Return the name of the device."""
return self._name
@property
def state(self):
""" Returns the state of the device. """
"""Return the state of the device."""
if self._state in (STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_AWAY) and \
self._pending_time and self._state_ts + self._pending_time > \
@@ -85,11 +83,11 @@ class ManualAlarm(alarm.AlarmControlPanel):
@property
def code_format(self):
""" One or more characters. """
"""One or more characters."""
return None if self._code is None else '.+'
def alarm_disarm(self, code=None):
""" Send disarm command. """
"""Send disarm command."""
if not self._validate_code(code, STATE_ALARM_DISARMED):
return
@@ -98,7 +96,7 @@ class ManualAlarm(alarm.AlarmControlPanel):
self.update_ha_state()
def alarm_arm_home(self, code=None):
""" Send arm home command. """
"""Send arm home command."""
if not self._validate_code(code, STATE_ALARM_ARMED_HOME):
return
@@ -112,7 +110,7 @@ class ManualAlarm(alarm.AlarmControlPanel):
self._state_ts + self._pending_time)
def alarm_arm_away(self, code=None):
""" Send arm away command. """
"""Send arm away command."""
if not self._validate_code(code, STATE_ALARM_ARMED_AWAY):
return
@@ -126,7 +124,7 @@ class ManualAlarm(alarm.AlarmControlPanel):
self._state_ts + self._pending_time)
def alarm_trigger(self, code=None):
""" Send alarm trigger command. No code needed. """
"""Send alarm trigger command. No code needed."""
self._state = STATE_ALARM_TRIGGERED
self._state_ts = dt_util.utcnow()
self.update_ha_state()
@@ -141,7 +139,7 @@ class ManualAlarm(alarm.AlarmControlPanel):
self._state_ts + self._pending_time + self._trigger_time)
def _validate_code(self, code, state):
""" Validate given code. """
"""Validate given code."""
check = self._code is None or code == self._code
if not check:
_LOGGER.warning('Invalid code given for %s', state)
@@ -1,6 +1,4 @@
"""
homeassistant.components.alarm_control_panel.mqtt
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This platform enables the possibility to control a MQTT alarm.
For more details about this platform, please refer to the documentation at
@@ -26,8 +24,7 @@ DEPENDENCIES = ['mqtt']
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the MQTT platform. """
"""Setup the MQTT platform."""
if config.get('state_topic') is None:
_LOGGER.error("Missing required variable: state_topic")
return False
@@ -51,10 +48,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
# pylint: disable=too-many-arguments, too-many-instance-attributes
# pylint: disable=abstract-method
class MqttAlarm(alarm.AlarmControlPanel):
""" represents a MQTT alarm status within home assistant. """
"""Represent a MQTT alarm status."""
def __init__(self, hass, name, state_topic, command_topic, qos,
payload_disarm, payload_arm_home, payload_arm_away, code):
"""Initalize the MQTT alarm panel."""
self._state = STATE_UNKNOWN
self._hass = hass
self._name = name
@@ -67,7 +65,7 @@ class MqttAlarm(alarm.AlarmControlPanel):
self._code = str(code) if code else None
def message_received(topic, payload, qos):
""" A new MQTT message has been received. """
"""A new MQTT message has been received."""
if payload not in (STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_AWAY, STATE_ALARM_PENDING,
STATE_ALARM_TRIGGERED):
@@ -80,47 +78,47 @@ class MqttAlarm(alarm.AlarmControlPanel):
@property
def should_poll(self):
""" No polling needed """
"""No polling needed."""
return False
@property
def name(self):
""" Returns the name of the device. """
"""Return the name of the device."""
return self._name
@property
def state(self):
""" Returns the state of the device. """
"""Return the state of the device."""
return self._state
@property
def code_format(self):
""" One or more characters if code is defined """
"""One or more characters if code is defined."""
return None if self._code is None else '.+'
def alarm_disarm(self, code=None):
""" Send disarm command. """
"""Send disarm command."""
if not self._validate_code(code, 'disarming'):
return
mqtt.publish(self.hass, self._command_topic,
self._payload_disarm, self._qos)
def alarm_arm_home(self, code=None):
""" Send arm home command. """
"""Send arm home command."""
if not self._validate_code(code, 'arming home'):
return
mqtt.publish(self.hass, self._command_topic,
self._payload_arm_home, self._qos)
def alarm_arm_away(self, code=None):
""" Send arm away command. """
"""Send arm away command."""
if not self._validate_code(code, 'arming away'):
return
mqtt.publish(self.hass, self._command_topic,
self._payload_arm_away, self._qos)
def _validate_code(self, code, state):
""" Validate given code. """
"""Validate given code."""
check = self._code is None or code == self._code
if not check:
_LOGGER.warning('Wrong code entered for %s', state)
@@ -1,6 +1,4 @@
"""
homeassistant.components.alarm_control_panel.nx584
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for NX584 alarm control panels.
For more details about this platform, please refer to the documentation at
@@ -16,12 +14,11 @@ from homeassistant.const import (
STATE_UNKNOWN)
REQUIREMENTS = ['pynx584==0.2']
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Setup nx584. """
"""Setup nx584 platform."""
host = config.get('host', 'localhost:5007')
try:
@@ -32,8 +29,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class NX584Alarm(alarm.AlarmControlPanel):
""" NX584-based alarm panel. """
"""Represents the NX584-based alarm panel."""
def __init__(self, hass, host, name):
"""Initalize the nx584 alarm panel."""
from nx584 import client
self._hass = hass
self._host = host
@@ -46,22 +45,22 @@ class NX584Alarm(alarm.AlarmControlPanel):
@property
def should_poll(self):
""" Polling needed. """
"""Polling needed."""
return True
@property
def name(self):
""" Returns the name of the device. """
"""Return the name of the device."""
return self._name
@property
def code_format(self):
""" Characters if code is defined. """
"""The characters if code is defined."""
return '[0-9]{4}([0-9]{2})?'
@property
def state(self):
""" Returns the state of the device. """
"""Return the state of the device."""
try:
part = self._alarm.list_partitions()[0]
zones = self._alarm.list_zones()
@@ -90,17 +89,17 @@ class NX584Alarm(alarm.AlarmControlPanel):
return STATE_ALARM_ARMED_AWAY
def alarm_disarm(self, code=None):
""" Send disarm command. """
"""Send disarm command."""
self._alarm.disarm(code)
def alarm_arm_home(self, code=None):
""" Send arm home command. """
"""Send arm home command."""
self._alarm.arm('home')
def alarm_arm_away(self, code=None):
""" Send arm away command. """
"""Send arm away command."""
self._alarm.arm('auto')
def alarm_trigger(self, code=None):
""" Alarm trigger command. """
"""Alarm trigger command."""
raise NotImplementedError()
@@ -1,10 +1,8 @@
"""
homeassistant.components.alarm_control_panel.verisure
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Interfaces with Verisure alarm control panel.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/verisure/
https://home-assistant.io/components/alarm_control_panel.verisure/
"""
import logging
@@ -19,8 +17,7 @@ _LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the Verisure platform. """
"""Setup the Verisure platform."""
alarms = []
if int(hub.config.get('alarm', '1')):
hub.update_alarms()
@@ -33,30 +30,31 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
# pylint: disable=abstract-method
class VerisureAlarm(alarm.AlarmControlPanel):
""" Represents a Verisure alarm status. """
"""Represent a Verisure alarm status."""
def __init__(self, device_id):
"""Initalize the Verisure alarm panel."""
self._id = device_id
self._state = STATE_UNKNOWN
self._digits = int(hub.config.get('code_digits', '4'))
@property
def name(self):
""" Returns the name of the device. """
"""Return the name of the device."""
return 'Alarm {}'.format(self._id)
@property
def state(self):
""" Returns the state of the device. """
"""Return the state of the device."""
return self._state
@property
def code_format(self):
""" code format as regex """
"""The code format as regex."""
return '^\\d{%s}$' % self._digits
def update(self):
""" Update alarm status """
"""Update alarm status."""
hub.update_alarms()
if hub.alarm_status[self._id].status == 'unarmed':
@@ -71,21 +69,21 @@ class VerisureAlarm(alarm.AlarmControlPanel):
hub.alarm_status[self._id].status)
def alarm_disarm(self, code=None):
""" Send disarm command. """
"""Send disarm command."""
hub.my_pages.alarm.set(code, 'DISARMED')
_LOGGER.info('verisure alarm disarming')
hub.my_pages.alarm.wait_while_pending()
self.update()
def alarm_arm_home(self, code=None):
""" Send arm home command. """
"""Send arm home command."""
hub.my_pages.alarm.set(code, 'ARMED_HOME')
_LOGGER.info('verisure alarm arming home')
hub.my_pages.alarm.wait_while_pending()
self.update()
def alarm_arm_away(self, code=None):
""" Send arm away command. """
"""Send arm away command."""
hub.my_pages.alarm.set(code, 'ARMED_AWAY')
_LOGGER.info('verisure alarm arming away')
hub.my_pages.alarm.wait_while_pending()
+9 -6
View File
@@ -97,21 +97,24 @@ def _handle_alexa(handler, path_match, data):
class SpeechType(enum.Enum):
"""Alexa speech types."""
"""The Alexa speech types."""
plaintext = "PlainText"
ssml = "SSML"
class CardType(enum.Enum):
"""Alexa card types."""
"""The Alexa card types."""
simple = "Simple"
link_account = "LinkAccount"
class AlexaResponse(object):
"""Helps generating the response for Alexa."""
"""Help generating the response for Alexa."""
def __init__(self, hass, intent=None):
"""Initialize the response."""
self.hass = hass
self.speech = None
self.card = None
@@ -125,7 +128,7 @@ class AlexaResponse(object):
self.variables = {}
def add_card(self, card_type, title, content):
""" Add a card to the response. """
"""Add a card to the response."""
assert self.card is None
card = {
@@ -141,7 +144,7 @@ class AlexaResponse(object):
self.card = card
def add_speech(self, speech_type, text):
""" Add speech to the response. """
"""Add speech to the response."""
assert self.speech is None
key = 'ssml' if speech_type == SpeechType.ssml else 'text'
@@ -163,7 +166,7 @@ class AlexaResponse(object):
}
def as_dict(self):
"""Returns response in an Alexa valid dict."""
"""Return response in an Alexa valid dict."""
response = {
'shouldEndSession': self.should_end_session
}
+11 -13
View File
@@ -1,8 +1,5 @@
"""
homeassistant.components.apcupsd
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Sets up and provides access to the status output of APCUPSd via its Network
Information Server (NIS).
Support for status output of APCUPSd via its Network Information Server (NIS).
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/apcupsd/
@@ -34,7 +31,7 @@ _LOGGER = logging.getLogger(__name__)
def setup(hass, config):
""" Use config values to set up a function enabling status retrieval. """
"""Use config values to set up a function enabling status retrieval."""
global DATA
host = config[DOMAIN].get(CONF_HOST, DEFAULT_HOST)
@@ -54,11 +51,14 @@ def setup(hass, config):
class APCUPSdData(object):
"""Stores the data retrieved from APCUPSd.
For each entity to use, acts as the single point responsible for fetching
updates from the server.
"""
Stores the data retrieved from APCUPSd for each entity to use, acts as the
single point responsible for fetching updates from the server.
"""
def __init__(self, host, port):
"""Initialize the data oject."""
from apcaccess import status
self._host = host
self._port = port
@@ -68,17 +68,15 @@ class APCUPSdData(object):
@property
def status(self):
""" Get latest update if throttle allows. Return status. """
"""Get latest update if throttle allows. Return status."""
self.update()
return self._status
def _get_status(self):
""" Get the status from APCUPSd and parse it into a dict. """
"""Get the status from APCUPSd and parse it into a dict."""
return self._parse(self._get(host=self._host, port=self._port))
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self, **kwargs):
"""
Fetch the latest status from APCUPSd and store it in self._status.
"""
"""Fetch the latest status from APCUPSd."""
self._status = self._get_status()
+19 -20
View File
@@ -34,7 +34,6 @@ _LOGGER = logging.getLogger(__name__)
def setup(hass, config):
"""Register the API with the HTTP interface."""
# /api - for validation purposes
hass.http.register_path('GET', URL_API, _handle_get_api)
@@ -84,11 +83,13 @@ def setup(hass, config):
hass.http.register_path(
'GET', URL_API_COMPONENTS, _handle_get_api_components)
# /error_log
hass.http.register_path('GET', URL_API_ERROR_LOG,
_handle_get_api_error_log)
hass.http.register_path('POST', URL_API_LOG_OUT, _handle_post_api_log_out)
# /template
hass.http.register_path('POST', URL_API_TEMPLATE,
_handle_post_api_template)
@@ -96,7 +97,7 @@ def setup(hass, config):
def _handle_get_api(handler, path_match, data):
"""Renders the debug interface."""
"""Render the debug interface."""
handler.write_json_message("API running.")
@@ -114,7 +115,7 @@ def _handle_get_api_stream(handler, path_match, data):
restrict = restrict.split(',')
def write_message(payload):
"""Writes a message to the output."""
"""Write a message to the output."""
with write_lock:
msg = "data: {}\n\n".format(payload)
@@ -127,7 +128,7 @@ def _handle_get_api_stream(handler, path_match, data):
block.set()
def forward_events(event):
"""Forwards events to the open request."""
"""Forward events to the open request."""
nonlocal gracefully_closed
if block.is_set() or event.event_type == EVENT_TIME_CHANGED:
@@ -171,17 +172,17 @@ def _handle_get_api_stream(handler, path_match, data):
def _handle_get_api_config(handler, path_match, data):
"""Returns the Home Assistant configuration."""
"""Return the Home Assistant configuration."""
handler.write_json(handler.server.hass.config.as_dict())
def _handle_get_api_states(handler, path_match, data):
"""Returns a dict containing all entity ids and their state."""
"""Return a dict containing all entity ids and their state."""
handler.write_json(handler.server.hass.states.all())
def _handle_get_api_states_entity(handler, path_match, data):
"""Returns the state of a specific entity."""
"""Return the state of a specific entity."""
entity_id = path_match.group('entity_id')
state = handler.server.hass.states.get(entity_id)
@@ -193,7 +194,7 @@ def _handle_get_api_states_entity(handler, path_match, data):
def _handle_post_state_entity(handler, path_match, data):
"""Handles updating the state of an entity.
"""Handle updating the state of an entity.
This handles the following paths:
/api/states/<entity_id>
@@ -240,15 +241,14 @@ def _handle_delete_state_entity(handler, path_match, data):
def _handle_get_api_events(handler, path_match, data):
"""Handles getting overview of event listeners."""
"""Handle getting overview of event listeners."""
handler.write_json(events_json(handler.server.hass))
def _handle_api_post_events_event(handler, path_match, event_data):
"""Handles firing of an event.
"""Handle firing of an event.
This handles the following paths:
/api/events/<event_type>
This handles the following paths: /api/events/<event_type>
Events from /api are threated as remote events.
"""
@@ -276,16 +276,15 @@ def _handle_api_post_events_event(handler, path_match, event_data):
def _handle_get_api_services(handler, path_match, data):
"""Handles getting overview of services."""
"""Handle getting overview of services."""
handler.write_json(services_json(handler.server.hass))
# pylint: disable=invalid-name
def _handle_post_api_services_domain_service(handler, path_match, data):
"""Handles calling a service.
"""Handle calling a service.
This handles the following paths:
/api/services/<domain>/<service>
This handles the following paths: /api/services/<domain>/<service>
"""
domain = path_match.group('domain')
service = path_match.group('service')
@@ -298,7 +297,7 @@ def _handle_post_api_services_domain_service(handler, path_match, data):
# pylint: disable=invalid-name
def _handle_post_api_event_forward(handler, path_match, data):
"""Handles adding an event forwarding target."""
"""Handle adding an event forwarding target."""
try:
host = data['host']
api_password = data['api_password']
@@ -331,7 +330,7 @@ def _handle_post_api_event_forward(handler, path_match, data):
def _handle_delete_api_event_forward(handler, path_match, data):
"""Handles deleting an event forwarding target."""
"""Handle deleting an event forwarding target."""
try:
host = data['host']
except KeyError:
@@ -354,12 +353,12 @@ def _handle_delete_api_event_forward(handler, path_match, data):
def _handle_get_api_components(handler, path_match, data):
"""Returns all the loaded components."""
"""Return all the loaded components."""
handler.write_json(handler.server.hass.config.components)
def _handle_get_api_error_log(handler, path_match, data):
"""Returns the logged errors for this session."""
"""Return the logged errors for this session."""
handler.write_file(handler.server.hass.config.path(ERROR_LOG_FILENAME),
False)
+14 -17
View File
@@ -1,8 +1,5 @@
"""
components.arduino
~~~~~~~~~~~~~~~~~~
Arduino component that connects to a directly attached Arduino board which
runs with the Firmata firmware.
Support for Arduino boards running with the Firmata firmware.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/arduino/
@@ -20,8 +17,7 @@ _LOGGER = logging.getLogger(__name__)
def setup(hass, config):
""" Setup the Arduino component. """
"""Setup the Arduino component."""
if not validate_config(config,
{DOMAIN: ['port']},
_LOGGER):
@@ -40,11 +36,11 @@ def setup(hass, config):
return False
def stop_arduino(event):
""" Stop the Arduino service. """
"""Stop the Arduino service."""
BOARD.disconnect()
def start_arduino(event):
""" Start the Arduino service. """
"""Start the Arduino service."""
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_arduino)
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_arduino)
@@ -53,15 +49,16 @@ def setup(hass, config):
class ArduinoBoard(object):
""" Represents an Arduino board. """
"""Representation of an Arduino board."""
def __init__(self, port):
"""Initialize the board."""
from PyMata.pymata import PyMata
self._port = port
self._board = PyMata(self._port, verbose=False)
def set_mode(self, pin, direction, mode):
""" Sets the mode and the direction of a given pin. """
"""Set the mode and the direction of a given pin."""
if mode == 'analog' and direction == 'in':
self._board.set_pin_mode(pin,
self._board.INPUT,
@@ -84,31 +81,31 @@ class ArduinoBoard(object):
self._board.PWM)
def get_analog_inputs(self):
""" Get the values from the pins. """
"""Get the values from the pins."""
self._board.capability_query()
return self._board.get_analog_response_table()
def set_digital_out_high(self, pin):
""" Sets a given digital pin to high. """
"""Set a given digital pin to high."""
self._board.digital_write(pin, 1)
def set_digital_out_low(self, pin):
""" Sets a given digital pin to low. """
"""Set a given digital pin to low."""
self._board.digital_write(pin, 0)
def get_digital_in(self, pin):
""" Gets the value from a given digital pin. """
"""Get the value from a given digital pin."""
self._board.digital_read(pin)
def get_analog_in(self, pin):
""" Gets the value from a given analog pin. """
"""Get the value from a given analog pin."""
self._board.analog_read(pin)
def get_firmata(self):
""" Return the version of the Firmata firmware. """
"""Return the version of the Firmata firmware."""
return self._board.get_firmata_version()
def disconnect(self):
""" Disconnects the board and closes the serial connection. """
"""Disconnect the board and close the serial connection."""
self._board.reset()
self._board.close()
+25 -74
View File
@@ -1,7 +1,5 @@
"""
homeassistant.components.automation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Allows to setup simple automation rules via the config file.
Allow to setup simple automation rules via the config file.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/automation/
@@ -11,14 +9,16 @@ import logging
from homeassistant.bootstrap import prepare_setup_platform
from homeassistant.const import CONF_PLATFORM
from homeassistant.components import logbook
from homeassistant.helpers.service import call_from_config
from homeassistant.helpers import extract_domain_configs
from homeassistant.helpers.service import (call_from_config,
validate_service_call)
DOMAIN = 'automation'
DEPENDENCIES = ['group']
CONF_ALIAS = 'alias'
CONF_SERVICE = 'service'
CONF_CONDITION = 'condition'
CONF_ACTION = 'action'
@@ -35,37 +35,23 @@ _LOGGER = logging.getLogger(__name__)
def setup(hass, config):
""" Sets up automation. """
config_key = DOMAIN
found = 1
"""Setup the automation."""
for config_key in extract_domain_configs(config, DOMAIN):
conf = config[config_key]
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)
if not isinstance(conf, list):
conf = [conf]
for list_no, config_block in enumerate(conf):
name = config_block.get(CONF_ALIAS, "{}, {}".format(config_key,
list_no))
_setup_automation(hass, config_block, name, config)
# 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)
# any scalar value is incorrect
else:
_LOGGER.error('Error in config in section %s.', config_key)
found += 1
config_key = "{} {}".format(DOMAIN, found)
return True
def _setup_automation(hass, config_block, name, config):
""" Setup one instance of automation """
"""Setup one instance of automation."""
action = _get_action(hass, config_block.get(CONF_ACTION, {}), name)
if action is None:
@@ -83,14 +69,14 @@ def _setup_automation(hass, config_block, name, config):
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 an action based on a configuration."""
validation_error = validate_service_call(config)
if validation_error:
_LOGGER.error(validation_error)
return None
def action():
""" Action to be executed. """
"""Action to be executed."""
_LOGGER.info('Executing %s', name)
logbook.log_entry(hass, name, 'has been triggered', DOMAIN)
@@ -99,43 +85,8 @@ def _get_action(hass, config, name):
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/')
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. """
"""Process if checks."""
cond_type = p_config.get(CONF_CONDITION_TYPE,
DEFAULT_CONDITION_TYPE).lower()
@@ -165,12 +116,12 @@ def _process_if(hass, config, p_config, action):
if cond_type == CONDITION_TYPE_AND:
def if_action():
""" AND all conditions. """
"""AND all conditions."""
if all(check() for check in checks):
action()
else:
def if_action():
""" OR all conditions. """
"""OR all conditions."""
if any(check() for check in checks):
action()
@@ -178,7 +129,7 @@ def _process_if(hass, config, p_config, action):
def _process_trigger(hass, config, trigger_configs, name, action):
""" Setup triggers. """
"""Setup the triggers."""
if isinstance(trigger_configs, dict):
trigger_configs = [trigger_configs]
@@ -195,7 +146,7 @@ def _process_trigger(hass, config, trigger_configs, name, action):
def _resolve_platform(method, hass, config, platform):
""" Find automation platform. """
"""Find the automation platform."""
if platform is None:
return None
platform = prepare_setup_platform(hass, config, DOMAIN, platform)
+3 -5
View File
@@ -1,7 +1,5 @@
"""
homeassistant.components.automation.event
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Offers event listening automation rules.
Offer event listening automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#event-trigger
@@ -15,7 +13,7 @@ _LOGGER = logging.getLogger(__name__)
def trigger(hass, config, action):
""" Listen for events based on config. """
"""Listen for events based on configuration."""
event_type = config.get(CONF_EVENT_TYPE)
if event_type is None:
@@ -25,7 +23,7 @@ def trigger(hass, config, action):
event_data = config.get(CONF_EVENT_DATA)
def handle_event(event):
""" Listens for events and calls the action when data matches. """
"""Listen for events and calls the action when data matches."""
if not event_data or all(val == event.data.get(key) for key, val
in event_data.items()):
action()
+3 -5
View File
@@ -1,7 +1,5 @@
"""
homeassistant.components.automation.mqtt
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Offers MQTT listening automation rules.
Offer MQTT listening automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#mqtt-trigger
@@ -17,7 +15,7 @@ CONF_PAYLOAD = 'payload'
def trigger(hass, config, action):
""" Listen for state changes based on `config`. """
"""Listen for state changes based on configuration."""
topic = config.get(CONF_TOPIC)
payload = config.get(CONF_PAYLOAD)
@@ -27,7 +25,7 @@ def trigger(hass, config, action):
return False
def mqtt_automation_listener(msg_topic, msg_payload, qos):
""" Listens for MQTT messages. """
"""Listen for MQTT messages."""
if payload is None or payload == msg_payload:
action()
@@ -1,7 +1,5 @@
"""
homeassistant.components.automation.numeric_state
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Offers numeric state listening automation rules.
Offer numeric state listening automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#numeric-state-trigger
@@ -21,7 +19,7 @@ _LOGGER = logging.getLogger(__name__)
def _renderer(hass, value_template, state):
"""Render state value."""
"""Render the state value."""
if value_template is None:
return state.state
@@ -29,7 +27,7 @@ def _renderer(hass, value_template, state):
def trigger(hass, config, action):
""" Listen for state changes based on `config`. """
"""Listen for state changes based on configuration."""
entity_id = config.get(CONF_ENTITY_ID)
if entity_id is None:
@@ -50,8 +48,7 @@ def trigger(hass, config, action):
# pylint: disable=unused-argument
def state_automation_listener(entity, from_s, to_s):
""" Listens for state changes and calls action. """
"""Listen for state changes and calls action."""
# Fire action if we go from outside range into range
if _in_range(above, below, renderer(to_s)) and \
(from_s is None or not _in_range(above, below, renderer(from_s))):
@@ -64,8 +61,7 @@ def trigger(hass, config, action):
def if_action(hass, config):
""" Wraps action method with state based condition. """
"""Wrap action method with state based condition."""
entity_id = config.get(CONF_ENTITY_ID)
if entity_id is None:
@@ -85,7 +81,7 @@ def if_action(hass, config):
renderer = partial(_renderer, hass, value_template)
def if_numeric_state():
""" Test numeric state condition. """
"""Test numeric state condition."""
state = hass.states.get(entity_id)
return state is not None and _in_range(above, below, renderer(state))
@@ -93,7 +89,7 @@ def if_action(hass, config):
def _in_range(range_start, range_end, value):
""" Checks if value is inside the range """
"""Check if value is inside the range."""
try:
value = float(value)
except ValueError:
+8 -12
View File
@@ -1,7 +1,5 @@
"""
homeassistant.components.automation.state
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Offers state listening automation rules.
Offer state listening automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#state-trigger
@@ -25,7 +23,7 @@ CONF_FOR = "for"
def get_time_config(config):
""" Helper function to extract the time specified in the config """
"""Helper function to extract the time specified in the configuration."""
if CONF_FOR not in config:
return None
@@ -51,7 +49,7 @@ def get_time_config(config):
def trigger(hass, config, action):
""" Listen for state changes based on `config`. """
"""Listen for state changes based on configuration."""
entity_id = config.get(CONF_ENTITY_ID)
if entity_id is None:
@@ -72,17 +70,15 @@ def trigger(hass, config, action):
return None
def state_automation_listener(entity, from_s, to_s):
""" Listens for state changes and calls action. """
"""Listen for state changes and calls action."""
def state_for_listener(now):
""" Fires on state changes after a delay and calls action. """
"""Fire on state changes after a delay and calls action."""
hass.bus.remove_listener(
EVENT_STATE_CHANGED, for_state_listener)
action()
def state_for_cancel_listener(entity, inner_from_s, inner_to_s):
""" Fires on state changes and cancels
for listener if state changed. """
"""Fire on changes and cancel for listener if changed."""
if inner_to_s == to_s:
return
hass.bus.remove_listener(EVENT_TIME_CHANGED, for_time_listener)
@@ -106,7 +102,7 @@ def trigger(hass, config, action):
def if_action(hass, config):
""" Wraps action method with state based condition. """
"""Wrap action method with state based condition."""
entity_id = config.get(CONF_ENTITY_ID)
state = config.get(CONF_STATE)
@@ -123,7 +119,7 @@ def if_action(hass, config):
state = str(state)
def if_state():
""" Test if condition. """
"""Test if condition."""
is_state = hass.states.is_state(entity_id, state)
return (time_delta is None and is_state or
time_delta is not None and
+5 -7
View File
@@ -1,7 +1,5 @@
"""
homeassistant.components.automation.sun
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Offers sun based automation rules.
Offer sun based automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#sun-trigger
@@ -29,7 +27,7 @@ _LOGGER = logging.getLogger(__name__)
def trigger(hass, config, action):
""" Listen for events based on config. """
"""Listen for events based on configuration."""
event = config.get(CONF_EVENT)
if event is None:
@@ -55,7 +53,7 @@ def trigger(hass, config, action):
def if_action(hass, config):
""" Wraps action method with sun based condition. """
"""Wrap action method with sun based condition."""
before = config.get(CONF_BEFORE)
after = config.get(CONF_AFTER)
@@ -106,8 +104,7 @@ def if_action(hass, config):
return sun.next_setting(hass) + after_offset
def time_if():
""" Validate time based if-condition """
"""Validate time based if-condition."""
now = dt_util.now()
before = before_func()
after = after_func()
@@ -126,6 +123,7 @@ def if_action(hass, config):
def _parse_offset(raw_offset):
"""Parse the offset."""
if raw_offset is None:
return timedelta(0)
+12 -10
View File
@@ -1,7 +1,5 @@
"""
homeassistant.components.automation.template
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Offers template automation rules.
Offer template automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#template-trigger
@@ -16,7 +14,7 @@ _LOGGER = logging.getLogger(__name__)
def trigger(hass, config, action):
""" Listen for state changes based on `config`. """
"""Listen for state changes based on configuration."""
value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is None:
@@ -27,7 +25,7 @@ def trigger(hass, config, action):
already_triggered = False
def event_listener(event):
""" Listens for state changes and calls action. """
"""Listen for state changes and calls action."""
nonlocal already_triggered
template_result = _check_template(hass, value_template)
@@ -43,8 +41,7 @@ def trigger(hass, config, action):
def if_action(hass, config):
""" Wraps action method with state based condition. """
"""Wrap action method with state based condition."""
value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is None:
@@ -55,11 +52,16 @@ def if_action(hass, config):
def _check_template(hass, value_template):
""" Checks if result of template is true """
"""Check if result of template is true."""
try:
value = template.render(hass, value_template, {})
except TemplateError:
_LOGGER.exception('Error parsing template')
except TemplateError as ex:
if ex.args and ex.args[0].startswith(
"UndefinedError: 'None' has no attribute"):
# Common during HA startup - so just a warning
_LOGGER.warning(ex)
else:
_LOGGER.error(ex)
return False
return value.lower() == 'true'
+6 -8
View File
@@ -1,7 +1,5 @@
"""
homeassistant.components.automation.time
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Offers time listening automation rules.
Offer time listening automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#time-trigger
@@ -24,7 +22,7 @@ _LOGGER = logging.getLogger(__name__)
def trigger(hass, config, action):
""" Listen for state changes based on `config`. """
"""Listen for state changes based on configuration."""
if CONF_AFTER in config:
after = dt_util.parse_time_str(config[CONF_AFTER])
if after is None:
@@ -42,7 +40,7 @@ def trigger(hass, config, action):
return False
def time_automation_listener(now):
""" Listens for time changes and calls action. """
"""Listen for time changes and calls action."""
action()
track_time_change(hass, time_automation_listener,
@@ -52,7 +50,7 @@ def trigger(hass, config, action):
def if_action(hass, config):
""" Wraps action method with time based condition. """
"""Wrap action method with time based condition."""
before = config.get(CONF_BEFORE)
after = config.get(CONF_AFTER)
weekday = config.get(CONF_WEEKDAY)
@@ -76,7 +74,7 @@ def if_action(hass, config):
return None
def time_if():
""" Validate time based if-condition """
"""Validate time based if-condition."""
now = dt_util.now()
if before is not None and now > now.replace(hour=before.hour,
minute=before.minute):
@@ -99,7 +97,7 @@ def if_action(hass, config):
def _error_time(value, key):
""" Helper method to print error. """
"""Helper method to print error."""
_LOGGER.error(
"Received invalid value for '%s': %s", key, value)
if isinstance(value, int):
+6 -8
View File
@@ -1,7 +1,5 @@
"""
homeassistant.components.automation.zone
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Offers zone automation rules.
Offer zone automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#zone-trigger
@@ -22,7 +20,7 @@ DEFAULT_EVENT = EVENT_ENTER
def trigger(hass, config, action):
""" Listen for state changes based on `config`. """
"""Listen for state changes based on configuration."""
entity_id = config.get(CONF_ENTITY_ID)
zone_entity_id = config.get(CONF_ZONE)
@@ -35,7 +33,7 @@ def trigger(hass, config, action):
event = config.get(CONF_EVENT, DEFAULT_EVENT)
def zone_automation_listener(entity, from_s, to_s):
""" Listens for state changes and calls action. """
"""Listen for state changes and calls action."""
if from_s and None in (from_s.attributes.get(ATTR_LATITUDE),
from_s.attributes.get(ATTR_LONGITUDE)) or \
None in (to_s.attributes.get(ATTR_LATITUDE),
@@ -57,7 +55,7 @@ def trigger(hass, config, action):
def if_action(hass, config):
""" Wraps action method with zone based condition. """
"""Wrap action method with zone based condition."""
entity_id = config.get(CONF_ENTITY_ID)
zone_entity_id = config.get(CONF_ZONE)
@@ -68,14 +66,14 @@ def if_action(hass, config):
return False
def if_in_zone():
""" Test if condition. """
"""Test if condition."""
return _in_zone(hass, zone_entity_id, hass.states.get(entity_id))
return if_in_zone
def _in_zone(hass, zone_entity_id, state):
""" Check if state is in zone. """
"""Check if state is in zone."""
if not state or None in (state.attributes.get(ATTR_LATITUDE),
state.attributes.get(ATTR_LONGITUDE)):
return False
@@ -1,6 +1,5 @@
"""
Component to interface with binary sensors (sensors which only know two states)
that can be monitored.
Component to interface with binary sensors.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/binary_sensor/
@@ -10,7 +9,8 @@ import logging
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity import Entity
from homeassistant.const import (STATE_ON, STATE_OFF)
from homeassistant.components import (bloomsky, mysensors, zwave, wink)
from homeassistant.components import (
bloomsky, mysensors, zwave, vera, wemo, wink)
DOMAIN = 'binary_sensor'
SCAN_INTERVAL = 30
@@ -38,6 +38,8 @@ DISCOVERY_PLATFORMS = {
bloomsky.DISCOVER_BINARY_SENSORS: 'bloomsky',
mysensors.DISCOVER_BINARY_SENSORS: 'mysensors',
zwave.DISCOVER_BINARY_SENSORS: 'zwave',
vera.DISCOVER_BINARY_SENSORS: 'vera',
wemo.DISCOVER_BINARY_SENSORS: 'wemo',
wink.DISCOVER_BINARY_SENSORS: 'wink'
}
@@ -1,5 +1,5 @@
"""
Provides a binary sensor to track online status of a UPS.
Support for tracking the online status of a UPS.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.apcupsd/
@@ -17,8 +17,10 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
class OnlineStatus(BinarySensorDevice):
"""Binary sensor to represent UPS online status."""
"""Represent UPS online status."""
def __init__(self, config, data):
"""Initialize the APCUPSd device."""
self._config = config
self._data = data
self._state = None
@@ -26,17 +28,14 @@ class OnlineStatus(BinarySensorDevice):
@property
def name(self):
""" The name of the UPS online status sensor. """
"""Return the name of the UPS online status sensor."""
return self._config.get("name", DEFAULT_NAME)
@property
def is_on(self):
"""True if the UPS is online, else False."""
"""Return true if the UPS is online, else false."""
return self._state == apcupsd.VALUE_ONLINE
def update(self):
"""
Get the status report from APCUPSd (or cache) and set this entity's
state.
"""
"""Get the status report from APCUPSd and set this entity's state."""
self._state = self._data.status[apcupsd.KEY_STATUS]
@@ -1,5 +1,5 @@
"""
The arest sensor will consume an exposed aREST API of a device.
Support for exposed aREST RESTful API of a device.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.arest/
@@ -22,7 +22,7 @@ CONF_PIN = 'pin'
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Get the aREST binary sensor."""
"""Setup the aREST binary sensor."""
resource = config.get(CONF_RESOURCE)
pin = config.get(CONF_PIN)
@@ -53,9 +53,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
# pylint: disable=too-many-instance-attributes, too-many-arguments
class ArestBinarySensor(BinarySensorDevice):
"""Implements an aREST binary sensor for a pin."""
"""Implement an aREST binary sensor for a pin."""
def __init__(self, arest, resource, name, pin):
"""Initialize the aREST device."""
self.arest = arest
self._resource = resource
self._name = name
@@ -70,30 +71,32 @@ class ArestBinarySensor(BinarySensorDevice):
@property
def name(self):
"""The name of the binary sensor."""
"""Return the name of the binary sensor."""
return self._name
@property
def is_on(self):
"""True if the binary sensor is on."""
"""Return true if the binary sensor is on."""
return bool(self.arest.data.get('state'))
def update(self):
"""Gets the latest data from aREST API."""
"""Get the latest data from aREST API."""
self.arest.update()
# pylint: disable=too-few-public-methods
class ArestData(object):
"""Class for handling the data retrieval for pins."""
def __init__(self, resource, pin):
"""Initialize the aREST data object."""
self._resource = resource
self._pin = pin
self.data = {}
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Gets the latest data from aREST device."""
"""Get the latest data from aREST device."""
try:
response = requests.get('{}/digital/{}'.format(
self._resource, self._pin), timeout=10)
@@ -19,7 +19,7 @@ SENSOR_TYPES = {
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the available BloomSky weather binary sensors."""
"""Setup the available BloomSky weather binary sensors."""
logger = logging.getLogger(__name__)
bloomsky = get_component('bloomsky')
sensors = config.get('monitored_conditions', SENSOR_TYPES)
@@ -35,7 +35,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class BloomSkySensor(BinarySensorDevice):
""" Represents a single binary sensor in a BloomSky device. """
"""Represent a single binary sensor in a BloomSky device."""
def __init__(self, bs, device, sensor_name):
"""Initialize a BloomSky binary sensor."""
@@ -53,7 +53,7 @@ class BloomSkySensor(BinarySensorDevice):
@property
def unique_id(self):
"""Unique ID for this sensor."""
"""Return the unique ID for this sensor."""
return self._unique_id
@property
@@ -63,7 +63,7 @@ class BloomSkySensor(BinarySensorDevice):
@property
def is_on(self):
"""If binary sensor is on."""
"""Return true if binary sensor is on."""
return self._state
def update(self):
@@ -1,6 +1,5 @@
"""
Allows to configure custom shell commands to turn a value into a logical value
for a binary sensor.
Support for custom shell commands to to retrieve values.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.command/
@@ -25,7 +24,7 @@ MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60)
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Add the Command Sensor."""
"""Setup the Command Sensor."""
if config.get('command') is None:
_LOGGER.error('Missing required variable: "command"')
return False
@@ -44,11 +43,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
# pylint: disable=too-many-arguments
class CommandBinarySensor(BinarySensorDevice):
"""
Represents a binary sensor that is returning a value of a shell commands.
"""
"""Represent a command line binary sensor."""
def __init__(self, hass, data, name, payload_on,
payload_off, value_template):
"""Initialize the Command line binary sensor."""
self._hass = hass
self.data = data
self._name = name
@@ -60,16 +59,16 @@ class CommandBinarySensor(BinarySensorDevice):
@property
def name(self):
"""The name of the sensor."""
"""Return the name of the sensor."""
return self._name
@property
def is_on(self):
"""True if the binary sensor is on."""
"""Return true if the binary sensor is on."""
return self._state
def update(self):
"""Gets the latest data and updates the state."""
"""Get the latest data and updates the state."""
self.data.update()
value = self.data.value
@@ -17,7 +17,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class DemoBinarySensor(BinarySensorDevice):
"""A Demo binary sensor."""
def __init__(self, name, state, sensor_class):
"""Initialize the demo sensor."""
self._name = name
self._state = state
self._sensor_type = sensor_class
+22 -8
View File
@@ -1,5 +1,5 @@
"""
Allows to configure a MQTT binary sensor.
Support for MQTT binary sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.mqtt/
@@ -7,7 +7,8 @@ https://home-assistant.io/components/binary_sensor.mqtt/
import logging
import homeassistant.components.mqtt as mqtt
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.binary_sensor import (BinarySensorDevice,
SENSOR_CLASSES)
from homeassistant.const import CONF_VALUE_TEMPLATE
from homeassistant.helpers import template
@@ -24,15 +25,20 @@ DEPENDENCIES = ['mqtt']
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Add MQTT binary sensor."""
if config.get('state_topic') is None:
_LOGGER.error('Missing required variable: state_topic')
return False
sensor_class = config.get('sensor_class')
if sensor_class not in SENSOR_CLASSES:
_LOGGER.warning('Unknown sensor class: %s', sensor_class)
sensor_class = None
add_devices([MqttBinarySensor(
hass,
config.get('name', DEFAULT_NAME),
config.get('state_topic', None),
sensor_class,
config.get('qos', DEFAULT_QOS),
config.get('payload_on', DEFAULT_PAYLOAD_ON),
config.get('payload_off', DEFAULT_PAYLOAD_OFF),
@@ -41,13 +47,16 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
# pylint: disable=too-many-arguments, too-many-instance-attributes
class MqttBinarySensor(BinarySensorDevice):
"""Represents a binary sensor that is updated by MQTT."""
def __init__(self, hass, name, state_topic, qos, payload_on, payload_off,
value_template):
"""Representation a binary sensor that is updated by MQTT."""
def __init__(self, hass, name, state_topic, sensor_class, qos, payload_on,
payload_off, value_template):
"""Initialize the MQTT binary sensor."""
self._hass = hass
self._name = name
self._state = False
self._state_topic = state_topic
self._sensor_class = sensor_class
self._payload_on = payload_on
self._payload_off = payload_off
self._qos = qos
@@ -73,10 +82,15 @@ class MqttBinarySensor(BinarySensorDevice):
@property
def name(self):
"""The name of the binary sensor."""
"""Return the name of the binary sensor."""
return self._name
@property
def is_on(self):
"""True if the binary sensor is on."""
"""Return true if the binary sensor is on."""
return self._state
@property
def sensor_class(self):
"""Return the class of this sensor."""
return self._sensor_class
@@ -90,7 +90,7 @@ class MySensorsBinarySensor(BinarySensorDevice):
@property
def should_poll(self):
"""MySensor gateway pushes its state to HA."""
"""Mysensor gateway pushes its state to HA."""
return False
@property
@@ -26,7 +26,6 @@ BINARY_TYPES = ['fan',
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup Nest binary sensors."""
logger = logging.getLogger(__name__)
try:
for structure in nest.NEST.structures:
@@ -66,6 +66,7 @@ class NX584ZoneSensor(BinarySensorDevice):
"""Represents a NX584 zone as a sensor."""
def __init__(self, zone, zone_type):
"""Initialize the nx594 binary sensor."""
self._zone = zone
self._zone_type = zone_type
@@ -81,7 +82,7 @@ class NX584ZoneSensor(BinarySensorDevice):
@property
def name(self):
"""Name of the binary sensor."""
"""Return the name of the binary sensor."""
return self._zone['name']
@property
@@ -95,6 +96,7 @@ class NX584Watcher(threading.Thread):
"""Event listener thread to process NX584 events."""
def __init__(self, client, zone_sensors):
"""Initialize nx584 watcher thread."""
super(NX584Watcher, self).__init__()
self.daemon = True
self._client = client
@@ -115,7 +117,7 @@ class NX584Watcher(threading.Thread):
self._process_zone_event(event)
def _run(self):
# Throw away any existing events so we don't replay history
"""Throw away any existing events so we don't replay history."""
self._client.get_events()
while True:
events = self._client.get_events()
@@ -123,6 +125,7 @@ class NX584Watcher(threading.Thread):
self._process_events(events)
def run(self):
"""Run the watcher."""
while True:
try:
self._run()
+22 -7
View File
@@ -1,12 +1,13 @@
"""
The rest binary sensor will consume responses sent by an exposed REST API.
Support for RESTful binary sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.rest/
"""
import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.binary_sensor import (BinarySensorDevice,
SENSOR_CLASSES)
from homeassistant.components.sensor.rest import RestData
from homeassistant.const import CONF_VALUE_TEMPLATE
from homeassistant.helpers import template
@@ -19,12 +20,17 @@ DEFAULT_METHOD = 'GET'
# pylint: disable=unused-variable
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup REST binary sensors."""
"""Setup the REST binary sensor."""
resource = config.get('resource', None)
method = config.get('method', DEFAULT_METHOD)
payload = config.get('payload', None)
verify_ssl = config.get('verify_ssl', True)
sensor_class = config.get('sensor_class')
if sensor_class not in SENSOR_CLASSES:
_LOGGER.warning('Unknown sensor class: %s', sensor_class)
sensor_class = None
rest = RestData(method, resource, payload, verify_ssl)
rest.update()
@@ -33,28 +39,37 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
return False
add_devices([RestBinarySensor(
hass, rest, config.get('name', DEFAULT_NAME),
hass,
rest,
config.get('name', DEFAULT_NAME),
sensor_class,
config.get(CONF_VALUE_TEMPLATE))])
# pylint: disable=too-many-arguments
class RestBinarySensor(BinarySensorDevice):
"""A REST binary sensor."""
"""Representation of a REST binary sensor."""
def __init__(self, hass, rest, name, value_template):
def __init__(self, hass, rest, name, sensor_class, value_template):
"""Initialize a REST binary sensor."""
self._hass = hass
self.rest = rest
self._name = name
self._sensor_class = sensor_class
self._state = False
self._value_template = value_template
self.update()
@property
def name(self):
"""Name of the binary sensor."""
"""Return the name of the binary sensor."""
return self._name
@property
def sensor_class(self):
"""Return the class of this sensor."""
return self._sensor_class
@property
def is_on(self):
"""Return true if the binary sensor is on."""
@@ -1,5 +1,5 @@
"""
Allows to configure a binary sensor using RPi GPIO.
Support for binary sensor using RPi GPIO.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.rpi_gpio/
@@ -20,8 +20,7 @@ _LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Sets up the Raspberry PI GPIO devices."""
"""Setup the Raspberry PI GPIO devices."""
pull_mode = config.get('pull_mode', DEFAULT_PULL_MODE)
bouncetime = config.get('bouncetime', DEFAULT_BOUNCETIME)
invert_logic = config.get('invert_logic', DEFAULT_INVERT_LOGIC)
@@ -36,10 +35,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
# pylint: disable=too-many-arguments, too-many-instance-attributes
class RPiGPIOBinarySensor(BinarySensorDevice):
"""Represents a binary sensor that uses Raspberry Pi GPIO."""
def __init__(self, name, port, pull_mode, bouncetime, invert_logic):
# pylint: disable=no-member
"""Represent a binary sensor that uses Raspberry Pi GPIO."""
def __init__(self, name, port, pull_mode, bouncetime, invert_logic):
"""Initialize the RPi binary sensor."""
# pylint: disable=no-member
self._name = name or DEVICE_DEFAULT_NAME
self._port = port
self._pull_mode = pull_mode
@@ -50,9 +50,10 @@ class RPiGPIOBinarySensor(BinarySensorDevice):
self._state = rpi_gpio.read_input(self._port)
def read_gpio(port):
"""Reads state from GPIO."""
"""Read state from GPIO."""
self._state = rpi_gpio.read_input(self._port)
self.update_ha_state()
rpi_gpio.edge_detect(self._port, read_gpio, self._bouncetime)
@property
@@ -62,10 +63,10 @@ class RPiGPIOBinarySensor(BinarySensorDevice):
@property
def name(self):
"""The name of the sensor."""
"""Return the name of the sensor."""
return self._name
@property
def is_on(self):
"""Returns the state of the entity."""
"""Return the state of the entity."""
return self._state != self._invert_logic
@@ -23,6 +23,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
class BinarySensor(BinarySensorDevice, Sensor):
"""A binary sensor which is on when its state == CONF_VALUE_ON."""
required = (CONF_VALUE_ON,)
@property
@@ -1,12 +1,13 @@
"""
homeassistant.components.binary_sensor.template
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for exposing a templated binary_sensor
Support for exposing a templated binary sensor.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.template/
"""
import logging
from homeassistant.components.binary_sensor import (BinarySensorDevice,
DOMAIN,
ENTITY_ID_FORMAT,
SENSOR_CLASSES)
from homeassistant.const import ATTR_FRIENDLY_NAME, CONF_VALUE_TEMPLATE
from homeassistant.core import EVENT_STATE_CHANGED
@@ -15,14 +16,12 @@ from homeassistant.helpers.entity import generate_entity_id
from homeassistant.helpers import template
from homeassistant.util import slugify
ENTITY_ID_FORMAT = DOMAIN + '.{}'
CONF_SENSORS = 'sensors'
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup template binary sensors."""
sensors = []
if config.get(CONF_SENSORS) is None:
_LOGGER.error('Missing configuration data for binary_sensor platform')
@@ -70,47 +69,54 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class BinarySensorTemplate(BinarySensorDevice):
"""A virtual binary_sensor that triggers from another sensor."""
"""A virtual binary sensor that triggers from another sensor."""
# pylint: disable=too-many-arguments
def __init__(self, hass, device, friendly_name, sensor_class,
value_template):
self._hass = hass
self._device = device
"""Initialize the Template binary sensor."""
self.hass = hass
self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, device,
hass=hass)
self._name = friendly_name
self._sensor_class = sensor_class
self._template = value_template
self._state = None
self.entity_id = generate_entity_id(
ENTITY_ID_FORMAT, device,
hass=hass)
self.update()
_LOGGER.info('Started template sensor %s', device)
hass.bus.listen(EVENT_STATE_CHANGED, self._event_listener)
def template_bsensor_event_listener(event):
"""Called when the target device changes state."""
self.update_ha_state(True)
def _event_listener(self, event):
self.update_ha_state(True)
@property
def should_poll(self):
return False
@property
def sensor_class(self):
return self._sensor_class
hass.bus.listen(EVENT_STATE_CHANGED,
template_bsensor_event_listener)
@property
def name(self):
"""Return the name of the sensor."""
return self._name
@property
def is_on(self):
"""Return true if sensor is on."""
return self._state
@property
def sensor_class(self):
"""Return the sensor class of the sensor."""
return self._sensor_class
@property
def should_poll(self):
"""No polling needed."""
return False
def update(self):
"""Get the latest data and update the state."""
try:
value = template.render(self._hass, self._template)
self._state = template.render(self.hass,
self._template).lower() == 'true'
except TemplateError as ex:
if ex.args and ex.args[0].startswith(
"UndefinedError: 'None' has no attribute"):
@@ -118,5 +124,4 @@ class BinarySensorTemplate(BinarySensorDevice):
_LOGGER.warning(ex)
return
_LOGGER.error(ex)
value = 'false'
self._state = value.lower() == 'true'
self._state = False
@@ -0,0 +1,69 @@
"""
Support for Vera binary sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.vera/
"""
import logging
import homeassistant.util.dt as dt_util
from homeassistant.const import (
ATTR_ARMED, ATTR_BATTERY_LEVEL, ATTR_LAST_TRIP_TIME, ATTR_TRIPPED)
from homeassistant.components.binary_sensor import (
BinarySensorDevice)
from homeassistant.components.vera import (
VeraDevice, VERA_DEVICES, VERA_CONTROLLER)
DEPENDENCIES = ['vera']
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Perform the setup for Vera controller devices."""
add_devices_callback(
VeraBinarySensor(device, VERA_CONTROLLER)
for device in VERA_DEVICES['binary_sensor'])
class VeraBinarySensor(VeraDevice, BinarySensorDevice):
"""Representation of a Vera Binary Sensor."""
def __init__(self, vera_device, controller):
"""Initialize the binary_sensor."""
self._state = False
VeraDevice.__init__(self, vera_device, controller)
@property
def device_state_attributes(self):
"""Return the state attributes."""
attr = {}
if self.vera_device.has_battery:
attr[ATTR_BATTERY_LEVEL] = self.vera_device.battery_level + '%'
if self.vera_device.is_armable:
armed = self.vera_device.is_armed
attr[ATTR_ARMED] = 'True' if armed else 'False'
if self.vera_device.is_trippable:
last_tripped = self.vera_device.last_trip
if last_tripped is not None:
utc_time = dt_util.utc_from_timestamp(int(last_tripped))
attr[ATTR_LAST_TRIP_TIME] = dt_util.datetime_to_str(
utc_time)
else:
attr[ATTR_LAST_TRIP_TIME] = None
tripped = self.vera_device.is_tripped
attr[ATTR_TRIPPED] = 'True' if tripped else 'False'
attr['Vera Device Id'] = self.vera_device.vera_device_id
return attr
@property
def is_on(self):
"""Return true if sensor is on."""
return self._state
def update(self):
"""Get the latest data and update the state."""
self._state = self.vera_device.is_tripped
@@ -0,0 +1,78 @@
"""
Support for WeMo sensors.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.wemo/
"""
import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.loader import get_component
DEPENDENCIES = ['wemo']
_LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument, too-many-function-args
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Register discovered WeMo binary sensors."""
import pywemo.discovery as discovery
if discovery_info is not None:
location = discovery_info[2]
mac = discovery_info[3]
device = discovery.device_from_description(location, mac)
if device:
add_devices_callback([WemoBinarySensor(device)])
class WemoBinarySensor(BinarySensorDevice):
"""Represents a WeMo binary sensor."""
def __init__(self, device):
"""Initialize the WeMo sensor."""
self.wemo = device
self._state = None
wemo = get_component('wemo')
wemo.SUBSCRIPTION_REGISTRY.register(self.wemo)
wemo.SUBSCRIPTION_REGISTRY.on(self.wemo, None, self._update_callback)
def _update_callback(self, _device, _params):
"""Called by the wemo device callback to update state."""
_LOGGER.info(
'Subscription update for %s',
_device)
if not hasattr(self, 'hass'):
self.update()
return
self.update_ha_state(True)
@property
def should_poll(self):
"""No polling needed with subscriptions."""
return False
@property
def unique_id(self):
"""Return the id of this WeMo device."""
return "{}.{}".format(self.__class__, self.wemo.serialnumber)
@property
def name(self):
"""Return the name of the sevice if any."""
return self.wemo.name
@property
def is_on(self):
"""True if sensor is on."""
return self._state
def update(self):
"""Update WeMo state."""
try:
self._state = self.wemo.get_state(True)
except AttributeError:
_LOGGER.warning('Could not update status for %s', self.name)
+13 -7
View File
@@ -10,7 +10,7 @@ from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.const import CONF_ACCESS_TOKEN
from homeassistant.helpers.entity import Entity
REQUIREMENTS = ['python-wink==0.6.2']
REQUIREMENTS = ['python-wink==0.6.4']
# These are the available sensors mapped to binary_sensor class
SENSOR_TYPES = {
@@ -22,7 +22,7 @@ SENSOR_TYPES = {
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Sets up the Wink platform."""
"""Setup the Wink platform."""
import pywink
if discovery_info is None:
@@ -42,16 +42,17 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class WinkBinarySensorDevice(BinarySensorDevice, Entity):
"""Represents a Wink sensor."""
"""Representation of a Wink sensor."""
def __init__(self, wink):
"""Initialize the Wink binary sensor."""
self.wink = wink
self._unit_of_measurement = self.wink.UNIT
self.capability = self.wink.capability()
@property
def is_on(self):
"""Return True if the binary sensor is on."""
"""Return true if the binary sensor is on."""
if self.capability == "loudness":
return self.wink.loudness_boolean()
elif self.capability == "vibration":
@@ -68,14 +69,19 @@ class WinkBinarySensorDevice(BinarySensorDevice, Entity):
@property
def unique_id(self):
""" Returns the id of this wink sensor """
"""Return the ID of this wink sensor."""
return "{}.{}".format(self.__class__, self.wink.device_id())
@property
def name(self):
""" Returns the name of the sensor if any. """
"""Return the name of the sensor if any."""
return self.wink.name()
@property
def available(self):
"""True if connection == True."""
return self.wink.available
def update(self):
""" Update state of the sensor. """
"""Update state of the sensor."""
self.wink.update_state()
@@ -19,8 +19,6 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
class ZigBeeBinarySensor(ZigBeeDigitalIn, BinarySensorDevice):
"""
Use multiple inheritance to turn a ZigBeeDigitalIn into a
BinarySensorDevice.
"""
"""Use ZigBeeDigitalIn as binary sensor."""
pass
@@ -23,17 +23,19 @@ DEPENDENCIES = []
PHILIO = 0x013c
PHILIO_SLIM_SENSOR = 0x0002
PHILIO_SLIM_SENSOR_MOTION = (PHILIO, PHILIO_SLIM_SENSOR, 0)
WENZHOU = 0x0118
WENZHOU_SLIM_SENSOR_MOTION = (WENZHOU, PHILIO_SLIM_SENSOR, 0)
WORKAROUND_NO_OFF_EVENT = 'trigger_no_off_event'
DEVICE_MAPPINGS = {
PHILIO_SLIM_SENSOR_MOTION: WORKAROUND_NO_OFF_EVENT,
WENZHOU_SLIM_SENSOR_MOTION: WORKAROUND_NO_OFF_EVENT,
}
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Z-Wave platform for sensors."""
if discovery_info is None or NETWORK is None:
return
@@ -63,9 +65,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class ZWaveBinarySensor(BinarySensorDevice, ZWaveDeviceEntity):
"""Represents a binary sensor within Z-Wave."""
"""Representation of a binary sensor within Z-Wave."""
def __init__(self, value, sensor_class):
"""Initialize the sensor."""
self._sensor_type = sensor_class
# pylint: disable=import-error
from openzwave.network import ZWaveNetwork
@@ -98,12 +101,10 @@ class ZWaveBinarySensor(BinarySensorDevice, ZWaveDeviceEntity):
class ZWaveTriggerSensor(ZWaveBinarySensor):
"""
Represents a stateless sensor which triggers events just 'On'
within Z-Wave.
"""
"""Representation of a stateless sensor within Z-Wave."""
def __init__(self, sensor_value, sensor_class, hass, re_arm_sec=60):
"""Initialize the sensor."""
super(ZWaveTriggerSensor, self).__init__(sensor_value, sensor_class)
self._hass = hass
self.re_arm_sec = re_arm_sec
+5 -10
View File
@@ -1,6 +1,4 @@
"""
homeassistant.components.bloomsky
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for BloomSky weather station.
For more details about this component, please refer to the documentation at
@@ -32,7 +30,7 @@ DISCOVER_CAMERAS = 'bloomsky.camera'
# pylint: disable=unused-argument,too-few-public-methods
def setup(hass, config):
""" Setup BloomSky component. """
"""Setup BloomSky component."""
if not validate_config(
config,
{DOMAIN: [CONF_API_KEY]},
@@ -57,13 +55,13 @@ def setup(hass, config):
class BloomSky(object):
""" Handle all communication with the BloomSky API. """
"""Handle all communication with the BloomSky API."""
# API documentation at http://weatherlution.com/bloomsky-api/
API_URL = "https://api.bloomsky.com/api/skydata"
def __init__(self, api_key):
"""Initialize the BookSky."""
self._api_key = api_key
self.devices = {}
_LOGGER.debug("Initial bloomsky device load...")
@@ -71,10 +69,7 @@ class BloomSky(object):
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def refresh_devices(self):
"""
Uses the API to retreive a list of devices associated with an
account along with all the sensors on the device.
"""
"""Use the API to retreive a list of devices."""
_LOGGER.debug("Fetching bloomsky update")
response = requests.get(self.API_URL,
headers={"Authorization": self._api_key},
@@ -84,7 +79,7 @@ class BloomSky(object):
elif response.status_code != 200:
_LOGGER.error("Invalid HTTP response: %s", response.status_code)
return
# create dictionary keyed off of the device unique id
# Create dictionary keyed off of the device unique id
self.devices.update({
device["DeviceID"]: device for device in response.json()
})
+1 -3
View File
@@ -10,9 +10,7 @@ SERVICE_BROWSE_URL = "browse_url"
def setup(hass, config):
"""
Listen for browse_url events and open the url in the default web browser.
"""
"""Listen for browse_url events."""
import webbrowser
hass.services.register(DOMAIN, SERVICE_BROWSE_URL,
+1 -1
View File
@@ -42,7 +42,7 @@ MJPEG_START_HEADER = 'Content-type: {0}\r\n\r\n'
# pylint: disable=too-many-branches
def setup(hass, config):
"""Initialize camera component."""
"""Setup the camera component."""
component = EntityComponent(
logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL,
DISCOVERY_PLATFORMS)
+7 -9
View File
@@ -1,6 +1,4 @@
"""
homeassistant.components.camera.bloomsky
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for a camera of a BloomSky weather station.
For more details about this component, please refer to the documentation at
@@ -18,17 +16,17 @@ DEPENDENCIES = ["bloomsky"]
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" set up access to BloomSky cameras """
"""Setup access to BloomSky cameras."""
bloomsky = get_component('bloomsky')
for device in bloomsky.BLOOMSKY.devices.values():
add_devices_callback([BloomSkyCamera(bloomsky.BLOOMSKY, device)])
class BloomSkyCamera(Camera):
""" Represents the images published from the BloomSky's camera. """
"""Representation of the images published from the BloomSky's camera."""
def __init__(self, bs, device):
""" set up for access to the BloomSky camera images """
"""Setup for access to the BloomSky camera images."""
super(BloomSkyCamera, self).__init__()
self._name = device["DeviceName"]
self._id = device["DeviceID"]
@@ -37,16 +35,16 @@ class BloomSkyCamera(Camera):
self._last_url = ""
# _last_image will store images as they are downloaded so that the
# frequent updates in home-assistant don't keep poking the server
# to download the same image over and over
# to download the same image over and over.
self._last_image = ""
self._logger = logging.getLogger(__name__)
def camera_image(self):
""" Update the camera's image if it has changed. """
"""Update the camera's image if it has changed."""
try:
self._url = self._bloomsky.devices[self._id]["Data"]["ImageURL"]
self._bloomsky.refresh_devices()
# if the url hasn't changed then the image hasn't changed
# If the URL hasn't changed then the image hasn't changed.
if self._url != self._last_url:
response = requests.get(self._url, timeout=10)
self._last_url = self._url
@@ -59,5 +57,5 @@ class BloomSkyCamera(Camera):
@property
def name(self):
""" The name of this BloomSky device. """
"""Return the name of this BloomSky device."""
return self._name
+1
View File
@@ -21,6 +21,7 @@ class DemoCamera(Camera):
"""A Demo camera."""
def __init__(self, name):
"""Initialize demo camera component."""
super().__init__()
self._name = name
+5 -7
View File
@@ -1,6 +1,4 @@
"""
homeassistant.components.camera.foscam
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This component provides basic support for Foscam IP cameras.
For more details about this platform, please refer to the documentation at
@@ -18,7 +16,7 @@ _LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Adds a Foscam IP Camera. """
"""Setup a Foscam IP Camera."""
if not validate_config({DOMAIN: config},
{DOMAIN: ['username', 'password', 'ip']}, _LOGGER):
return None
@@ -28,9 +26,10 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
# pylint: disable=too-many-instance-attributes
class FoscamCamera(Camera):
""" An implementation of a Foscam IP camera. """
"""An implementation of a Foscam IP camera."""
def __init__(self, device_info):
"""Initialize a Foscam camera."""
super(FoscamCamera, self).__init__()
ip_address = device_info.get('ip')
@@ -48,8 +47,7 @@ class FoscamCamera(Camera):
self._name, self._snap_picture_url)
def camera_image(self):
""" Return a still image reponse from the camera. """
"""Return a still image reponse from the camera."""
# Send the request to snap a picture and return raw jpg data
response = requests.get(self._snap_picture_url)
@@ -57,5 +55,5 @@ class FoscamCamera(Camera):
@property
def name(self):
""" Return the name of this device. """
"""Return the name of this camera."""
return self._name
+5 -8
View File
@@ -1,6 +1,4 @@
"""
homeassistant.components.camera.generic
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for IP Cameras.
For more details about this platform, please refer to the documentation at
@@ -19,7 +17,7 @@ _LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Adds a generic IP Camera. """
"""Setup a generic IP Camera."""
if not validate_config({DOMAIN: config}, {DOMAIN: ['still_image_url']},
_LOGGER):
return None
@@ -29,11 +27,10 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
# pylint: disable=too-many-instance-attributes
class GenericCamera(Camera):
"""
A generic implementation of an IP camera that is reachable over a URL.
"""
"""A generic implementation of an IP camera."""
def __init__(self, device_info):
"""Initialize a generic camera."""
super().__init__()
self._name = device_info.get('name', 'Generic Camera')
self._username = device_info.get('username')
@@ -41,7 +38,7 @@ class GenericCamera(Camera):
self._still_image_url = device_info['still_image_url']
def camera_image(self):
""" Return a still image response from the camera. """
"""Return a still image response from the camera."""
if self._username and self._password:
try:
response = requests.get(
@@ -61,5 +58,5 @@ class GenericCamera(Camera):
@property
def name(self):
""" Return the name of this device. """
"""Return the name of this device."""
return self._name
+8 -12
View File
@@ -1,6 +1,4 @@
"""
homeassistant.components.camera.mjpeg
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for IP Cameras.
For more details about this platform, please refer to the documentation at
@@ -23,7 +21,7 @@ _LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Adds a mjpeg IP Camera. """
"""Setup a MJPEG IP Camera."""
if not validate_config({DOMAIN: config}, {DOMAIN: ['mjpeg_url']},
_LOGGER):
return None
@@ -33,11 +31,10 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
# pylint: disable=too-many-instance-attributes
class MjpegCamera(Camera):
"""
A generic implementation of an IP camera that is reachable over a URL.
"""
"""An implementation of an IP camera that is reachable over a URL."""
def __init__(self, device_info):
"""Initialize a MJPEG camera."""
super().__init__()
self._name = device_info.get('name', 'Mjpeg Camera')
self._username = device_info.get('username')
@@ -45,7 +42,7 @@ class MjpegCamera(Camera):
self._mjpeg_url = device_info['mjpeg_url']
def camera_stream(self):
""" Return a mjpeg stream image response directly from the camera. """
"""Return a MJPEG stream image response directly from the camera."""
if self._username and self._password:
return requests.get(self._mjpeg_url,
auth=HTTPBasicAuth(self._username,
@@ -56,10 +53,9 @@ class MjpegCamera(Camera):
stream=True)
def camera_image(self):
""" Return a still image response from the camera. """
"""Return a still image response from the camera."""
def process_response(response):
""" Take in a response object, return the jpg from it. """
"""Take in a response object, return the jpg from it."""
data = b''
for chunk in response.iter_content(1024):
data += chunk
@@ -73,7 +69,7 @@ class MjpegCamera(Camera):
return process_response(response)
def mjpeg_stream(self, handler):
""" Generate an HTTP MJPEG stream from the camera. """
"""Generate an HTTP MJPEG stream from the camera."""
response = self.camera_stream()
content_type = response.headers[CONTENT_TYPE_HEADER]
@@ -88,5 +84,5 @@ class MjpegCamera(Camera):
@property
def name(self):
""" Return the name of this device. """
"""Return the name of this camera."""
return self._name
+9 -4
View File
@@ -1,6 +1,4 @@
"""
homeassistant.components.camera.uvc
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for Ubiquiti's UVC cameras.
For more details about this platform, please refer to the documentation at
@@ -20,7 +18,7 @@ _LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Discover cameras on a Unifi NVR. """
"""Discover cameras on a Unifi NVR."""
if not validate_config({DOMAIN: config}, {DOMAIN: ['nvr', 'key']},
_LOGGER):
return None
@@ -60,9 +58,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class UnifiVideoCamera(Camera):
""" A Ubiquiti Unifi Video Camera. """
"""A Ubiquiti Unifi Video Camera."""
def __init__(self, nvr, uuid, name):
"""Initialize an Unifi camera."""
super(UnifiVideoCamera, self).__init__()
self._nvr = nvr
self._uuid = uuid
@@ -73,23 +72,28 @@ class UnifiVideoCamera(Camera):
@property
def name(self):
"""Return the name of this camera."""
return self._name
@property
def is_recording(self):
"""Return true if the camera is recording."""
caminfo = self._nvr.get_camera(self._uuid)
return caminfo['recordingSettings']['fullTimeRecordEnabled']
@property
def brand(self):
"""Return the brand of this camera."""
return 'Ubiquiti'
@property
def model(self):
"""Return the model of this camera."""
caminfo = self._nvr.get_camera(self._uuid)
return caminfo['model']
def _login(self):
"""Login to the camera."""
from uvcclient import camera as uvc_camera
from uvcclient import store as uvc_store
@@ -131,6 +135,7 @@ class UnifiVideoCamera(Camera):
return True
def camera_image(self):
"""Return the image of this camera."""
from uvcclient import camera as uvc_camera
if not self._camera:
if not self._login():
+17 -21
View File
@@ -1,8 +1,5 @@
"""
homeassistant.components.configurator
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A component to allow pieces of code to request configuration from the user.
Support to allow pieces of code to request configuration from the user.
Initiate a request by calling the `request_config` method with a callback.
This will return a request id that has to be used for future calls.
@@ -38,9 +35,10 @@ _LOGGER = logging.getLogger(__name__)
def request_config(
hass, name, callback, description=None, description_image=None,
submit_caption=None, fields=None):
""" Create a new request for config.
Will return an ID to be used for sequent calls. """
"""Create a new request for configuration.
Will return an ID to be used for sequent calls.
"""
instance = _get_instance(hass)
request_id = instance.request_config(
@@ -53,7 +51,7 @@ def request_config(
def notify_errors(request_id, error):
""" Add errors to a config request. """
"""Add errors to a config request."""
try:
_REQUESTS[request_id].notify_errors(request_id, error)
except KeyError:
@@ -62,7 +60,7 @@ def notify_errors(request_id, error):
def request_done(request_id):
""" Mark a config request as done. """
"""Mark a configuration request as done."""
try:
_REQUESTS.pop(request_id).request_done(request_id)
except KeyError:
@@ -71,12 +69,12 @@ def request_done(request_id):
def setup(hass, config):
""" Set up Configurator. """
"""Setup the configurator component."""
return True
def _get_instance(hass):
""" Get an instance per hass object. """
"""Get an instance per hass object."""
try:
return _INSTANCES[hass]
except KeyError:
@@ -89,11 +87,10 @@ def _get_instance(hass):
class Configurator(object):
"""
Class to keep track of current configuration requests.
"""
"""The class to keep track of current configuration requests."""
def __init__(self, hass):
"""Initialize the configurator."""
self.hass = hass
self._cur_id = 0
self._requests = {}
@@ -104,8 +101,7 @@ class Configurator(object):
def request_config(
self, name, callback,
description, description_image, submit_caption, fields):
""" Setup a request for configuration. """
"""Setup a request for configuration."""
entity_id = generate_entity_id(ENTITY_ID_FORMAT, name, hass=self.hass)
if fields is None:
@@ -133,7 +129,7 @@ class Configurator(object):
return request_id
def notify_errors(self, request_id, error):
""" Update the state with errors. """
"""Update the state with errors."""
if not self._validate_request_id(request_id):
return
@@ -147,7 +143,7 @@ class Configurator(object):
self.hass.states.set(entity_id, STATE_CONFIGURE, new_data)
def request_done(self, request_id):
""" Remove the config request. """
"""Remove the configuration request."""
if not self._validate_request_id(request_id):
return
@@ -160,13 +156,13 @@ class Configurator(object):
self.hass.states.set(entity_id, STATE_CONFIGURED)
def deferred_remove(event):
""" Remove the request state. """
"""Remove the request state."""
self.hass.states.remove(entity_id)
self.hass.bus.listen_once(EVENT_TIME_CHANGED, deferred_remove)
def handle_service_call(self, call):
""" Handle a configure service call. """
"""Handle a configure service call."""
request_id = call.data.get(ATTR_CONFIGURE_ID)
if not self._validate_request_id(request_id):
@@ -180,10 +176,10 @@ class Configurator(object):
callback(call.data.get(ATTR_FIELDS, {}))
def _generate_unique_id(self):
""" Generates a unique configurator id. """
"""Generate a unique configurator ID."""
self._cur_id += 1
return "{}-{}".format(id(self), self._cur_id)
def _validate_request_id(self, request_id):
""" Validate that the request belongs to this instance. """
"""Validate that the request belongs to this instance."""
return request_id in self._requests
+4 -10
View File
@@ -1,7 +1,5 @@
"""
homeassistant.components.conversation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides functionality to have conversations with Home Assistant.
Support for functionality to have conversations with Home Assistant.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/conversation/
@@ -25,19 +23,18 @@ REQUIREMENTS = ['fuzzywuzzy==0.8.0']
def setup(hass, config):
""" Registers the process service. """
"""Register the process service."""
from fuzzywuzzy import process as fuzzyExtract
logger = logging.getLogger(__name__)
def process(service):
""" Parses text into commands for Home Assistant. """
"""Parse text into commands."""
if ATTR_TEXT not in service.data:
logger.error("Received process service call without a text")
return
text = service.data[ATTR_TEXT].lower()
match = REGEX_TURN_COMMAND.match(text)
if not match:
@@ -45,11 +42,8 @@ def setup(hass, config):
return
name, command = match.groups()
entities = {state.entity_id: state.name for state in hass.states.all()}
entity_ids = fuzzyExtract.extractOne(name,
entities,
entity_ids = fuzzyExtract.extractOne(name, entities,
score_cutoff=65)[2]
if not entity_ids:
@@ -1,8 +1,5 @@
"""
homeassistant.components.device_sun_light_trigger
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides functionality to turn on lights based on the state of the sun and
devices.
Provides functionality to turn on lights based on the states.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/device_sun_light_trigger/
@@ -12,9 +9,9 @@ from datetime import timedelta
import homeassistant.util.dt as dt_util
from homeassistant.const import STATE_HOME, STATE_NOT_HOME
from homeassistant.helpers.event import track_point_in_time, track_state_change
from . import device_tracker, group, light, sun
from homeassistant.helpers.event import track_point_in_time
from homeassistant.helpers.event_decorators import track_state_change
from homeassistant.loader import get_component
DOMAIN = "device_sun_light_trigger"
DEPENDENCIES = ['light', 'device_tracker', 'group', 'sun']
@@ -29,28 +26,26 @@ CONF_LIGHT_GROUP = 'light_group'
CONF_DEVICE_GROUP = 'device_group'
# pylint: disable=too-many-branches
# pylint: disable=too-many-locals
def setup(hass, config):
""" Triggers to turn lights on or off based on device precense. """
"""The triggers to turn lights on or off based on device presence."""
logger = logging.getLogger(__name__)
device_tracker = get_component('device_tracker')
group = get_component('group')
light = get_component('light')
sun = get_component('sun')
disable_turn_off = 'disable_turn_off' in config[DOMAIN]
light_group = config[DOMAIN].get(CONF_LIGHT_GROUP,
light.ENTITY_ID_ALL_LIGHTS)
light_profile = config[DOMAIN].get(CONF_LIGHT_PROFILE, LIGHT_PROFILE)
device_group = config[DOMAIN].get(CONF_DEVICE_GROUP,
device_tracker.ENTITY_ID_ALL_DEVICES)
logger = logging.getLogger(__name__)
device_entity_ids = group.get_entity_ids(hass, device_group,
device_tracker.DOMAIN)
if not device_entity_ids:
logger.error("No devices found to track")
return False
# Get the light IDs from the specified group
@@ -58,116 +53,105 @@ def setup(hass, config):
if not light_ids:
logger.error("No lights found to turn on ")
return False
def calc_time_for_light_when_sunset():
""" Calculates the time when to start fading lights in when sun sets.
Returns None if no next_setting data available. """
"""Calculate the time when to start fading lights in when sun sets.
Returns None if no next_setting data available.
"""
next_setting = sun.next_setting(hass)
if next_setting:
return next_setting - LIGHT_TRANSITION_TIME * len(light_ids)
else:
if not next_setting:
return None
return next_setting - LIGHT_TRANSITION_TIME * len(light_ids)
def schedule_light_on_sun_rise(entity, old_state, new_state):
"""The moment sun sets we want to have all the lights on.
We will schedule to have each light start after one another
and slowly transition in."""
def turn_light_on_before_sunset(light_id):
"""Helper function to turn on lights.
def turn_light_on_before_sunset(light_id):
""" Helper function to turn on lights slowly if there
are devices home and the light is not on yet. """
if device_tracker.is_on(hass) and not light.is_on(hass, light_id):
light.turn_on(hass, light_id,
transition=LIGHT_TRANSITION_TIME.seconds,
profile=light_profile)
def turn_on(light_id):
""" Lambda can keep track of function parameters but not local
parameters. If we put the lambda directly in the below statement
only the last light will be turned on.. """
return lambda now: turn_light_on_before_sunset(light_id)
start_point = calc_time_for_light_when_sunset()
if start_point:
for index, light_id in enumerate(light_ids):
track_point_in_time(
hass, turn_on(light_id),
(start_point + index * LIGHT_TRANSITION_TIME))
Speed is slow if there are devices home and the light is not on yet.
"""
if not device_tracker.is_on(hass) or light.is_on(hass, light_id):
return
light.turn_on(hass, light_id,
transition=LIGHT_TRANSITION_TIME.seconds,
profile=light_profile)
# Track every time sun rises so we can schedule a time-based
# pre-sun set event
track_state_change(hass, sun.ENTITY_ID, schedule_light_on_sun_rise,
sun.STATE_BELOW_HORIZON, sun.STATE_ABOVE_HORIZON)
@track_state_change(sun.ENTITY_ID, sun.STATE_BELOW_HORIZON,
sun.STATE_ABOVE_HORIZON)
def schedule_lights_at_sun_set(hass, entity, old_state, new_state):
"""The moment sun sets we want to have all the lights on.
# If the sun is already above horizon
# schedule the time-based pre-sun set event
We will schedule to have each light start after one another
and slowly transition in.
"""
start_point = calc_time_for_light_when_sunset()
if not start_point:
return
def turn_on(light_id):
"""Lambda can keep track of function parameters.
No local parameters. If we put the lambda directly in the below
statement only the last light will be turned on.
"""
return lambda now: turn_light_on_before_sunset(light_id)
for index, light_id in enumerate(light_ids):
track_point_in_time(hass, turn_on(light_id),
start_point + index * LIGHT_TRANSITION_TIME)
# If the sun is already above horizon schedule the time-based pre-sun set
# event.
if sun.is_on(hass):
schedule_light_on_sun_rise(None, None, None)
schedule_lights_at_sun_set(hass, None, None, None)
def check_light_on_dev_state_change(entity, old_state, new_state):
""" Function to handle tracked device state changes. """
@track_state_change(device_entity_ids, STATE_NOT_HOME, STATE_HOME)
def check_light_on_dev_state_change(hass, entity, old_state, new_state):
"""Handle tracked device state changes."""
# pylint: disable=unused-variable
lights_are_on = group.is_on(hass, light_group)
light_needed = not (lights_are_on or sun.is_on(hass))
# Specific device came home ?
if entity != device_tracker.ENTITY_ID_ALL_DEVICES and \
new_state.state == STATE_HOME:
# These variables are needed for the elif check
now = dt_util.now()
start_point = calc_time_for_light_when_sunset()
# These variables are needed for the elif check
now = dt_util.now()
start_point = calc_time_for_light_when_sunset()
# Do we need lights?
if light_needed:
logger.info("Home coming event for %s. Turning lights on", entity)
light.turn_on(hass, light_ids, profile=light_profile)
# Do we need lights?
if light_needed:
# Are we in the time span were we would turn on the lights
# if someone would be home?
# Check this by seeing if current time is later then the point
# in time when we would start putting the lights on.
elif (start_point and
start_point < now < sun.next_setting(hass)):
logger.info(
"Home coming event for %s. Turning lights on", entity)
# Check for every light if it would be on if someone was home
# when the fading in started and turn it on if so
for index, light_id in enumerate(light_ids):
if now > start_point + index * LIGHT_TRANSITION_TIME:
light.turn_on(hass, light_id)
light.turn_on(hass, light_ids, profile=light_profile)
else:
# If this light didn't happen to be turned on yet so
# will all the following then, break.
break
# Are we in the time span were we would turn on the lights
# if someone would be home?
# Check this by seeing if current time is later then the point
# in time when we would start putting the lights on.
elif (start_point and
start_point < now < sun.next_setting(hass)):
# Check for every light if it would be on if someone was home
# when the fading in started and turn it on if so
for index, light_id in enumerate(light_ids):
if now > start_point + index * LIGHT_TRANSITION_TIME:
light.turn_on(hass, light_id)
else:
# If this light didn't happen to be turned on yet so
# will all the following then, break.
break
# Did all devices leave the house?
elif (entity == device_group and
new_state.state == STATE_NOT_HOME and lights_are_on and
not disable_turn_off):
if not disable_turn_off:
@track_state_change(device_group, STATE_HOME, STATE_NOT_HOME)
def turn_off_lights_when_all_leave(hass, entity, old_state, new_state):
"""Handle device group state change."""
# pylint: disable=unused-variable
if not group.is_on(hass, light_group):
return
logger.info(
"Everyone has left but there are lights on. Turning them off")
light.turn_off(hass, light_ids)
# Track home coming of each device
track_state_change(
hass, device_entity_ids, check_light_on_dev_state_change,
STATE_NOT_HOME, STATE_HOME)
# Track when all devices are gone to shut down lights
track_state_change(
hass, device_group, check_light_on_dev_state_change,
STATE_HOME, STATE_NOT_HOME)
return True
@@ -1,14 +1,11 @@
"""
homeassistant.components.device_tracker
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides functionality to keep track of devices.
Provide functionality to keep track of devices.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/device_tracker/
"""
# pylint: disable=too-many-instance-attributes, too-many-arguments
# pylint: disable=too-many-locals
import csv
from datetime import timedelta
import logging
import os
@@ -36,7 +33,6 @@ ENTITY_ID_ALL_DEVICES = group.ENTITY_ID_FORMAT.format('all_devices')
ENTITY_ID_FORMAT = DOMAIN + '.{}'
CSV_DEVICES = "known_devices.csv"
YAML_DEVICES = 'known_devices.yaml'
CONF_TRACK_NEW = "track_new_devices"
@@ -72,7 +68,7 @@ _LOGGER = logging.getLogger(__name__)
def is_on(hass, entity_id=None):
""" Returns if any or specified device is home. """
"""Return the state if any or a specified device is home."""
entity = entity_id or ENTITY_ID_ALL_DEVICES
return hass.states.is_state(entity, STATE_HOME)
@@ -80,23 +76,21 @@ def is_on(hass, entity_id=None):
def see(hass, mac=None, dev_id=None, host_name=None, location_name=None,
gps=None, gps_accuracy=None, battery=None):
""" Call service to notify you see device. """
"""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}
(ATTR_GPS, gps),
(ATTR_GPS_ACCURACY, gps_accuracy),
(ATTR_BATTERY, battery)) if value is not None}
hass.services.call(DOMAIN, SERVICE_SEE, data)
def setup(hass, config):
""" Setup 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)
conf = config.get(DOMAIN, {})
if isinstance(conf, list):
@@ -114,7 +108,7 @@ def setup(hass, config):
devices)
def setup_platform(p_type, p_config, disc_info=None):
""" Setup a device tracker platform. """
"""Setup a device tracker platform."""
platform = prepare_setup_platform(hass, config, DOMAIN, p_type)
if platform is None:
return
@@ -140,21 +134,21 @@ def setup(hass, config):
setup_platform(p_type, p_config)
def device_tracker_discovered(service, info):
""" Called when a device tracker platform is discovered. """
"""Called when a device tracker platform is discovered."""
setup_platform(DISCOVERY_PLATFORMS[service], {}, info)
discovery.listen(hass, DISCOVERY_PLATFORMS.keys(),
device_tracker_discovered)
def update_stale(now):
""" Clean up stale devices. """
"""Clean up stale devices."""
tracker.update_stale(now)
track_utc_time_change(hass, update_stale, second=range(0, 60, 5))
tracker.setup_group()
def see_service(call):
""" Service to see a device. """
"""Service to see a device."""
args = {key: value for key, value in call.data.items() if key in
(ATTR_MAC, ATTR_DEV_ID, ATTR_HOST_NAME, ATTR_LOCATION_NAME,
ATTR_GPS, ATTR_GPS_ACCURACY, ATTR_BATTERY)}
@@ -169,8 +163,10 @@ def setup(hass, config):
class DeviceTracker(object):
""" Track devices """
"""Representation of a device tracker."""
def __init__(self, hass, consider_home, track_new, home_range, devices):
"""Initialize a device tracker."""
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}
@@ -187,7 +183,7 @@ class DeviceTracker(object):
def see(self, mac=None, dev_id=None, host_name=None, location_name=None,
gps=None, gps_accuracy=None, battery=None):
""" Notify device tracker that you see a device. """
"""Notify the 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')
@@ -226,14 +222,14 @@ class DeviceTracker(object):
update_config(self.hass.config.path(YAML_DEVICES), dev_id, device)
def setup_group(self):
""" Initializes group for all tracked devices. """
"""Initialize group for all tracked devices."""
entity_ids = (dev.entity_id for dev in self.devices.values()
if dev.track)
self.group = group.Group(
self.hass, GROUP_NAME_ALL_DEVICES, entity_ids, False)
def update_stale(self, now):
""" Update stale devices. """
"""Update stale devices."""
with self.lock:
for device in self.devices.values():
if (device.track and device.last_update_home and
@@ -242,7 +238,7 @@ class DeviceTracker(object):
class Device(Entity):
""" Tracked device. """
"""Represent a tracked device."""
host_name = None
location_name = None
@@ -251,12 +247,13 @@ class Device(Entity):
last_seen = None
battery = None
# Track if the last update of this device was HOME
# Track if the last update of this device was HOME.
last_update_home = False
_state = STATE_NOT_HOME
def __init__(self, hass, consider_home, home_range, track, dev_id, mac,
name=None, picture=None, away_hide=False):
"""Initialize a device."""
self.hass = hass
self.entity_id = ENTITY_ID_FORMAT.format(dev_id)
@@ -282,29 +279,29 @@ class Device(Entity):
@property
def gps_home(self):
""" Return if device is within range of home. """
"""Return if device is within range of home."""
distance = max(
0, self.hass.config.distance(*self.gps) - self.gps_accuracy)
return self.gps is not None and distance <= self.home_range
@property
def name(self):
""" Returns the name of the entity. """
"""Return 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 the state of the device."""
return self._state
@property
def entity_picture(self):
"""Picture of the device."""
"""Return the picture of the device."""
return self.config_picture
@property
def state_attributes(self):
""" Device state attributes. """
"""Return the device state attributes."""
attr = {}
if self.gps:
@@ -319,12 +316,12 @@ class Device(Entity):
@property
def hidden(self):
""" If device should be hidden. """
"""If device should be hidden."""
return self.away_hide and self.state != STATE_HOME
def seen(self, host_name=None, location_name=None, gps=None,
gps_accuracy=0, battery=None):
""" Mark the device as seen. """
"""Mark the device as seen."""
self.last_seen = dt_util.utcnow()
self.host_name = host_name
self.location_name = location_name
@@ -342,12 +339,12 @@ class Device(Entity):
self.update()
def stale(self, now=None):
""" Return if device state is stale. """
"""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. """
"""Update state of entity."""
if not self.last_seen:
return
elif self.location_name:
@@ -370,23 +367,8 @@ class Device(Entity):
self.last_update_home = True
def convert_csv_config(csv_path, yaml_path):
""" Convert CSV config file format to YAML. """
used_ids = set()
with open(csv_path) as inp:
for row in csv.DictReader(inp):
dev_id = util.ensure_unique_string(
(util.slugify(row['name']) or DEVICE_DEFAULT_NAME).lower(),
used_ids)
used_ids.add(dev_id)
device = Device(None, None, None, row['track'] == '1', dev_id,
row['device'], row['name'], row['picture'])
update_config(yaml_path, dev_id, device)
return True
def load_config(path, hass, consider_home, home_range):
""" Load devices from YAML config file. """
"""Load devices from YAML configuration file."""
if not os.path.isfile(path):
return []
return [
@@ -398,7 +380,7 @@ def load_config(path, hass, consider_home, home_range):
def setup_scanner_platform(hass, config, scanner, see_device):
""" Helper method to connect scanner-based platform to device tracker. """
"""Helper method to connect scanner-based platform to device tracker."""
interval = util.convert(config.get(CONF_SCAN_INTERVAL), int,
DEFAULT_SCAN_INTERVAL)
@@ -406,7 +388,7 @@ def setup_scanner_platform(hass, config, scanner, see_device):
seen = set()
def device_tracker_scan(now):
""" Called when interval matches. """
"""Called when interval matches."""
for mac in scanner.scan_devices():
if mac in seen:
host_name = None
@@ -422,7 +404,7 @@ def setup_scanner_platform(hass, config, scanner, see_device):
def update_config(path, dev_id, device):
""" Add device to YAML config file. """
"""Add device to YAML configuration file."""
with open(path, 'a') as out:
out.write('\n')
out.write('{}:\n'.format(device.dev_id))
@@ -1,8 +1,5 @@
"""
homeassistant.components.device_tracker.actiontec
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Device tracker platform that supports scanning an Actiontec MI424WR
(Verizon FIOS) router for device presence.
Support for Actiontec MI424WR (Verizon FIOS) routers.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.actiontec/
@@ -20,7 +17,7 @@ from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.helpers import validate_config
from homeassistant.util import Throttle
# Return cached results if last scan was less then this time ago
# Return cached results if last scan was less then this time ago.
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__)
@@ -34,7 +31,7 @@ _LEASES_REGEX = re.compile(
# pylint: disable=unused-argument
def get_scanner(hass, config):
""" Validates config and returns an Actiontec scanner. """
"""Validate the configuration and return an Actiontec scanner."""
if not validate_config(config,
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
_LOGGER):
@@ -46,12 +43,10 @@ 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."""
def __init__(self, config):
"""Initialize the scanner."""
self.host = config[CONF_HOST]
self.username = config[CONF_USERNAME]
self.password = config[CONF_PASSWORD]
@@ -62,15 +57,12 @@ class ActiontecDeviceScanner(object):
_LOGGER.info("actiontec scanner initialized")
def scan_devices(self):
"""
Scans for new devices and return a list containing found device ids.
"""
"""Scan for new devices and return a list with found device IDs."""
self._update_info()
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. """
"""Return 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:
@@ -80,9 +72,9 @@ class ActiontecDeviceScanner(object):
@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.
"""Ensure the information from the router is up to date.
Return boolean if scanning successful.
"""
_LOGGER.info("Scanning")
if not self.success_init:
@@ -100,7 +92,7 @@ class ActiontecDeviceScanner(object):
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: ')
@@ -1,8 +1,5 @@
"""
homeassistant.components.device_tracker.aruba
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Device tracker platform that supports scanning a Aruba Access Point for device
presence.
Support for Aruba Access Points.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.aruba/
@@ -31,7 +28,7 @@ _DEVICES_REGEX = re.compile(
# pylint: disable=unused-argument
def get_scanner(hass, config):
""" Validates config and returns a Aruba scanner. """
"""Validate the configuration and return a Aruba scanner."""
if not validate_config(config,
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
_LOGGER):
@@ -43,9 +40,10 @@ def get_scanner(hass, config):
class ArubaDeviceScanner(object):
""" This class queries a Aruba Acces Point for connected devices. """
"""This class queries a Aruba Access Point for connected devices."""
def __init__(self, config):
"""Initialize the scanner."""
self.host = config[CONF_HOST]
self.username = config[CONF_USERNAME]
self.password = config[CONF_PASSWORD]
@@ -54,20 +52,17 @@ class ArubaDeviceScanner(object):
self.last_results = {}
# Test the router is accessible
# Test the router is accessible.
data = self.get_aruba_data()
self.success_init = data is not None
def scan_devices(self):
"""
Scans for new devices and return a list containing found device IDs.
"""
"""Scan for new devices and return a list with found device IDs."""
self._update_info()
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. """
"""Return 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:
@@ -77,9 +72,9 @@ 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.
"""Ensure the information from the Aruba Access Point is up to date.
Return boolean if scanning successful.
"""
if not self.success_init:
return False
@@ -93,8 +88,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."""
import pexpect
connect = "ssh {}@{}"
ssh = pexpect.spawn(connect.format(self.username, self.host))
@@ -1,8 +1,5 @@
"""
homeassistant.components.device_tracker.asuswrt
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Device tracker platform that supports scanning a ASUSWRT router for device
presence.
Support for ASUSWRT routers.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.asuswrt/
@@ -18,7 +15,7 @@ from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.helpers import validate_config
from homeassistant.util import Throttle
# Return cached results if last scan was less then this time ago
# Return cached results if last scan was less then this time ago.
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__)
@@ -39,7 +36,7 @@ _IP_NEIGH_REGEX = re.compile(
# pylint: disable=unused-argument
def get_scanner(hass, config):
""" Validates config and returns an ASUS-WRT scanner. """
"""Validate the configuration and return an ASUS-WRT scanner."""
if not validate_config(config,
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
_LOGGER):
@@ -51,12 +48,10 @@ def get_scanner(hass, config):
class AsusWrtDeviceScanner(object):
"""
This class queries a router running ASUSWRT firmware
for connected devices. Adapted from DD-WRT scanner.
"""
"""This class queries a router running ASUSWRT firmware."""
def __init__(self, config):
"""Initialize the scanner."""
self.host = config[CONF_HOST]
self.username = str(config[CONF_USERNAME])
self.password = str(config[CONF_PASSWORD])
@@ -65,20 +60,17 @@ class AsusWrtDeviceScanner(object):
self.last_results = {}
# Test the router is accessible
# Test the router is accessible.
data = self.get_asuswrt_data()
self.success_init = data is not None
def scan_devices(self):
"""
Scans for new devices and return a list containing found device IDs.
"""
"""Scan for new devices and return a list with found device IDs."""
self._update_info()
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. """
"""Return 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:
@@ -88,9 +80,9 @@ 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.
"""Ensure the information from the ASUSWRT router is up to date.
Return boolean if scanning successful.
"""
if not self.success_init:
return False
@@ -109,7 +101,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: ')
@@ -138,9 +130,8 @@ class AsusWrtDeviceScanner(object):
_LOGGER.warning("Could not parse lease row: %s", lease)
continue
# For leases where the client doesn't set a hostname, ensure
# it is blank and not '*', which breaks the entity_id down
# the line
# 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 = ''
@@ -1,8 +1,5 @@
"""
homeassistant.components.device_tracker.ddwrt
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Device tracker platform that supports scanning a DD-WRT router for device
presence.
Support for DD-WRT routers.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.ddwrt/
@@ -19,7 +16,7 @@ from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.helpers import validate_config
from homeassistant.util import Throttle
# Return cached results if last scan was less then this time ago
# Return cached results if last scan was less then this time ago.
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__)
@@ -30,7 +27,7 @@ _MAC_REGEX = re.compile(r'(([0-9A-Fa-f]{1,2}\:){5}[0-9A-Fa-f]{1,2})')
# pylint: disable=unused-argument
def get_scanner(hass, config):
""" Validates config and returns a DD-WRT scanner. """
"""Validate the configuration and return a DD-WRT scanner."""
if not validate_config(config,
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
_LOGGER):
@@ -43,12 +40,10 @@ def get_scanner(hass, config):
# pylint: disable=too-many-instance-attributes
class DdWrtDeviceScanner(object):
"""
This class queries a wireless router running DD-WRT firmware
for connected devices. Adapted from Tomato scanner.
"""
"""This class queries a wireless router running DD-WRT firmware."""
def __init__(self, config):
"""Initialize the scanner."""
self.host = config[CONF_HOST]
self.username = config[CONF_USERNAME]
self.password = config[CONF_PASSWORD]
@@ -65,19 +60,15 @@ 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.
"""
"""Scan for new devices and return a list with found device IDs."""
self._update_info()
return self.last_results
def get_device_name(self, device):
""" Returns the name of the given device or None if we don't know. """
"""Return the name of the given device or None if we don't know."""
with self.lock:
# if not initialised and not already scanned and not found
# If not initialised and not already scanned and not found.
if device not in self.mac2name:
url = 'http://{}/Status_Lan.live.asp'.format(self.host)
data = self.get_ddwrt_data(url)
@@ -90,15 +81,15 @@ class DdWrtDeviceScanner(object):
if not dhcp_leases:
return None
# remove leading and trailing single quotes
# Remove leading and trailing single quotes.
cleaned_str = dhcp_leases.strip().strip('"')
elements = cleaned_str.split('","')
num_clients = int(len(elements)/5)
self.mac2name = {}
for idx in range(0, num_clients):
# this is stupid but the data is a single array
# This is stupid but the data is a single array
# every 5 elements represents one hosts, the MAC
# is the third element and the name is the first
# is the third element and the name is the first.
mac_index = (idx * 5) + 2
if mac_index < len(elements):
mac = elements[mac_index]
@@ -108,9 +99,9 @@ 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.
"""Ensure the information from the DD-WRT router is up to date.
Return boolean if scanning successful.
"""
if not self.success_init:
return False
@@ -135,7 +126,7 @@ class DdWrtDeviceScanner(object):
# regex's out values so I guess I have to do the same,
# LAME!!!
# remove leading and trailing single quotes
# Remove leading and trailing single quotes.
clean_str = active_clients.strip().strip("'")
elements = clean_str.split("','")
@@ -145,7 +136,7 @@ class DdWrtDeviceScanner(object):
return True
def get_ddwrt_data(self, url):
""" Retrieve data from DD-WRT and return parsed result. """
"""Retrieve data from DD-WRT and return parsed result."""
try:
response = requests.get(
url,
@@ -167,7 +158,7 @@ class DdWrtDeviceScanner(object):
def _parse_ddwrt_response(data_str):
""" Parse the DD-WRT data format. """
"""Parse the DD-WRT data format."""
return {
key: val for key, val in _DDWRT_DATA_REGEX
.findall(data_str)}
@@ -1,25 +1,17 @@
"""
homeassistant.components.device_tracker.demo
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Demo platform for the device tracker.
device_tracker:
platform: demo
"""
"""Demo platform for the device tracker."""
import random
from homeassistant.components.device_tracker import DOMAIN
def setup_scanner(hass, config, see):
""" Set up a demo tracker. """
"""Setup the demo tracker."""
def offset():
""" Return random offset. """
"""Return random offset."""
return (random.randrange(500, 2000)) / 2e5 * random.choice((-1, 1))
def random_see(dev_id, name):
""" Randomize a sighting. """
"""Randomize a sighting."""
see(
dev_id=dev_id,
host_name=name,
@@ -30,7 +22,7 @@ def setup_scanner(hass, config, see):
)
def observe(call=None):
""" Observe three entities. """
"""Observe three entities."""
random_see('demo_paulus', 'Paulus')
random_see('demo_anne_therese', 'Anne Therese')
@@ -1,8 +1,5 @@
"""
homeassistant.components.device_tracker.fritz
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Device tracker platform that supports scanning a FRITZ!Box router for device
presence.
Support for FRITZ!Box routers.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.fritz/
@@ -17,15 +14,14 @@ from homeassistant.util import Throttle
REQUIREMENTS = ['fritzconnection==0.4.6']
# Return cached results if last scan was less then this time ago
# Return cached results if last scan was less then this time ago.
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__)
# noinspection PyUnusedLocal
def get_scanner(hass, config):
""" Validates config and returns FritzBoxScanner. """
"""Validate the configuration and return FritzBoxScanner."""
if not validate_config(config,
{DOMAIN: []},
_LOGGER):
@@ -37,22 +33,12 @@ def get_scanner(hass, config):
# pylint: disable=too-many-instance-attributes
class FritzBoxScanner(object):
"""
This class queries a FRITZ!Box router. It is using the
fritzconnection library for communication with the router.
"""This class queries a FRITZ!Box router."""
The API description can be found under:
https://pypi.python.org/pypi/fritzconnection/0.4.6
This scanner retrieves the list of known hosts and checks their
corresponding states (on, or off).
Due to a bug of the fritzbox api (router side) it is not possible
to track more than 16 hosts.
"""
def __init__(self, config):
"""Initialize the scanner."""
self.last_results = []
self.host = '169.254.1.1' # This IP is valid for all fritzboxes
self.host = '169.254.1.1' # This IP is valid for all FRITZ!Box router.
self.username = 'admin'
self.password = ''
self.success_init = True
@@ -68,7 +54,7 @@ class FritzBoxScanner(object):
if CONF_PASSWORD in config.keys():
self.password = config[CONF_PASSWORD]
# Establish a connection to the FRITZ!Box
# Establish a connection to the FRITZ!Box.
try:
self.fritz_box = fc.FritzHosts(address=self.host,
user=self.username,
@@ -77,7 +63,7 @@ class FritzBoxScanner(object):
self.fritz_box = None
# At this point it is difficult to tell if a connection is established.
# So just check for null objects ...
# So just check for null objects.
if self.fritz_box is None or not self.fritz_box.modelname:
self.success_init = False
@@ -90,7 +76,7 @@ class FritzBoxScanner(object):
"with IP: %s", self.host)
def scan_devices(self):
""" Scan for new devices and return a list of found device ids. """
"""Scan for new devices and return a list of found device ids."""
self._update_info()
active_hosts = []
for known_host in self.last_results:
@@ -99,7 +85,7 @@ class FritzBoxScanner(object):
return active_hosts
def get_device_name(self, mac):
""" Returns the name of the given device or None if is not known. """
"""Return the name of the given device or None if is not known."""
ret = self.fritz_box.get_specific_host_entry(mac)["NewHostName"]
if ret == {}:
return None
@@ -107,7 +93,7 @@ class FritzBoxScanner(object):
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
""" Retrieves latest information from the FRITZ!Box. """
"""Retrieve latest information from the FRITZ!Box."""
if not self.success_init:
return False
@@ -1,7 +1,5 @@
"""
homeassistant.components.device_tracker.icloud
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Device tracker platform that supports scanning iCloud devices.
Support for iCloud connected devices.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.icloud/
@@ -14,19 +12,19 @@ from homeassistant.helpers.event import track_utc_time_change
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['pyicloud==0.7.2']
REQUIREMENTS = ['pyicloud==0.8.1']
CONF_INTERVAL = 'interval'
DEFAULT_INTERVAL = 8
def setup_scanner(hass, config, see):
""" Set up the iCloud Scanner. """
"""Setup the iCloud Scanner."""
from pyicloud import PyiCloudService
from pyicloud.exceptions import PyiCloudFailedLoginException
from pyicloud.exceptions import PyiCloudNoDevicesException
# Get the username and password from the configuration
# Get the username and password from the configuration.
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
@@ -45,14 +43,14 @@ def setup_scanner(hass, config, see):
return False
def keep_alive(now):
""" Keeps authenticating iCloud connection. """
"""Keep authenticating iCloud connection."""
api.authenticate()
_LOGGER.info("Authenticate against iCloud")
track_utc_time_change(hass, keep_alive, second=0)
def update_icloud(now):
""" Authenticate against iCloud and scan for devices. """
"""Authenticate against iCloud and scan for devices."""
try:
# The session timeouts if we are not using it so we
# have to re-authenticate. This will send an email.
@@ -1,7 +1,5 @@
"""
homeassistant.components.device_tracker.locative
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Locative platform for the device tracker.
Support for the Locative platform.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.locative/
@@ -20,12 +18,10 @@ URL_API_LOCATIVE_ENDPOINT = "/api/locative"
def setup_scanner(hass, config, see):
""" Set up an endpoint for the Locative app. """
"""Setup an endpoint for the Locative application."""
# POST would be semantically better, but that currently does not work
# since Locative sends the data as key1=value1&key2=value2
# in the request body, while Home Assistant expects json there.
hass.http.register_path(
'GET', URL_API_LOCATIVE_ENDPOINT,
partial(_handle_get_api_locative, hass, see))
@@ -34,8 +30,7 @@ def setup_scanner(hass, config, see):
def _handle_get_api_locative(hass, see, handler, path_match, data):
""" Locative message received. """
"""Locative message received."""
if not _check_data(handler, data):
return
@@ -76,6 +71,7 @@ def _handle_get_api_locative(hass, see, handler, path_match, data):
def _check_data(handler, data):
"""Check the data."""
if 'latitude' not in data or 'longitude' not in data:
handler.write_text("Latitude and longitude not specified.",
HTTP_UNPROCESSABLE_ENTITY)
+12 -27
View File
@@ -1,8 +1,5 @@
"""
homeassistant.components.device_tracker.luci
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Device tracker platform that supports scanning a OpenWRT router for device
presence.
Support for OpenWRT (luci) routers.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.luci/
@@ -20,14 +17,14 @@ from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.helpers import validate_config
from homeassistant.util import Throttle
# Return cached results if last scan was less then this time ago
# Return cached results if last scan was less then this time ago.
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__)
def get_scanner(hass, config):
""" Validates config and returns a Luci scanner. """
"""Validate the configuration and return a Luci scanner."""
if not validate_config(config,
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
_LOGGER):
@@ -40,20 +37,13 @@ def get_scanner(hass, config):
# pylint: disable=too-many-instance-attributes
class LuciDeviceScanner(object):
"""
This class queries a wireless router running OpenWrt firmware
for connected devices. Adapted from Tomato scanner.
"""This class queries a wireless router running OpenWrt firmware.
# opkg install luci-mod-rpc
for this to work on the router.
The API is described here:
http://luci.subsignal.org/trac/wiki/Documentation/JsonRpcHowTo
(Currently, we do only wifi iwscan, and no DHCP lease access.)
Adapted from Tomato scanner.
"""
def __init__(self, config):
"""Initialize the scanner."""
host = config[CONF_HOST]
username, password = config[CONF_USERNAME], config[CONF_PASSWORD]
@@ -70,17 +60,12 @@ 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.
"""
"""Scan for new devices and return a list with found device IDs."""
self._update_info()
return self.last_results
def get_device_name(self, device):
""" Returns the name of the given device or None if we don't know. """
"""Return the name of the given device or None if we don't know."""
with self.lock:
if self.mac2name is None:
url = 'http://{}/cgi-bin/luci/rpc/uci'.format(self.host)
@@ -100,8 +85,8 @@ class LuciDeviceScanner(object):
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
"""
Ensures the information from the Luci router is up to date.
"""Ensure the information from the Luci router is up to date.
Returns boolean if scanning successful.
"""
if not self.success_init:
@@ -127,7 +112,7 @@ class LuciDeviceScanner(object):
def _req_json_rpc(url, method, *args, **kwargs):
""" Perform one JSON RPC operation. """
"""Perform one JSON RPC operation."""
data = json.dumps({'method': method, 'params': args})
try:
res = requests.post(url, data=data, timeout=5, **kwargs)
@@ -157,6 +142,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)
@@ -1,7 +1,5 @@
"""
homeassistant.components.device_tracker.mqtt
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
MQTT platform for the device tracker.
Support for tracking MQTT enabled devices.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.mqtt/
@@ -22,7 +20,7 @@ _LOGGER = logging.getLogger(__name__)
def setup_scanner(hass, config, see):
""" Set up a MQTT tracker. """
"""Setup the MQTT tracker."""
devices = config.get(CONF_DEVICES)
qos = util.convert(config.get(CONF_QOS), int, DEFAULT_QOS)
@@ -34,7 +32,7 @@ def setup_scanner(hass, config, see):
dev_id_lookup = {}
def device_tracker_message_received(topic, payload, qos):
""" MQTT message received. """
"""MQTT message received."""
see(dev_id=dev_id_lookup[topic], location_name=payload)
for dev_id, topic in devices.items():
@@ -1,8 +1,5 @@
"""
homeassistant.components.device_tracker.netgear
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Device tracker platform that supports scanning a Netgear router for device
presence.
Support for Netgear routers.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.netgear/
@@ -15,7 +12,7 @@ from homeassistant.components.device_tracker import DOMAIN
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.util import Throttle
# Return cached results if last scan was less then this time ago
# Return cached results if last scan was less then this time ago.
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__)
@@ -23,7 +20,7 @@ REQUIREMENTS = ['pynetgear==0.3.2']
def get_scanner(hass, config):
""" Validates config and returns a Netgear scanner. """
"""Validate the configuration and returns a Netgear scanner."""
info = config[DOMAIN]
host = info.get(CONF_HOST)
username = info.get(CONF_USERNAME)
@@ -39,9 +36,10 @@ def get_scanner(hass, config):
class NetgearDeviceScanner(object):
""" This class queries a Netgear wireless router using the SOAP-API. """
"""Queries a Netgear wireless router using the SOAP-API."""
def __init__(self, host, username, password):
"""Initialize the scanner."""
import pynetgear
self.last_results = []
@@ -66,15 +64,13 @@ class NetgearDeviceScanner(object):
_LOGGER.error("Failed to Login")
def scan_devices(self):
"""
Scans for new devices and return a list containing found device ids.
"""
"""Scan for new devices and return a list with found device IDs."""
self._update_info()
return (device.mac for device in self.last_results)
def get_device_name(self, mac):
""" Returns the name of the given device or None if we don't know. """
"""Return the name of the given device or None if we don't know."""
try:
return next(device.name for device in self.last_results
if device.mac == mac)
@@ -83,8 +79,8 @@ class NetgearDeviceScanner(object):
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
"""
Retrieves latest information from the Netgear router.
"""Retrieve latest information from the Netgear router.
Returns boolean if scanning successful.
"""
if not self.success_init:
@@ -93,4 +89,9 @@ class NetgearDeviceScanner(object):
with self.lock:
_LOGGER.info("Scanning")
self.last_results = self._api.get_attached_devices() or []
results = self._api.get_attached_devices()
if results is None:
_LOGGER.warning('Error scanning devices')
self.last_results = results or []
@@ -1,7 +1,5 @@
"""
homeassistant.components.device_tracker.nmap
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Device tracker platform that supports scanning a network with nmap.
Support for scanning a network with nmap.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.nmap_scanner/
@@ -26,11 +24,11 @@ _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.3']
REQUIREMENTS = ['python-nmap==0.6.0']
def get_scanner(hass, config):
""" Validates config and returns a Nmap scanner. """
"""Validate the configuration and return a Nmap scanner."""
if not validate_config(config, {DOMAIN: [CONF_HOSTS]},
_LOGGER):
return None
@@ -43,7 +41,7 @@ Device = namedtuple("Device", ["mac", "name", "ip", "last_update"])
def _arp(ip_address):
""" Get the MAC address for a given IP. """
"""Get the MAC address for a given IP."""
cmd = ['arp', '-n', ip_address]
arp = subprocess.Popen(cmd, stdout=subprocess.PIPE)
out, _ = arp.communicate()
@@ -55,9 +53,10 @@ 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):
"""Initialize the scanner."""
self.last_results = []
self.hosts = config[CONF_HOSTS]
@@ -68,17 +67,13 @@ class NmapDeviceScanner(object):
_LOGGER.info("nmap scanner initialized")
def scan_devices(self):
"""
Scans for new devices and return a list containing found device ids.
"""
"""Scan for new devices and return a list with found device IDs."""
self._update_info()
return [device.mac for device in self.last_results]
def get_device_name(self, mac):
""" Returns the name of the given device or None if we don't know. """
"""Return the name of the given device or None if we don't know."""
filter_named = [device.name for device in self.last_results
if device.mac == mac]
@@ -89,8 +84,8 @@ class NmapDeviceScanner(object):
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
"""
Scans the network for devices.
"""Scan the network for devices.
Returns boolean if scanning successful.
"""
_LOGGER.info("Scanning")
@@ -1,7 +1,5 @@
"""
homeassistant.components.device_tracker.owntracks
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
OwnTracks platform for the device tracker.
Support the OwnTracks platform.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.owntracks/
@@ -28,13 +26,15 @@ _LOGGER = logging.getLogger(__name__)
LOCK = threading.Lock()
CONF_MAX_GPS_ACCURACY = 'max_gps_accuracy'
def setup_scanner(hass, config, see):
""" Set up an OwnTracks tracker. """
"""Setup an OwnTracks tracker."""
max_gps_accuracy = config.get(CONF_MAX_GPS_ACCURACY)
def owntracks_location_update(topic, payload, qos):
""" MQTT message received. """
"""MQTT message received."""
# Docs on available data:
# http://owntracks.org/booklet/tech/json/#_typelocation
try:
@@ -45,7 +45,9 @@ def setup_scanner(hass, config, see):
'Unable to parse payload as JSON: %s', payload)
return
if not isinstance(data, dict) or data.get('_type') != 'location':
if (not isinstance(data, dict) or data.get('_type') != 'location') or (
'acc' in data and max_gps_accuracy is not None and data[
'acc'] > max_gps_accuracy):
return
dev_id, kwargs = _parse_see_args(topic, data)
@@ -63,8 +65,7 @@ def setup_scanner(hass, config, see):
def owntracks_event_update(topic, payload, qos):
# pylint: disable=too-many-branches, too-many-statements
""" MQTT event (geofences) received. """
"""MQTT event (geofences) received."""
# Docs on available data:
# http://owntracks.org/booklet/tech/json/#_typetransition
try:
@@ -98,14 +99,11 @@ def setup_scanner(hass, config, see):
_LOGGER.info("Added beacon %s", location)
else:
# Normal region
if not zone.attributes.get('passive'):
kwargs['location_name'] = location
regions = REGIONS_ENTERED[dev_id]
if location not in regions:
regions.append(location)
_LOGGER.info("Enter region %s", location)
_set_gps_from_zone(kwargs, zone)
_set_gps_from_zone(kwargs, location, zone)
see(**kwargs)
see_beacons(dev_id, kwargs)
@@ -120,16 +118,22 @@ def setup_scanner(hass, config, see):
if new_region:
# Exit to previous region
zone = hass.states.get("zone.{}".format(new_region))
if not zone.attributes.get('passive'):
kwargs['location_name'] = new_region
_set_gps_from_zone(kwargs, zone)
_set_gps_from_zone(kwargs, new_region, zone)
_LOGGER.info("Exit to %s", new_region)
see(**kwargs)
see_beacons(dev_id, kwargs)
else:
_LOGGER.info("Exit to GPS")
# Check for GPS accuracy
if not ('acc' in data and
max_gps_accuracy is not None and
data['acc'] > max_gps_accuracy):
see(**kwargs)
see_beacons(dev_id, kwargs)
see(**kwargs)
see_beacons(dev_id, kwargs)
else:
_LOGGER.info("Inaccurate GPS reported")
beacons = MOBILE_BEACONS_ACTIVE[dev_id]
if location in beacons:
@@ -143,8 +147,7 @@ def setup_scanner(hass, config, see):
return
def see_beacons(dev_id, kwargs_param):
""" Set active beacons to the current location """
"""Set active beacons to the current location."""
kwargs = kwargs_param.copy()
# the battery state applies to the tracking device, not the beacon
kwargs.pop('battery', None)
@@ -154,16 +157,13 @@ def setup_scanner(hass, config, see):
see(**kwargs)
mqtt.subscribe(hass, LOCATION_TOPIC, owntracks_location_update, 1)
mqtt.subscribe(hass, EVENT_TOPIC, owntracks_event_update, 1)
return True
def _parse_see_args(topic, data):
""" Parse the OwnTracks location parameters,
into the format see expects. """
"""Parse the OwnTracks location parameters, into the format see expects."""
parts = topic.split('/')
dev_id = '{}_{}'.format(parts[1], parts[2])
host_name = parts[1]
@@ -179,12 +179,12 @@ def _parse_see_args(topic, data):
return dev_id, kwargs
def _set_gps_from_zone(kwargs, zone):
""" Set the see parameters from the zone parameters """
def _set_gps_from_zone(kwargs, location, zone):
"""Set the see parameters from the zone parameters."""
if zone is not None:
kwargs['gps'] = (
zone.attributes['latitude'],
zone.attributes['longitude'])
kwargs['gps_accuracy'] = zone.attributes['radius']
kwargs['location_name'] = location
return kwargs
+15 -20
View File
@@ -1,8 +1,5 @@
"""
homeassistant.components.device_tracker.snmp
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Device tracker platform that supports fetching WiFi associations
through SNMP.
Support for fetching WiFi associations through SNMP.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.snmp/
@@ -17,7 +14,7 @@ from homeassistant.const import CONF_HOST
from homeassistant.helpers import validate_config
from homeassistant.util import Throttle
# Return cached results if last scan was less then this time ago
# Return cached results if last scan was less then this time ago.
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
_LOGGER = logging.getLogger(__name__)
@@ -29,7 +26,7 @@ CONF_BASEOID = "baseoid"
# pylint: disable=unused-argument
def get_scanner(hass, config):
""" Validates config and returns an snmp scanner """
"""Validate the configuration and return an snmp scanner."""
if not validate_config(config,
{DOMAIN: [CONF_HOST, CONF_COMMUNITY, CONF_BASEOID]},
_LOGGER):
@@ -41,10 +38,10 @@ def get_scanner(hass, config):
class SnmpScanner(object):
"""
This class queries any SNMP capable Acces Point for connected devices.
"""
"""Queries any SNMP capable Access Point for connected devices."""
def __init__(self, config):
"""Initialize the scanner."""
from pysnmp.entity.rfc3413.oneliner import cmdgen
self.snmp = cmdgen.CommandGenerator()
@@ -61,25 +58,23 @@ class SnmpScanner(object):
self.success_init = data is not None
def scan_devices(self):
"""
Scans for new devices and return a list containing found device IDs.
"""
"""Scan for new devices and return a list with found device IDs."""
self._update_info()
return [client['mac'] for client in self.last_results]
return [client['mac'] for client in self.last_results
if client.get('mac')]
# Supressing no-self-use warning
# pylint: disable=R0201
def get_device_name(self, device):
""" Returns the name of the given device or None if we don't know. """
"""Return the name of the given device or None if we don't know."""
# We have no names
return None
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
"""
Ensures the information from the WAP is up to date.
Returns boolean if scanning successful.
"""Ensure the information from the WAP is up to date.
Return boolean if scanning successful.
"""
if not self.success_init:
return False
@@ -93,8 +88,7 @@ class SnmpScanner(object):
return True
def get_snmp_data(self):
""" Fetch mac addresses from WAP via SNMP. """
"""Fetch MAC addresses from WAP via SNMP."""
devices = []
errindication, errstatus, errindex, restable = self.snmp.nextCmd(
@@ -111,6 +105,7 @@ class SnmpScanner(object):
for resrow in restable:
for _, val in resrow:
mac = binascii.hexlify(val.asOctets()).decode('utf-8')
_LOGGER.debug('Found mac %s', mac)
mac = ':'.join([mac[i:i+2] for i in range(0, len(mac), 2)])
devices.append({'mac': mac})
return devices
@@ -1,8 +1,5 @@
"""
homeassistant.components.device_tracker.thomson
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Device tracker platform that supports scanning a THOMSON router for device
presence.
Support for THOMSON routers.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.thomson/
@@ -18,7 +15,7 @@ from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.helpers import validate_config
from homeassistant.util import Throttle
# Return cached results if last scan was less then this time ago
# Return cached results if last scan was less then this time ago.
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
_LOGGER = logging.getLogger(__name__)
@@ -35,7 +32,7 @@ _DEVICES_REGEX = re.compile(
# pylint: disable=unused-argument
def get_scanner(hass, config):
""" Validates config and returns a THOMSON scanner. """
"""Validate the configuration and return a THOMSON scanner."""
if not validate_config(config,
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
_LOGGER):
@@ -47,12 +44,10 @@ def get_scanner(hass, config):
class ThomsonDeviceScanner(object):
"""
This class queries a router running THOMSON firmware
for connected devices. Adapted from ASUSWRT scanner.
"""
"""This class queries a router running THOMSON firmware."""
def __init__(self, config):
"""Initialize the scanner."""
self.host = config[CONF_HOST]
self.username = config[CONF_USERNAME]
self.password = config[CONF_PASSWORD]
@@ -61,20 +56,17 @@ class ThomsonDeviceScanner(object):
self.last_results = {}
# Test the router is accessible
# Test the router is accessible.
data = self.get_thomson_data()
self.success_init = data is not None
def scan_devices(self):
""" Scans for new devices and return a
list containing found device ids. """
"""Scan for new devices and return a list with found device IDs."""
self._update_info()
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. """
"""Return 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:
@@ -84,9 +76,9 @@ 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.
"""Ensure the information from the THOMSON router is up to date.
Return boolean if scanning successful.
"""
if not self.success_init:
return False
@@ -97,14 +89,14 @@ class ThomsonDeviceScanner(object):
if not data:
return False
# flag C stands for CONNECTED
# Flag C stands for CONNECTED
active_clients = [client for client in data.values() if
client['status'].find('C') != -1]
self.last_results = active_clients
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,8 +1,5 @@
"""
homeassistant.components.device_tracker.tomato
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Device tracker platform that supports scanning a Tomato router for device
presence.
Support for Tomato routers.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.tomato/
@@ -20,7 +17,7 @@ from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.helpers import validate_config
from homeassistant.util import Throttle
# Return cached results if last scan was less then this time ago
# Return cached results if last scan was less then this time ago.
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
CONF_HTTP_ID = "http_id"
@@ -29,7 +26,7 @@ _LOGGER = logging.getLogger(__name__)
def get_scanner(hass, config):
""" Validates config and returns a Tomato scanner. """
"""Validate the configuration and returns a Tomato scanner."""
if not validate_config(config,
{DOMAIN: [CONF_HOST, CONF_USERNAME,
CONF_PASSWORD, CONF_HTTP_ID]},
@@ -40,14 +37,10 @@ def get_scanner(hass, config):
class TomatoDeviceScanner(object):
""" This class queries a wireless router running Tomato firmware
for connected devices.
A description of the Tomato API can be found on
http://paulusschoutsen.nl/blog/2013/10/tomato-api-documentation/
"""
"""This class queries a wireless router running Tomato firmware."""
def __init__(self, config):
"""Initialize the scanner."""
host, http_id = config[CONF_HOST], config[CONF_HTTP_ID]
username, password = config[CONF_USERNAME], config[CONF_PASSWORD]
@@ -68,16 +61,13 @@ class TomatoDeviceScanner(object):
self.success_init = self._update_tomato_info()
def scan_devices(self):
""" Scans for new devices and return a
list containing found device ids. """
"""Scan for new devices and return a list with found device IDs."""
self._update_tomato_info()
return [item[1] for item in self.last_results['wldev']]
def get_device_name(self, device):
""" Returns the name of the given device or None if we don't know. """
"""Return the name of the given device or None if we don't know."""
filter_named = [item[0] for item in self.last_results['dhcpd_lease']
if item[2] == device]
@@ -88,19 +78,17 @@ class TomatoDeviceScanner(object):
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_tomato_info(self):
""" Ensures the information from the Tomato router is up to date.
Returns boolean if scanning successful. """
"""Ensure the information from the Tomato router is up to date.
Return boolean if scanning successful.
"""
with self.lock:
self.logger.info("Scanning")
try:
response = requests.Session().send(self.req, timeout=3)
# Calling and parsing the Tomato api here. We only need the
# wldev and dhcpd_lease values. For API description see:
# http://paulusschoutsen.nl/
# blog/2013/10/tomato-api-documentation/
# wldev and dhcpd_lease values.
if response.status_code == 200:
for param, value in \
@@ -109,7 +97,6 @@ class TomatoDeviceScanner(object):
if param == 'wldev' or param == 'dhcpd_lease':
self.last_results[param] = \
json.loads(value.replace("'", '"'))
return True
elif response.status_code == 401:
@@ -117,29 +104,25 @@ class TomatoDeviceScanner(object):
self.logger.exception((
"Failed to authenticate, "
"please check your username and password"))
return False
except requests.exceptions.ConnectionError:
# We get this if we could not connect to the router or
# an invalid http_id was supplied
# an invalid http_id was supplied.
self.logger.exception((
"Failed to connect to the router"
" or invalid http_id supplied"))
return False
except requests.exceptions.Timeout:
# We get this if we could not connect to the router or
# an invalid http_id was supplied
# an invalid http_id was supplied.
self.logger.exception(
"Connection to the router timed out")
return False
except ValueError:
# If json decoder could not parse the response
# If JSON decoder could not parse the response.
self.logger.exception(
"Failed to parse response from router")
return False
+110 -62
View File
@@ -1,13 +1,11 @@
"""
homeassistant.components.device_tracker.tplink
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Device tracker platform that supports scanning a TP-Link router for device
presence.
Support for TP-Link routers.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.tplink/
"""
import base64
import hashlib
import logging
import re
import threading
@@ -27,30 +25,31 @@ _LOGGER = logging.getLogger(__name__)
def get_scanner(hass, config):
""" Validates config and returns a TP-Link scanner. """
"""Validate the configuration and return a TP-Link scanner."""
if not validate_config(config,
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
_LOGGER):
return None
scanner = Tplink3DeviceScanner(config[DOMAIN])
scanner = Tplink4DeviceScanner(config[DOMAIN])
if not scanner.success_init:
scanner = Tplink3DeviceScanner(config[DOMAIN])
if not scanner.success_init:
scanner = Tplink2DeviceScanner(config[DOMAIN])
if not scanner.success_init:
scanner = TplinkDeviceScanner(config[DOMAIN])
if not scanner.success_init:
scanner = TplinkDeviceScanner(config[DOMAIN])
return scanner if scanner.success_init else None
class TplinkDeviceScanner(object):
"""
This class queries a wireless router running TP-Link firmware
for connected devices.
"""
"""This class queries a wireless router running TP-Link firmware."""
def __init__(self, config):
"""Initialize the scanner."""
host = config[CONF_HOST]
username, password = config[CONF_USERNAME], config[CONF_PASSWORD]
@@ -66,29 +65,21 @@ 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.
"""
"""Scan for new devices and return a list with found device IDs."""
self._update_info()
return self.last_results
# 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 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.
"""
"""Ensure the information from the TP-Link router is up to date.
Return boolean if scanning successful.
"""
with self.lock:
_LOGGER.info("Loading wireless clients...")
@@ -107,34 +98,24 @@ class TplinkDeviceScanner(object):
class Tplink2DeviceScanner(TplinkDeviceScanner):
"""
This class queries a wireless router running newer version of TP-Link
firmware for connected devices.
"""
"""This class queries a router with newer version of TP-Link firmware."""
def scan_devices(self):
"""
Scans for new devices and return a list containing found device ids.
"""
"""Scan for new devices and return a list with 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 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.
"""
"""Ensure the information from the TP-Link router is up to date.
Return boolean if scanning successful.
"""
with self.lock:
_LOGGER.info("Loading wireless clients...")
@@ -172,46 +153,36 @@ class Tplink2DeviceScanner(TplinkDeviceScanner):
class Tplink3DeviceScanner(TplinkDeviceScanner):
"""
This class queries the Archer C9 router running version 150811 or higher
of TP-Link firmware for connected devices.
"""
"""This class queries the Archer C9 router with version 150811 or high."""
def __init__(self, config):
"""Initialize the scanner."""
self.stok = ''
self.sysauth = ''
super(Tplink3DeviceScanner, self).__init__(config)
def scan_devices(self):
"""
Scans for new devices and return a list containing found device ids.
"""
"""Scan for new devices and return a list with 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 firmware doesn't save the name of the wireless device.
We are forced to use the MAC address as name here.
"""
return self.last_results.get(device)
def _get_auth_tokens(self):
"""
Retrieves auth tokens from the router.
"""
"""Retrieve auth tokens from the router."""
_LOGGER.info("Retrieving auth tokens...")
url = 'http://{}/cgi-bin/luci/;stok=/login?form=login' \
.format(self.host)
referer = 'http://{}/webpages/login.html'.format(self.host)
# if possible implement rsa encryption of password here
# If possible implement rsa encryption of password here.
response = requests.post(url,
params={'operation': 'login',
'username': self.username,
@@ -232,11 +203,10 @@ class Tplink3DeviceScanner(TplinkDeviceScanner):
@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.
"""
"""Ensure the information from the TP-Link router is up to date.
Return boolean if scanning successful.
"""
with self.lock:
if (self.stok == '') or (self.sysauth == ''):
self._get_auth_tokens()
@@ -281,3 +251,81 @@ class Tplink3DeviceScanner(TplinkDeviceScanner):
return True
return False
class Tplink4DeviceScanner(TplinkDeviceScanner):
"""This class queries an Archer C7 router with TP-Link firmware 150427."""
def __init__(self, config):
"""Initialize the scanner."""
self.credentials = ''
self.token = ''
super(Tplink4DeviceScanner, self).__init__(config)
def scan_devices(self):
"""Scan for new devices and return a list with found device IDs."""
self._update_info()
return self.last_results
# pylint: disable=no-self-use
def get_device_name(self, device):
"""The firmware doesn't save the name of the wireless device."""
return None
def _get_auth_tokens(self):
"""Retrieve auth tokens from the router."""
_LOGGER.info("Retrieving auth tokens...")
url = 'http://{}/userRpm/LoginRpm.htm?Save=Save'.format(self.host)
# Generate md5 hash of password
password = hashlib.md5(self.password.encode('utf')).hexdigest()
credentials = '{}:{}'.format(self.username, password).encode('utf')
# Encode the credentials to be sent as a cookie.
self.credentials = base64.b64encode(credentials).decode('utf')
# Create the authorization cookie.
cookie = 'Authorization=Basic {}'.format(self.credentials)
response = requests.get(url, headers={'cookie': cookie})
try:
result = re.search(r'window.parent.location.href = '
r'"https?:\/\/.*\/(.*)\/userRpm\/Index.htm";',
response.text)
if not result:
return False
self.token = result.group(1)
return True
except ValueError:
_LOGGER.error("Couldn't fetch auth tokens!")
return False
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
"""Ensure the information from the TP-Link router is up to date.
Return boolean if scanning successful.
"""
with self.lock:
if (self.credentials == '') or (self.token == ''):
self._get_auth_tokens()
_LOGGER.info("Loading wireless clients...")
url = 'http://{}/{}/userRpm/WlanStationRpm.htm' \
.format(self.host, self.token)
referer = 'http://{}'.format(self.host)
cookie = 'Authorization=Basic {}'.format(self.credentials)
page = requests.get(url, headers={
'cookie': cookie,
'referer': referer
})
result = self.parse_macs.findall(page.text)
if not result:
return False
self.last_results = [mac.replace("-", ":") for mac in result]
return True
+12 -31
View File
@@ -1,8 +1,5 @@
"""
homeassistant.components.device_tracker.ubus
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Device tracker platform that supports scanning a OpenWRT router for device
presence.
Support for OpenWRT (ubus) routers.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.ubus/
@@ -20,14 +17,14 @@ from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.helpers import validate_config
from homeassistant.util import Throttle
# Return cached results if last scan was less then this time ago
# Return cached results if last scan was less then this time ago.
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__)
def get_scanner(hass, config):
""" Validates config and returns a Luci scanner. """
"""Validate the configuration and return an ubus scanner."""
if not validate_config(config,
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
_LOGGER):
@@ -41,23 +38,13 @@ def get_scanner(hass, config):
# pylint: disable=too-many-instance-attributes
class UbusDeviceScanner(object):
"""
This class queries a wireless router running OpenWrt firmware
for connected devices. Adapted from Tomato scanner.
Configure your routers' ubus ACL based on following instructions:
http://wiki.openwrt.org/doc/techref/ubus
Read only access will be fine.
To use this class you have to install rpcd-mod-file package
in your OpenWrt router:
opkg install rpcd-mod-file
This class queries a wireless router running OpenWrt firmware.
Adapted from Tomato scanner.
"""
def __init__(self, config):
"""Initialize the scanner."""
host = config[CONF_HOST]
username, password = config[CONF_USERNAME], config[CONF_PASSWORD]
@@ -73,17 +60,12 @@ class UbusDeviceScanner(object):
self.success_init = self.session_id is not None
def scan_devices(self):
"""
Scans for new devices and return a list containing found device ids.
"""
"""Scan for new devices and return a list with found device IDs."""
self._update_info()
return self.last_results
def get_device_name(self, device):
""" Returns the name of the given device or None if we don't know. """
"""Return the name of the given device or None if we don't know."""
with self.lock:
if self.leasefile is None:
result = _req_json_rpc(self.url, self.session_id,
@@ -112,8 +94,8 @@ class UbusDeviceScanner(object):
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
"""
Ensures the information from the Luci router is up to date.
"""Ensure the information from the Luci router is up to date.
Returns boolean if scanning successful.
"""
if not self.success_init:
@@ -141,8 +123,7 @@ class UbusDeviceScanner(object):
def _req_json_rpc(url, session_id, rpcmethod, subsystem, method, **params):
""" Perform one JSON RPC operation. """
"""Perform one JSON RPC operation."""
data = json.dumps({"jsonrpc": "2.0",
"id": 1,
"method": rpcmethod,
@@ -167,7 +148,7 @@ def _req_json_rpc(url, session_id, rpcmethod, subsystem, method, **params):
def _get_session_id(url, username, password):
""" Get authentication token for the given host+username+password. """
"""Get the authentication token for the given host+username+password."""
res = _req_json_rpc(url, "00000000000000000000000000000000", 'call',
'session', 'login', username=username,
password=password)
@@ -1,7 +1,8 @@
"""
homeassistant.components.device_tracker.unifi
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Device tracker platform that supports scanning a Unifi WAP controller
Support for Unifi WAP controllers.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.unifi/
"""
import logging
import urllib
@@ -17,7 +18,7 @@ CONF_PORT = 'port'
def get_scanner(hass, config):
""" Sets up unifi device_tracker """
"""Setup Unifi device_tracker."""
from unifi.controller import Controller
if not validate_config(config, {DOMAIN: [CONF_USERNAME,
@@ -50,10 +51,12 @@ class UnifiScanner(object):
"""Provide device_tracker support from Unifi WAP client data."""
def __init__(self, controller):
"""Initialize the scanner."""
self._controller = controller
self._update()
def _update(self):
"""Get the clients from the device."""
try:
clients = self._controller.get_clients()
except urllib.error.HTTPError as ex:
@@ -63,12 +66,12 @@ class UnifiScanner(object):
self._clients = {client['mac']: client for client in clients}
def scan_devices(self):
""" Scans for devices. """
"""Scan for devices."""
self._update()
return self._clients.keys()
def get_device_name(self, mac):
""" Returns the name (if known) of the device.
"""Return the name (if known) of the device.
If a name has been set in Unifi, then return that, else
return the hostname if it has been detected.
+8 -9
View File
@@ -15,7 +15,7 @@ from homeassistant.const import (
EVENT_PLATFORM_DISCOVERED)
DOMAIN = "discovery"
REQUIREMENTS = ['netdisco==0.5.2']
REQUIREMENTS = ['netdisco==0.5.5']
SCAN_INTERVAL = 300 # seconds
@@ -25,6 +25,7 @@ SERVICE_CAST = 'google_cast'
SERVICE_NETGEAR = 'netgear_router'
SERVICE_SONOS = 'sonos'
SERVICE_PLEX = 'plex_mediaserver'
SERVICE_SQUEEZEBOX = 'logitech_mediaserver'
SERVICE_HANDLERS = {
SERVICE_WEMO: "wemo",
@@ -33,6 +34,7 @@ SERVICE_HANDLERS = {
SERVICE_NETGEAR: 'device_tracker',
SERVICE_SONOS: 'media_player',
SERVICE_PLEX: 'media_player',
SERVICE_SQUEEZEBOX: 'media_player',
}
@@ -55,10 +57,7 @@ def listen(hass, service, callback):
def discover(hass, service, discovered=None, component=None, hass_config=None):
"""Fire discovery event.
Can ensure a component is loaded.
"""
"""Fire discovery event. Can ensure a component is loaded."""
if component is not None:
bootstrap.setup_component(hass, component, hass_config)
@@ -73,7 +72,7 @@ def discover(hass, service, discovered=None, component=None, hass_config=None):
def setup(hass, config):
""" Starts a discovery service. """
"""Start a discovery service."""
logger = logging.getLogger(__name__)
from netdisco.service import DiscoveryService
@@ -84,13 +83,13 @@ def setup(hass, config):
lock = threading.Lock()
def new_service_listener(service, info):
""" Called when a new service is found. """
"""Called when a new service is found."""
with lock:
logger.info("Found new service: %s %s", service, info)
component = SERVICE_HANDLERS.get(service)
# We do not know how to handle this service
# We do not know how to handle this service.
if not component:
return
@@ -105,7 +104,7 @@ def setup(hass, config):
# pylint: disable=unused-argument
def start_discovery(event):
""" Start discovering. """
"""Start discovering."""
netdisco = DiscoveryService(SCAN_INTERVAL)
netdisco.add_listener(new_service_listener)
netdisco.start()
+4 -8
View File
@@ -1,7 +1,5 @@
"""
homeassistant.components.downloader
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides functionality to download files.
Support for functionality to download files.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/downloader/
@@ -28,8 +26,7 @@ CONF_DOWNLOAD_DIR = 'download_dir'
# pylint: disable=too-many-branches
def setup(hass, config):
""" Listens for download events to download files. """
"""Listen for download events to download files."""
logger = logging.getLogger(__name__)
if not validate_config(config, {DOMAIN: [CONF_DOWNLOAD_DIR]}, logger):
@@ -50,14 +47,13 @@ def setup(hass, config):
return False
def download_file(service):
""" Starts thread to download file specified in the url. """
"""Start thread to download file specified in the URL."""
if ATTR_URL not in service.data:
logger.error("Service called but 'url' parameter not specified.")
return
def do_download():
""" Downloads the file. """
"""Download the file."""
try:
url = service.data[ATTR_URL]
+10 -11
View File
@@ -1,7 +1,5 @@
"""
homeassistant.components.ecobee
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Ecobee component
Support for Ecobee.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/ecobee/
@@ -31,12 +29,12 @@ _LOGGER = logging.getLogger(__name__)
ECOBEE_CONFIG_FILE = 'ecobee.conf'
_CONFIGURING = {}
# Return cached results if last scan was less then this time ago
# Return cached results if last scan was less then this time ago.
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=180)
def request_configuration(network, hass, config):
""" Request configuration steps from the user. """
"""Request configuration steps from the user."""
configurator = get_component('configurator')
if 'ecobee' in _CONFIGURING:
configurator.notify_errors(
@@ -46,7 +44,7 @@ def request_configuration(network, hass, config):
# pylint: disable=unused-argument
def ecobee_configuration_callback(callback_data):
""" Actions to do when our configuration callback is called. """
"""The actions to do when our configuration callback is called."""
network.request_tokens()
network.update()
setup_ecobee(hass, network, config)
@@ -62,7 +60,7 @@ def request_configuration(network, hass, config):
def setup_ecobee(hass, network, config):
""" Setup Ecobee thermostat. """
"""Setup Ecobee thermostat."""
# If ecobee has a PIN then it needs to be configured.
if network.pin is not None:
request_configuration(network, hass, config)
@@ -93,22 +91,23 @@ def setup_ecobee(hass, network, config):
# pylint: disable=too-few-public-methods
class EcobeeData(object):
""" Gets the latest data and update the states. """
"""Get the latest data and update the states."""
def __init__(self, config_file):
"""Initialize the Ecobee data object."""
from pyecobee import Ecobee
self.ecobee = Ecobee(config_file)
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
""" Get the latest data from pyecobee. """
"""Get the latest data from pyecobee."""
self.ecobee.update()
_LOGGER.info("ecobee data updated successfully.")
def setup(hass, config):
"""
Setup Ecobee.
"""Setup Ecobee.
Will automatically load thermostat and sensor components to support
devices discovered on the network.
"""
+7 -14
View File
@@ -1,9 +1,4 @@
"""
homeassistant.components.frontend
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides a frontend for Home Assistant.
"""
"""Handle the frontend for Home Assistant."""
import re
import os
import logging
@@ -32,7 +27,7 @@ _FINGERPRINT = re.compile(r'^(\w+)-[a-z0-9]{32}\.(\w+)$', re.IGNORECASE)
def setup(hass, config):
""" Setup serving the frontend. """
"""Setup serving the frontend."""
for url in FRONTEND_URLS:
hass.http.register_path('GET', url, _handle_get_root, False)
@@ -58,7 +53,7 @@ def setup(hass, config):
def _handle_get_api_bootstrap(handler, path_match, data):
""" Returns all data needed to bootstrap Home Assistant. """
"""Return all data needed to bootstrap Home Assistant."""
hass = handler.server.hass
handler.write_json({
@@ -70,7 +65,7 @@ def _handle_get_api_bootstrap(handler, path_match, data):
def _handle_get_root(handler, path_match, data):
""" Renders the frontend. """
"""Render the frontend."""
handler.send_response(HTTP_OK)
handler.send_header('Content-type', 'text/html; charset=utf-8')
handler.end_headers()
@@ -95,7 +90,7 @@ def _handle_get_root(handler, path_match, data):
def _handle_get_service_worker(handler, path_match, data):
""" Returns service worker for the frontend. """
"""Return service worker for the frontend."""
if handler.server.development:
sw_path = "home-assistant-polymer/build/service_worker.js"
else:
@@ -106,7 +101,7 @@ def _handle_get_service_worker(handler, path_match, data):
def _handle_get_static(handler, path_match, data):
""" Returns a static file for the frontend. """
"""Return a static file for the frontend."""
req_file = util.sanitize_path(path_match.group('file'))
# Strip md5 hash out
@@ -120,9 +115,7 @@ def _handle_get_static(handler, path_match, data):
def _handle_get_local(handler, path_match, data):
"""
Returns a static file from the hass.config.path/www for the frontend.
"""
"""Return a static file from the hass.config.path/www for the frontend."""
req_file = util.sanitize_path(path_match.group('file'))
path = handler.server.hass.config.path('www', req_file)
@@ -1,2 +1,2 @@
""" DO NOT MODIFY. Auto-generated by update_mdi script """
VERSION = "2f4adc5d3ad6d2f73bf69ed29b7594fd"
"""DO NOT MODIFY. Auto-generated by update_mdi script."""
VERSION = "df49e6b7c930eb39b42ff1909712e95e"
+2 -2
View File
@@ -1,2 +1,2 @@
""" DO NOT MODIFY. Auto-generated by build_frontend script """
VERSION = "a4d021cb50ed079fcfda7369ed2f0d4a"
"""DO NOT MODIFY. Auto-generated by build_frontend script."""
VERSION = "49974cb3bb443751f7548e4e3b353304"
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -33,13 +33,13 @@ _LOGGER = logging.getLogger(__name__)
def is_closed(hass, entity_id=None):
"""Returns if the garage door is closed based on the statemachine."""
"""Return if the garage door is closed based on the statemachine."""
entity_id = entity_id or ENTITY_ID_ALL_GARAGE_DOORS
return hass.states.is_state(entity_id, STATE_CLOSED)
def close_door(hass, entity_id=None):
"""Closes all or specified garage door."""
"""Close all or a specified garage door."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
hass.services.call(DOMAIN, SERVICE_CLOSE, data)
@@ -58,7 +58,7 @@ def setup(hass, config):
component.setup(config)
def handle_garage_door_service(service):
"""Handles calls to the garage door services."""
"""Handle calls to the garage door services."""
target_locks = component.extract_from_service(service)
for item in target_locks:
@@ -81,7 +81,8 @@ def setup(hass, config):
class GarageDoorDevice(Entity):
"""Represents a garage door."""
"""Representation of a garage door."""
# pylint: disable=no-self-use
@property
def is_closed(self):
@@ -98,7 +99,7 @@ class GarageDoorDevice(Entity):
@property
def state(self):
"""Returns the state of the garage door."""
"""Return the state of the garage door."""
closed = self.is_closed
if closed is None:
return STATE_UNKNOWN
@@ -19,7 +19,9 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
class DemoGarageDoor(GarageDoorDevice):
"""Provides a demo garage door."""
def __init__(self, name, state):
"""Initialize the garage door."""
self._name = name
self._state = state
+13 -7
View File
@@ -9,11 +9,11 @@ import logging
from homeassistant.components.garage_door import GarageDoorDevice
from homeassistant.const import CONF_ACCESS_TOKEN
REQUIREMENTS = ['python-wink==0.6.2']
REQUIREMENTS = ['python-wink==0.6.4']
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Sets up the Wink garage door platform."""
"""Setup the Wink garage door platform."""
import pywink
if discovery_info is None:
@@ -32,19 +32,20 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class WinkGarageDoorDevice(GarageDoorDevice):
"""Represents a Wink garage door."""
"""Representation of a Wink garage door."""
def __init__(self, wink):
"""Initialize the garage door."""
self.wink = wink
@property
def unique_id(self):
"""Returns the id of this wink garage door."""
"""Return the ID of this wink garage door."""
return "{}.{}".format(self.__class__, self.wink.device_id())
@property
def name(self):
"""Returns the name of the garage door if any."""
"""Return the name of the garage door if any."""
return self.wink.name()
def update(self):
@@ -53,11 +54,16 @@ class WinkGarageDoorDevice(GarageDoorDevice):
@property
def is_closed(self):
"""Returns true if door is closed."""
"""Return true if door is closed."""
return self.wink.state() == 0
@property
def available(self):
"""True if connection == True."""
return self.wink.available
def close_door(self):
"""Closes the door."""
"""Close the door."""
self.wink.set_state(0)
def open_door(self):
+4 -3
View File
@@ -1,6 +1,5 @@
"""
Component that records all events and state changes and feeds the data to
a Graphite installation.
Component that sends data to aGraphite installation.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/graphite/
@@ -35,8 +34,10 @@ def setup(hass, config):
class GraphiteFeeder(threading.Thread):
"""Feeds data to Graphite."""
"""Feed data to Graphite."""
def __init__(self, hass, host, port, prefix):
"""Initialize the feeder."""
super(GraphiteFeeder, self).__init__(daemon=True)
self._hass = hass
self._host = host
+64 -54
View File
@@ -1,11 +1,11 @@
"""
homeassistant.components.group
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides functionality to group devices that can be turned on or off.
Provides functionality to group entities.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/group/
"""
import threading
import homeassistant.core as ha
from homeassistant.const import (
ATTR_ENTITY_ID, CONF_ICON, CONF_NAME, STATE_CLOSED, STATE_HOME,
@@ -32,7 +32,7 @@ _GROUP_TYPES = [(STATE_ON, STATE_OFF), (STATE_HOME, STATE_NOT_HOME),
def _get_group_on_off(state):
""" Determine the group on/off states based on a state. """
"""Determine the group on/off states based on a state."""
for states in _GROUP_TYPES:
if state in states:
return states
@@ -41,7 +41,7 @@ def _get_group_on_off(state):
def is_on(hass, entity_id):
""" Returns if the group state is in its ON-state. """
"""Test if the group state is in its ON-state."""
state = hass.states.get(entity_id)
if state:
@@ -54,8 +54,7 @@ def is_on(hass, entity_id):
def expand_entity_ids(hass, entity_ids):
""" Returns the given list of entity ids and expands group ids into
the entity ids it represents if found. """
"""Return entity_ids with group entity ids replaced by their members."""
found_ids = []
for entity_id in entity_ids:
@@ -86,7 +85,7 @@ def expand_entity_ids(hass, entity_ids):
def get_entity_ids(hass, entity_id, domain_filter=None):
""" Get the entity ids that make up this group. """
"""Get members of this group."""
entity_id = entity_id.lower()
try:
@@ -107,7 +106,7 @@ def get_entity_ids(hass, entity_id, domain_filter=None):
def setup(hass, config):
""" Sets up all groups found definded in the configuration. """
"""Setup all groups found definded in the configuration."""
for object_id, conf in config.get(DOMAIN, {}).items():
if not isinstance(conf, dict):
conf = {CONF_ENTITIES: conf}
@@ -127,12 +126,12 @@ def setup(hass, config):
class Group(Entity):
""" Tracks a group of entity ids. """
"""Track a group of entity ids."""
# pylint: disable=too-many-instance-attributes, too-many-arguments
def __init__(self, hass, name, entity_ids=None, user_defined=True,
icon=None, view=False, object_id=None):
"""Initialize a group."""
self.hass = hass
self._name = name
self._state = STATE_UNKNOWN
@@ -146,6 +145,7 @@ class Group(Entity):
self.group_on = None
self.group_off = None
self._assumed_state = False
self._lock = threading.Lock()
if entity_ids is not None:
self.update_tracked_entity_ids(entity_ids)
@@ -154,26 +154,32 @@ class Group(Entity):
@property
def should_poll(self):
"""No need to poll because groups will update themselves."""
return False
@property
def name(self):
"""Return the name of the group."""
return self._name
@property
def state(self):
"""Return the state of the group."""
return self._state
@property
def icon(self):
"""Return the icon of the group."""
return self._icon
@property
def hidden(self):
"""If group should be hidden or not."""
return not self._user_defined or self._view
@property
def state_attributes(self):
"""Return the state attributes for the group."""
data = {
ATTR_ENTITY_ID: self.tracking,
ATTR_ORDER: self._order,
@@ -186,11 +192,11 @@ class Group(Entity):
@property
def assumed_state(self):
"""Return True if unable to access real state of entity."""
"""Test if any member has an assumed state."""
return self._assumed_state
def update_tracked_entity_ids(self, entity_ids):
""" Update the tracked entity IDs. """
"""Update the member entity IDs."""
self.stop()
self.tracking = tuple(ent_id.lower() for ent_id in entity_ids)
self.group_on, self.group_off = None, None
@@ -200,30 +206,30 @@ class Group(Entity):
self.start()
def start(self):
""" Starts the tracking. """
"""Start tracking members."""
track_state_change(
self.hass, self.tracking, self._state_changed_listener)
def stop(self):
""" Unregisters the group from Home Assistant. """
"""Unregister the group from Home Assistant."""
self.hass.states.remove(self.entity_id)
self.hass.bus.remove_listener(
ha.EVENT_STATE_CHANGED, self._state_changed_listener)
def update(self):
""" Query all the tracked states and determine current group state. """
"""Query all members and determine current group state."""
self._state = STATE_UNKNOWN
self._update_group_state()
def _state_changed_listener(self, entity_id, old_state, new_state):
""" Listener to receive state changes of tracked entities. """
"""Respond to a member state changing."""
self._update_group_state(new_state)
self.update_ha_state()
@property
def _tracking_states(self):
"""States that the group is tracking."""
"""The states that the group is tracking."""
states = []
for entity_id in self.tracking:
@@ -242,49 +248,53 @@ class Group(Entity):
"""
# pylint: disable=too-many-branches
# To store current states of group entities. Might not be needed.
states = None
gr_state, gr_on, gr_off = self._state, self.group_on, self.group_off
with self._lock:
states = None
gr_state = self._state
gr_on = self.group_on
gr_off = self.group_off
# We have not determined type of group yet
if gr_on is None:
if tr_state is None:
states = self._tracking_states
# We have not determined type of group yet
if gr_on is None:
if tr_state is None:
states = self._tracking_states
for state in states:
gr_on, gr_off = \
_get_group_on_off(state.state)
if gr_on is not None:
break
else:
gr_on, gr_off = _get_group_on_off(tr_state.state)
for state in states:
gr_on, gr_off = \
_get_group_on_off(state.state)
if gr_on is not None:
break
else:
gr_on, gr_off = _get_group_on_off(tr_state.state)
if gr_on is not None:
self.group_on, self.group_off = gr_on, gr_off
if gr_on is not None:
self.group_on, self.group_off = gr_on, gr_off
# We cannot determine state of the group
if gr_on is None:
return
# We cannot determine state of the group
if gr_on is None:
return
if tr_state is None or (gr_state == gr_on and
tr_state.state == gr_off):
if states is None:
states = self._tracking_states
if tr_state is None or (gr_state == gr_on and
tr_state.state == gr_off):
if states is None:
states = self._tracking_states
if any(state.state == gr_on for state in states):
self._state = gr_on
else:
self._state = gr_off
if any(state.state == gr_on for state in states):
self._state = gr_on
else:
self._state = gr_off
elif tr_state.state in (gr_on, gr_off):
self._state = tr_state.state
elif tr_state.state in (gr_on, gr_off):
self._state = tr_state.state
if tr_state is None or self._assumed_state and \
not tr_state.attributes.get(ATTR_ASSUMED_STATE):
if states is None:
states = self._tracking_states
if tr_state is None or self._assumed_state and \
not tr_state.attributes.get(ATTR_ASSUMED_STATE):
if states is None:
states = self._tracking_states
self._assumed_state = any(state.attributes.get(ATTR_ASSUMED_STATE)
for state in states)
self._assumed_state = any(
state.attributes.get(ATTR_ASSUMED_STATE) for state
in states)
elif tr_state.attributes.get(ATTR_ASSUMED_STATE):
self._assumed_state = True
elif tr_state.attributes.get(ATTR_ASSUMED_STATE):
self._assumed_state = True
+28 -20
View File
@@ -1,6 +1,4 @@
"""
homeassistant.components.history
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provide pre-made queries on top of the recorder component.
For more details about this component, please refer to the documentation at
@@ -11,7 +9,7 @@ from collections import defaultdict
from datetime import timedelta
from itertools import groupby
import homeassistant.components.recorder as recorder
from homeassistant.components import recorder, script
import homeassistant.util.dt as dt_util
from homeassistant.const import HTTP_BAD_REQUEST
@@ -19,13 +17,14 @@ DOMAIN = 'history'
DEPENDENCIES = ['recorder', 'http']
SIGNIFICANT_DOMAINS = ('thermostat',)
IGNORE_DOMAINS = ('zone', 'scene',)
URL_HISTORY_PERIOD = re.compile(
r'/api/history/period(?:/(?P<date>\d{4}-\d{1,2}-\d{1,2})|)')
def last_5_states(entity_id):
""" Return the last 5 states for entity_id. """
"""Return the last 5 states for entity_id."""
entity_id = entity_id.lower()
query = """
@@ -38,17 +37,18 @@ def last_5_states(entity_id):
def get_significant_states(start_time, end_time=None, entity_id=None):
"""Return states changes during UTC period start_time - end_time.
"""
Return states changes during UTC period start_time - end_time.
Significant states are all states where there is a state change,
as well as all states from certain domains (for instance
thermostat so that we get current temperature in our graphs).
"""
where = """
(domain in ({}) or last_changed=last_updated)
AND last_updated > ?
""".format(",".join(["'%s'" % x for x in SIGNIFICANT_DOMAINS]))
(domain IN ({}) OR last_changed=last_updated)
AND domain NOT IN ({}) AND last_updated > ?
""".format(",".join("'%s'" % x for x in SIGNIFICANT_DOMAINS),
",".join("'%s'" % x for x in IGNORE_DOMAINS))
data = [start_time]
@@ -63,15 +63,14 @@ def get_significant_states(start_time, end_time=None, entity_id=None):
query = ("SELECT * FROM states WHERE {} "
"ORDER BY entity_id, last_updated ASC").format(where)
states = recorder.query_states(query, data)
states = (state for state in recorder.query_states(query, data)
if _is_significant(state))
return states_to_json(states, start_time, entity_id)
def state_changes_during_period(start_time, end_time=None, entity_id=None):
"""
Return states changes during UTC period start_time - end_time.
"""
"""Return states changes during UTC period start_time - end_time."""
where = "last_changed=last_updated AND last_changed > ? "
data = [start_time]
@@ -92,7 +91,7 @@ def state_changes_during_period(start_time, end_time=None, entity_id=None):
def get_states(utc_point_in_time, entity_ids=None, run=None):
""" Returns the states at a specific point in time. """
"""Return the states at a specific point in time."""
if run is None:
run = recorder.run_information(utc_point_in_time)
@@ -121,7 +120,7 @@ def get_states(utc_point_in_time, entity_ids=None, run=None):
def states_to_json(states, start_time, entity_id):
"""Converts SQL results into JSON friendly data structure.
"""Convert SQL results into JSON friendly data structure.
This takes our state list and turns it into a JSON friendly data
structure {'entity_id': [list of states], 'entity_id2': [list of states]}
@@ -130,7 +129,6 @@ def states_to_json(states, start_time, entity_id):
each list of states, otherwise our graphs won't start on the Y
axis correctly.
"""
result = defaultdict(list)
entity_ids = [entity_id] if entity_id is not None else None
@@ -148,7 +146,7 @@ def states_to_json(states, start_time, entity_id):
def get_state(utc_point_in_time, entity_id, run=None):
""" Return a state at a specific point in time. """
"""Return a state at a specific point in time."""
states = get_states(utc_point_in_time, (entity_id,), run)
return states[0] if states else None
@@ -156,7 +154,7 @@ def get_state(utc_point_in_time, entity_id, run=None):
# pylint: disable=unused-argument
def setup(hass, config):
""" Setup history hooks. """
"""Setup the history hooks."""
hass.http.register_path(
'GET',
re.compile(
@@ -172,14 +170,14 @@ def setup(hass, config):
# pylint: disable=unused-argument
# pylint: disable=invalid-name
def _api_last_5_states(handler, path_match, data):
""" Return the last 5 states for an entity id as JSON. """
"""Return the last 5 states for an entity id as JSON."""
entity_id = path_match.group('entity_id')
handler.write_json(last_5_states(entity_id))
def _api_history_period(handler, path_match, data):
""" Return history over a period of time. """
"""Return history over a period of time."""
date_str = path_match.group('date')
one_day = timedelta(seconds=86400)
@@ -200,3 +198,13 @@ def _api_history_period(handler, path_match, data):
handler.write_json(
get_significant_states(start_time, end_time, entity_id).values())
def _is_significant(state):
"""Test if state is significant for history charts.
Will only test for things that are not filtered out in SQL.
"""
# scripts that are not cancellable will never change state
return (state.domain != 'script' or
state.attributes.get(script.ATTR_CAN_CANCEL))
+50 -49
View File
@@ -1,6 +1,4 @@
"""
homeassistant.components.http
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This module provides an API and a HTTP interface for debug purposes.
For more details about the RESTful API, please refer to the documentation at
@@ -52,7 +50,7 @@ _LOGGER = logging.getLogger(__name__)
def setup(hass, config):
""" Sets up the HTTP API and debug interface. """
"""Set up the HTTP API and debug interface."""
conf = config.get(DOMAIN, {})
api_password = util.convert(conf.get(CONF_API_PASSWORD), str)
@@ -76,7 +74,8 @@ def setup(hass, config):
hass.bus.listen_once(
ha.EVENT_HOMEASSISTANT_START,
lambda event:
threading.Thread(target=server.start, daemon=True).start())
threading.Thread(target=server.start, daemon=True,
name='HTTP-server').start())
hass.http = server
hass.config.api = rem.API(util.get_local_ip(), api_password, server_port,
@@ -87,15 +86,16 @@ def setup(hass, config):
# pylint: disable=too-many-instance-attributes
class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer):
""" Handle HTTP requests in a threaded fashion. """
# pylint: disable=too-few-public-methods
"""Handle HTTP requests in a threaded fashion."""
# pylint: disable=too-few-public-methods
allow_reuse_address = True
daemon_threads = True
# pylint: disable=too-many-arguments
def __init__(self, server_address, request_handler_class,
hass, api_password, development, ssl_certificate, ssl_key):
"""Initialize the server."""
super().__init__(server_address, request_handler_class)
self.server_address = server_address
@@ -119,9 +119,9 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer):
self.socket = context.wrap_socket(self.socket, server_side=True)
def start(self):
""" Starts the HTTP server. """
"""Start the HTTP server."""
def stop_http(event):
""" Stops the HTTP server. """
"""Stop the HTTP server."""
self.shutdown()
self.hass.bus.listen_once(ha.EVENT_HOMEASSISTANT_STOP, stop_http)
@@ -140,19 +140,18 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer):
self.serve_forever()
def register_path(self, method, url, callback, require_auth=True):
""" Registers a path with the server. """
"""Register a path with the server."""
self.paths.append((method, url, callback, require_auth))
def log_message(self, fmt, *args):
""" Redirect built-in log to HA logging """
"""Redirect built-in log to HA logging."""
# pylint: disable=no-self-use
_LOGGER.info(fmt, *args)
# pylint: disable=too-many-public-methods,too-many-locals
class RequestHandler(SimpleHTTPRequestHandler):
"""
Handles incoming HTTP requests
"""Handle incoming HTTP requests.
We extend from SimpleHTTPRequestHandler instead of Base so we
can use the guess content type methods.
@@ -161,13 +160,13 @@ class RequestHandler(SimpleHTTPRequestHandler):
server_version = "HomeAssistant/1.0"
def __init__(self, req, client_addr, server):
""" Contructor, call the base constructor and set up session """
"""Constructor, call the base constructor and set up session."""
# Track if this was an authenticated request
self.authenticated = False
SimpleHTTPRequestHandler.__init__(self, req, client_addr, server)
def log_message(self, fmt, *arguments):
""" Redirect built-in log to HA logging """
"""Redirect built-in log to HA logging."""
if self.server.api_password is None:
_LOGGER.info(fmt, *arguments)
else:
@@ -176,7 +175,7 @@ class RequestHandler(SimpleHTTPRequestHandler):
if isinstance(arg, str) else arg for arg in arguments))
def _handle_request(self, method): # pylint: disable=too-many-branches
""" Does some common checks and calls appropriate method. """
"""Perform some common checks and call appropriate method."""
url = urlparse(self.path)
# Read query input. parse_qs gives a list for each value, we want last
@@ -238,9 +237,10 @@ class RequestHandler(SimpleHTTPRequestHandler):
# Did we find a handler for the incoming request?
if handle_request_method:
# For some calls we need a valid password
msg = "API password missing or incorrect."
if require_auth and not self.authenticated:
self.write_json_message(
"API password missing or incorrect.", HTTP_UNAUTHORIZED)
self.write_json_message(msg, HTTP_UNAUTHORIZED)
_LOGGER.warning(msg)
return
handle_request_method(self, path_match, data)
@@ -254,33 +254,36 @@ class RequestHandler(SimpleHTTPRequestHandler):
self.end_headers()
def do_HEAD(self): # pylint: disable=invalid-name
""" HEAD request handler. """
"""HEAD request handler."""
self._handle_request('HEAD')
def do_GET(self): # pylint: disable=invalid-name
""" GET request handler. """
"""GET request handler."""
self._handle_request('GET')
def do_POST(self): # pylint: disable=invalid-name
""" POST request handler. """
"""POST request handler."""
self._handle_request('POST')
def do_PUT(self): # pylint: disable=invalid-name
""" PUT request handler. """
"""PUT request handler."""
self._handle_request('PUT')
def do_DELETE(self): # pylint: disable=invalid-name
""" DELETE request handler. """
"""DELETE request handler."""
self._handle_request('DELETE')
def write_json_message(self, message, status_code=HTTP_OK):
""" Helper method to return a message to the caller. """
"""Helper method to return a message to the caller."""
self.write_json({'message': message}, status_code=status_code)
def write_json(self, data=None, status_code=HTTP_OK, location=None):
""" Helper method to return JSON to the caller. """
"""Helper method to return JSON to the caller."""
json_data = json.dumps(data, indent=4, sort_keys=True,
cls=rem.JSONEncoder).encode('UTF-8')
self.send_response(status_code)
self.send_header(HTTP_HEADER_CONTENT_TYPE, CONTENT_TYPE_JSON)
self.send_header(HTTP_HEADER_CONTENT_LENGTH, str(len(json_data)))
if location:
self.send_header('Location', location)
@@ -290,23 +293,23 @@ class RequestHandler(SimpleHTTPRequestHandler):
self.end_headers()
if data is not None:
self.wfile.write(
json.dumps(data, indent=4, sort_keys=True,
cls=rem.JSONEncoder).encode("UTF-8"))
self.wfile.write(json_data)
def write_text(self, message, status_code=HTTP_OK):
""" Helper method to return a text message to the caller. """
"""Helper method to return a text message to the caller."""
msg_data = message.encode('UTF-8')
self.send_response(status_code)
self.send_header(HTTP_HEADER_CONTENT_TYPE, CONTENT_TYPE_TEXT_PLAIN)
self.send_header(HTTP_HEADER_CONTENT_LENGTH, str(len(msg_data)))
self.set_session_cookie_header()
self.end_headers()
self.wfile.write(message.encode("UTF-8"))
self.wfile.write(msg_data)
def write_file(self, path, cache_headers=True):
""" Returns a file to the user. """
"""Return a file to the user."""
try:
with open(path, 'rb') as inp:
self.write_file_pointer(self.guess_type(path), inp,
@@ -318,10 +321,7 @@ class RequestHandler(SimpleHTTPRequestHandler):
_LOGGER.exception("Unable to serve %s", path)
def write_file_pointer(self, content_type, inp, cache_headers=True):
"""
Helper function to write a file pointer to the user.
Does not do error handling.
"""
"""Helper function to write a file pointer to the user."""
do_gzip = 'gzip' in self.headers.get(HTTP_HEADER_ACCEPT_ENCODING, '')
self.send_response(HTTP_OK)
@@ -354,7 +354,7 @@ class RequestHandler(SimpleHTTPRequestHandler):
self.copyfile(inp, self.wfile)
def set_cache_header(self):
""" Add cache headers if not in development """
"""Add cache headers if not in development."""
if self.server.development:
return
@@ -369,7 +369,7 @@ class RequestHandler(SimpleHTTPRequestHandler):
self.date_time_string(time.time()+cache_time))
def set_session_cookie_header(self):
""" Add the header for the session cookie and return session id. """
"""Add the header for the session cookie and return session ID."""
if not self.authenticated:
return None
@@ -387,13 +387,13 @@ class RequestHandler(SimpleHTTPRequestHandler):
return session_id
def verify_session(self):
""" Verify that we are in a valid session. """
"""Verify that we are in a valid session."""
return self.get_cookie_session_id() is not None
def get_cookie_session_id(self):
"""
Extracts the current session id from the
cookie or returns None if not set or invalid
"""Extract the current session ID from the cookie.
Return None if not set or invalid.
"""
if 'Cookie' not in self.headers:
return None
@@ -417,7 +417,7 @@ class RequestHandler(SimpleHTTPRequestHandler):
return None
def destroy_session(self):
""" Destroys session. """
"""Destroy the session."""
session_id = self.get_cookie_session_id()
if session_id is None:
@@ -428,27 +428,28 @@ class RequestHandler(SimpleHTTPRequestHandler):
def session_valid_time():
""" Time till when a session will be valid. """
"""Time till when a session will be valid."""
return date_util.utcnow() + timedelta(seconds=SESSION_TIMEOUT_SECONDS)
class SessionStore(object):
""" Responsible for storing and retrieving http sessions """
"""Responsible for storing and retrieving HTTP sessions."""
def __init__(self):
""" Set up the session store """
"""Setup the session store."""
self._sessions = {}
self._lock = threading.RLock()
@util.Throttle(SESSION_CLEAR_INTERVAL)
def _remove_expired(self):
""" Remove any expired sessions. """
"""Remove any expired sessions."""
now = date_util.utcnow()
for key in [key for key, valid_time in self._sessions.items()
if valid_time < now]:
self._sessions.pop(key)
def is_valid(self, key):
""" Return True if a valid session is given. """
"""Return True if a valid session is given."""
with self._lock:
self._remove_expired()
@@ -456,19 +457,19 @@ class SessionStore(object):
self._sessions[key] > date_util.utcnow())
def extend_validation(self, key):
""" Extend a session validation time. """
"""Extend a session validation time."""
with self._lock:
if key not in self._sessions:
return
self._sessions[key] = session_valid_time()
def destroy(self, key):
""" Destroy a session by key. """
"""Destroy a session by key."""
with self._lock:
self._sessions.pop(key, None)
def create(self):
""" Creates a new session. """
"""Create a new session."""
with self._lock:
session_id = util.get_random_string(20)
+4 -7
View File
@@ -1,7 +1,5 @@
"""
homeassistant.components.ifttt
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This component enable you to trigger Maker IFTTT recipes.
Support to trigger Maker IFTTT recipes.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/ifttt/
@@ -27,7 +25,7 @@ REQUIREMENTS = ['pyfttt==0.3']
def trigger(hass, event, value1=None, value2=None, value3=None):
""" Trigger a Maker IFTTT recipe. """
"""Trigger a Maker IFTTT recipe."""
data = {
ATTR_EVENT: event,
ATTR_VALUE1: value1,
@@ -38,15 +36,14 @@ def trigger(hass, event, value1=None, value2=None, value3=None):
def setup(hass, config):
""" Setup the ifttt service component. """
"""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. """
"""Handle IFTTT trigger service calls."""
event = call.data.get(ATTR_EVENT)
value1 = call.data.get(ATTR_VALUE1)
value2 = call.data.get(ATTR_VALUE2)
+5 -1
View File
@@ -31,8 +31,10 @@ CONF_USERNAME = 'username'
CONF_PASSWORD = 'password'
CONF_SSL = 'ssl'
CONF_VERIFY_SSL = 'verify_ssl'
CONF_BLACKLIST = 'blacklist'
# pylint: disable=too-many-locals
def setup(hass, config):
"""Setup the InfluxDB component."""
from influxdb import InfluxDBClient, exceptions
@@ -52,6 +54,7 @@ def setup(hass, config):
ssl = util.convert(conf.get(CONF_SSL), bool, DEFAULT_SSL)
verify_ssl = util.convert(conf.get(CONF_VERIFY_SSL), bool,
DEFAULT_VERIFY_SSL)
blacklist = conf.get(CONF_BLACKLIST, [])
try:
influx = InfluxDBClient(host=host, port=port, username=username,
@@ -67,7 +70,8 @@ def setup(hass, config):
def influx_event_listener(event):
"""Listen for new messages on the bus and sends them to Influx."""
state = event.data.get('new_state')
if state is None or state.state in (STATE_UNKNOWN, ''):
if state is None or state.state in (STATE_UNKNOWN, '') \
or state.entity_id in blacklist:
return
try:
+8 -10
View File
@@ -1,6 +1,4 @@
"""
homeassistant.components.input_boolean
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Component to keep track of user controlled booleans for within automation.
For more details about this component, please refer to the documentation
@@ -41,7 +39,7 @@ def turn_off(hass, entity_id):
def setup(hass, config):
""" Set up input boolean. """
"""Set up input boolean."""
if not isinstance(config.get(DOMAIN), dict):
_LOGGER.error('Expected %s config to be a dictionary', DOMAIN)
return False
@@ -68,7 +66,7 @@ def setup(hass, config):
return False
def toggle_service(service):
""" Handle a calls to the input boolean services. """
"""Handle a calls to the input boolean services."""
target_inputs = component.extract_from_service(service)
for input_b in target_inputs:
@@ -86,10 +84,10 @@ def setup(hass, config):
class InputBoolean(ToggleEntity):
""" Represent a boolean input. """
"""Representation of a boolean input."""
def __init__(self, object_id, name, state, icon):
""" Initialize a boolean input. """
"""Initialize a boolean input."""
self.entity_id = ENTITY_ID_FORMAT.format(object_id)
self._name = name
self._state = state
@@ -97,22 +95,22 @@ class InputBoolean(ToggleEntity):
@property
def should_poll(self):
"""If entitiy should be polled."""
"""If entity should be polled."""
return False
@property
def name(self):
"""Name of the boolean input."""
"""Return name of the boolean input."""
return self._name
@property
def icon(self):
"""Icon to be used for this entity."""
"""Returh the icon to be used for this entity."""
return self._icon
@property
def is_on(self):
"""True if entity is on."""
"""Return true if entity is on."""
return self._state
def turn_on(self, **kwargs):

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