Compare commits

...

880 Commits
0.17.2 ... 0.25

Author SHA1 Message Date
Paulus Schoutsen
86bbfb00ad Version bump to 0.25 2016-07-30 12:43:40 -07:00
Paulus Schoutsen
06a68d0c62 Merge pull request #2654 from home-assistant/dev
0.25
2016-07-30 11:33:41 -07:00
Paulus Schoutsen
99b27b1ec6 Update frontend 2016-07-30 11:22:44 -07:00
Paulus Schoutsen
1a64f14bea Add commented out default password (#2656) 2016-07-30 10:40:51 -07:00
Fabian Affolter
52a3aa1ca5 Add timeout (fixes #2661) (#2666) 2016-07-30 10:36:56 -07:00
Nolan Gilley
a94e8f48e0 Install mysqlclient and psycopg2 (#2662)
I don't know if this is the right place for this, but I'm tired of having to install mysqlclient or psycopg2 after every docker update if I want to use mysql of postgres.
2016-07-30 10:30:14 -07:00
Scott O'Neil
822a263622 Fixing PEP257 issues in #2633 (#2658) 2016-07-30 10:30:13 +02:00
John
caa7e770be Expand to respond to basic node events (#2615)
Allows zwave devices that can only push out basic set commands to be
captured by hass as zwave.node_events.
2016-07-29 21:56:03 +02:00
Paulus Schoutsen
48fbec0a49 Merge branch 'master' into dev 2016-07-29 12:17:50 -07:00
John Lindley
b5fb382c1c Add group state for locks (#2647)
* Add group state for locks

Added  ", (STATE_LOCKED, STATE_UNLOCKED)" to _GROUP_TYPES

Don't have a working HA right now, so can't test..

* Modified from homeassistant.const import

* Removed white space

* Line length change

* Removed white space.. again!
2016-07-29 11:55:18 -07:00
Paulus Schoutsen
d5e652d244 Update panel.html 2016-07-29 09:28:15 -07:00
Pascal Vizeli
548d154cd8 fix telegram bug (#2653) 2016-07-29 15:20:23 +02:00
Paulus Schoutsen
55624bcff9 Add custom panel example using React (#2651) 2016-07-29 00:49:58 -07:00
Nolan Gilley
3c51d2df0f load the last good state from db if speedtest data is None (#2645)
* load the last good state from db if speedtest data is None

* return if recorder is not available
2016-07-28 20:58:55 -07:00
Dean Camera
ce3c89db6e Add MPC-HC Media Player Component (#2635)
* Initial media_player component for the MPC-HC web API.

* Update .coveragerc to exclude the MPC-HC media player component.

* We don't need a session for every HTTP fetch.

* Use host in configuration YAML to match Kodi component.

* Fix PyLint errors.

* Fix PEP8 errors and use more idiomatic Python to get dict() values.

* Add MPC-HC remote command capabilities for basic control.
2016-07-28 20:54:22 -07:00
Scott O'Neil
bf3c0472bb Adding tests for sonos registration (#2633) 2016-07-28 20:40:58 -07:00
Adam Garcia
6a3c5b093b Update to group component to properly handle zone changes in tracked devices (#2631)
* pep8 fixes for group and test

* update to pass linting

* docstring fix.

* reduced length of docstring on test.
2016-07-28 20:40:25 -07:00
Nolan Gilley
bce4be88dc check for error while running speedtest (#2643) 2016-07-28 09:25:31 -07:00
Paulus Schoutsen
ec8802ec44 Update frontend 2016-07-28 09:22:15 -07:00
Fabian Affolter
6e5e97554b Merge pull request #2642 from fabaff/x10
Remove Awesome Light artefacts
2016-07-28 07:35:02 +02:00
Fabian Affolter
79783e01d7 Remove Awesoe Light artefacts 2016-07-28 07:04:12 +02:00
schneefux
26983aa646 Hyperion active (#2634)
* Hyperion lets you turn it on and off

* Update hyperion to use recent API functions

The plugin now gets the active color from the server.
Add a configuration option "default_color" to customize the turn_on color.
2016-07-27 21:11:12 -07:00
fotoetienne
a0f72e3569 Add support for x10 lights (#2637)
* Add support for x10 lights

* X10 linting and add to .coveragerc
2016-07-27 20:54:02 -07:00
Paulus Schoutsen
1620680127 Use local timezone for log and history dates (#2622)
* Use local timezone for log and history dates

* home-assistant-js fix

* Submodule updates not included so travis can build

* Separate Date and DateTime http validators

* Include submodule reference

* Update frontend
2016-07-27 20:43:46 -07:00
Johann Kellerman
4f89230251 Update icloud to respect track=false. (#2640) 2016-07-27 20:38:55 -07:00
William Scanlon
cdb6f3717d Removed Google Voice SMS notification support (#2628) 2016-07-27 20:37:07 -07:00
Fabian Heredia Montiel
ae97218582 Improvement typing core (#2624)
* Add package typing

* Add util/location typing

* FIX: lint wrong order of imports

* Fix sometyping and add helpers/entity typing

* Mypy import trick

* Add asteroid to test requiremts to fix pylint issue

* Fix deprecated function isSet for is_set

* Add loader.py typing

* Improve typing bootstrap
2016-07-27 20:33:49 -07:00
Johann Kellerman
8c728d1b4e Update icloud device_tracker (#2614)
*  slugify() for dev_id (fixes #2162) [Keep space replacement to not impact known_devices.yaml]
*  pyicloud upgrade 0.9.1
*  config validation
*  Only poll icloud every 4 minutes...
*  Immediately pull device state on HASS start
*  Added new test with icloud char e' acute [chr(233)]
* Suppress pyicloud logging
2016-07-26 23:53:31 +02:00
Fabian Affolter
fed2c33b54 Add get_config (#2627) 2016-07-26 08:50:38 -07:00
John Arild Berentsen
b4990d61f9 Make sure zwave values are updated regardles of manual or frontend update, (#2595)
* Make sure values are updated regardles of manual or frontend update,

* Devices with set_switch command was not happy with fast updating.

* Binary triggersensors command was not happy with refreshed updating.
2016-07-26 08:26:40 +02:00
Cameron Bulock
0eac187d97 DirecTV Receiver Media Player Component (#2559)
* DirecTV receiver component

* styling cleanup

* Updated coveragerc and requirements all

* using string format

* linter fixes
2016-07-25 23:20:56 -07:00
Open Home Automation
de6f49c06f Add the option to add additional tags when logging to InfluxDB (#2613) 2016-07-25 23:01:57 -07:00
Paulus Schoutsen
f1632496f0 Allow circular dependency with discovery (#2616) 2016-07-25 22:49:10 -07:00
Nathan Henrie
9c76b30e24 Add timeout kwarg to call_service() and API.__call__() (#2612)
Fixes #2611

Adds a timeout kwarg to call_service and API.__call__ with default set
to 5 (as per previous behavior). Will not change existing behavior but
will allow remote Python API calls to specify a longer (or shorter)
timeout if they know that a script takes longer than 5 seconds to
return.
2016-07-25 22:35:33 -07:00
Paulus Schoutsen
78c298e563 Fix test to test Norway fix (#2626) 2016-07-25 22:02:12 -07:00
vladonemo
14707630ae Implementing set_hvac_mode for Nest (#2621) 2016-07-25 08:29:40 -07:00
Paulus Schoutsen
8ee4503d7c Exclude tests in dependencies in test dir from pytest (#2618) 2016-07-25 08:26:07 -07:00
Johann Kellerman
4195254280 Update Qwikswitch: fix typing, add validation, shutdown (#2603)
* Update Qwikswitch: fix typing, add validation, shutdown

* Delay startup listener, fix validation

* Fix workerpool errors
2016-07-23 17:03:29 -07:00
Open Home Automation
2484ee53b8 Knx thermostat (#2575)
* Major rewrite of the KNX multi address device. This class wasn't used before, but the new class will be the base for the LNX thermostat module

* newer KNXIP version needed as the previous version had a serious bug

* Update knxip to later version

* Added thermostat module

* First implementation of a KNX thermostat module

* Minor cleanup

* Removed unsed code
2016-07-23 13:54:20 -07:00
Johann Kellerman
4cf618334c Update recorder. (#2549)
* Update recorder.

models.py:
 - Use scoped_session in models.py to fix shutdown error
__init__.py:
 - Session _commit & retry method
 - Single session var for purge_data
 - Ensure single _INSTANCE
 - repeat purge every 2 days
 - show correct time in log_error

* _commit

* Restore models to old functionality, swap purge, remove _INSTANCE cleanup from tests, typing ignore Base class

* pylint

* Remove recorder from model unit test
2016-07-23 11:25:17 -07:00
Fabian Heredia Montiel
d4f78e8552 Type Hints - Core/Utils/Helpers Part 1 (#2592)
* Fix deprecated(moved) import

* Add util/dt typing

* Green on mypy util/dt

* Fix some errors

* First part of yping util/yaml

* Add more typing to util/yaml
2016-07-23 11:07:08 -07:00
Neil Lathwood
34ca1dac7d Added Russound RNET support (#2591)
* Added Russound RNET support

* Fixed farcy issues

* Updated volume_level + fixed requirements_all.txt

* Updated syntax + changed variable
2016-07-23 10:51:56 -07:00
Fabian Affolter
d808d90d26 Upgrade sendgrid to 3.0.7 (#2604) 2016-07-23 10:51:20 -07:00
Paulus Schoutsen
487f3b2951 Update frontend 2016-07-23 10:19:26 -07:00
Nicolas Graziano
d202929de5 Float value for input slider (#2607)
* Allow input_slider value to be a float number.

* Change input_slider unit test to allow float number.
2016-07-23 09:53:16 -07:00
Fabian Affolter
6a189eb18d Merge pull request #2605 from rostved/readme-api-url-fix
Fixed REST API URL in readme.
2016-07-23 14:00:57 +02:00
Mikkel Rostved
67dada226a Fixed REST API URL in readme. 2016-07-23 12:51:25 +02:00
Fabian Affolter
57c2dea02d Add timestamp filters (#2596) 2016-07-22 19:47:43 -07:00
Fabian Affolter
3122c0279f Upgrade slacker to 0.9.24 (#2597) 2016-07-22 19:25:06 -07:00
Fabian Affolter
843e997292 Upgrade netdisco to 0.7.0 (#2598) 2016-07-22 19:24:51 -07:00
Fabian Affolter
a3ff001eec Upgrade voluptuous to 0.9.1 (#2602) 2016-07-22 19:24:23 -07:00
John Arild Berentsen
8389a0abe3 Position fix, updating fix and start-stop for zwave rollershutter (#2594) 2016-07-22 10:01:40 +02:00
Paulus Schoutsen
c21a956895 Speed up MyPy test (#2584) 2016-07-21 23:54:25 -07:00
Pascal Vizeli
e5c42a676d Update pyhomematic to version 0.1.10 (#2589) 2016-07-21 20:49:30 +02:00
Paulus Schoutsen
a513e1cc35 Update frontend 2016-07-21 08:41:48 -07:00
John Arild Berentsen
a0d71c9cb2 Positioning issue for zwave rollershutter. fix for #2581 (#2587)
This fixes issue: #2486
2016-07-21 15:07:48 +02:00
John Arild Berentsen
3441170827 Missing Fortrezz siren fix for #2581 (#2586) 2016-07-21 12:46:15 +02:00
John Arild Berentsen
c56fa7cfed Thermostat and hvac status fix for #2465 (#2585) 2016-07-21 12:20:43 +02:00
Paulus Schoutsen
2ea2a62d45 Update service worker 2016-07-20 23:40:40 -07:00
Paulus Schoutsen
a764683f3a Merge pull request #2583 from home-assistant/hotfix-24-1
Hotfix 24 1
2016-07-20 22:45:03 -07:00
Paulus Schoutsen
19cb1a954f Version bump to 0.24.1 2016-07-20 22:42:46 -07:00
Nathan Henrie
7a1e2de49f Don't overwrite the config directory (#2570)
Closes #2566

The `else` seems to have been an error and was overwriting a non-default config directory with the default location.
2016-07-20 22:42:30 -07:00
Fabian Heredia Montiel
08226a4864 Type Hints - __main__ (#2574)
* Add __main__ type hints

* Fix most errors of __main__

* Add ignore for script.run()

* Add type annotations for from_config_dict and from_config_file

* Fix errors

* Fix requirement error

* Add mypy type check to tests

* Enable travis typing check

* Messed up the tox deps

* Laxer type checker
2016-07-20 22:38:52 -07:00
Robbie Trencheny
d570d38d5c Change path to favicon in GNTP
This broke when #2537 was merged.
2016-07-20 14:46:16 -07:00
Teagan Glenn
ae5dfbdf55 Allow templates for delays in scripts (#2560) 2016-07-20 20:26:17 +02:00
William Scanlon
53f9809567 Wink water leak sensor (#2572) 2016-07-20 07:39:45 -07:00
John Arild Berentsen
aed9ab0271 Added more binary sensor and switch classes. Ref.Pepper1 database (#2573) 2016-07-20 16:21:09 +02:00
Paulus Schoutsen
59029f2830 Update frontend 2016-07-19 23:36:52 -07:00
Scott O'Neil
46216c3bda Fix services registration, and adding schema util to sonos (#2558)
* Moving service registration into def so that it can be called for both discovery methods

* Adding use of schemas to sonos
2016-07-19 22:37:24 -07:00
Nathan Henrie
aa079625d4 Don't overwrite the config directory (#2570)
Closes #2566

The `else` seems to have been an error and was overwriting a non-default config directory with the default location.
2016-07-19 21:51:38 -07:00
Brent
dee9244566 Move location lookup before zone checks. (#2557) 2016-07-19 19:51:14 -07:00
Daniel Høyer Iversen
a6e95db618 MagicLight/Flux WiFi Color LED Light Component (#2534)
* Initial version for flux light

* Update version of flux_led library

* update flux led
2016-07-19 19:32:10 -07:00
Fredrik Haglund
8f04e03f73 Added support for luminance value (#2562) 2016-07-19 19:16:31 -07:00
Daniel Høyer Iversen
d64dae8fcf Rfxtrx sensor (#2563)
* fire event rfxtrx sensor

* Add fire_event to rfxtrx sensor config

* Add test for rfxtrx fire event in sensor
2016-07-19 19:15:50 -07:00
Nolan Gilley
3dd869f0c2 expect a list of devices from config (#2567)
support multiple components. (#2565)
2016-07-19 19:14:41 -07:00
Greg Dowling
e34bfb7381 Tidy / Refactor Vera (#2569)
* Add power attribute to switch.

* Move device_state_attributes into base class.

* Fix imports following refactor.

* Bump pyvera version - should add contributed support for older (UI5) version dimmers and locks.

* Refactor device lookup to be based on vera classes, push category back into library.

* Add generic power attribute, fix inherited class order bug.

* Tidy.
2016-07-19 19:13:33 -07:00
Paulus Schoutsen
7c431911d1 Update frontend 2016-07-19 02:37:22 -07:00
Paulus Schoutsen
5001c9729f Update frontend 2016-07-18 21:29:50 -07:00
John Arild Berentsen
32f228f984 zxt 120 has changed in ozw (#2551) 2016-07-18 16:20:17 +02:00
Paulus Schoutsen
541fffc7fa Update frontend 2016-07-17 23:23:31 -07:00
Paulus Schoutsen
389c13c891 Add ensure config script (#2548) 2016-07-17 15:24:42 -07:00
Daniel Zozin
027266ed8b Fix initialization state for GPIO switches configured with inverted logic (#2550)
When switches are configured to use inverted logic, the GPIO pins initial
state has to be inverted as well (set to HIGH)
2016-07-17 15:18:16 -07:00
Fabian Affolter
ddcad275f7 Upgrade pytz to 2016.6.1 (#2541) 2016-07-17 13:07:11 -07:00
Fabian Affolter
64d5a328f3 Upgrade cherrypy to 6.1.1 (#2538) 2016-07-17 13:06:41 -07:00
Fabian Affolter
1b447fb56f Upgrade python-twitch to 1.3.0 (#2540) 2016-07-17 13:05:50 -07:00
Fabian Affolter
9bed64e9c0 Upgrade python-telegram-bot to 5.0.0 (#2542) 2016-07-17 13:05:38 -07:00
Dan
1da94928c6 Fix bug with imap sensor (#2546)
Fixed bug where the new connection was not saved when a reconnect
attempt was made; broadended the exception catching.
2016-07-17 13:02:14 -07:00
Fabian Affolter
a8f34eb728 Merge pull request #2545 from deisi/acer_pyserial_update
repaired dependency of the acer projector switch
2016-07-17 18:08:17 +02:00
Malte
1002a1b7c9 run gen_requirements.py 2016-07-17 18:00:41 +02:00
Malte Deiseroth
f261aac9cb repaired dependency of the acer projector switch 2016-07-17 16:45:58 +02:00
Daniel Høyer Iversen
cfbc749000 Merge pull request #2539 from home-assistant/rfxtrx_tests
Rfxtrx tests
2016-07-17 11:34:36 +02:00
Daniel
98550b5465 rfxtrx light tests 2016-07-17 11:14:29 +02:00
Daniel
034f1b9499 rfxtrx switch tests 2016-07-17 10:27:27 +02:00
Daniel
c79cd905fe rfxtrx sensor tests 2016-07-17 10:24:08 +02:00
Daniel
294883a174 rfxtrx core tests 2016-07-17 10:20:24 +02:00
Paulus Schoutsen
f94319e7cb Merge pull request #2537 from home-assistant/frontend-panels
Frontend panels
2016-07-16 23:54:12 -07:00
Paulus Schoutsen
38c50c830f Fix linting errors 2016-07-16 23:45:38 -07:00
Paulus Schoutsen
925a623445 Build frontend 2016-07-16 23:24:17 -07:00
Paulus Schoutsen
fd5aad1ee7 Add panel_iframe component 2016-07-16 23:21:34 -07:00
Paulus Schoutsen
22b4aebeb3 Add support for dynamic frontend panels 2016-07-16 23:21:34 -07:00
Fabian Affolter
89639822f1 Fix version 2016-07-17 00:25:49 +02:00
Fabian Affolter
35a57e1385 Prepare for next development cycle 2016-07-17 00:23:57 +02:00
Fabian Affolter
8c44ecc4ba Update version 2016-07-17 00:20:41 +02:00
Fabian Affolter
dc0f16c9dd Merge pull request #2509 from home-assistant/dev
0.24
2016-07-17 00:03:26 +02:00
Paulus Schoutsen
16c71ab207 Make sqlalchemy main dependency to help migration (#2536) 2016-07-16 11:39:44 -07:00
Johann Kellerman
06d70544bc Update rpi_gpio.py (#2530)
Should be pullup, since the sensor pulls to ground (at least the one on AndrewHilliday's site)

Or do we want this configurable?
2016-07-16 11:10:41 -07:00
Pascal Vizeli
1877906fdf small bugfix (#2532) 2016-07-16 11:06:36 -07:00
Fabian Affolter
95d033f1af Round output of wind speed and humidity (#2535) 2016-07-16 11:05:29 -07:00
Paulus Schoutsen
7cff107c17 Update frontend 2016-07-16 02:15:46 -07:00
Fabian Affolter
89972ed940 Add validation and switch python-mystrom (#2529) 2016-07-15 09:02:20 -07:00
Pascal Vizeli
6694f29918 add media_player/clear_playlist and line-in/tv support to sonos (#2527)
* add media_player/clear_playlist and line-in/tv support to sonos

* add support source radio

* fix bug

* print TV/Line-In as media_title

* implement universal player

* add to demo platform

* Update demo.py

Better handling for demo object

* add unit tests

* fix unit test
2016-07-15 09:00:41 -07:00
Fabian Affolter
c1798dbe1f Catch ImportError (#2526) 2016-07-14 15:15:53 -07:00
William Scanlon
3246b58437 Support for Wink lock user codes (#2525) 2016-07-14 13:31:16 -07:00
Michaël Arnauts
63356fb5eb supported_media_commands should check for SERVICE_SELECT_SOURCE instead of SUPPORT_SELECT_SOURCE (#2482) 2016-07-14 11:14:49 -07:00
Paulus Schoutsen
ef64e11b50 known devices yaml robustness (#2523) 2016-07-13 23:56:02 -07:00
Paulus Schoutsen
e38b7d97d2 Update frontend 2016-07-13 23:05:40 -07:00
Paulus Schoutsen
8984a6b161 update frontend 2016-07-13 19:11:33 -07:00
Paulus Schoutsen
49b595e32e Update frontend 2016-07-13 19:05:25 -07:00
Johann Kellerman
a60a342864 Logbook: Query databse as_utc(). dt: Use pytz's localize (#2521) 2016-07-13 18:45:55 -07:00
Paulus Schoutsen
88b3aa54a8 Update README.rst 2016-07-13 18:43:04 -07:00
Fabian Affolter
a0c1c918b8 Switch to xmltodict and pass over missing temperature (fixes #2433) (#2463)
* Switch to xmltodict and pass over missing temperature (fixes #2433)

* Add guard clauses
2016-07-13 18:30:11 -07:00
Pascal Vizeli
675283c23e Merge pull request #2520 from pvizeli/Homematic_pro
homematic update to pyhomematic 0.1.9
2016-07-13 23:29:22 +02:00
Pascal Vizeli
c023d1d656 homematic update to pyhomematic 0.1.9 2016-07-13 23:15:21 +02:00
John Arild Berentsen
ce4891fe8e Fix node inclusion and exclusion. Also add secure inclusion. (#2519)
Fix node inclusion and exclusion.
2016-07-13 19:56:14 +02:00
John Arild Berentsen
82d98f5b89 Zwave Node attributes was missing from binary sensors. (#2516)
Fixes #2505
2016-07-13 18:01:59 +02:00
heytcass
2900855061 Update README.rst (#2517)
Editing for typos, clarifying.
2016-07-13 08:59:26 -07:00
Greg Dowling
e31d4863c7 Merge pull request #2514 from home-assistant/bump_pyloopenergy
Bump pyloopenergy version.
2016-07-13 17:34:30 +02:00
Paulus Schoutsen
af736a3e71 Update frontend (temp map solution) 2016-07-13 08:32:13 -07:00
Daniel Høyer Iversen
16feb1c55e Fix issue #2290 for rfxtrx (#2498)
* Fix issue #2290 for rfxtrx

* update tests for rfxtrx sensor

* Replace state_unkown with None in rfxtrx sensor

* Update test_rfxtrx.py
2016-07-13 07:46:11 -07:00
Fabian Affolter
497bc6ac0d Update docstrings (#2513) 2016-07-13 14:47:29 +02:00
pavoni
cae8f8a006 Bump pyloopenergy version. 2016-07-13 13:21:17 +02:00
Fabian Affolter
82e992c63c Links docs (#2510)
* Add link to docs

* Fix link to docs

* Update docstrings

* Fix link
2016-07-13 11:10:31 +02:00
Paulus Schoutsen
3dcafafc6a Merge branch 'master' into dev
Conflicts:
	homeassistant/const.py
2016-07-12 22:31:54 -07:00
Fabian Affolter
ebcda4076e Upgrade zeroconf to 0.17.6 (#2503) 2016-07-12 21:56:23 -07:00
Robbie Trencheny
011f82f9e3 Uber sensor now works with UberPool and has a bit cleaner logic. Also upgraded to latest version of the SDK and switched all single quotes to double quotes (#2507) 2016-07-12 21:52:21 -07:00
Pascal Vizeli
8ed2c8e6a4 add photo functionality to telegram (#2506)
* add photo functionality to telegram

* basic auth need password and username
2016-07-12 21:48:33 -07:00
Brent
b9cadbecaa Allow device_tracker and sensor entity for google travel times (#2479)
* Allow owntracks entity for google travel times

* Added ability to use sensor state as location

* Added zone checks for google travel timesg

* Updated to use global constents and the location helper

* Fixed type in method name and removed redundant validation

* Changed domain condition to be a bit more elegant

* Updated to allow friendly name in any instance including the config

* Fixed bad python syntax and used helper methods
2016-07-12 21:46:11 -07:00
Dan
e1db639317 add hvac mode support to radiotherm (#2442)
* add hvac mode support to radiotherm

off/cool/heat/auto modes are supported

* Moved STATE_AUTO to thermostat component, fix lint

Moved STATE_AUTO to thermostat platform. Fixed lint error.
2016-07-12 21:43:49 -07:00
rhooper
beeae17cab Merge pull request #2489 from home-assistant/recorder-tests
Add more recorder tests
2016-07-12 11:48:22 -07:00
Paulus Schoutsen
8fcfb9136c Update frontend 2016-07-12 09:16:21 -07:00
Fabian Affolter
62c11dde17 Upgrade python-telegram-bot to 4.3.3 (#2504) 2016-07-12 17:51:11 +02:00
Nolan Gilley
e58615b2a5 Join by joaoapps component & notify platform (#2315)
* initial support for Join notifier

add more functions for Join

* rename to joaoapps_join

add message default in schema

move api_key check

* move special join services to their own component

update coveragerc and requirements_all

add icon and smallicon
2016-07-12 08:10:33 -07:00
Fabian Affolter
bef2f87ddc Docstrings (#2502)
* Update docstrings

* Update docstrings

* Add link to docs
2016-07-12 16:46:29 +02:00
John Arild Berentsen
45a8b74d7f Add missing sensor command_class into sensor component (#2501)
command_class_sensor_alarm was also missing from sensor component.
2016-07-12 15:40:55 +02:00
Daniel Høyer Iversen
09a4336bc5 Fix bug in rfxtrx for int device id (#2497) 2016-07-12 01:45:22 -07:00
Paulus Schoutsen
6d60287455 Update frontend 2016-07-12 00:10:05 -07:00
Paulus Schoutsen
6cb91e66c8 Update frontend 2016-07-11 22:07:34 -07:00
Keaton Taylor
2189516966 Clamp brightness between 0 and 255 (#2494)
* Clamp brightness between 0 and 255

Change to ensure that values over 255 supplied by the config will be
clamed to a max value of 255.

* Revert "Clamp brightness between 0 and 255"

This reverts commit c87238e8b5.

* Clamp brightness between 0 and 255

Change to ensure that values over 255 supplied by the config will be
clamed to a max value of 255.
2016-07-11 12:39:46 -07:00
Paulus Schoutsen
1738db9ccc Update models.py 2016-07-11 12:38:35 -07:00
Paulus Schoutsen
e0dd5a8558 Tweak Recorder 2016-07-11 08:56:07 -07:00
John Arild Berentsen
f4f2da5dc7 Missing command class for sensor (#2492) 2016-07-11 16:33:34 +02:00
Daniel Høyer Iversen
085d026ab6 Merge pull request #2487 from home-assistant/rfxtrx
Rfxtrx
2016-07-11 09:18:51 +02:00
Daniel
3b14189021 Make rfxtrx sensor not crash when unknown sensor is discovered 2016-07-11 08:59:14 +02:00
Daniel
6b9e1f3263 update rfxtrx to version 0.9 to support lighting4 2016-07-11 08:54:15 +02:00
Dan
bde2f0d5a0 Imap sensor (#2485)
* Imap unread email sensor

Checks the inbox of a imap account for unread emails. Tested against
gmail.

Example config:

```
sensor:
  - platform: imap
    name: gmail test
    user: USER
    password: PASSWORD
    server: imap.gmail.com
    port: 993

```

* added to .coveragerc

* Code cleanup and typo fix.

* Added port range validation

* Fix lint errors
2016-07-10 13:21:53 -07:00
Daniel Matuschek
50ea3c7744 Implementation of a KNX platform driver and a KNX switch (#2439)
* Implementation of a KNX platform driver and a KNX switch

* Starting working on a KNX thermostat implementation

* Removed KNX thermostat implementation from this branch again

* Make gateway parameter optional (can be auto-detected in many cases)

* Removed check for double initialisation

* KNX messages now will be handled internally and not send to the Home Assistant message bus

* Call update_ha_state only if should_poll is false

* Removed unused HASS variable

* knxip library version changed

* pylint optimization
2016-07-10 10:36:54 -07:00
Fabian Affolter
bde9e4e9c0 Upgrade googlemaps to 2.4.4 (#2481) 2016-07-10 10:32:38 -07:00
GadgetReactor
609458052c New Switch Platform: TPLink Switch (HS100 / HS110) (#2453)
* New Switch Platform: TPLink Switch (HS100 / HS110)

### Information

The TPLink switch platform allows you to control the state of your TPLink Wi-Fi Smart Plugs.

Supported devices (tested):
HS100 (UK)

It should also work with the HS110.

To use your D-Link smart plugs in your installation, add the following to your configuration.yaml file:

"""
# Example configuration.yaml entry
switch:
  platform: tplink
  host: IP_ADRRESS
  name: TPLink Switch
"""

### Configuration variables:

host (Required): The IP address of your TPlink plug, eg. http://192.168.1.105
name (Optional): The name to use when displaying this switch.

* Update tplink.py

Bug fixes

* Separate to a standalone library

* Removed unnecessary imports

* Code cleanup and update reference library link

* TPLink switch support (#2453)

* updated requirements
2016-07-10 09:48:02 -07:00
clach04
344fb9c8b4 Fix typos in demo switch doc strings (#2480) 2016-07-09 09:35:55 +02:00
koen01
03ef74b4ab Add 'Sound' to rfxtrx DATA_TYPES (#2477)
Fixes reception of SelectPlus and correctly adds the chime sensor.
2016-07-08 09:00:21 -07:00
Dale Higgs
ab63fbff3f Fix AsusWRT to prevent SSH key confusion (#2467)
Changed "pub_key" to "ssh_key" while maintaining backwards compatibility. Quotes were also updated to match across the file.
2016-07-08 08:58:31 -07:00
Pascal Vizeli
2ab2f68318 Yahoo! weather support (#2457)
* initial import yahoo weather

* fix temperature in HA style

* add suggestion from @fabaff

* change with suggestion from @balloob
2016-07-08 08:48:38 -07:00
John Arild Berentsen
5d6c13c12c Fix missing generic command class for binary sensors (#2475) 2016-07-08 13:40:04 +02:00
Brent
ff5c3c9f98 Added attributes to the statsd data (#2440)
* Added attributes to the statsd data

* Updated to allow optional attribute logging
2016-07-07 23:09:02 -07:00
Paulus Schoutsen
31b8e49ad2 Fix PyLint 1.6 issues (#2471) 2016-07-07 18:54:16 -07:00
Neil Lathwood
978ebb9c59 Updated braviatv media player to support power status (#2470)
* Updated braviatv media player to support power status

* Updated requirements_all.txt
2016-07-07 18:28:01 -07:00
Johann Kellerman
85e3dfe6a6 Exclude secrets.yaml in yaml !include_directories (#2450) 2016-07-06 22:17:02 -07:00
Marcelo Moreira de Mello - mmello
cf5aeebba6 - Added code validation on Simplisafe module on alarm_control_panel (#2455) 2016-07-06 21:55:47 -07:00
John Arild Berentsen
3e3d9c881e Return name of location to lock instead of serial number. (#2460) 2016-07-06 18:33:58 -07:00
Fabian Affolter
216a756590 Upgrade pyowm to 2.3.2 (fixes #2452) (#2464) 2016-07-06 18:31:11 -07:00
Dale Higgs
db23320659 Add names, units and icons to APCUPSd Sensor (#2443)
* Add names, units and icons to APCUPSd Sensor

* Fix farcy errors

* Attempt fix of errors

* Remove "type:" from configuration

* Remove duplicate "mdi:" prefix
2016-07-06 18:25:57 -07:00
Fabian Affolter
c634cbf866 Upgrade slacker to 0.9.21 (#2458) 2016-07-06 16:48:58 +02:00
Fabian Affolter
ceb332bc31 Upgrade python-telegram-bot to 4.3.2 (#2459) 2016-07-06 16:48:43 +02:00
Dale Higgs
86e3fdee1c Fix flood of errors if Plex server goes offline (#2447) 2016-07-05 10:50:43 -07:00
Fabian Affolter
0f4acb59fe Change schema for elevation to int (#2436) 2016-07-05 08:01:59 -07:00
Paulus Schoutsen
c5b2df01d9 Update frontend 2016-07-04 10:40:43 -07:00
Jordan Keith
83a72ab4dc Update unifi.py to support sites (#2434)
* Update unifi.py

Add support for a site that is not the default within the Unifi Controller.

i.e. A controller with multiple sites:

 - Home
 - Friends
 - Parents (default)

Supplying the identifier for 'Home' now means that the devices tracked will be associated with 'Home'.

* Update test_unifi.py

Fix test modules as well.
2016-07-04 08:20:00 -07:00
Johann Kellerman
2cdef7fb2f Persistent_notification service description (#2407)
* Persistent_notification service description

* Add service name to services.yaml
2016-07-03 18:33:23 -07:00
Paulus Schoutsen
659d67f362 properly cleanup after config test 2016-07-03 18:24:17 -07:00
Brent
ffccca1f60 Updated to new statsd library and added state change counters (#2429) 2016-07-03 15:21:18 -07:00
Brent
ef74bd9892 Updated to version 3.1.2 and fixed invalid host setup error (#2431) 2016-07-03 15:17:08 -07:00
Paulus Schoutsen
3447fdc76f Make scripts available via CLI (#2426)
* Rename sqlalchemy migrate script

* Add script support to CLI
2016-07-03 11:38:14 -07:00
rhooper
a2e45b8fdd Switch to SQLAlchemy for the Recorder component. Gives the ability t… (#2377)
* Switch to SQLAlchemy for the Recorder component.  Gives the ability to use MySQL or other.

* fixes for failed lint

* add conversion script

* code review fixes and refactor to use to_native() model methods and execute() helper

* move script to homeassistant.scripts module

* style fixes my tox lint/flake8 missed

* move exclusion up
2016-07-02 11:22:51 -07:00
Fabian Affolter
a65f196d19 Use XML source instead of website (#2400) 2016-07-02 11:22:29 -07:00
William Scanlon
a74cdc7b0d SimpliSafe Alarm (#2409) 2016-07-02 11:21:15 -07:00
rhooper
449be29022 support newer deCONZ api versions (#2410) 2016-07-02 11:18:54 -07:00
Fabian Affolter
ba8e417390 Upgrade python-telegram-bot to 4.3.1 (#2414) 2016-07-02 11:16:14 -07:00
Fabian Affolter
cad995a5f4 Upgrade slacker to 0.9.18 (#2415) 2016-07-02 11:15:39 -07:00
Fabian Affolter
06efee7ecf Upgrade fuzzywuzzy to 0.11.0 (#2416) 2016-07-02 11:12:48 -07:00
Paulus Schoutsen
bacc14d845 Merge pull request #2421 from armills/zwave-color-bulbs
Move Aeotec bulb color logic to Zwave workaround
2016-07-02 11:11:44 -07:00
Paulus Schoutsen
6f8a733434 Merge pull request #2424 from home-assistant/hotfix-23-1
Hotfix 0.23.1
2016-07-02 10:20:57 -07:00
Paulus Schoutsen
906e64fdb5 Bump version to 0.23.1 2016-07-02 10:06:24 -07:00
William Scanlon
8e406a70f6 Downgraded pubnub version (#2420) 2016-07-02 10:06:09 -07:00
AlucardZero
8d9f4a1754 check for OP_NO_COMPRESSION support before trying to use it (#2423) 2016-07-02 10:06:09 -07:00
rhooper
0a53b863cd bump pyvera version to 0.2.13 (#2406) 2016-07-02 10:06:09 -07:00
Fabian Affolter
80feb322f9 A mini update (#2418) 2016-07-02 10:05:19 -07:00
William Scanlon
2b514139eb Downgraded pubnub version (#2420) 2016-07-02 10:04:51 -07:00
AlucardZero
2b8dfb2a0e check for OP_NO_COMPRESSION support before trying to use it (#2423) 2016-07-02 10:03:49 -07:00
Adam Mills
6477122b23 Move Aeotec bulb color logic to Zwave workaround
Default behavior for warm/cold white channels is to assume the white
channel is mixed with the rgb. This is a sane default and should support
the Fibaro RGBW LED controller.
2016-07-02 12:08:01 -04:00
Fabian Affolter
1e9db41028 Remove unused links (#2417) 2016-07-02 15:06:13 +02:00
Fabian Affolter
21d3be4027 Fix update (#2402) 2016-07-02 09:09:22 +02:00
rhooper
48b3c98646 bump pyvera version to 0.2.13 (#2406) 2016-07-01 18:47:55 -07:00
Fabian Affolter
15803d1773 Move content to devel docs (fixes #2403) (#2408) 2016-07-02 00:47:54 +02:00
Fabian Affolter
3870d2e0cd Docstring updates (#2404)
* Fix docstring

* Fix typo

* Update docstrings

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

* Update docstrings

* Add link to docs

* Add link to docs and update docstrings

* Update docstring

* Update docstrings and fix typos

* Add link to docs

* Add link to docs

* Add link to docs and update docstrings

* Fix link to docs and update docstrings

* Remove blank line

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

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

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

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

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

* Move elevation to core config

* Migrate forecast test to requests-mock

* Migrate YR tests to requests-mock

* Add requests_mock to requirements_test.txt

* Move conf code from bootstrap to config

* More config fixes

* Fix some more issues

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

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

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

* Updated comment with better decription

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

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

* Add tests for push state changes

* Fix style issues

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

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

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

* Private Secrets Dict, removed cmdline, fixed log level

* Secrets limited to yaml only

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

* Create OpenExchangeRates Sensor

* Add openexchangerate sensor

* Update openexchangerates.py

* Added params dict

* Update openexchangerates.py

* Update openexchangerates.py

* Update openexchangerates.py

* Update openexchangerates.py

* Added API key validation

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

* Refactor of zwave discovery

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

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

* Fix indentation error

* second indentation fix

* Fix for pylint too many statements error

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

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

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

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

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

* Remove data dictionary reference

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

* Improvements to make it work on my poor raspberry 1

* Just a typo

* A few fixes in order to pass pylint

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

* A few improvements

* Just a typo in a comment

* Rebase from HM/dev

* Update requirements by executing the script/gen_requirements_all.py

* More isolation level for braviarc lib

* Remove unnecessary StringIO usage

* Revert submodule polymer commit

* Small refactorization and clean up of unused functions

* Executed script/gen_requirements_all.py

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

* Fix missing parameter detected by pylint

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

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

* Better filtering, by @wokar

* Fix typo

* Remove polling from component, and loop fix

* linter fix

* Filter to channel devices to correct component

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

* Added a sensor component and cleanup

* Completed initial development.

* Fixing pylint issues.

* Fix more pylint issues

* Fixed more validation issues.

* Final pylint issues

* Final tweaks prior to PR.

* Fixed final pylint issue

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

* Fixing final lint issues

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

* Update __init__.py

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

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

* Security upgrades to template rendering in shell_command.

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

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

fix const import

use plexapi

add myplex support for remote access

use first server if server not specified

use list comprehension

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

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

* Added proper de-init of LIRC

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

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

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

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

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

* Some more refactoring and code review workoff

* Dict switch refactor

* CamelCase for data lookup

* Fixing unit_of_measure update

* Better default return for unit_of_measurement

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

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

* Added Pandora to .coveragerc ignore

* Fixes some docstring formats in Pandora

* More minor formatting tweaks for Pandora

* Eliminated non-portable assumption from Pandora component

* Updated Pandora to properly update currently-playing song.

* Docstring fixes in Pandora

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

* Made Pandora client verification a function instead of method.

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

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

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

* Fix check of entity_id in service function

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

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

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

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

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

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

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

with a bit of polishing

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

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

* removing the wrapper hack

* added in requirements file

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

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

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

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

* Added restrict parameter

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

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

* Add new Netatmo library

This new API will provide access to the Welcome Camera

* Basic support for Netatmo Welcome camera

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

* Remove useless REQUIREMENTS

* Fixes for Netatmo Welcome support

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

* Update requirements for Netatmo components

* Fix multi-camera support for Welcome

* Fix pep8 error/warning

* This commit also adds improved logging for bad credentials

* Add Throttle decorator for Welcome update function

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

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

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

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

* Fixed code style errors.

* Moved color dictionary to module per suggestion.

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

* Fix lint errors

* Use snake case

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

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

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

* Improve error message when entity id is invalid
2016-06-08 20:55:08 -07:00
Paulus Schoutsen
29ffa5c282 Version bump to 0.22.0.dev0 2016-06-07 19:28:13 -07:00
Paulus Schoutsen
d7b0929a32 Merge pull request #2183 from home-assistant/dev
0.21
2016-06-07 19:27:55 -07:00
Paulus Schoutsen
31489a56db Merge remote-tracking branch 'origin/master' into dev
Conflicts:
	homeassistant/const.py
2016-06-07 19:27:23 -07:00
Paulus Schoutsen
3e09a7360e Version bump to 0.21 2016-06-07 19:26:43 -07:00
Daniel Høyer Iversen
0cdd752d6c Fixed bug in google time travel (#2202)
Fixed bug in google time travel  when arrival time is given
2016-06-07 19:19:47 -07:00
Adam Mills
027c0b3168 Add turn_off_action to kodi media player (#2224)
A new configuration option `turn_off_action` is added to kodi. It may be
one of: none, quit, hibernate, suspend, reboot, or poweroff. The
appropriate command is sent to kodi when the turn_off action is
requested. Default value is none.

Kodi will only report turn_off supported if it is configured to
something other than none.
2016-06-07 19:18:25 -07:00
Paulus Schoutsen
271546d101 Merge branch 'pr/2251' into dev
Conflicts:
	homeassistant/components/switch/template.py
2016-06-07 19:16:14 -07:00
Johann Kellerman
d1ed17e7db Default parameter for .run() 2016-06-07 23:00:09 +02:00
Alex Harvey
fb2fb5ea73 zwave auto heal at midnight (#2213)
* zwave auto heal at midnight

* fix debug to info, running heal, any heal will send a logger event
2016-06-07 09:29:15 -07:00
John Arild Berentsen
202a8dba8e Hvac fix (#2221)
* Zwave hvac fix

* Zwave hvac fix and move max min temp to base

* Tests
2016-06-07 08:43:46 -07:00
Kyle Hendricks
042a482ef1 Add sensor for DTE Energy Bridge (#2247)
Currently only measures instantaneous energy usage in kW
2016-06-07 08:42:34 -07:00
Alexander Fortin
fff413e04e Improve vagrant provisioner resiliency (#2252)
This should make it easier to fix race conditions that might arise if
box is destroyed but setup_done placeholder file is not removed
properly
2016-06-07 08:35:40 -07:00
Dan Smith
e29459a1ae Merge pull request #2241 from kk7ds/unifi-3.2
Add support for UniFi Video >= 3.2.0
2016-06-06 20:33:45 -07:00
Dan Smith
49de55e75b Add support for UniFi Video >= 3.2.0
Unfortunately, Ubiquiti changed their (supposedly versioned) API in
3.2.0 which causes us to have to refer to cameras by id instead of
UUID. The firmware for 3.2.x also changed the on-camera login procedures
and snapshot functionality significantly.

This bumps the requirement for uvcclient to 0.9.0, which supports the
newer API and makes the tweaks necessary to interact properly.
2016-06-06 20:28:52 -07:00
Hugo D
ee4b1e2b78 The metric unit of pressure is mbar not mBar (#2248)
This is useful to vaoid having several graph for the same type of data
According to wikipedia:
Units derived from the bar include the megabar (symbol: Mbar),
kilobar (symbol: kbar), decibar (symbol: dbar), centibar (symbol: cbar),
and millibar (symbol: mbar or mb).
2016-06-06 08:00:26 -07:00
Johann Kellerman
ed44d28fc0 service helper replaced with script helper (#2242) 2016-06-06 07:36:04 -07:00
Johann Kellerman
d5f9c1bc01 Updated template switch to cache Script objects 2016-06-06 06:41:29 +02:00
Fabian Affolter
f69c900977 Add schema (#2226) 2016-06-05 16:00:51 -07:00
Fabian Affolter
9a7ea72fa0 Upgrade schiene to 0.17 (#2231) 2016-06-05 15:59:54 -07:00
Fabian Affolter
fd4a9cf7c5 Upgrade fuzzywuzzy to 0.10.0 (#2234) 2016-06-05 15:58:54 -07:00
Fabian Affolter
0fe375049a Upgrade slacker to 0.9.16 (#2235) 2016-06-05 15:58:21 -07:00
Fabian Affolter
69f2f0f34a Upgrade pysnmp to 4.3.2 (#2236)
* Upgrade pysnmp to 4.3.2

* Fix pylint issue
2016-06-05 15:57:46 -07:00
Alex Harvey
076fdc3f8b Add a robots.txt (#2207) 2016-06-05 18:48:59 -04:00
Johann Kellerman
8887c2a8af service helper replaced with script helper 2016-06-05 21:44:57 +02:00
Fabian Affolter
f4594027fd Upgrade blockchain to 1.3.3 (#2220) 2016-06-04 12:55:46 +02:00
Robbie Trencheny
59a0005e5c Add CORS to WSGI (#2209)
* Add CORS support to WSGI

* Remove X-HA-Access as a CORS header, because as @JshWright so elegantly put it: "CORS controls access to response headers, not request headers"
2016-06-03 12:53:43 -07:00
Johann Kellerman
9157f722a4 Update Qwikswitch library version (#2214) 2016-06-02 18:47:29 -07:00
Fabian Affolter
7f2a1c61da Upgrade python-telegram-bot to 4.2.0 (#2204) 2016-06-02 04:38:39 -07:00
Sam Riley
0eb9516ea7 Support for RFY protocol (#2199) 2016-06-02 03:48:42 -07:00
Greg Dowling
3bb3a70347 Merge pull request #2203 from home-assistant/bump_loopenergy
Bump pyloopenergy version.
2016-06-02 11:21:48 +01:00
pavoni
0262269b00 Bump loopenergy version. Increased interval before deciding connection is dead and reconnecting. 2016-06-02 11:08:24 +01:00
Greg Dowling
780d62ac5c Merge pull request #2201 from home-assistant/fix_pywemo_ssdp_decode_utf8
Bump pywemo version to fix ssdp discovery encoding issue.
2016-06-02 10:09:13 +01:00
pavoni
5fca9e170e Bump pywemo version to fix ssdp discovery encoding issue. 2016-06-02 09:58:54 +01:00
Jacob Tomlinson
ca7415e935 Added rfxtrx rollershutter (#2030)
* Added rfxtrx rollershutter

* Updated mock command with real one

* Corrected test string
2016-06-02 00:39:58 -07:00
Alex Harvey
26d3c3b0d6 Update PULL_REQUEST_TEMPLATE.md (#2198)
* Update PULL_REQUEST_TEMPLATE.md

* Update PULL_REQUEST_TEMPLATE.md
2016-06-01 23:57:03 -07:00
Paulus Schoutsen
81f8764bb8 Update frontend 2016-06-01 23:47:31 -07:00
Nolan Gilley
24d2eaa6ca flux platform as a switch (#2097)
* flux platform as a switch

* use track_time_change. broken :(

* use track_utc_time_change instead of track_time_change

* add some basic tests

* use brightness from RGB_to_xy

* config_schema validation

* back to platform schema. what was i doing?

* more broken tests :(

* 644

* fix some time bugs

* add working tests. config validation still not right

* bug fixes and more test cases.
2016-06-01 23:38:19 -07:00
Paulus Schoutsen
f868df1035 Fix Norway (#2197) 2016-06-01 23:02:46 -07:00
Paulus Schoutsen
d0988422d4 Merge pull request #2196 from home-assistant/revert-2192-expose-required-ssl-in-discoveries
Revert "Report whether SSL is required in discovery"
2016-06-01 22:48:17 -07:00
Robbie Trencheny
f522d95328 Revert "Report whether SSL is required in discovery" 2016-06-01 22:37:16 -07:00
Robbie Trencheny
c856c67790 Report whether SSL is required in discoverables, like /api/discovery_info and ZeroConf (#2192) 2016-06-01 19:45:19 -07:00
Paulus Schoutsen
6c5efd5b7e Merge pull request #2195 from home-assistant/hotfix-20-3
Hotfix 20 3
2016-06-01 17:39:42 -07:00
Paulus Schoutsen
c3b6086d80 Version bump to 0.20.3 2016-06-01 17:36:04 -07:00
Paulus Schoutsen
0d93369154 Optimize foreacast.io API calls 2016-06-01 17:35:50 -07:00
Paulus Schoutsen
4e064f91fd Merge pull request #2191 from home-assistant/forecast-api-calls
Optimize foreacast.io API calls
2016-06-01 17:32:55 -07:00
Fabian Affolter
f9e53ca22f Add windows 10 tile (#2166) 2016-06-01 14:04:08 -07:00
Paulus Schoutsen
f8bdc835f8 Optimize foreacast.io API calls 2016-06-01 09:20:29 -07:00
Fabian Affolter
1f602be80a Remove print (already covered by logger) (#2184) 2016-05-31 14:02:31 -07:00
Josh Wright
fe4d971427 Re-add config validation for the http component (#2186)
This commit adds back the config validation for the http component. It
was removed during the WSGI shuffle. This is just a direct copy of what
@robbiet480 added in ab294d12f7 (with some testing to verify it still
works).
2016-05-31 14:00:12 -07:00
Paul Philippov
e5efc2e430 Improve Internet Time calculation. (#2185) 2016-05-31 07:19:00 -07:00
Paulus Schoutsen
537a2a6ef6 Improve index.html template 2016-05-30 23:45:02 -07:00
Paulus Schoutsen
11cc065845 Merge remote-tracking branch 'origin/master' into dev
Conflicts:
	homeassistant/const.py
2016-05-30 10:40:50 -07:00
Paulus Schoutsen
3ac31b2c1b Fix broken tests + linting 2016-05-30 10:19:12 -07:00
Paulus Schoutsen
a91f937245 Fix linting issues 2016-05-30 10:08:49 -07:00
Fabian Affolter
fed2584d8a Add azimuth (#1951)
* Add azimuth

* Place elevation and azimuth together in update part
2016-05-29 15:03:29 -07:00
Paulus Schoutsen
eaa8e5f29d Merge branch 'pr/2139' into dev
Conflicts:
	.coveragerc
2016-05-29 15:02:24 -07:00
Bart274
65fbba0e79 List entity_ids in config and only react to them (#2144)
* List entity_ids in config and only react to them

This allows us to define a list of entity_ids in the config to make the
template sensor, binary sensor and switch only react to state changes of
these entities instead of listening to all state changes.

* Forgot to import the track_state_change function

* Changed test for added entity_ids to config

* Use default MATCH_ALL and remove event_listener
2016-05-29 14:34:21 -07:00
Alexander Fortin
19522b1f39 Feedreader: add file data storage (#2147)
Right now we ignore already parsed entries and store the information
at runtime, but it will not survive a restart. This patch adds storage
functionality storing pickled file into default config folder when
feed has `published_parsed` support.
2016-05-29 14:33:53 -07:00
Jan Harkes
afe84c2a8b Allow time condition windows to cross midnight. (#2158)
* Allow time condition windows to cross midnight.

* Address comments.

Fold _in_time_window back into the time() condition test.
Use specific time values to test the time window.
2016-05-29 14:32:32 -07:00
Warren Konkel
952436aa0b Insteon support for brightness (#2169)
* Insteon support for brightness

* Farcy fix for unused constants.

* Remove unused constant and fix whitespace.

* Prevent toggle switches from jumping between states.

* 255 not 256
2016-05-29 14:31:14 -07:00
Olimpiu Rob
8a577c8e0d Added Osram lightify platform (#2170)
* Added Osram Lightify light component

* Added color temperature and fade transition support to Osram Lightify

* Added Osram Lightify light component

* Added color temperature and fade transition support to Osram Lightify

* Updated docstring

* Added osramlightify to ignore list on coveragerc and updated docstrings

* Fixed linting issues
2016-05-29 14:29:49 -07:00
rubund
bf940bd1f3 Initial support for EnOcean (#2177)
* Initial support for EnOcean

Tested to work with:
 - Eltako FUD61 dimmer
 - Eltako FT55 battery-less switch
 - Permundo PSC234 (switch and power monitor)

* Rerun gen_requirements_all.py
2016-05-29 14:28:03 -07:00
rubund
03e8627b12 New option for the netatmo platform: station (#2178)
This is necessary if multiple weather stations are associated with
one Netatmo account.
2016-05-29 14:25:11 -07:00
Brent
e886303f08 Fixed roku exception when device is powered off or looses connection (#2173) 2016-05-29 14:24:06 -07:00
Fabian Affolter
4b0df51b40 Vendorize vincenty requirement (#2176) 2016-05-29 11:55:16 -07:00
Paulus Schoutsen
8494ac7cef Update frontend 2016-05-29 09:50:30 -07:00
Alexander Fortin
5076ebe43c Add Vagrant setup (#2171) 2016-05-28 23:58:09 -07:00
Paulus Schoutsen
05b2559df8 Update frontend 2016-05-28 23:28:57 -07:00
Paulus Schoutsen
70b74da3eb Update frontend 2016-05-28 18:38:46 -07:00
Paulus Schoutsen
9e0b107991 Update frontend 2016-05-28 11:32:35 -07:00
Paulus Schoutsen
92d05ccb5c Fix lint errors 2016-05-28 10:52:44 -07:00
Paulus Schoutsen
bfdb51a558 Bugfixes for urls with dates 2016-05-28 10:37:22 -07:00
Paulus Schoutsen
e10b00f341 Update frontend 2016-05-27 21:45:38 -07:00
Paulus Schoutsen
cd87c40bbf Update frontend 2016-05-27 01:29:48 -07:00
Paulus Schoutsen
d02bc3deaa Update frontend gzip 2016-05-26 23:08:15 -07:00
Paulus Schoutsen
1798df7686 Handle invalid dev ids for dev tracker + owntracks (#2174) 2016-05-26 21:49:44 -07:00
ntouran
d505398917 Locked in required version of python-lirc for LIRC component 2016-05-26 07:53:17 -07:00
s1gnalrunner
70d6ce5b79 Fixed issue with edimax SP-1101 switches (#2105)
* Fixed issue with edimax SP-1101 switches

* Added missing ValueError exception
2016-05-26 05:53:10 -07:00
Daniel Høyer Iversen
71452c11c1 Fix bug in google travel time. Default option dictionary must contain mode. (#2134) 2016-05-26 05:52:17 -07:00
ntouran
e30f2bf912 Cleanups to LIRC module 2016-05-25 22:26:00 -07:00
ntouran
262d95b7b1 Merge remote-tracking branch 'origin/dev' into lirc 2016-05-25 09:21:58 -07:00
William Scanlon
ca3da0e53e Round temp and percentage for octoprint sensors (#2128) 2016-05-25 09:10:59 -07:00
Fabian Affolter
49882255c4 Upgrade astral to 1.1 (#2131) 2016-05-25 09:10:08 -07:00
Scott Bartuska
3db31cb951 Update PyISY to 1.0.6 (#2133)
* Update PyISY to 1.0.6 

1.0.6 is the newest version of PyISY

* PyISY to 1.0.6
2016-05-25 09:09:40 -07:00
Paulus Schoutsen
415cfc2537 WSGI: Hide password in logs (#2164)
* WSGI: Hide password in logs

* Add auth + pw in logs tests
2016-05-24 23:19:37 -07:00
wokar
88bb136813 lg_netcast: fix exception on missing access_token (#2150)
* specified default value for acccess_token to prevent exception on init
2016-05-24 08:36:40 -07:00
Paulus Schoutsen
4cecc626f4 manifest.json: remove trailing commas 2016-05-23 22:45:35 -07:00
Paulus Schoutsen
644d5de890 Merge pull request #2154 from home-assistant/hotfix-20-2
Hotfix 20 2
2016-05-23 22:25:59 -07:00
ntouran
148b8c5055 Updated requirements for LIRC 2016-05-23 21:47:46 -07:00
ntouran
09161ae615 Moved lirc out of sensor package. 2016-05-23 21:36:48 -07:00
ntouran
c1f96aabb0 Changed LIRC component so that it just fires events on the bus. 2016-05-23 21:26:49 -07:00
Robbie Trencheny
343625d539 If we have duration_in_traffic use that as the state, otherwise use duration 2016-05-23 23:43:22 -04:00
Robbie Trencheny
2e10b4bf67 If no departure time is set, use now as the default. If departure time is set but does not have a :, assume its a preformed Unix timestamp and send along as raw input. Assume same for arrival_time. 2016-05-23 23:43:06 -04:00
Jan Harkes
dc8e55fb8b Don't even bother trying to kill stray child processes.
When we change our process group id we don't get keyboard interrupt
signals passed if our parent is a bash script.
2016-05-23 23:30:41 -04:00
Jan Harkes
d86a5a1e91 Don't even bother trying to kill stray child processes.
When we change our process group id we don't get keyboard interrupt
signals passed if our parent is a bash script.
2016-05-23 23:29:53 -04:00
Jan Harkes
1327051277 Version bump to 0.20.2 2016-05-23 23:29:53 -04:00
Josh Wright
712c51e283 Fix TLS with eventlet (#2151)
* Fix TLS with eventlet

This fixes a simple error on my part when implementing the WSGI stuff.

eventlet.wrap_ssl() returns a wrapped socket, it does not modify the
object passed to it. We need to grab the returned value and use that.

* Fix style issue
2016-05-23 17:39:55 -07:00
Robbie Trencheny
c96f73d1be If we have duration_in_traffic use that as the state, otherwise use duration 2016-05-23 14:05:12 -07:00
Robbie Trencheny
b3afb386b7 If no departure time is set, use now as the default. If departure time is set but does not have a :, assume its a preformed Unix timestamp and send along as raw input. Assume same for arrival_time. 2016-05-23 13:48:47 -07:00
Robbie Trencheny
2544635921 Update issue template to prettify the header. 2016-05-23 13:08:47 -07:00
ntouran
4e5b5f2204 LIRC: Responded to some code review requests but not the big one 2016-05-22 22:19:10 -07:00
Paulus Schoutsen
98de7c9287 Upgrade eventlet to 0.19 2016-05-22 20:14:46 -07:00
ntouran
80e60efd8f Removed LIRC dependency from requirements due to "complex" compliation
User will have to install lirc and python-lirc manually.
2016-05-22 16:28:20 -07:00
ntouran
b3e9e1dfcd added LIRC component to .coveragerc 2016-05-22 16:11:26 -07:00
ntouran
40bc49aaae Added LIRC component for responding to IR remote commands 2016-05-22 14:15:09 -07:00
Paulus Schoutsen
3c364fa7e9 Merge pull request #2132 from home-assistant/hotfix-20-1
Hotfix 0.20.1
2016-05-22 09:11:47 -07:00
Jan Harkes
05946ae5a2 Ignore assertions from python threading when looking for leaked threads. (#2130)
While looking for leaked resources (threads) after shutdown and before restart
we in some cases get an assertion in the python threading module where we find
a thread marked as running at the python level but it has no associated thread
at the C level.
2016-05-22 00:35:33 -04:00
Jan Harkes
ceb0ec5fa4 Ignore assertions from python threading when looking for leaked threads.
While looking for leaked resources (threads) after shutdown and before restart
we in some cases get an assertion in the python threading module where we find
a thread marked as running at the python level but it has no associated thread
at the C level.
2016-05-22 00:22:19 -04:00
Jan Harkes
a28196df9a Version bump to 0.20.1 2016-05-22 00:21:19 -04:00
Paulus Schoutsen
c7cc045acd Use only 1 event listener for event stream. 2016-05-21 18:24:03 -07:00
Paulus Schoutsen
225a672a92 Fix Dockerfile 2016-05-21 17:03:46 -07:00
Paulus Schoutsen
4d5eb0e3fc EventStream to sent ping on start to notify browser 2016-05-21 16:31:22 -07:00
Paulus Schoutsen
a68ab07e72 Another attempt to fix SSL in Docker 2016-05-21 16:23:03 -07:00
Paulus Schoutsen
ec4fe7e6e6 update frontend gz 2016-05-21 16:00:59 -07:00
Paulus Schoutsen
0b4b46d80b Merge pull request #2063 from home-assistant/feature/wsgi
Feature/wsgi
2016-05-21 15:14:12 -07:00
Paulus Schoutsen
3bbdd9fedd Remove unused import 2016-05-21 15:01:55 -07:00
Paulus Schoutsen
2ed135439a Remove gzip API 2016-05-21 15:01:35 -07:00
Paulus Schoutsen
1750b22e59 Gzip all the things 2016-05-21 15:01:35 -07:00
Paulus Schoutsen
9c5e7a9584 Add gzip for static resources 2016-05-21 15:01:35 -07:00
Paulus Schoutsen
9b03848a2e Comment out eventstream tests 2016-05-21 15:01:35 -07:00
Paulus Schoutsen
548d415f94 Clean up EventStream 2016-05-21 15:01:35 -07:00
Paulus Schoutsen
18be276b08 Make event stream tests work on Travis ? 2016-05-21 15:01:35 -07:00
Paulus Schoutsen
9aa9e57890 Cleanup 2016-05-21 15:01:35 -07:00
Paulus Schoutsen
8fe2654862 Update requirements with new static update 2016-05-21 15:01:34 -07:00
Paulus Schoutsen
794ff20987 Get EventStream working 2016-05-21 15:01:34 -07:00
Paulus Schoutsen
fe794d7fd8 Access camera images using access token 2016-05-21 15:01:34 -07:00
Paulus Schoutsen
585fbb1c02 Cache files in static folder for a year 2016-05-21 15:01:34 -07:00
Paulus Schoutsen
e4b697b1ed Generate gzip for frontend/mdi 2016-05-21 15:01:34 -07:00
Paulus Schoutsen
de5533e3c2 Fix auth frontend 2016-05-21 15:01:34 -07:00
Paulus Schoutsen
5aa0158761 Add url validators 2016-05-21 15:01:34 -07:00
Paulus Schoutsen
4d7555957c Fix camera 2016-05-21 15:01:34 -07:00
Josh Wright
aa34fe15b2 Friendlier exceptions for misconfigured views
If a view is missing a url or name attribute, this will result
in a more actionable exception being raised.
2016-05-21 15:01:34 -07:00
Paulus Schoutsen
1096232e17 More WIP 2016-05-21 15:01:34 -07:00
Josh Wright
54ecab7590 Improve view registration comments
Clarify that HomeAssistantWSGI.register_view() can handle either instantiated or uninstantiated view classes.
2016-05-21 15:01:33 -07:00
Paulus Schoutsen
15e329a588 Tons of fixes - WIP 2016-05-21 15:01:33 -07:00
Paulus Schoutsen
768c98d359 Fix import issues 2016-05-21 15:01:02 -07:00
Josh Wright
6490378de3 Add some missing view registrations 2016-05-21 15:01:01 -07:00
Josh Wright
d0320a9099 WIP: Add WSGI stack
This is a fair chunk of the way towards adding a WSGI compatible stack
for Home Assistant. The majot missing piece is auth/sessions. I was
undecided on implementing the current auth mechanism, or adding a new
mechanism (likely based on Werkzeug's signed cookies).

Plenty of TODOs...
2016-05-21 15:01:01 -07:00
Paulus Schoutsen
9116eb166b Version bump to 0.21.0.dev0 2016-05-21 14:19:06 -07:00
Paulus Schoutsen
37bd93a975 Version bump to 0.20 2016-05-21 14:17:02 -07:00
Paulus Schoutsen
b78765a41f Merge pull request #2113 from home-assistant/dev
0.20
2016-05-21 14:15:42 -07:00
Paulus Schoutsen
ab60b32326 Update frontend 2016-05-21 14:06:07 -07:00
Jan Harkes
3ea179cc0b Let systemd handle home-assistant process restarts. (#2127) 2016-05-21 12:58:14 -07:00
Paulus Schoutsen
5bedf5d604 Upgrade Nest to 2.9.2 (#2126) 2016-05-21 11:57:33 -07:00
Ardi Mehist
d8c1959715 Add support for Logentries (#1945)
* Add support for Logentries

Supports sending has events to Logentries web hook endpoint
see logentries.com for more

Inspired by the Splunk component

* bugfix

* fix summary

* fix test

* fix logentries url and tests

* update tests

* mock token

* Bug fixes

* typo

* typo

* fix string splitting

* remove redundant backslash
2016-05-21 11:21:23 -07:00
Robbie Trencheny
0f1c4d2f8c GTFS fixes (#2119)
* Change to official PyGTFS source

* Threading fixes for GTFS

* Actually pygtfs 0.1.3

* Update requirements_all.txt

* Update gtfs version
2016-05-21 11:04:18 -07:00
Igor Shults
3ce6c732ab #2120 Fix hvac z-wave fan list (#2121)
* #2120 Fix hvac z-wave fan list

* Properly name methods
2016-05-21 10:56:20 -07:00
Nolan Gilley
191fc8f8d4 Change color_RGB_to_xy formula & return brightness (#2095)
* Use RGB to XY calculations from Philips Hue developer site

* uppercase X,Y,Z

* rename cx,cy to x,y

* return brightness in color_RGB_to_xy

* remove try/catch

* update existing platforms using color_RGB_to_xy

* improve wemo w/ jaharkes suggestion

* allow brightness override of rgb_to_xy
2016-05-21 10:19:27 -07:00
Johann Kellerman
31c2d45a7a Updated pyqwikswitch & QS<->HA UI behaviour (#2123)
* Updated pyqwikswitch & constants

* Disable too-many-locals
2016-05-21 10:12:42 -07:00
Dan
dee6355cc5 Onkyo updates (#2084)
* use sane defaults for openzwave config

Use sane default if libopenzwave is installed. In most cases this will
mean that the zwave config path will not need to e manually specified.

* Resuming work on onkyo component

* Source control added to UI for onkyo receiver

Source will now display in the UI. Source mappings can be defined in the
config, and a rudimentary mapping is defined by default as a fallback.
When the onkyo source is updated, it will resolve to a defined name if
possible. This may break existing automations.

* fix lint errors

* Updated Onkyo receiver

Now takes an optional ip/name in additional to atempting to discover
deivces.

Source select will now take a sources mapping in the config. It will
provide default values if no source mapping is provided.

example:

- platform: onkyo
  host: 10.0.0.2
  name: receiver
  sources:
    HTPC: 'pc'
    Chromecast: 'aux1'
    Bluray: 'bd'
    Wii U: 'game'

* fix pylint error

* Use HA's error log instead of stack trace

* Flipped source mappings, code cleanup
2016-05-21 10:04:08 -07:00
Robbie Trencheny
c9b5ea97da Fix docstring issues with MoldIndicator 2016-05-21 10:03:24 -07:00
Felix
eaebe83429 Moldindicator Sensor (#1575)
* Adds MoldIndicator sensor platform

This sensor may be used to get an indication for possible mold growth in rooms.
It calculates the humidity at a pre-calibrated indoor point (wall, window).

* Automatic conversion to Fahrenheit for mold_indicator

* Minor change to critical temp label

* Fixed docstrings and styles

* Minor changes to MoldIndicator implementation

* Added first (non-working) implementation for mold_indicator test

* Small style changes

* Minor improvements to mold_indicator

* Completed unit test for mold indicator

* Fix to moldindicator initialization

* Adds missing period. Now that really matters..

* Adds test for sensor_changed function
2016-05-21 09:58:59 -07:00
Fabian Affolter
7f0b8c5e70 Docs (#2124)
* Add link to docs

* Update link
2016-05-21 16:59:52 +02:00
Jan Harkes
53d51a467d Single process restart fixes (#2118)
* Ignore permission errors on setpgid.

When launched in a docker container we got a permission denied error
from setpgid.

* Don't fail if we find our own pidfile.

When we restart using exec we are running a new instance of home-assistant with
the same process id so we shouldn't be surprised to find an existing pidfile in
that case.

* Allow restart to work when started as python -m homeassistant.

When we are started with `python -m homeassistant`, the restart command line
becomes `python /path/to/hass/homeassistant/__main__.py`. But in that case the
python path includes `/path/to/hass/homeassistant` instead of `/path/to/hass`
and we fail on the first import.

Fix this by recognizing `/__main__.py` as part of the first argument and
injecting the proper path as PYTHONPATH environment before we start the new
home-assistant instance.
2016-05-20 11:45:16 -07:00
Alexander Fortin
7eeb623b8f Add media_player.sonos_group_players service (#2087)
Sonos platform supports a `party mode` feature that groups all
available players into a single group, of which the calling player
will be the coordinator.
2016-05-20 09:54:15 -07:00
Jan Harkes
6b724f7da4 Not sure why, but this fixed a bad filedescriptor error. (#2116) 2016-05-20 07:03:08 -07:00
Alexander Fortin
a4409da700 Add add_uri_to_queue support to (sonos) media player (#1946)
Sonos (SoCo) supports add_uri_to_queue capability, making it possible
to stream media available via HTTP for example. This patch extends
media_player component and sonos platform to support this feature
2016-05-19 23:30:19 -07:00
John Arild Berentsen
1eb3181c14 Fix fitbit KeyError (#2077)
* Fix fitbit KeyError

* Set units compared to temperature_unit

* Pass true or false for is_metric
2016-05-19 23:28:53 -07:00
wokar
f7b401a20e Added the lg_netcast platform to control a LG Smart TV running NetCast 3.0 or 4.0 (#2081)
* Added the `lgtv` platform to control a LG Smart TV running NetCast 3.0
(LG Smart TV models released in 2012) and NetCast 4.0 (LG Smart TV models released in 2013).

* Fixed multi-line docstring closing quotes

* Rename lgtv to lg_netcast

* Rename lgtv to lg_netcast

* Extracted class to control the LG TV into a separate Python package 'pylgnetcast' and changed requirements accordingly.

* regenerated requirements_all.txt with script

* now uses pylgnetcast v0.2.0 which uses the requests package for the communication with the TV

* fixed lint error: Catching too general exception Exception
2016-05-19 23:27:47 -07:00
Jan Harkes
5f92ceeea9 Allow for restart without using parent/child processes. (#1793)
* Allow for restart without using parent/child processes.

Assuming that we normally correctly shut down running threads and
release resources, we just do some minimal scrubbing of open file
descriptors and child processes which would stay around across an
exec() boundary.

* Use sys.executable instead of multiprocessing.spawn.get_executable()

* Limit how many file descriptors we try to close.

Don't even try to close on OSX/Darwin until we figure out how to
recognize guarded fds because the kernel will yell at us, and kill
the process.

* Use the close on exec flag on MacOS to clean up.

* Introduce a small process runner to handle restart on windows.

* Handle missing signal.SIGHUP on Windows.
2016-05-19 23:20:59 -07:00
Per Sandström
f0f1fadee1 redirect daemon file descriptors (#2103) 2016-05-19 23:20:07 -07:00
Paulus Schoutsen
32f97dc578 Merge remote-tracking branch 'origin/master' into dev
Conflicts:
	homeassistant/const.py
2016-05-19 22:32:34 -07:00
Greg Dowling
631ba2ef0d Merge pull request #2110 from home-assistant/bump_loop_energy
Bump loop energy library version.
2016-05-19 17:19:06 +01:00
pavoni
62de16804b Bump loop energy library version. 2016-05-19 17:12:19 +01:00
Paulus Schoutsen
3d919f1235 Merge pull request #2108 from home-assistant/owntracks_fixes
Owntracks fixes
2016-05-19 08:28:36 -07:00
pavoni
8ff9506138 Ignore acc: 0 updates. 2016-05-19 16:16:43 +01:00
pavoni
dd1703469e Handle region enter/leave with spaces. 2016-05-19 16:04:55 +01:00
Daniel Høyer Iversen
5f98a70c21 Fix bug in flaky rfxtrx test (#2107) 2016-05-19 06:36:11 -07:00
Fabian Affolter
bfd64ce96e Upgrade python-telegram-bot to 4.1.1 (#2102) 2016-05-18 17:05:08 -07:00
Fabian Affolter
a032e649f5 Upgrade psutil to 4.2.0 (#2101) 2016-05-18 17:04:59 -07:00
Robbie Trencheny
c96a5d5b2b Fix profile usage with aws notify platforms (#2100) 2016-05-17 16:51:38 -07:00
Robbie Trencheny
a565cc4b73 Catch a gntp networkerror (#2099) 2016-05-17 16:51:32 -07:00
froz
8d34b76d51 Restored telnet as an option. Activate with config option 'protocol: telnet'. Default is ssh (#2096) 2016-05-17 15:55:12 -07:00
happyleavesaoc
15f89fc636 add some include_dir options (#2074)
* add some include_dir options

* validate, and extend instead of add

* add yaml include tests
2016-05-17 15:47:44 -07:00
Robbie Trencheny
a431277de1 Accept human readable color names to change light colors (#2075)
* Add support for providing color_name which accepts a CSS3 valid, human readable string such as red or blue

* Forgot the schema validation!

* ugh farcy

* use html5_parse_legacy_color for more input options

* Add webcolors==1.5 to setup.py

* Block pylint no-member errors on tuple

* add color_name_to_rgb test

* whoops

* revert changes to individual platforms

* If color_name is set, pop it off params and set rgb_color with it

* Forgot to reset wink.py

* Import the legacy function as color_name_to_rgb directly

* reset test_color.py

* Improve light services.yaml
2016-05-17 00:06:55 -07:00
Alexander Fortin
7208ff515e Better handle exceptions from Sonos players (#2085)
Sonos players can be dynamically set in various modes, for example
as TV players or Line-IN or straming from radios channels, therefore
some methods could not be available, and when invoked they cause
long exceptions to be logged. This partially solves the problem
reducing the output and logging some more informative error message
2016-05-16 22:58:57 -07:00
Paulus Schoutsen
0a79a5e964 Update frontend repo 2016-05-16 21:59:39 -07:00
Daniel Høyer Iversen
8e766daa11 Merge pull request #2086 from home-assistant/time_travel_fix
Round minutes to integer in google travel time, Fix issue #2080
2016-05-16 11:45:43 +02:00
Daniel
4ded795740 Round minutes to integer in google travel time, Fix issue #2080 2016-05-16 11:37:17 +02:00
Robbie Trencheny
84cb7a4f20 Add AWS notify platforms (Lambda, SNS, SQS) (#2073)
* AWS SNS notify platform

* Attach kwargs as MessageAttributes

* Initial pass of AWS SQS platform

* Add Lambda notify platform

* Remove unused import

* Change single quotes to double quotes because I am crazy

* Forgot to run pydocstyle

* Improve context support for Lambda

* compress the message_attributes logic
2016-05-15 13:17:35 -07:00
Rowan
cbf0caa88a Last.fm sensor (#2071)
* Last.fm component

* Pylint fixes

* Last.fm component

* Pylint fixes

* Updated with `.coveragerc` and `requirements_all.txt`

* Pylint fixes

* Updated

* Pylint fix

* Pylint fix
2016-05-15 13:11:41 -07:00
Brent
88d13f0ac9 Added support for the roku media player (#2046) 2016-05-15 13:00:31 -07:00
mnestor
3ed6be5b4e add link ability to configurator (#2035) 2016-05-15 12:56:29 -07:00
Richard Cox
0340710e5c Support for Nest Protect smoke alarms (#2076)
* Support for Nest Protect smoke alarms

* Fixing formatting issues from tox
2016-05-15 12:29:12 -07:00
froz
49acdaa8fd Device Tracker - ASUSWRT: Replaced telnet with ssh (#2079) 2016-05-15 12:20:17 -07:00
Alex Harvey
ffbc99fac2 Merge pull request #2059 from infamy/justyns-purge_old_data
Justyns purge old data
2016-05-14 23:55:28 -07:00
Johann Kellerman
6dae005b65 Resolved UI flicker, new config vars, brightness up to 255, fixed buttons, fixed race condition (#2072) 2016-05-14 14:21:05 -07:00
Robbie Trencheny
0adc853741 Add notify.twilio_sms component (#2070) 2016-05-14 14:09:28 -07:00
Robbie Trencheny
6254d4a983 Add lines for associated documentation PR 2016-05-14 14:02:14 -07:00
Robbie Trencheny
a7db208b8a Fix Google Voice documentation URL 2016-05-14 13:32:00 -07:00
Rowan
429bf2c143 Google Play Music Desktop Player component (#1788)
* Added GPM Desktop Plaeyr component

* Updated requirements_all.txt

* Pylint fix

* Updated GPMDP.py to include @balloob's comments

* Updated to work with the latest version of GPMDP

* Removed setting "self._ws.recv()" as a variable

* Made line 52 shorter

* Updated to check weather it is connected or not

* Pylint and @balloob fixes

* Updated with simplified code and pylint fix

* Made `json.loads` shorter

* Pylint fix
2016-05-14 13:28:42 -07:00
happyleavesaoc
8df91e6a17 numeric state: validate multiple entities (#2066)
* validate multiple entities

* point to current entity
2016-05-14 12:29:57 -07:00
Daniel Høyer Iversen
8656bbbc79 fix bugs in google travel time (#2069) 2016-05-14 12:14:13 -07:00
Daniel Høyer Iversen
630b7377bd Refactor get_age in util/dt (#2067) 2016-05-14 12:05:46 -07:00
Daniel Høyer Iversen
0626a80186 Merge pull request #2068 from home-assistant/yaml_env
Add test for yaml enviroment
2016-05-14 20:33:46 +02:00
Daniel
24788b106b Add test for yaml enviroment 2016-05-14 20:20:27 +02:00
Igor Shults
c5401b21c2 Fix typo in system monitor ('recieved') (#2062) 2016-05-14 09:45:32 -07:00
mnestor
954b56475e YAML: add !include_named_dir and ! include_list_dir (#2054)
* add include_dir constructor for yaml parsing

* changed to allow for flat and name based directory including

* fixed ci errors

* changed flat to list
2016-05-13 21:16:04 -07:00
Alex Harvey
53d7e0730c Fixes for farcy 2016-05-13 14:43:22 -07:00
Alex Harvey
cba85cad8d Fixes for farcy 2016-05-13 14:42:08 -07:00
Lewis Juggins
96b73684eb Update Dockerfile to use OpenSSL 1.0.2h to resolve certificate issues (#2057) 2016-05-13 07:55:52 -07:00
Robbie Trencheny
aa7fa7b550 Dont default to driving anymore, re: #2047 2016-05-12 22:49:12 -07:00
Robbie Trencheny
d229cb46b1 Google travel time improvements (#2047)
* Update google_travel_time.py

* Update google_travel_time.py

* pylint: disable=too-many-instance-attributes

* Add the mode to the title of the sensor

* Expose the travel mode on the sensor attributes

* Big improvements to the Google Travel Time sensor. Allow passing any options that Google supports in the options dict of your configuration. Deprecate travel_mode. Change name format to show the mode

* fu farcy

* Dynamically convert departure and arrival times

* Add a warning if user provides both departure and arrival times

* Add deprecation warning for travel_mode outside options and other minor fixes

* Use a copy of options dict to not overwrite the departure/arrival times constantly.

* Remove default travel_mode, but set default options.mode to driving

* Google doesnt let us query time in the past, so if the date we generate from a time string is in the past, add 1 day

* spacing fix

* Add config validation for all possible parameters

* flake8 and pylint fixes
2016-05-12 22:37:08 -07:00
happyleavesaoc
8682e2def8 supervisord sensor (#2056) 2016-05-12 22:16:58 -07:00
Johann Kellerman
65ac1ae84a Added QwikSwitch component & platforms (#1970)
* Added QwikSwitch platform

farcy - worst than my english teacher

* Clean up comments

* Import only inside functions

* Moved imports, no global var, load_platform

* add_device reworked

* Only serializable content on bus

* Fixed imports & removed some logging
2016-05-12 21:39:30 -07:00
Alex Harvey
93fd6fa11b fixes for pep and delay start 2016-05-12 10:33:22 -07:00
Alex Harvey
67b0365f62 update to latest base 2016-05-12 10:32:28 -07:00
Paulus Schoutsen
f1eda430cd Update rpi_rf.py 2016-05-12 00:13:48 -07:00
Paulus Schoutsen
69929f15fb Ignore RPI-RF in requirements_all 2016-05-11 22:56:05 -07:00
Robbie Trencheny
d553c7c8e7 Merge pull request #2027 from robbiet480/relative-time-filter
Add a Jinja filter for relative time
2016-05-11 22:50:56 -07:00
Robbie Trencheny
4d0b9f1e94 Stupid blank lines 2016-05-11 22:44:44 -07:00
Robbie Trencheny
fca4ec2b3e simplify the relative_time function 2016-05-11 22:37:37 -07:00
Robbie Trencheny
b75aa6ac08 Add get_age tests 2016-05-11 22:29:55 -07:00
Nolan Gilley
894ceacd40 Add Ecobee notify platform (#2021)
* add send_message to ecobee via service call

* farcy fixes

* fix pydocstyle

* ecobee notify component
2016-05-11 22:03:21 -07:00
Johann Kellerman
fbe940139a Discovery listener on all EntityComponents (#2042) 2016-05-11 21:58:22 -07:00
happyleavesaoc
c341ae0a39 Media Player - MPD: handle more exceptions (#2045) 2016-05-11 21:53:56 -07:00
Nolan Gilley
f9d97c4356 fix away mode. issue 2032 (#2044) 2016-05-11 21:52:56 -07:00
Nolan Gilley
b8a5d392c5 Fix speedtest by removing Throttle and adding second parameter for track_time_change (#2040) 2016-05-11 08:24:50 -07:00
Paulus Schoutsen
fd8240241f Merge pull request #2038 from home-assistant/hotfix-19-4
Hotfix 0.19.4: Fix script syntax validation of AND and OR condition
2016-05-10 21:57:22 -07:00
Paulus Schoutsen
3c9e493494 Make AND and OR conditions valid (#2037) 2016-05-10 21:49:58 -07:00
Paulus Schoutsen
786a0154b1 Version bump to 0.19.4 2016-05-10 21:48:05 -07:00
Paulus Schoutsen
dd6ab79e35 Make AND and OR conditions valid 2016-05-10 21:47:46 -07:00
Erik Eriksson
2f118c5327 log received mqtt messages (#2031) 2016-05-10 21:12:14 -07:00
Nolan Gilley
a7d1f52ac8 Use Throttle on speedtest update (#2036)
* use throttle

* fix flake8
2016-05-10 20:51:55 -07:00
Robbie Trencheny
5317f700d7 Merge pull request #2033 from home-assistant/hotfix-0193
Hotfix 0193
2016-05-10 13:56:15 -07:00
Robbie Trencheny
01eb2d5c84 Increment version 2016-05-10 13:42:23 -07:00
Paulus Schoutsen
0893ddcab7 Update README.rst 2016-05-10 13:41:23 -07:00
Landrash
e77a7f4385 Fixed minor miss-spelling (#2028)
Changed millileters to milliliters.
Changed case of mmol/l to mmol/L.
2016-05-10 13:40:39 -07:00
Robbie Trencheny
39e7942dce Fitbit flake8 and pylint fixes. Forgot to do it before pushing :( 2016-05-10 13:40:34 -07:00
Robbie Trencheny
faf5ffe610 Minor Fitbit tweaks. Correct the copy, dont require auth on the routes, get the client_id/client_secret from fitbit.conf instead of the YAML 2016-05-10 13:40:25 -07:00
Robbie Trencheny
8d2dc48261 en_UK->en_GB. Closes #2019. 2016-05-10 13:40:17 -07:00
Paulus Schoutsen
c7cfa8d245 Update README.rst 2016-05-10 13:36:03 -07:00
Robbie Trencheny
16933abce9 Remove humanize and use a relative time thing that @balloob found on Github 2016-05-10 00:04:53 -07:00
Landrash
8163b986c9 Fixed minor miss-spelling (#2028)
Changed millileters to milliliters.
Changed case of mmol/l to mmol/L.
2016-05-09 23:48:48 -07:00
Robbie Trencheny
d5a1c52359 Add a Jinja filter for relative time 2016-05-09 23:31:02 -07:00
Nolan Gilley
26ea4e41cb Bring back custom scan intervals and service for speedtest.net component (#1980)
* Bring back the functionality that was removed in PR 1717. This includes the speedtest service and the ability to define the scan times in the configuration file.  Restore default functionality of 1 scan per hour on the hour.

* remove unnecessary code.
2016-05-09 22:49:26 -07:00
Johann Kellerman
ec9544b9c3 Add a load_platform mechanism (#2012)
* discovery.load_platform method

* rm grep
2016-05-09 22:48:03 -07:00
Fabian Affolter
1d0bc1ee66 Upgrade flake8 to 2.5.4 (#2018) 2016-05-09 22:33:21 -07:00
Robbie Trencheny
9729c44d53 Merge pull request #2023 from philipbl/fix_slack
Fix problem with Slack default channel
2016-05-09 16:37:38 -07:00
Robbie Trencheny
a7292af3b1 Fitbit flake8 and pylint fixes. Forgot to do it before pushing :( 2016-05-09 15:33:04 -07:00
Robbie Trencheny
c8cbc528eb Minor Fitbit tweaks. Correct the copy, dont require auth on the routes, get the client_id/client_secret from fitbit.conf instead of the YAML 2016-05-09 15:31:47 -07:00
Philip Lundrigan
8735bfe926 Fix problem with default channel 2016-05-09 16:19:19 -06:00
Robbie Trencheny
25e8c7bc5f en_UK->en_GB. Closes #2019. 2016-05-09 15:14:33 -07:00
jazzaj
499257c8e1 Corrected link to documentation (#2022) 2016-05-09 23:30:22 +02:00
Paulus Schoutsen
6856283896 Make HVAC naming consistent (#2017) 2016-05-09 07:53:01 -07:00
Paulus Schoutsen
20dad9f194 Add HVAC to demo 2016-05-08 23:21:26 -07:00
Paulus Schoutsen
09483e3be4 More fault tolerant discovery 2016-05-08 21:23:03 -07:00
Paulus Schoutsen
8ae5708fa2 Merge pull request #2010 from home-assistant/hotfix-0192
Hotfix 0192
2016-05-08 10:19:54 -07:00
Paulus Schoutsen
1e42f85a9c Version bump to 0.19.2 2016-05-08 09:53:22 -07:00
John Arild Berentsen
92d71a6612 Fix for not recognizing Z-Wave thermostats (#2006)
* Fix for not recognizing thermostats

* Properly ignore zxt-120

* fix
2016-05-08 09:53:09 -07:00
John Arild Berentsen
ab2e85840f Fix for not recognizing Z-Wave thermostats (#2006)
* Fix for not recognizing thermostats

* Properly ignore zxt-120

* fix
2016-05-08 09:52:16 -07:00
Paulus Schoutsen
8257e3f384 Fix automation deprecation warning 2016-05-07 22:31:22 -07:00
Paulus Schoutsen
e40908d67c Improve config validation error message 2016-05-07 22:31:22 -07:00
Paulus Schoutsen
1abbb43ebd Merge pull request #2004 from home-assistant/hotfix-019-1
Hotfix 019 1
2016-05-07 22:31:00 -07:00
Paulus Schoutsen
69ecedefad Version bump to 0.19.1 2016-05-07 22:24:20 -07:00
Paulus Schoutsen
0f6c9d2f75 Fix automation deprecation warning 2016-05-07 22:24:13 -07:00
Paulus Schoutsen
c58fb00f03 Improve config validation error message 2016-05-07 22:24:04 -07:00
Paulus Schoutsen
e67729b2f4 Version bump to 0.20.0.dev0 2016-05-07 12:55:41 -07:00
Paulus Schoutsen
6b1f9a32dd Merge pull request #1995 from home-assistant/dev
0.19
2016-05-07 12:55:27 -07:00
Paulus Schoutsen
7c8c5e7763 Version bump to 0.19 2016-05-07 12:51:40 -07:00
Robbie Trencheny
8cb48615af Fitbit Sensor Take Deux (#2002)
* Fitbit Sensor

* Add configurator image
2016-05-07 12:46:45 -07:00
Robbie Trencheny
952d1c796e Add CORS (Cross Origin Resource Sharing) support to HTTP (#2000) 2016-05-07 12:21:28 -07:00
Fabian Affolter
89c22e6d8a Upgrade pytest to 2.9.1 (#2001) 2016-05-07 12:12:52 -07:00
Fabian Affolter
16adc30210 Upgrade pylint to 1.5.5 (#1999) 2016-05-07 11:49:58 -07:00
Paulus Schoutsen
2a9c822454 Update HBMQTT (#1998) 2016-05-07 10:59:23 -07:00
Daniel Høyer Iversen
6cae7c0307 Add google maps travel time sensor (#1987)
* Add travel time sensor

* Throttle update of Time travel sensor

* Rename travel time to google travel time, validate the api key
2016-05-07 10:17:28 -07:00
Paulus Schoutsen
6901e5ea5e Random fixes (#1996)
* OwnTracks handle malformed data better

Fixes #1991 .

* Remove dependency for util.dt
2016-05-07 10:16:14 -07:00
Paulus Schoutsen
8a16a7d943 Update frontend 2016-05-07 10:10:09 -07:00
Daniel Høyer Iversen
21dd8162b3 Improve error message for invalid key in config (#1975)
* Improve error message for invalid key in config

* Refactor log exception in config validation
2016-05-07 07:35:42 -07:00
Paulus Schoutsen
67f3fcc5cf Update frontend 2016-05-06 23:35:15 -07:00
Robbie Trencheny
a1480582d9 Add /api/discovery_info (#1791)
* Allow /api/ and /api/config to be accessed without auth. If config is accessed without auth, only show minimal information. Also improves comments

* Re-enable auth on /api/ since a lot of tests get broken if it does not require auth

* Move the discovery info from /api/config to /api/discovery_info

* Flake8 fixes
2016-05-06 22:11:35 -07:00
Paulus Schoutsen
713c7a5fd7 Merge branch 'master' into dev
Conflicts:
	homeassistant/const.py
2016-05-06 21:46:50 -07:00
Nolan Gilley
fb3b3db04e Check for 'unknown' sensor values in ecobee (#1983) 2016-05-06 18:50:32 -07:00
Charles Spirakis
b86a1ece01 Allow conversion from date strings to "unix" timestamp. (#1985)
"unix" timestamp is number of seconds since Jan 1, 1970 UTC.
This allows scripts that use templates to generate time
deltas in seconds if desired from state attributes such
as last_updated.

Some examples:

timestamp now is
{{ as_timestamp(now) }}

timstamp of last change is
{{ as_timestamp(states.binary_sensor.garage_door.last_changed) }}

seconds since last change is
{{ as_timestamp(now) - as_timestamp(states.binary_sensor.garage_door.last_changed) }}
2016-05-06 18:33:46 -07:00
Daniel Høyer Iversen
ca0ea6c2f3 Rfxtrx bug fix (#1992)
* Rfxtrx bug fix

* Added Sensor Status to data_types

Missing dataype for security1 sensors

* Misspelling
2016-05-06 18:24:43 -07:00
William Scanlon
1cd59cf2a9 Added battery level to wink devices (#1979) 2016-05-06 18:19:37 -07:00
Fabian Affolter
72cf7fd9c2 Add timeout to request for location elevation (#1978) 2016-05-06 18:10:13 -07:00
Ellis Percival
c72ab42c19 Enable ZigBee "push" updates for digital/analog sensors. (#1976) 2016-05-06 18:09:18 -07:00
Fabian Affolter
d73f8d5253 Upgrade to PyMata 2.12 (#1993) 2016-05-06 18:03:28 -07:00
Fabian Affolter
98bedf1bd6 Update links to docs and doc strings (#1994) 2016-05-06 18:03:18 -07:00
Kyle Hendricks
4a28be9a94 [Pioneer AVR] Display the currently select input source as the media title (#1974) 2016-05-06 17:57:57 -07:00
Hernán
393bd88091 Add Media Player Stop command + Kodi support for it (#1960)
* Started adding Stop command to Kodi media player

* minor

* minor

* minor

* abstract-method fixed
2016-05-06 17:57:00 -07:00
Fabian Affolter
e5d1ed9439 Add dweet export component (#1818) 2016-05-06 17:55:26 -07:00
Fabian Affolter
ddfda89fc9 Update link to docs 2016-05-05 09:49:52 +02:00
Fabian Affolter
2274806bee UPdate link to docs 2016-05-05 00:32:11 +02:00
Fabian Affolter
7995829790 Fix typo 2016-05-05 00:28:51 +02:00
Alex Harvey
d5031d90c4 Update README.rst (#1981)
Splitting dev and general chat links in the badges
2016-05-04 22:40:31 +02:00
Robbie Trencheny
2874ad3445 Merge pull request #1973 from kylehendricks/fix-unresolved-merge-conflict
Fix an unresolved merge conflict
2016-05-03 19:42:27 -07:00
Kyle Hendricks
8fc07ee6cd Fix an unresolved merge conflict 2016-05-03 22:34:26 -04:00
Per Sandström
4850a65ed0 add reconnect retry and longer timeouts for verisure (#1944) 2016-05-03 18:53:11 -07:00
Paulus Schoutsen
4643dcde9c Merge branch 'pr/1969' into dev
Conflicts:
	homeassistant/components/zwave.py
2016-05-03 18:48:48 -07:00
devdelay
39e03eebcf Add Z-Wave lock support 2016-05-03 18:47:52 -07:00
Brent
34193de158 Update yaml parser for handling environment variables (#1967) 2016-05-03 18:41:14 -07:00
Alexander Fortin
298b9d1f12 Limit number of processed entries by Feedreader (#1966)
* process only last 20 available entries to avoid bombing event
  bus when parsing huge feeds
* trigger first update only when HA has completed startup,
  allowing components to complete subscriptions to feedreader
  events
* quote url in logs for better readability
2016-05-03 18:40:30 -07:00
William Scanlon
6d9254ce25 Support for OctoPrint sensors (#1924) 2016-05-03 18:35:11 -07:00
John Arild Berentsen
1a59ba735f Initial HVAC component + Z-Wave platform (#1912) 2016-05-03 18:27:51 -07:00
deisi
b2abe552a0 Added a switch to control beamers from acer (#1913) 2016-05-03 18:23:38 -07:00
Paulus Schoutsen
2a972b7fe3 Fix script condition issue (Thanks @bart274) (#1972) 2016-05-03 18:19:55 -07:00
Kyle Hendricks
f2176e54ba Add Pioneer AVR media_player support (#1968) 2016-05-02 22:09:27 -07:00
Paulus Schoutsen
79653a672d Script cleanup (#1963) 2016-05-02 22:05:09 -07:00
Andrew LeCody
a6a5e4fda2 Forecast.io: Added minutely, hourly, and daily summary (#1943)
* Forecast.io: Added minutely, hourly, and daily summary

* Resolve pylint issue (Too many instance attributes)
2016-05-02 21:56:15 -07:00
Tim Harton
f6df5bc390 Mqtt client key auth (#1935)
* Made changes so that the mqtt configuration can accept client keys and certs for auth.

* Need to figure out how the broker_config works, it's failing tests

* Fixed it so all tests passed and ssl feature works for all brokers
which are't embedded

* Bring into line with pep8

* Added config validation which has allowed me to make the code simpler
2016-05-01 23:21:28 -07:00
Brent
b51561cd9b Fixed slack component bug with getting the channel when sending a message (#1954) 2016-05-01 23:10:21 -07:00
Fabian Affolter
4710b38fad Add support for sensor classes (#1950) 2016-05-01 23:05:53 -07:00
Martin Hjelmare
069a4b1706 Refactor mysensors component
* Add MySensorsDeviceEntity class to hold the common attributes,
	properties and methods for mysensors entities.
* Inherit from MySensorsDeviceEntity class in binary_sensor, light,
	sensor and switch mysensors platforms.
* Remove not needed attribute and method for const in GatewayWrapper
	class. The const attribute is already set in the wrapped object.
* Clean up state property for mysensors sensor entities.
* Inherit from MySensorsLightRGB in MySensorsLightRGBW class.
* Remove use of get_component in mysensors component and platforms.
* Clean up update method in MySensorsDeviceEntity class.
2016-04-30 15:27:59 +02:00
Fabian Affolter
cfd7ca344e Upgrade pushbullet.py to 0.10.0 (#1939) 2016-04-29 07:49:46 +02:00
Robbie Trencheny
abc00c76bc Merge pull request #1914 from robbiet480/uber-metered-fix
Uber: Dont load price sensors for metered (read: taxi/cabs) products
2016-04-28 17:57:59 -07:00
Robbie Trencheny
f8340b94bc Fix variable name 2016-04-28 17:57:03 -07:00
Erik Eriksson
cc202b886b ELIQ Online platform cleanup (#1942) 2016-04-28 17:33:33 +02:00
Paulus Schoutsen
588a0cc947 Update frontend 2016-04-28 16:32:25 +02:00
Alexander Fortin
8c943c966a Improve feedreader roboustness (#1926)
* bugfix: ignore not existing property in feedreader item
* add info and debug data
* split logic in smaller and hopefully easier to understand functions
2016-04-28 15:01:44 +02:00
Paulus Schoutsen
1d28fa712f Fix script merge conflict 2016-04-28 13:39:44 +02:00
gwendalg
31e019e88a script: Fix log output (#1918)
Signed-off-by: Gwendal Grignou <gwendal@gmail.com>
2016-04-28 13:19:38 +02:00
Fabian Affolter
157036c1d2 Upgrade pytz to 2016.4 (#1932)
* Upgrade pytz to 2016.4

* Upgrade pytz to 2016.4
2016-04-28 13:02:21 +02:00
Paulus Schoutsen
6354399d55 Initial script condition support (#1910) 2016-04-28 12:03:57 +02:00
Dennis Karpienski
953223b81b Yamaha: added mapping and exclude lists (#1880)
* added mapping and exclude lists

* reworked pr

* made code more pythonic
2016-04-28 12:03:24 +02:00
Paulus Schoutsen
0d261be6ce Update ISSUE_TEMPLATE.md 2016-04-28 11:38:38 +02:00
Fabian Affolter
d608153dbb Upgrade jsonrpc-requests to 0.2 (#1937) 2016-04-28 10:06:15 +02:00
Fabian Affolter
f5429c89b6 Upgrade TwitterAPI to 2.4.1 (#1931) 2016-04-28 08:25:55 +02:00
Fabian Affolter
7331f4910e Upgrade schiene to 0.15 (#1929) 2016-04-28 08:23:23 +02:00
Fabian Affolter
6b81773075 Upgrade messagebird to 1.2.0 (#1928) 2016-04-28 08:23:03 +02:00
Brad Johnson
725b336683 Upgrading to python-wink 0.7.5. (#1925)
This fixes a bug where light bulb statuses were failing to update on the HA polling update call.
2016-04-28 08:22:39 +02:00
Fabian Affolter
f0bcdc6bd8 Upgrade python-telegram-bot to 4.0.1 (#1927) 2016-04-28 08:09:38 +02:00
Kyle Hendricks
59e95f0d86 Fix a couple dead links in the README (#1936) 2016-04-28 07:35:36 +02:00
Josh Wright
1859c84e6d Ignore .venv (#1923)
When using virtualfish ('virtualenvwrapper' for the fish shell), you
can create a .venv file in a directory that contains the name of a
virtualenv that will be activated automatically when you cd into that
directory. This is a good and useful thing, but since folks will have
different names for their virtualenvs, we should ignore this file.

That... and I'm probably the only one using fish/virtualfish...
2016-04-27 15:27:49 -04:00
gwendalg
2b7e1a2cc9 mqtt: Fix logic when embedded and broker configs are present. (#1919)
Fix test to prevent early exit of mqtt init handler when
both embedded and broker configs are present.

Signed-off-by: Gwendal Grignou <gwendal@gmail.com>
2016-04-26 12:17:56 +02:00
Daniel Høyer Iversen
6717215438 Merge pull request #1905 from home-assistant/rfxtrx_sensor_fix
Rfxtrx: Simplified configuration
2016-04-26 11:54:11 +02:00
blackdog70
5159cc525b Arduino: Fixed pin_mode digital input (#1909) 2016-04-26 11:46:54 +02:00
happyleavesaoc
9b3403943c update snapcast with source select (#1908)
* update snapcast

* fix id
2016-04-26 11:46:08 +02:00
Dan
b4be508741 Make zwave component user sane defaults. (#1891)
Change the zwave config default path to be valid in all but rare
edge cases. This will let that config value be truely optional.

Also check that libopenzwave is installed and print a warrning if
it cannot be found pointing at the site.
2016-04-26 11:41:20 +02:00
Micha LaQua
08d60fb04b Initial support for generic 433Mhz GPIO adapters on a Raspberry Pi (#1865)
* initial support for generic 433mhz gpio adapters

* rpi-rf: refactor id_on/id_off to code_on/code_off

fits the purpose better and improves understanding

* rpi-rf: use v0.9.4

* rpi-rf: update features and dependencies to v0.9.5

includes the ability to optionally specify a protocol for each switch

* rpi-rf: remove explicit RPi.GPIO dependency

already a dependency of the rpi-rf module

* rpi-rf: make setting gpio, code_on and code_off mandatory

* rpi-rf: remove unused value_template

* rpi-rf: only enable TX once if there are switches
2016-04-26 11:35:01 +02:00
Dan Cinnamon
7154603567 Summary: Enhanced to make more robust and efficient. (#1917)
Prevented a switch from being turned on twice.

Made the module regex more robust.

Refactored the code to reduce the amount of network traffic to/from pulseaudio.

Fixed pylint issues
2016-04-26 11:29:20 +02:00
Robbie Trencheny
ee9996374c Dont load price sensors for metered (read: taxi/cabs) products 2016-04-25 11:43:18 -07:00
John Arild Berentsen
69daa383dd Exclude locally built python-openzwave from tox. (#1911)
Excludes `build` directory built by running build_python_openzwave script. When running tox test locally.
2016-04-25 12:06:28 +02:00
Daniel
2ca1f7542f Refactor rfxtrx code 2016-04-24 13:42:59 +02:00
Bart274
d4fe6f385a Update pyicloud version (#1849)
* Update requirements_all.txt

* Update icloud.py
2016-04-24 06:49:04 +02:00
deisi
3b0a35f571 Ontime and delay attribute are allways present in deutsche_bahn sensor (#1875) 2016-04-24 06:43:25 +02:00
Jeff Schroeder
448edecdd1 Merge pull request #1896 from fabaff/slacker-upgrade
Upgrade slacker to 0.9.10
2016-04-23 21:43:03 -05:00
Jeff Schroeder
d0357cd42f Merge pull request #1901 from ishults/dev
Use protocol of web page for yr.no pictures
2016-04-23 21:18:31 -05:00
Igor Shults
7afdca1121 Use protocol of web page for yr.no pictures 2016-04-23 21:09:39 -05:00
Daniel
55b51cb3fa Update rfxtrx tests to handle new config 2016-04-23 20:13:24 +02:00
Daniel
74022a3978 New configuration for rfxtrx sensor 2016-04-23 19:55:05 +02:00
Fabian Affolter
d232020de4 Upgrade slacker to 0.9.10 2016-04-23 18:20:28 +02:00
sander76
27ce394571 external dependency evohomeclient 0.2.4 gave a json decode error. (#1895)
* external dependency evohomeclient 0.2.4 gave a json decode error.
upped to 0.2.5
2016-04-23 18:03:43 +02:00
Paulus Schoutsen
413b8d34e4 Merge pull request #1892 from home-assistant/netdisco-update
Netdisco update
2016-04-23 08:41:08 +02:00
Paulus Schoutsen
8f8d936539 Netdisco bump to 0.6.6 2016-04-23 08:34:07 +02:00
Paulus Schoutsen
197d9639f9 Merge pull request #1882 from home-assistant/service-parameters
Trigger variables in automation actions
2016-04-23 07:46:26 +02:00
Paulus Schoutsen
14bd630c1d Service/Script cleanup 2016-04-23 07:11:21 +02:00
Paulus Schoutsen
533799656e Cache script object for Alexa 2016-04-23 07:10:57 +02:00
Paulus Schoutsen
1d8554359c Allow setting entity namespace (#1887) 2016-04-23 06:34:49 +02:00
Paulus Schoutsen
cc42f2d8be Show error message frontend if can't find server (#1886) 2016-04-23 06:30:34 +02:00
Per Sandström
ba696888c1 Merge pull request #1889 from persandstrom/verisure_updated_links
Verisure: new version and changed "key" in smartplug
2016-04-22 20:15:26 +02:00
Per Sandström
a4083bab1a new version and changed "key" 2016-04-22 20:02:54 +02:00
Fabian Affolter
1c6a2f87eb Add link to docs 2016-04-22 15:48:40 +02:00
Paulus Schoutsen
4a5411a957 Allow calling scripts from Alexa 2016-04-22 05:30:30 -04:00
Paulus Schoutsen
612a017bc6 Automation: Allow embedding script definition 2016-04-21 22:36:14 -04:00
Paulus Schoutsen
b8e4db9161 Script entities to allow passing in variables 2016-04-21 22:24:23 -04:00
Paulus Schoutsen
26863284b6 Script helper: support variables 2016-04-21 21:42:20 -04:00
Paulus Schoutsen
09a771a026 Move script component tests to script helper tests 2016-04-21 21:29:28 -04:00
Paulus Schoutsen
f76d545a08 Add script logic into helper. 2016-04-21 21:06:05 -04:00
Dennis Karpienski
2333c0ca3b WebOS component fixes
* fixed some exceptions

* add requirements to notify

* added optimistic state to power off

* run requirements script
2016-04-21 20:35:56 -04:00
Paulus Schoutsen
4e568f8b99 Automation: Add trigger context and expose to action 2016-04-21 13:59:42 -07:00
Paulus Schoutsen
c4913a87e4 Alexa: Expose intent variables to service calls 2016-04-21 12:27:23 -07:00
Paulus Schoutsen
4acb121689 Allow variables in service.call_from_config 2016-04-21 12:22:19 -07:00
Daniel Høyer Iversen
3318c55c65 Heat control config validation
* heat control configuration validation

* fix heat contol test
2016-04-21 07:59:35 -07:00
Daniel Høyer Iversen
d3fb69783d Tellstick config validation 2016-04-21 07:57:28 -07:00
Dan
43a94995c2 Update unifi version
New unifi version has a fix that will allow it to install correctly
2016-04-21 07:56:19 -07:00
John Arild Berentsen
dfa37511ad Improve recognition for zwave thermostats 2016-04-21 07:51:31 -07:00
Paulus Schoutsen
4745282a95 Update underline of header README 2016-04-20 19:00:12 -07:00
Paulus Schoutsen
1d3c6da6c8 Version bump to 0.18.2 for pypi 2016-04-20 18:59:47 -07:00
Paulus Schoutsen
623b023ac0 Version bump to 0.19.0.dev0 2016-04-20 18:44:27 -07:00
Paulus Schoutsen
8f18cb34d6 Release 0.18 #1854
0.18
2016-04-20 18:43:54 -07:00
Paulus Schoutsen
b6e956a3d0 Version bump to 0.18 2016-04-20 18:43:21 -07:00
Paulus Schoutsen
3e0f64e4f6 Update frontend 2016-04-20 18:13:25 -07:00
Dennis Karpienski
934cd876b4 fixed some issues with webos (#1856)
* fixed some issues

* fixed linter
2016-04-20 14:09:50 -07:00
Christoph Wagner
fbfdf5a286 Removed redundant Throttle decorator (#1862) 2016-04-20 00:20:30 -07:00
Paulus Schoutsen
7d4b11f4ec Fix wrong merge 2016-04-19 22:59:29 -07:00
Paulus Schoutsen
4abbbf0f50 Merge remote-tracking branch 'origin/master' into dev
Conflicts:
	homeassistant/bootstrap.py
	homeassistant/components/frontend/version.py
	homeassistant/const.py
2016-04-19 22:53:26 -07:00
Paulus Schoutsen
0c58a1a499 Sort .coveragerc 2016-04-19 21:45:29 -07:00
Paulus Schoutsen
bdc39ff905 Update frontend 2016-04-19 21:39:44 -07:00
Paulus Schoutsen
9d391becc1 Add mysensors tcp ethernet gateway (#1861)
* Bump version of pymysensors to 0.6, which includes the tcp gateway.
* Update requirements_all.txt.
* Replace CONF_PORT with CONF_DEVICE and ATTR_PORT with ATTR_DEVICE.
* Add tcp_port in config.
* Try to guess if tcp or serial gateway is configured, by validating
	device name as an ip address. If successful setup tcp gateway, if it
	fails, setup serial gateway.
* Update device_state_attributes to show correct device, ethernet or
	serial.
2016-04-19 21:00:56 -07:00
Paulus Schoutsen
2e79e9d5bb Correct celcius to celsius (#1860) 2016-04-19 20:30:44 -07:00
Jon Evans
9090672146 Pass format through to temper_device so that Fahrenheit works (#1858) 2016-04-19 20:00:03 -07:00
Dennis Karpienski
46274d4393 add media_player webos tv platform (#1853)
* added webos tv platform

* moved coverage entries
2016-04-19 08:53:58 -07:00
Paulus Schoutsen
177d0c20d9 Add bluepy package dependency to Dockerfile 2016-04-19 08:49:05 -07:00
Chema García
b9fd4919b3 Set name to hyperion assets (#1822)
* set name to hyperion assets

by specifying the name in the configuration:

light kodi_xxx:
  name: "Kodi XXX"
  platform: hyperion
  host: 192.168.1.222
  port: 19444

* Update hyperion.py

fix extra space
2016-04-19 08:19:27 -07:00
Manoj
c1ca13d613 Add bluetooth device tracker (#1830)
This tracker discovers new devices on boot and tracks bluetooth devices
periodically based on interval_seconds value. Devices discovered are
stored with 'BT_' as the prefix for device mac.

Requires PyBluez
2016-04-19 08:18:46 -07:00
Alexander Fortin
c0a7b0f474 Add feedreader component (#1836) 2016-04-19 08:14:36 -07:00
Markus Peter
e61ffff646 Support for EQ3 Bluetooth Smart Thermostats (#1839)
* Initial Support for EQ3 Bluetooth Smart Radiator Thermostats

* tox runs successfully

* Moved device specific stuff to bluepy_devices library

* lint fix
2016-04-19 08:12:27 -07:00
Daniel Høyer Iversen
16fe7dd937 rfxtrx config validation 2016-04-19 07:13:58 -07:00
Greg Dowling
5b0ab5c118 Merge pull request #1842 from home-assistant/add_imperial_meter
Add support for imperial gas meter and user specified gas calorific value.
2016-04-18 12:10:07 +01:00
Dan
276c2070be Do not setup or configure a hue bridge twice (#1848)
Tracks previously configured bridges by IP and prevents duplicates
from being configured
2016-04-17 22:41:31 -07:00
Paulus Schoutsen
c89e9d87e8 Catch platform setup error (#1847) 2016-04-17 22:07:53 -07:00
Brad Johnson
09693bf16c Upgrading to python-wink 0.7.4 and improving RGB color support in HA (#1832) 2016-04-17 19:07:21 -07:00
Jan Harkes
bb439129e6 Bump pywemo version to 0.4.2 2016-04-17 21:02:31 -04:00
Jan-Preben Mossin
5931bac695 Only add visible sonos devices
Some Sonos devices (e.g SUB) does not have
a upnp media renderer, but are discovered as sonos
devices. Creating a SonosDevice object from such a
device will fail.
2016-04-17 16:45:16 -07:00
John Arild Berentsen
b45c0cd735 Zwave import fixes for stability (#1845) 2016-04-17 14:46:51 -07:00
Paulus Schoutsen
ea38742067 Update bootstrap frontend script 2016-04-17 13:59:11 -07:00
Flavio Castelli
b5f1c1332a Sonos: better handling of offline players (#1829)
When a sonos player goes offline an endless stream of
exceptions is raised. That happens because homeassistant keeps trying
to refresh its status.

This can happen even when a sonos player has gone offline **before**
homeassistant is started. The sonos players take some time before
realizing one of their mates is no longer online, leading to the same
stream of exceptions inside of homeassistant.

Three types of exceptions of can be raised:

  - `requests.packages.urllib3.exceptions.MaxRetryError`
  - `requests.packages.urllib3.exceptions.NewConnectionError`
  - `TimeoutError`

It's not possible to handle all of them with a single `except` block
because they are raised in a random order and after some delays. That
means a 2nd or 3rd exception can take place while handling the 1st one.

The only solution is to check whether a a player is actually reachable
by attempting to connect to a service that must be running on it.

Also all the players in a 'unknown' state should not be polled by
homeassistant (despite of their brand).

I'm going to upstream the `_is_reachable` method I added to the
`sonos.py` file into `SoCo`. In the meantime we must ship this piece
of code with homeassistant.

Signed-off-by: Flavio Castelli <fcastelli@suse.com>
2016-04-17 13:17:13 -07:00
Dan Cinnamon
a482c4b2a1 Allow mpd to handle the play_media service. (#1831)
Even though there's not really a place in the UI for it, it will now be possible to set a playlist as part of a script, and ultimately a scene.
2016-04-17 12:34:33 -07:00
deisi
e8bf47ff59 Fix for the issue #1323 (#1835) 2016-04-17 12:32:04 -07:00
Paulus Schoutsen
ecf411c4c5 Merge pull request #1838 from MartinHjelmare/fix-influxdb
Fix field type conflict in influxdb
2016-04-17 10:43:47 -07:00
pavoni
31f061f4d3 Add support for imperial meter and user soecified calorific value. 2016-04-17 18:34:40 +01:00
icovada
b29dedaecf SCSGATE: Actually cycle through all devices to register (#1841)
* SCSGATE: Actually cycle through all devices to register

Modify _activate_next_device function to actually cycle through self-devices_to_register instead of simply checking whether there are any and onl registering one

* Deleted extra white lines
2016-04-17 10:17:42 -07:00
MartinHjelmare
ca2d969198 Fix field type conflict in influxdb
* Add STATE_UNAVAILABLE to states that are ignored when writing to the
  database. This will avoid a field type error for string if the field
  already contains a different type, eg integer.
* Add test for ignored states for influxdb.
* Clean up influxdb tests.
2016-04-16 20:25:39 +02:00
Paulus Schoutsen
37a28c799f Merge pull request #1833 from home-assistant/iso8601
Use standardised datetime format
2016-04-16 10:06:33 -07:00
Paulus Schoutsen
1cfbeabaec Update frontend 2016-04-16 10:05:29 -07:00
Paulus Schoutsen
68d92c3196 Use standardised datetime format 2016-04-16 01:46:50 -07:00
Justyn Shull
bf3b77e1f2 Change sqlite queries to work with older versions of sqlite 2016-04-15 21:18:51 -05:00
Justyn Shull
d5ca97b1f6 Add tests for purging old states and events 2016-04-15 21:02:17 -05:00
Justyn Shull
fd48fc5f83 Add CONFIG_SCHEMA to verify config. Move purge_days key name to
CONF_PURGE_DAYS so it can be changed easier later.

Use 'recorder' domain instead of 'history' domain.

Pass purge_days config directly into Recorder object instead of passing
the config object around.
2016-04-15 19:54:30 -05:00
Justyn Shull
c89cd6a68c Add 'purge_days' option to the history/recorder component
Issue https://github.com/balloob/home-assistant/issues/1337

When purge_days is set under the history component, recorder.py will
delete all events and states that are older than purge_days days ago.

Currently, this is only done once at start up.   A vacuum command is
also run to free up the disk space sqlite would still use after deleting
records.
2016-04-15 19:54:30 -05:00
Paulus Schoutsen
e7520ef401 Update frontend + mdi 2016-04-14 22:57:41 -07:00
Paulus Schoutsen
f5ee3e6e13 Merge pull request #1821 from home-assistant/service-validations
Service validations
2016-04-13 22:38:14 -07:00
Alex Harvey
0abedd8b46 fix for plex not detecting plex players after power up (#1824) 2016-04-13 22:33:30 -07:00
Paulus Schoutsen
abd376cbba Merge pull request #1816 from JshWright/feature/accurate-away-temps-nest
Improve Nest target temp display
2016-04-13 22:01:01 -07:00
Øystein Hansen
8b986b0b97 mysensors.py Prevent sensor name mixup (#1802)
Change the sensor name schema so nodes are not mixed up. The dot that was pressent before was ignored by home-assistant.
Before name:  <skeatchname> <node_id><child_id>  eg. wall 107
New name   :  <skeatchname> <node_id> <child_id>  eg. wall 10 7
This will result in a Home Assistant Entity ID of: sensor.wall_10_7
2016-04-13 21:22:40 -07:00
Dennis Karpienski
f1d8667d7e Yamaha AVR Input Select (#1796)
* added select input for yamaha

* set source_list to none on init
2016-04-13 21:13:12 -07:00
Jan Harkes
298a1c2af7 Service validation for alarm_control_panel component. 2016-04-13 13:45:11 -04:00
Fabian Affolter
9d71a8c4b9 Merge pull request #1819 from fabaff/typos
Update file header and fix typos
2016-04-13 19:35:06 +02:00
Jan Harkes
5cdaee7ebb Service validation for browser component. 2016-04-13 12:57:47 -04:00
Jan Harkes
7c9729b9c1 Service validation for conversation component. 2016-04-13 12:48:39 -04:00
Jan Harkes
003bd24976 Service validation for downloader component. 2016-04-13 12:35:08 -04:00
Jan Harkes
652fe7e0f2 Service validation for garage_door component. 2016-04-13 12:35:08 -04:00
Jan Harkes
49b002dc53 Service validation for ifttt component. 2016-04-13 12:35:07 -04:00
Jan Harkes
7e0d9bc709 Service validation for input_boolean component. 2016-04-13 12:35:07 -04:00
Jan Harkes
bfdbbbddac Service validation for input_select component. 2016-04-13 12:35:07 -04:00
Jan Harkes
620d7a92f0 Service validation for input_slider component. 2016-04-13 12:35:07 -04:00
Jan Harkes
8b40346db3 Service validation for keyboard component. 2016-04-13 12:35:07 -04:00
Jan Harkes
5c520b0d35 Service validation for lock component. 2016-04-13 12:35:07 -04:00
Jan Harkes
d6f3123937 Service validation for logbook component. 2016-04-13 12:35:07 -04:00
Jan Harkes
7ffc254a87 Service validation for notify component. 2016-04-13 12:35:07 -04:00
Jan Harkes
40e36126bc Service validation for rollershutter component. 2016-04-13 12:35:07 -04:00
Jan Harkes
ad6f5d3b1d Service validation for scene component. 2016-04-13 12:35:07 -04:00
Jan Harkes
567d1065b2 Service validation for script component. 2016-04-13 12:35:07 -04:00
Jan Harkes
d90f31bf6e Config and service validation for shell_command component. 2016-04-13 12:35:07 -04:00
Jan Harkes
1deaf2fe8f Service validation for switch component. 2016-04-13 12:35:07 -04:00
Jan Harkes
730514cea8 Service validation for the thermostat component. 2016-04-13 12:35:07 -04:00
Fabian Affolter
85901d60c4 Update file header and fix typos 2016-04-13 13:50:28 +02:00
Robbie Trencheny
05469e6d9b Update pyGTFS version 2016-04-13 00:40:12 -07:00
Paulus Schoutsen
1f96c4ad88 Update submodule 2016-04-12 23:57:07 -07:00
Robbie Trencheny
c121c9aa39 Fix links in README.rst 2016-04-12 21:37:10 -07:00
Paulus Schoutsen
3ed104529c Merge pull request #1811 from fabaff/moving
Update links
2016-04-12 21:16:01 -07:00
Jan Harkes
0e10f7ced9 Ignore tests/config/deps/ for both git and flake8.
Sometimes py.test leave some packages around in tests/config/deps.
Make sure these do not accidentally get pulled into a commit or
cause a local tox run to fail.
2016-04-12 18:46:34 -04:00
Jan Harkes
4aa43bbf7a New version of betamax breaks the yr.no sensor test.
Pin to betamax-0.5.1 for now.
2016-04-12 18:46:34 -04:00
Josh Wright
1685bbe1f7 Fix copy/paste logic error 2016-04-12 18:45:57 -04:00
Josh Wright
942d722dcf Improve target temperature selection logic
When picking which of the high/low temperatures to display as the
"target", it makes more sense to take the outside temperature into
consideration, rather than the current temperature of the thermostat.

If the temperature outside is less than the temperature of the
thermostat, then we are far more likely to end up in "heating" mode
eventually, and vice versa, regardless of where the current inside
temp falls in the range between high and low.
2016-04-12 18:25:24 -04:00
Josh Wright
c4a71fbfa7 Include away temps in target temps
The target temperature methods were not taking the 'away' status
into account. leading to misleading target temps being reported.
2016-04-12 18:15:24 -04:00
Jan Harkes
ebf45012fb Ignore tests/config/deps/ for both git and flake8.
Sometimes py.test leave some packages around in tests/config/deps.
Make sure these do not accidentally get pulled into a commit or
cause a local tox run to fail.
2016-04-12 14:58:02 -04:00
Jan Harkes
0bf49aea6f New version of betamax breaks the yr.no sensor test.
Pin to betamax-0.5.1 for now.
2016-04-12 14:43:04 -04:00
Daniel Høyer Iversen
cd80e41b32 Allow negative elevation in yr config 2016-04-12 07:54:03 -07:00
Fabian Affolter
4ecd8d5f11 Update links 2016-04-12 14:18:18 +02:00
Paulus Schoutsen
5b8a2b00df Update frontend 2016-04-12 00:24:19 -07:00
Alexander Fortin
24569e6169 Make owntracks.py pylint clean (#1808)
* refactor validation logic into validate_payload function
* add debugging messages to validation
2016-04-11 22:02:47 -07:00
Fabian Affolter
4a16d8064d Enable certificate verification and allow non-encrypted communication (#1763) 2016-04-11 21:55:17 -07:00
Jan Harkes
9d848731d9 Nest config validation (#1810)
* Config validation for Nest platforms.
2016-04-11 21:52:19 -07:00
Jan Harkes
656e187729 Some fixes for yr config validation. (#1809)
The setup_platform function is trying to get CONF_LATITUDE and CONF_LONGITUDE,
but the validation schema was not accepting these.

Also moved CONF_MONITORED_CONDITIONS and CONF_ELEVATION to homeassistant.const
because they are used in other places.
2016-04-11 21:44:39 -07:00
Jan Harkes
241735c924 Change local library path from {config_dir}/lib to {config_dir}/deps. (#1799)
Just on the off chance that someone who happens to run as root and also
doesn't correctly parse "just remove config /lib and restart".
2016-04-11 20:07:50 -07:00
Paulus Schoutsen
3de51bf75d Merge pull request #1807 from balloob/release-0.17.3
0.17.3
2016-04-11 19:56:41 -07:00
Paulus Schoutsen
4023021b21 Version bump to 0.17.3 2016-04-11 19:48:37 -07:00
Jan Harkes
2c665ca3e4 Do not propagate api password (#1797)
* Do not propagate API password in service requests.

It makes service validation fail. The choice is to either handle it as an
optional key in every service handler and make sure it doesn't end up in event
stream and notifications, or to strip it as early as possible.

* Some places still need a forwarded api password.

- Event forwarding/remote api uses the local api password to
  authenticate against the remote instance.
- The generated index.html at '/' embeds the api password.
2016-04-11 19:48:16 -07:00
Jan Harkes
4857117dda Do not propagate api password (#1797)
* Do not propagate API password in service requests.

It makes service validation fail. The choice is to either handle it as an
optional key in every service handler and make sure it doesn't end up in event
stream and notifications, or to strip it as early as possible.

* Some places still need a forwarded api password.

- Event forwarding/remote api uses the local api password to
  authenticate against the remote instance.
- The generated index.html at '/' embeds the api password.
2016-04-11 19:37:15 -07:00
Josh Wright
80d6e9f08f Use constant time comparison for http authentication (#1804)
In order to prevent a potential timing attack, it's important to make
sure the password check takes the same amount of time, regardless of
how many characters in the candidate password match the real password.

This commit does increase the verbosity of the authentication check.
Generally it is a good idea for authentication logic to be very clear,
even if that requires some extra verbosity.
2016-04-11 19:36:25 -07:00
Robbie Trencheny
0018d2b3f5 Fix flake8 error on zeroconf.py 2016-04-10 18:55:54 -07:00
Robbie Trencheny
5d8cd6d49d Merge pull request #1790 from robbiet480/upnp
UPnP port mapping component
2016-04-10 18:47:47 -07:00
Robbie Trencheny
ed6958f477 Change needs_auth ZeroConf key 2016-04-10 18:46:57 -07:00
Robbie Trencheny
27d624fc4a Merge remote-tracking branch 'upstream/dev' into upnp 2016-04-10 18:24:04 -07:00
Paulus Schoutsen
948aa6838d Remove stale print 2016-04-10 18:22:25 -07:00
Robbie Trencheny
6f149d414a Disable import-error on upnp 2016-04-10 18:19:32 -07:00
Robbie Trencheny
f1e46e63c0 Merge pull request #1733 from robbiet480/http-zeroconf
ZeroConf component
2016-04-10 18:17:06 -07:00
Robbie Trencheny
a3959d5e01 Update netdisco dependency to 0.6.4 (deja vu all over again!) 2016-04-10 18:10:31 -07:00
Robbie Trencheny
eca1631f1b Update netdisco dependency to 0.6.3 2016-04-10 18:05:30 -07:00
Robbie Trencheny
f30b406334 Merge remote-tracking branch 'upstream/dev' into http-zeroconf 2016-04-10 18:04:56 -07:00
Robbie Trencheny
1de45ebe8b Fix api_password conditional and close zeroconf when we shut down 2016-04-10 17:59:21 -07:00
Robbie Trencheny
5b51f682ca Remove unnecessary disable=no-name-in-module 2016-04-10 17:49:07 -07:00
Robbie Trencheny
41c3f695b4 UPnP port mapping component 2016-04-10 17:44:57 -07:00
Paulus Schoutsen
197388a9b2 Prevent device tracker error 2016-04-10 17:35:33 -07:00
Dennis Karpienski
a862e994c7 Thinkingcleaner support (#1784)
* added first implementation of thinking cleaner

* fix lock release

* fixed tox flaws

* updated coveragerc

* fixed lock

* changed update lock

* fixed codestyle
2016-04-10 16:59:53 -07:00
Markus Peter
fec45033bc Expanded homematic component with MAX! support via homegear (#1783)
* Expanded homematic component with MAX! support via homegear
Also multithreading fixes

* fixed tox errors

* incorporate changes suggested by balloob

* replaced HomematicConfig Container Class with namedtuple

* fixed lint errors
2016-04-10 16:26:08 -07:00
Robbie Trencheny
e1ffdcc5f1 Use hass.config.api instead of hass.http 2016-04-10 16:09:52 -07:00
Robbie Trencheny
085d90ed67 Revert all http.py changes 2016-04-10 16:08:00 -07:00
Daniel Høyer Iversen
769d958464 Config validation for rfxtrx sensor (#1780) 2016-04-10 16:05:32 -07:00
Robbie Trencheny
e70338dfe1 Block zeroconf from tests 2016-04-10 16:03:40 -07:00
Robbie Trencheny
beac69ad17 Final clean up, flake8, pylint, change a variable name, remove unnecessary imports 2016-04-10 16:02:07 -07:00
Robbie Trencheny
c33c2c01d2 Break Zeroconf into its own component 2016-04-10 15:34:04 -07:00
Paulus Schoutsen
4f834ba3f1 Make all entity_ids lowercase (#1786) 2016-04-10 15:20:20 -07:00
Paulus Schoutsen
6d65b0bbd7 Update frontend 2016-04-10 15:18:44 -07:00
Daniel Høyer Iversen
d3493c7e5a Config validation of yr sensor (#1767) 2016-04-10 10:43:05 -07:00
Josh Wright
24257fe4a3 Don't round values in Thermostat internal state (#1782)
Since all values coming out of the Thermostat component pass though the
_convert_for_display() method (which handles any necessary rounding),
there is no need to round values that only exist in the internal state
of the thermostat device. It serves no purpose and risks rounding
errors/precision loss.
2016-04-10 10:41:13 -07:00
Paulus Schoutsen
3d98b8b5b3 Update frontend 2016-04-10 01:43:40 -07:00
Paulus Schoutsen
988dd2ca83 Update frontend 2016-04-10 01:33:01 -07:00
Joel Asher Friedman
9808c0e3fd mqtt garage door component (#1742) 2016-04-09 20:31:53 -07:00
OpenDave15
91b1ebaeb7 Allow use of pynetgear 3.3 port parameter. (#1777)
* Allow use of pynetgear 3.3 port parameter.

* Fix lint problem
2016-04-09 20:29:06 -07:00
Paulus Schoutsen
1e86044590 Update netdisco to 0.6.2 2016-04-09 20:24:34 -07:00
Jan Harkes
73859f59f0 Make yaml config parser errors look less like bugs. (#1776)
Instead of nested tracebacks, show a simpler error message.

    Config directory: /home/user/.homeassistant
    ERROR:homeassistant.util.yaml:duplicate key: "script"
      in "/home/user/.homeassistant/configuration.yaml", line 95, column 0
      in "/home/user/.homeassistant/configuration.yaml", line 108, column 0
2016-04-09 15:25:01 -07:00
Paulus Schoutsen
b87e2437aa Prevent device tracker from creating invalid YAML (#1774) 2016-04-09 13:38:51 -07:00
Jan Harkes
446d367aeb Accept group without entities in configuration. (#1768)
* Accept group without entities in configuration.

People seem to use these as placeholders for future expansion of their
home automation dreams, and we used to accept them.  We still have to
specify at least one of 'name', 'view' or 'icon' so that the group is
parsed as a dictionary.

* Also accept empty entities: key in a group.

* Additional fix for empty entities value in a group config.
2016-04-09 09:24:18 -07:00
Paulus Schoutsen
982f8f41ae Update frontend with weblink fix 2016-04-09 09:09:17 -07:00
Jan Harkes
7a25ae3e2c Fix for light service validation. (#1770)
Incorrect validation tested if passed value was a list instead of
a member of the list.
2016-04-09 09:07:13 -07:00
Jan Harkes
3aa4727b18 Fix for MQTT config validation on the protocol field. (#1765) 2016-04-09 09:03:41 -07:00
Paulus Schoutsen
b3bad10dab Merge pull request #1758 from turbokongen/patch-1
We need to allow extra keys on top level componenet config
2016-04-09 09:02:55 -07:00
Fabian Affolter
c210242c80 Add comment about location of hass (fixes #1723) 2016-04-09 10:55:42 +02:00
John Arild Berentsen
f36cfcdbd9 We need to allow extra keys on top level componenet config
fixes #1756
2016-04-09 10:50:46 +02:00
Paulus Schoutsen
ff47cffe8a Version bump to 0.18.0.dev0 2016-04-08 21:43:29 -07:00
Robbie Trencheny
27aabd961c Remove unnecessary variable 2016-04-06 13:51:43 -07:00
Robbie Trencheny
86dad0c045 Add ZeroConf support to http.py 2016-04-06 13:51:26 -07:00
527 changed files with 31227 additions and 13898 deletions

View File

@@ -3,20 +3,27 @@ source = homeassistant
omit =
homeassistant/__main__.py
homeassistant/scripts/*.py
# omit pieces of code that rely on external devices being present
homeassistant/components/alarm_control_panel/alarmdotcom.py
homeassistant/components/alarm_control_panel/nx584.py
homeassistant/components/apcupsd.py
homeassistant/components/*/apcupsd.py
homeassistant/components/arduino.py
homeassistant/components/*/arduino.py
homeassistant/components/apcupsd.py
homeassistant/components/*/apcupsd.py
homeassistant/components/bloomsky.py
homeassistant/components/*/bloomsky.py
homeassistant/components/dweet.py
homeassistant/components/*/dweet.py
homeassistant/components/ecobee.py
homeassistant/components/*/ecobee.py
homeassistant/components/envisalink.py
homeassistant/components/*/envisalink.py
homeassistant/components/insteon_hub.py
homeassistant/components/*/insteon_hub.py
@@ -26,21 +33,40 @@ omit =
homeassistant/components/modbus.py
homeassistant/components/*/modbus.py
homeassistant/components/tellstick.py
homeassistant/components/*/tellstick.py
homeassistant/components/mysensors.py
homeassistant/components/*/mysensors.py
homeassistant/components/nest.py
homeassistant/components/*/nest.py
homeassistant/components/octoprint.py
homeassistant/components/*/octoprint.py
homeassistant/components/qwikswitch.py
homeassistant/components/*/qwikswitch.py
homeassistant/components/rpi_gpio.py
homeassistant/components/*/rpi_gpio.py
homeassistant/components/scsgate.py
homeassistant/components/*/scsgate.py
homeassistant/components/tellduslive.py
homeassistant/components/*/tellduslive.py
homeassistant/components/tellstick.py
homeassistant/components/*/tellstick.py
homeassistant/components/*/thinkingcleaner.py
homeassistant/components/vera.py
homeassistant/components/*/vera.py
homeassistant/components/ecobee.py
homeassistant/components/*/ecobee.py
homeassistant/components/verisure.py
homeassistant/components/*/verisure.py
homeassistant/components/*/webostv.py
homeassistant/components/wemo.py
homeassistant/components/*/wemo.py
@@ -53,18 +79,23 @@ omit =
homeassistant/components/zwave.py
homeassistant/components/*/zwave.py
homeassistant/components/mysensors.py
homeassistant/components/*/mysensors.py
homeassistant/components/enocean.py
homeassistant/components/*/enocean.py
homeassistant/components/nest.py
homeassistant/components/*/nest.py
homeassistant/components/netatmo.py
homeassistant/components/*/netatmo.py
homeassistant/components/rpi_gpio.py
homeassistant/components/*/rpi_gpio.py
homeassistant/components/homematic.py
homeassistant/components/*/homematic.py
homeassistant/components/scsgate.py
homeassistant/components/*/scsgate.py
homeassistant/components/knx.py
homeassistant/components/switch/knx.py
homeassistant/components/binary_sensor/knx.py
homeassistant/components/thermostat/knx.py
homeassistant/components/alarm_control_panel/alarmdotcom.py
homeassistant/components/alarm_control_panel/nx584.py
homeassistant/components/alarm_control_panel/simplisafe.py
homeassistant/components/binary_sensor/arest.py
homeassistant/components/binary_sensor/rest.py
homeassistant/components/browser.py
@@ -76,6 +107,8 @@ omit =
homeassistant/components/device_tracker/actiontec.py
homeassistant/components/device_tracker/aruba.py
homeassistant/components/device_tracker/asuswrt.py
homeassistant/components/device_tracker/bluetooth_tracker.py
homeassistant/components/device_tracker/bt_home_hub_5.py
homeassistant/components/device_tracker/ddwrt.py
homeassistant/components/device_tracker/fritz.py
homeassistant/components/device_tracker/icloud.py
@@ -89,32 +122,53 @@ omit =
homeassistant/components/device_tracker/ubus.py
homeassistant/components/discovery.py
homeassistant/components/downloader.py
homeassistant/components/feedreader.py
homeassistant/components/garage_door/wink.py
homeassistant/components/garage_door/rpi_gpio.py
homeassistant/components/hdmi_cec.py
homeassistant/components/ifttt.py
homeassistant/components/joaoapps_join.py
homeassistant/components/keyboard.py
homeassistant/components/light/blinksticklight.py
homeassistant/components/light/flux_led.py
homeassistant/components/light/hue.py
homeassistant/components/light/hyperion.py
homeassistant/components/light/lifx.py
homeassistant/components/light/limitlessled.py
homeassistant/components/light/osramlightify.py
homeassistant/components/light/x10.py
homeassistant/components/lirc.py
homeassistant/components/media_player/braviatv.py
homeassistant/components/media_player/cast.py
homeassistant/components/media_player/cmus.py
homeassistant/components/media_player/denon.py
homeassistant/components/media_player/directv.py
homeassistant/components/media_player/firetv.py
homeassistant/components/media_player/gpmdp.py
homeassistant/components/media_player/itunes.py
homeassistant/components/media_player/kodi.py
homeassistant/components/media_player/lg_netcast.py
homeassistant/components/media_player/mpchc.py
homeassistant/components/media_player/mpd.py
homeassistant/components/media_player/onkyo.py
homeassistant/components/media_player/panasonic_viera.py
homeassistant/components/media_player/pandora.py
homeassistant/components/media_player/pioneer.py
homeassistant/components/media_player/plex.py
homeassistant/components/media_player/roku.py
homeassistant/components/media_player/russound_rnet.py
homeassistant/components/media_player/samsungtv.py
homeassistant/components/media_player/snapcast.py
homeassistant/components/media_player/sonos.py
homeassistant/components/media_player/squeezebox.py
homeassistant/components/media_player/onkyo.py
homeassistant/components/media_player/panasonic_viera.py
homeassistant/components/media_player/yamaha.py
homeassistant/components/notify/aws_lambda.py
homeassistant/components/notify/aws_sns.py
homeassistant/components/notify/aws_sqs.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/joaoapps_join.py
homeassistant/components/notify/message_bird.py
homeassistant/components/notify/nma.py
homeassistant/components/notify/pushbullet.py
@@ -126,6 +180,7 @@ omit =
homeassistant/components/notify/smtp.py
homeassistant/components/notify/syslog.py
homeassistant/components/notify/telegram.py
homeassistant/components/notify/twilio_sms.py
homeassistant/components/notify/twitter.py
homeassistant/components/notify/xmpp.py
homeassistant/components/scene/hunterdouglas_powerview.py
@@ -134,22 +189,31 @@ omit =
homeassistant/components/sensor/cpuspeed.py
homeassistant/components/sensor/deutsche_bahn.py
homeassistant/components/sensor/dht.py
homeassistant/components/sensor/dweet.py
homeassistant/components/sensor/dte_energy_bridge.py
homeassistant/components/sensor/efergy.py
homeassistant/components/sensor/eliqonline.py
homeassistant/components/sensor/fitbit.py
homeassistant/components/sensor/fixer.py
homeassistant/components/sensor/forecast.py
homeassistant/components/sensor/glances.py
homeassistant/components/sensor/google_travel_time.py
homeassistant/components/sensor/gtfs.py
homeassistant/components/sensor/netatmo.py
homeassistant/components/sensor/nzbget.py
homeassistant/components/sensor/imap.py
homeassistant/components/sensor/lastfm.py
homeassistant/components/sensor/loopenergy.py
homeassistant/components/sensor/neurio_energy.py
homeassistant/components/sensor/nzbget.py
homeassistant/components/sensor/onewire.py
homeassistant/components/sensor/openweathermap.py
homeassistant/components/sensor/openexchangerates.py
homeassistant/components/sensor/plex.py
homeassistant/components/sensor/rest.py
homeassistant/components/sensor/sabnzbd.py
homeassistant/components/sensor/snmp.py
homeassistant/components/sensor/speedtest.py
homeassistant/components/sensor/steam_online.py
homeassistant/components/sensor/supervisord.py
homeassistant/components/sensor/swiss_hydrological_data.py
homeassistant/components/sensor/swiss_public_transport.py
homeassistant/components/sensor/systemmonitor.py
homeassistant/components/sensor/temper.py
@@ -159,20 +223,28 @@ omit =
homeassistant/components/sensor/twitch.py
homeassistant/components/sensor/uber.py
homeassistant/components/sensor/worldclock.py
homeassistant/components/sensor/yweather.py
homeassistant/components/switch/acer_projector.py
homeassistant/components/switch/arest.py
homeassistant/components/switch/dlink.py
homeassistant/components/switch/edimax.py
homeassistant/components/switch/hikvisioncam.py
homeassistant/components/switch/mystrom.py
homeassistant/components/switch/netio.py
homeassistant/components/switch/orvibo.py
homeassistant/components/switch/pulseaudio_loopback.py
homeassistant/components/switch/rest.py
homeassistant/components/switch/rpi_rf.py
homeassistant/components/switch/tplink.py
homeassistant/components/switch/transmission.py
homeassistant/components/switch/wake_on_lan.py
homeassistant/components/thermostat/eq3btsmart.py
homeassistant/components/thermostat/heatmiser.py
homeassistant/components/thermostat/homematic.py
homeassistant/components/thermostat/proliphix.py
homeassistant/components/thermostat/radiotherm.py
homeassistant/components/upnp.py
homeassistant/components/zeroconf.py
[report]

View File

@@ -1,4 +1,6 @@
Feature requests should go in the forum: https://community.home-assistant.io/c/feature-requests
Make sure you are running the latest version of Home Assistant before reporting an issue.
You should only file an issue if you found a bug. Feature and enhancement requests should go in [the Feature Requests section](https://community.home-assistant.io/c/feature-requests) of our community forum:
**Home Assistant release (`hass --version`):**

View File

@@ -1,7 +1,9 @@
**Description:**
**Related issue (if applicable):** #
**Related issue (if applicable):** fixes #
**Pull request in [home-assistant.io](https://github.com/home-assistant/home-assistant.io) with documentation (if applicable):** home-assistant/home-assistant.io#
**Example entry for `configuration.yaml` (if applicable):**
```yaml
@@ -10,7 +12,10 @@
**Checklist:**
If code communicates with devices:
If user exposed functionality or configuration variables are added/changed:
- [ ] Documentation added/updated in [home-assistant.io](https://github.com/home-assistant/home-assistant.io)
If code communicates with devices, web services, or a:
- [ ] 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]).
@@ -21,8 +26,5 @@ 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
[ex-requir]: https://github.com/balloob/home-assistant/blob/dev/homeassistant/components/keyboard.py#L16
[ex-import]: https://github.com/balloob/home-assistant/blob/dev/homeassistant/components/keyboard.py#L51
[ex-requir]: https://github.com/home-assistant/home-assistant/blob/dev/homeassistant/components/keyboard.py#L16
[ex-import]: https://github.com/home-assistant/home-assistant/blob/dev/homeassistant/components/keyboard.py#L51

12
.gitignore vendored
View File

@@ -7,8 +7,10 @@ config/custom_components/*
!config/custom_components/example.py
!config/custom_components/hello_world.py
!config/custom_components/mqtt_example.py
!config/custom_components/react_panel
tests/config/home-assistant.log
tests/testing_config/deps
tests/testing_config/home-assistant.log
# Hide sublime text stuff
*.sublime-project
@@ -77,7 +79,15 @@ nosetests.xml
pyvenv.cfg
pip-selfcheck.json
venv
.venv
# vimmy stuff
*.swp
*.swo
ctags.tmp
# vagrant stuff
virtualization/vagrant/setup_done
virtualization/vagrant/.vagrant
virtualization/vagrant/config

2
.gitmodules vendored
View File

@@ -1,3 +1,3 @@
[submodule "homeassistant/components/frontend/www_static/home-assistant-polymer"]
path = homeassistant/components/frontend/www_static/home-assistant-polymer
url = https://github.com/balloob/home-assistant-polymer.git
url = https://github.com/home-assistant/home-assistant-polymer.git

View File

@@ -8,8 +8,13 @@ matrix:
env: TOXENV=requirements
- python: "3.5"
env: TOXENV=lint
- python: "3.5"
env: TOXENV=typing
- python: "3.5"
env: TOXENV=py35
allow_failures:
- python: "3.5"
env: TOXENV=typing
cache:
directories:
- $HOME/.cache/pip

View File

@@ -4,84 +4,10 @@ Everybody is invited and welcome to contribute to Home Assistant. There is a lot
The process is straight-forward.
- Fork the Home Assistant [git repository](https://github.com/balloob/home-assistant).
- Fork the Home Assistant [git repository](https://github.com/home-assistant/home-assistant).
- Write the code for your device, notification service, sensor, or IoT thing.
- Ensure tests work.
- Create a Pull Request against the [**dev**](https://github.com/balloob/home-assistant/tree/dev) branch of Home Assistant.
- Create a Pull Request against the [**dev**](https://github.com/home-assistant/home-assistant/tree/dev) branch of Home Assistant.
Still interested? Then you should read the next sections and get more details.
Still interested? Then you should take a peak at the [developer documentation](https://home-assistant.io/developers/) to get more details.
## Adding support for a new device
For help on building your component, please see the [developer documentation](https://home-assistant.io/developers/) on [home-assistant.io](https://home-assistant.io/).
After you finish adding support for your device:
- Check that all dependencies are included via the `REQUIREMENTS` variable in your platform/component and only imported inside functions that use them.
- Add any new dependencies to `requirements_all.txt` if needed. Use `script/gen_requirements_all.py`.
- Update the `.coveragerc` file to exclude your platform if there are no tests available or your new code uses a 3rd party library for communication with the device/service/sensor.
- Provide some documentation for [home-assistant.io](https://home-assistant.io/). It's OK to just add a docstring with configuration details (sample entry for `configuration.yaml` file and alike) to the file header as a start. Visit the [website documentation](https://home-assistant.io/developers/website/) for further information on contributing to [home-assistant.io](https://github.com/balloob/home-assistant.io).
- Make sure all your code passes ``pylint`` and ``flake8`` (PEP8 and some more) validation. To check your repository, run `tox` or `script/lint`.
- Create a Pull Request against the [**dev**](https://github.com/balloob/home-assistant/tree/dev) branch of Home Assistant.
- Check for comments and suggestions on your Pull Request and keep an eye on the [CI output](https://travis-ci.org/balloob/home-assistant/).
If you add a platform for an existing component, there is usually no need for updating the frontend. Only if you've added a new component that should show up in the frontend, there are more steps needed:
- Update the file [`home-assistant-icons.html`](https://github.com/balloob/home-assistant/blob/master/homeassistant/components/frontend/www_static/polymer/resources/home-assistant-icons.html) with an icon for your domain ([pick one from this list](https://www.polymer-project.org/1.0/components/core-elements/demo.html#core-icon)).
- Update the demo component with two states that it provides.
- Add your component to `home-assistant.conf.example`.
Since you've updated `home-assistant-icons.html`, you've made changes to the frontend:
- Run `build_frontend`. This will build a new version of the frontend. Make sure you add the changed files `frontend.py` and `frontend.html` to the commit.
### Setting states
It is the responsibility of the component to maintain the states of the devices in your domain. Each device should be a single state and, if possible, a group should be provided that tracks the combined state of the devices.
A state can have several attributes that will help the frontend in displaying your state:
- `friendly_name`: this name will be used as the name of the device
- `entity_picture`: this picture will be shown instead of the domain icon
- `unit_of_measurement`: this will be appended to the state in the interface
- `hidden`: This is a suggestion to the frontend on if the state should be hidden
These attributes are defined in [homeassistant.components](https://github.com/balloob/home-assistant/blob/master/homeassistant/components/__init__.py#L25).
### Proper Visibility Handling
Generally, when creating a new entity for Home Assistant you will want it to be a class that inherits the [homeassistant.helpers.entity.Entity](https://github.com/balloob/home-assistant/blob/master/homeassistant/helpers/entity.py) class. If this is done, visibility will be handled for you.
You can set a suggestion for your entity's visibility by setting the hidden property by doing something similar to the following.
```python
self.hidden = True
```
This will SUGGEST that the active frontend hides the entity. This requires that the active frontend support hidden cards (the default frontend does) and that the value of hidden be included in your attributes dictionary (see above). The Entity abstract class will take care of this for you.
Remember: The suggestion set by your component's code will always be overwritten by user settings in the configuration.yaml file. This is why you may set hidden to be False, but the property may remain True (or vice-versa).
### Working on the frontend
The frontend is composed of [Polymer](https://www.polymer-project.org) web-components and compiled into the file `frontend.html`. During development you do not want to work with the compiled version but with the seperate files. To have Home Assistant serve the seperate files, set `development=1` for the *http-component* in your config.
When you are done with development and ready to commit your changes, run `build_frontend`, set `development=0` in your config and validate that everything still works.
## Testing your code
To test your code before submission, used the `tox` tool.
```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.
You can optionally run tests on only one tox target using the `-e` option to select an environment.
For instance `tox -e lint` will run the linters only, `tox -e py34` will run unit tests only on python 3.4.
### Notes on PyLint and PEP8 validation
In case a PyLint warning cannot be avoided, add a comment to disable the PyLint check for that line. This can be done using the format `# pylint: disable=YOUR-ERROR-NAME`. Example of an unavoidable PyLint warning is if you do not use the passed in datetime if you're listening for time change.

View File

@@ -8,9 +8,9 @@ WORKDIR /usr/src/app
RUN pip3 install --no-cache-dir colorlog cython
# For the nmap tracker
# For the nmap tracker, bluetooth tracker, Z-Wave
RUN apt-get update && \
apt-get install -y --no-install-recommends nmap net-tools cython3 libudev-dev sudo && \
apt-get install -y --no-install-recommends nmap net-tools cython3 libudev-dev sudo libglib2.0-dev && \
apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
COPY script/build_python_openzwave script/build_python_openzwave
@@ -19,7 +19,9 @@ RUN script/build_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
# certifi breaks Debian based installs
RUN pip3 install --no-cache-dir -r requirements_all.txt && pip3 uninstall -y certifi && \
pip3 install mysqlclient psycopg2
# Copy source
COPY . .

View File

@@ -1,5 +1,5 @@
Home Assistant |Build Status| |Coverage Status| |Join the chat at https://gitter.im/balloob/home-assistant|
===========================================================================================================
Home Assistant |Build Status| |Coverage Status| |Join the chat at https://gitter.im/home-assistant/home-assistant| |Join the dev chat at https://gitter.im/home-assistant/home-assistant/devs|
==============================================================================================================================================================================================
Home Assistant is a home automation platform running on Python 3. The
goal of Home Assistant is to be able to track and control all devices at
@@ -18,7 +18,7 @@ tutorials and documentation.
|screenshot-states|
Examples of devices it can interface it:
Examples of devices Home Assistant can interface with:
- Monitoring connected devices to a wireless router:
`OpenWrt <https://openwrt.org/>`__,
@@ -61,13 +61,13 @@ Examples of devices it can interface it:
- `See full list of supported
devices <https://home-assistant.io/components/>`__
Built home automation on top of your devices:
Build home automation on top of your devices:
- Keep a precise history of every change to the state of your house
- Turn on the lights when people get home after sun set
- Turn on lights slowly during sun set to compensate for less light
- Turn on the lights when people get home after sunset
- Turn on lights slowly during sunset to compensate for less light
- Turn off all lights and devices when everybody leaves the house
- Offers a `REST API <https://home-assistant.io/developers/api/>`__
- Offers a `REST API <https://home-assistant.io/developers/rest_api/>`__
and can interface with MQTT for easy integration with other projects
like `OwnTracks <http://owntracks.org/>`__
- Allow sending notifications using
@@ -75,24 +75,26 @@ Built home automation on top of your devices:
(NMA) <http://www.notifymyandroid.com/>`__,
`PushBullet <https://www.pushbullet.com/>`__,
`PushOver <https://pushover.net/>`__, `Slack <https://slack.com/>`__,
`Telegram <https://telegram.org/>`__, and `Jabber
`Telegram <https://telegram.org/>`__, `Join <http://joaoapps.com/join/>`__, and `Jabber
(XMPP) <http://xmpp.org>`__
The system is built modular so support for other devices or actions can
The system is built using a modular approach so support for other devices or actions can
be implemented easily. See also the `section on
architecture <https://home-assistant.io/developers/architecture.html>`__
architecture <https://home-assistant.io/developers/architecture/>`__
and the `section on creating your own
components <https://home-assistant.io/developers/creating_components.html>`__.
components <https://home-assistant.io/developers/creating_components/>`__.
If you run into issues while using Home Assistant or during development
of a component, check the `Home Assistant help
section <https://home-assistant.io/help/>`__ how to reach us.
.. |Build Status| image:: https://travis-ci.org/balloob/home-assistant.svg?branch=master
:target: https://travis-ci.org/balloob/home-assistant
.. |Coverage Status| image:: https://img.shields.io/coveralls/balloob/home-assistant.svg
:target: https://coveralls.io/r/balloob/home-assistant?branch=master
.. |Join the chat at https://gitter.im/balloob/home-assistant| image:: https://badges.gitter.im/Join%20Chat.svg
:target: https://gitter.im/balloob/home-assistant?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
.. |screenshot-states| image:: https://raw.github.com/balloob/home-assistant/master/docs/screenshots.png
.. |Build Status| image:: https://travis-ci.org/home-assistant/home-assistant.svg?branch=master
:target: https://travis-ci.org/home-assistant/home-assistant
.. |Coverage Status| image:: https://img.shields.io/coveralls/home-assistant/home-assistant.svg
:target: https://coveralls.io/r/home-assistant/home-assistant?branch=master
.. |Join the chat at https://gitter.im/home-assistant/home-assistant| image:: https://img.shields.io/badge/gitter-general-blue.svg
:target: https://gitter.im/home-assistant/home-assistant?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
.. |Join the dev chat at https://gitter.im/home-assistant/home-assistant/devs| image:: https://img.shields.io/badge/gitter-development-yellowgreen.svg
:target: https://gitter.im/home-assistant/home-assistant/devs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
.. |screenshot-states| image:: https://raw.github.com/home-assistant/home-assistant/master/docs/screenshots.png
:target: https://home-assistant.io/demo/

View File

@@ -7,7 +7,10 @@ homeassistant:
latitude: 32.87336
longitude: 117.22743
# C for Celcius, F for Fahrenheit
# Impacts weather/sunrise data
elevation: 665
# C for Celsius, F for Fahrenheit
temperature_unit: C
# Pick yours from here:
@@ -22,6 +25,9 @@ http:
# Set to 1 to enable development mode
# development: 1
# Enable the frontend
frontend:
light:
# platform: hue
@@ -30,17 +36,12 @@ wink:
access_token: 'YOUR_TOKEN'
device_tracker:
# The following types are available: ddwrt, netgear, tomato, luci,
# and nmap_tracker
# The following tracker are available:
# https://home-assistant.io/components/#presence-detection
platform: netgear
host: 192.168.1.1
username: admin
password: PASSWORD
# http_id is needed for Tomato routers only
# http_id: ABCDEFGHH
# For nmap_tracker, only the IP addresses to scan are needed:
# hosts: 192.168.1.1/24 # netmask prefix notation or
# hosts: 192.168.1.1-255 # address range
chromecast:
@@ -71,24 +72,25 @@ device_sun_light_trigger:
# A comma separated list of states that have to be tracked as a single group
# Grouped states should share the same type of states (ON/OFF or HOME/NOT_HOME)
# You can also have groups within groups.
# https://home-assistant.io/components/group/
group:
Home:
- group.living_room
- group.kitchen
living_room:
- light.Bowl
- light.Ceiling
- light.TV_back_light
kitchen:
- light.fan_bulb_1
- light.fan_bulb_2
children:
- device_tracker.child_1
- device_tracker.child_2
default_view:
view: yes
entities:
- group.awesome_people
- group.climate
process:
# items are which processes to look for: <entity_id>: <search string within ps>
xbmc: XBMC.App
kitchen:
name: Kitchen
entities:
- switch.kitchen_pin_3
upstairs:
name: Kids
icon: mdi:account-multiple
view: yes
entities:
- input_boolean.notify_home
- camera.demo_camera
example:
@@ -102,6 +104,7 @@ browser:
keyboard:
# https://home-assistant.io/getting-started/automation/
automation:
- alias: 'Rule 1 Light on in the evening'
trigger:
@@ -123,7 +126,6 @@ automation:
entity_id: group.living_room
- alias: 'Rule 2 - Away Mode'
trigger:
- platform: state
entity_id: group.all_devices
@@ -136,6 +138,14 @@ automation:
# Sensors need to be added into the configuration.yaml as sensor:, sensor 2:, sensor 3:, etc.
# Each sensor label should be unique or your sensors might not load correctly.
# Another way to do is to collect all entries under one "sensor:"
# sensor:
# - platform: mqtt
# name: "MQTT Sensor 1"
# - platform: mqtt
# name: "MQTT Sensor 2"
#
# Details: https://home-assistant.io/getting-started/devices/
sensor:
platform: systemmonitor
@@ -146,14 +156,6 @@ sensor:
arg: '/home'
- type: 'disk_use'
arg: '/home'
- type: 'disk_free'
arg: '/'
- type: 'memory_use_percent'
- type: 'memory_use'
- type: 'memory_free'
- type: 'processor_use'
- type: 'process'
arg: 'octave-cli'
sensor 2:
platform: forecast
@@ -163,14 +165,6 @@ sensor 2:
- precip_type
- precip_intensity
- temperature
- dew_point
- wind_speed
- wind_bearing
- cloud_cover
- humidity
- pressure
- visibility
- ozone
script:
# Turns on the bedroom lights and then the living room lights 1 minute later

View File

@@ -14,7 +14,7 @@ To use the mqtt_example component you will need to add the following to your
configuration.yaml file.
mqtt_example:
topic: home-assistant/mqtt_example
topic: "home-assistant/mqtt_example"
"""
import homeassistant.loader as loader
@@ -29,7 +29,7 @@ DEFAULT_TOPIC = 'home-assistant/mqtt_example'
def setup(hass, config):
"""Setup the MQTT example component."""
"""Setup the MQTT example component."""
mqtt = loader.get_component('mqtt')
topic = config[DOMAIN].get('topic', DEFAULT_TOPIC)
entity_id = 'mqtt_example.last_message'

View File

@@ -0,0 +1,30 @@
"""
Custom panel example showing TodoMVC using React.
Will add a panel to control lights and switches using React. Allows configuring
the title via configuration.yaml:
react_panel:
title: 'home'
"""
import os
from homeassistant.components.frontend import register_panel
DOMAIN = 'react_panel'
DEPENDENCIES = ['frontend']
PANEL_PATH = os.path.join(os.path.dirname(__file__), 'panel.html')
def setup(hass, config):
"""Initialize custom panel."""
title = config.get(DOMAIN, {}).get('title')
config = None if title is None else {'title': title}
register_panel(hass, 'react', PANEL_PATH,
title='TodoMVC', icon='mdi:checkbox-marked-outline',
config=config)
return True

View File

@@ -0,0 +1,415 @@
<script src="https://fb.me/react-15.2.1.min.js"></script>
<script src="https://fb.me/react-dom-15.2.1.min.js"></script>
<!-- for development, replace with:
<script src="https://fb.me/react-15.2.1.js"></script>
<script src="https://fb.me/react-dom-15.2.1.js"></script>
-->
<!--
CSS taken from ReactJS TodoMVC example by Pete Hunt
http://todomvc.com/examples/react/
-->
<style>
.todoapp input[type="checkbox"] {
outline: none;
}
.todoapp {
background: #fff;
margin: 130px 0 40px 0;
position: relative;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),
0 25px 50px 0 rgba(0, 0, 0, 0.1);
}
.todoapp h1 {
position: absolute;
top: -155px;
width: 100%;
font-size: 100px;
font-weight: 100;
text-align: center;
color: rgba(175, 47, 47, 0.15);
-webkit-text-rendering: optimizeLegibility;
-moz-text-rendering: optimizeLegibility;
text-rendering: optimizeLegibility;
}
.todoapp .main {
position: relative;
border-top: 1px solid #e6e6e6;
}
.todoapp .todo-list {
margin: 0;
padding: 0;
list-style: none;
}
.todoapp .todo-list li {
position: relative;
font-size: 24px;
border-bottom: 1px solid #ededed;
}
.todoapp .todo-list li:last-child {
border-bottom: none;
}
.todoapp .todo-list li .toggle {
text-align: center;
width: 40px;
/* auto, since non-WebKit browsers doesn't support input styling */
height: auto;
position: absolute;
top: 0;
bottom: 0;
margin: auto 0;
border: none; /* Mobile Safari */
-webkit-appearance: none;
appearance: none;
cursor: pointer;
}
.todoapp .todo-list li .toggle:focus {
border-left: 3px solid rgba(175, 47, 47, 0.35);
}
.todoapp .todo-list li .toggle:after {
content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="-10 -18 100 135"><circle cx="50" cy="50" r="50" fill="none" stroke="#ededed" stroke-width="3"/></svg>');
}
.todoapp .todo-list li .toggle:checked:after {
content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="-10 -18 100 135"><circle cx="50" cy="50" r="50" fill="none" stroke="#bddad5" stroke-width="3"/><path fill="#5dc2af" d="M72 25L42 71 27 56l-4 4 20 20 34-52z"/></svg>');
}
.todoapp .todo-list li label {
white-space: pre-line;
word-break: break-all;
padding: 15px 60px 15px 15px;
margin-left: 45px;
display: block;
line-height: 1.2;
transition: color 0.4s;
}
.todoapp .todo-list li.completed label {
color: #d9d9d9;
text-decoration: line-through;
}
.todoapp .footer {
color: #777;
padding: 10px 15px;
height: 20px;
text-align: center;
border-top: 1px solid #e6e6e6;
}
.todoapp .footer:before {
content: '';
position: absolute;
right: 0;
bottom: 0;
left: 0;
height: 50px;
overflow: hidden;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2),
0 8px 0 -3px #f6f6f6,
0 9px 1px -3px rgba(0, 0, 0, 0.2),
0 16px 0 -6px #f6f6f6,
0 17px 2px -6px rgba(0, 0, 0, 0.2);
}
.todoapp .todo-count {
float: left;
text-align: left;
font-weight: 300;
}
.todoapp .toggle-menu {
position: absolute;
right: 15px;
font-weight: 300;
color: rgba(175, 47, 47, 0.75);
}
.todoapp .filters {
margin: 0;
padding: 0;
list-style: none;
position: absolute;
right: 0;
left: 0;
}
.todoapp .filters li {
display: inline;
}
.todoapp .filters li a {
color: inherit;
margin: 3px;
padding: 3px 7px;
text-decoration: none;
border: 1px solid transparent;
border-radius: 3px;
}
.todoapp .filters li a.selected,
.filters li a:hover {
border-color: rgba(175, 47, 47, 0.1);
}
.todoapp .filters li a.selected {
border-color: rgba(175, 47, 47, 0.2);
}
/*
Hack to remove background from Mobile Safari.
Can't use it globally since it destroys checkboxes in Firefox
*/
@media screen and (-webkit-min-device-pixel-ratio:0) {
.todoapp .toggle-all,
.todoapp .todo-list li .toggle {
background: none;
}
.todoapp .todo-list li .toggle {
height: 40px;
}
.todoapp .toggle-all {
-webkit-transform: rotate(90deg);
transform: rotate(90deg);
-webkit-appearance: none;
appearance: none;
}
}
@media (max-width: 430px) {
.todoapp .footer {
height: 50px;
}
.todoapp .filters {
bottom: 10px;
}
}
</style>
<dom-module id='ha-panel-react'>
<template>
<style>
:host {
background: #f5f5f5;
display: block;
height: 100%;
overflow: auto;
}
.mount {
font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
line-height: 1.4em;
color: #4d4d4d;
min-width: 230px;
max-width: 550px;
margin: 0 auto;
-webkit-font-smoothing: antialiased;
-moz-font-smoothing: antialiased;
font-smoothing: antialiased;
font-weight: 300;
}
</style>
<div id='mount' class='mount'></div>
</template>
</dom-module>
<script>
// Example uses ES6. Will only work in modern browsers
class TodoMVC extends React.Component {
constructor(props) {
super(props);
this.state = {
filter: 'all',
// load initial value of entities
entities: this.props.hass.reactor.evaluate(
this.props.hass.entityGetters.visibleEntityMap),
};
}
componentDidMount() {
// register to entity updates
this._unwatchHass = this.props.hass.reactor.observe(
this.props.hass.entityGetters.visibleEntityMap,
entities => this.setState({entities}))
}
componentWillUnmount() {
// unregister to entity updates
this._unwatchHass();
}
handlePickFilter(filter, ev) {
ev.preventDefault();
this.setState({filter});
}
handleEntityToggle(entity, ev) {
this.props.hass.serviceActions.callService(
entity.domain, 'toggle', { entity_id: entity.entityId });
}
handleToggleMenu(ev) {
ev.preventDefault();
Polymer.Base.fire('open-menu', null, {node: ev.target});
}
entityRow(entity) {
const completed = entity.state === 'on';
return React.createElement(
'li', {
className: completed && 'completed',
key: entity.entityId,
},
React.createElement(
"div", { className: "view" },
React.createElement(
"input", {
checked: completed,
className: "toggle",
type: "checkbox",
onChange: ev => this.handleEntityToggle(entity, ev),
}),
React.createElement("label", null, entity.entityDisplay)));
}
filterRow(filter) {
return React.createElement(
"li", { key: filter },
React.createElement(
"a", {
href: "#",
className: this.state.filter === filter && "selected",
onClick: ev => this.handlePickFilter(filter, ev),
},
filter.substring(0, 1).toUpperCase() + filter.substring(1)
)
);
}
render() {
const { entities, filter } = this.state;
if (!entities) return null;
const filters = ['all', 'light', 'switch'];
const showEntities = filter === 'all' ?
entities.filter(ent => filters.includes(ent.domain)) :
entities.filter(ent => ent.domain == filter);
return React.createElement(
'div', { className: 'todoapp-wrapper' },
React.createElement(
"section", { className: "todoapp" },
React.createElement(
"div", null,
React.createElement(
"header", { className: "header" },
React.createElement("h1", null, this.props.title || "todos")
),
React.createElement(
"section", { className: "main" },
React.createElement(
"ul", { className: "todo-list" },
showEntities.valueSeq().map(ent => this.entityRow(ent)))
)
),
React.createElement(
"footer", { className: "footer" },
React.createElement(
"span", { className: "todo-count" },
showEntities.filter(ent => ent.state === 'off').size + " items left"
),
React.createElement(
"ul", { className: "filters" },
filters.map(filter => this.filterRow(filter))
),
!this.props.showMenu && React.createElement(
"a", {
className: "toggle-menu",
href: '#',
onClick: ev => this.handleToggleMenu(ev),
},
"Show menu"
)
)
));
}
}
Polymer({
is: 'ha-panel-react',
properties: {
// Home Assistant object
hass: {
type: Object,
},
// If should render in narrow mode
narrow: {
type: Boolean,
value: false,
},
// If sidebar is currently shown
showMenu: {
type: Boolean,
value: false,
},
// Home Assistant panel info
// panel.config contains config passed to register_panel serverside
panel: {
type: Object,
}
},
// This will make sure we forward changed properties to React
observers: [
'propsChanged(hass, narrow, showMenu, panel)',
],
// Mount React when element attached
attached: function () {
this.mount(this.hass, this.narrow, this.showMenu, this.panel);
},
// Called when properties change
propsChanged: function (hass, narrow, showMenu, panel) {
this.mount(hass, narrow, showMenu, panel);
},
// Render React. Debounce in case multiple properties change.
mount: function (hass, narrow, showMenu, panel) {
this.debounce('mount', function () {
ReactDOM.render(React.createElement(TodoMVC, {
hass: hass,
narrow: narrow,
showMenu: showMenu,
title: panel.config ? panel.config.title : null
}), this.$.mount);
}.bind(this));
},
// Unmount React node when panel no longer in use.
detached: function () {
ReactDOM.unmountComponentAtNode(this.$.mount);
},
});
</script>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 232 KiB

567
docs/swagger.yaml Normal file
View File

@@ -0,0 +1,567 @@
swagger: '2.0'
info:
title: Home Assistant
description: Home Assistant REST API
version: "1.0.0"
# the domain of the service
host: localhost:8123
# array of all schemes that your API supports
schemes:
- http
- https
securityDefinitions:
api_key:
type: apiKey
description: API password
name: api_password
in: query
# api_key:
# type: apiKey
# description: API password
# name: x-ha-access
# in: header
# will be prefixed to all paths
basePath: /api
consumes:
- application/json
produces:
- application/json
paths:
/:
get:
summary: API alive message
description: Returns message if API is up and running.
tags:
- Core
responses:
200:
description: API is up and running
schema:
$ref: '#/definitions/Message'
default:
description: Error
schema:
$ref: '#/definitions/Message'
/config:
get:
summary: API alive message
description: Returns the current configuration as JSON.
tags:
- Core
responses:
200:
description: Current configuration
schema:
$ref: '#/definitions/ApiConfig'
default:
description: Error
schema:
$ref: '#/definitions/Message'
/discovery_info:
get:
summary: Basic information about Home Assistant instance
tags:
- Core
responses:
200:
description: Basic information
schema:
$ref: '#/definitions/DiscoveryInfo'
default:
description: Error
schema:
$ref: '#/definitions/Message'
/bootstrap:
get:
summary: Returns all data needed to bootstrap Home Assistant.
tags:
- Core
responses:
200:
description: Bootstrap information
schema:
$ref: '#/definitions/BootstrapInfo'
default:
description: Error
schema:
$ref: '#/definitions/Message'
/events:
get:
summary: Array of event objects.
description: Returns an array of event objects. Each event object contain event name and listener count.
tags:
- Events
responses:
200:
description: Events
schema:
type: array
items:
$ref: '#/definitions/Event'
default:
description: Error
schema:
$ref: '#/definitions/Message'
/services:
get:
summary: Array of service objects.
description: Returns an array of service objects. Each object contains the domain and which services it contains.
tags:
- Services
responses:
200:
description: Services
schema:
type: array
items:
$ref: '#/definitions/Service'
default:
description: Error
schema:
$ref: '#/definitions/Message'
/history:
get:
summary: Array of state changes in the past.
description: Returns an array of state changes in the past. Each object contains further detail for the entities.
tags:
- State
responses:
200:
description: State changes
schema:
type: array
items:
$ref: '#/definitions/History'
default:
description: Error
schema:
$ref: '#/definitions/Message'
/states:
get:
summary: Array of state objects.
description: |
Returns an array of state objects. Each state has the following attributes: entity_id, state, last_changed and attributes.
tags:
- State
responses:
200:
description: States
schema:
type: array
items:
$ref: '#/definitions/State'
default:
description: Error
schema:
$ref: '#/definitions/Message'
/states/{entity_id}:
get:
summary: Specific state object.
description: |
Returns a state object for specified entity_id.
tags:
- State
parameters:
- name: entity_id
in: path
description: entity_id of the entity to query
required: true
type: string
responses:
200:
description: State
schema:
$ref: '#/definitions/State'
404:
description: Not found
schema:
$ref: '#/definitions/Message'
default:
description: Error
schema:
$ref: '#/definitions/Message'
post:
description: |
Updates or creates the current state of an entity.
tags:
- State
consumes:
- application/json
parameters:
- name: entity_id
in: path
description: entity_id to set the state of
required: true
type: string
- $ref: '#/parameters/State'
responses:
200:
description: State of existing entity was set
schema:
$ref: '#/definitions/State'
201:
description: State of new entity was set
schema:
$ref: '#/definitions/State'
headers:
location:
type: string
description: location of the new entity
default:
description: Error
schema:
$ref: '#/definitions/Message'
/error_log:
get:
summary: Error log
description: |
Retrieve all errors logged during the current session of Home Assistant as a plaintext response.
tags:
- Core
produces:
- text/plain
responses:
200:
description: Plain text error log
default:
description: Error
schema:
$ref: '#/definitions/Message'
/camera_proxy/camera.{entity_id}:
get:
summary: Camera image.
description: |
Returns the data (image) from the specified camera entity_id.
tags:
- Camera
produces:
- image/jpeg
parameters:
- name: entity_id
in: path
description: entity_id of the camera to query
required: true
type: string
responses:
200:
description: Camera image
schema:
type: file
default:
description: Error
schema:
$ref: '#/definitions/Message'
/events/{event_type}:
post:
description: |
Fires an event with event_type
tags:
- Events
consumes:
- application/json
parameters:
- name: event_type
in: path
description: event_type to fire event with
required: true
type: string
- $ref: '#/parameters/EventData'
responses:
200:
description: Response message
schema:
$ref: '#/definitions/Message'
default:
description: Error
schema:
$ref: '#/definitions/Message'
/services/{domain}/{service}:
post:
description: |
Calls a service within a specific domain. Will return when the service has been executed or 10 seconds has past, whichever comes first.
tags:
- Services
consumes:
- application/json
parameters:
- name: domain
in: path
description: domain of the service
required: true
type: string
- name: service
in: path
description: service to call
required: true
type: string
- $ref: '#/parameters/ServiceData'
responses:
200:
description: List of states that have changed while the service was being executed. The result will include any changed states that changed while the service was being executed, even if their change was the result of something else happening in the system.
schema:
type: array
items:
$ref: '#/definitions/State'
default:
description: Error
schema:
$ref: '#/definitions/Message'
/template:
post:
description: |
Render a Home Assistant template.
tags:
- Template
consumes:
- application/json
produces:
- text/plain
parameters:
- $ref: '#/parameters/Template'
responses:
200:
description: Returns the rendered template in plain text.
schema:
type: string
default:
description: Error
schema:
$ref: '#/definitions/Message'
/event_forwarding:
post:
description: |
Setup event forwarding to another Home Assistant instance.
tags:
- Core
consumes:
- application/json
parameters:
- $ref: '#/parameters/EventForwarding'
responses:
200:
description: It will return a message if event forwarding was setup successful.
schema:
$ref: '#/definitions/Message'
default:
description: Error
schema:
$ref: '#/definitions/Message'
delete:
description: |
Cancel event forwarding to another Home Assistant instance.
tags:
- Core
consumes:
- application/json
parameters:
- $ref: '#/parameters/EventForwarding'
responses:
200:
description: It will return a message if event forwarding was cancelled successful.
schema:
$ref: '#/definitions/Message'
default:
description: Error
schema:
$ref: '#/definitions/Message'
/stream:
get:
summary: Server-sent events
description: The server-sent events feature is a one-way channel from your Home Assistant server to a client which is acting as a consumer.
tags:
- Core
- Events
produces:
- text/event-stream
parameters:
- name: restrict
in: query
description: comma-separated list of event_types to filter
required: false
type: string
responses:
default:
description: Stream of events
schema:
type: object
x-events:
state_changed:
type: object
properties:
entity_id:
type: string
old_state:
$ref: '#/definitions/State'
new_state:
$ref: '#/definitions/State'
definitions:
ApiConfig:
type: object
properties:
components:
type: array
description: List of component types
items:
type: string
description: Component type
latitude:
type: number
format: float
description: Latitude of Home Assistant server
longitude:
type: number
format: float
description: Longitude of Home Assistant server
location_name:
type: string
temperature_unit:
type: string
time_zone:
type: string
version:
type: string
DiscoveryInfo:
type: object
properties:
base_url:
type: string
location_name:
type: string
requires_api_password:
type: boolean
version:
type: string
BootstrapInfo:
type: object
properties:
config:
$ref: '#/definitions/ApiConfig'
events:
type: array
items:
$ref: '#/definitions/Event'
services:
type: array
items:
$ref: '#/definitions/Service'
states:
type: array
items:
$ref: '#/definitions/State'
Event:
type: object
properties:
event:
type: string
listener_count:
type: integer
Service:
type: object
properties:
domain:
type: string
services:
type: object
additionalProperties:
$ref: '#/definitions/DomainService'
DomainService:
type: object
properties:
description:
type: string
fields:
type: object
description: Object with service fields that can be called
State:
type: object
properties:
attributes:
$ref: '#/definitions/StateAttributes'
state:
type: string
entity_id:
type: string
last_changed:
type: string
format: date-time
StateAttributes:
type: object
additionalProperties:
type: string
History:
allOf:
- $ref: '#/definitions/State'
- type: object
properties:
last_updated:
type: string
format: date-time
Message:
type: object
properties:
message:
type: string
parameters:
State:
name: body
in: body
description: State parameter
required: false
schema:
type: object
required:
- state
properties:
attributes:
$ref: '#/definitions/StateAttributes'
state:
type: string
EventData:
name: body
in: body
description: event_data
required: false
schema:
type: object
ServiceData:
name: body
in: body
description: service_data
required: false
schema:
type: object
Template:
name: body
in: body
description: Template to render
required: true
schema:
type: object
required:
- template
properties:
template:
description: Jinja2 template string
type: string
EventForwarding:
name: body
in: body
description: Event Forwarding parameter
required: true
schema:
type: object
required:
- host
- api_password
properties:
host:
type: string
api_password:
type: string
port:
type: integer

View File

@@ -3,11 +3,12 @@ from __future__ import print_function
import argparse
import os
import signal
import platform
import subprocess
import sys
import threading
import time
from multiprocessing import Process
from typing import Optional, List
from homeassistant.const import (
__version__,
@@ -17,7 +18,7 @@ from homeassistant.const import (
)
def validate_python():
def validate_python() -> None:
"""Validate we're running the right Python version."""
major, minor = sys.version_info[:2]
req_major, req_minor = REQUIRED_PYTHON_VER
@@ -28,10 +29,10 @@ def validate_python():
sys.exit(1)
def ensure_config_path(config_dir):
def ensure_config_path(config_dir: str) -> None:
"""Validate the configuration directory."""
import homeassistant.config as config_util
lib_dir = os.path.join(config_dir, 'lib')
lib_dir = os.path.join(config_dir, 'deps')
# Test if configuration directory exists
if not os.path.isdir(config_dir):
@@ -57,7 +58,7 @@ def ensure_config_path(config_dir):
sys.exit(1)
def ensure_config_file(config_dir):
def ensure_config_file(config_dir: str) -> str:
"""Ensure configuration file exists."""
import homeassistant.config as config_util
config_path = config_util.ensure_config_exists(config_dir)
@@ -69,7 +70,7 @@ def ensure_config_file(config_dir):
return config_path
def get_arguments():
def get_arguments() -> argparse.Namespace:
"""Get parsed passed in arguments."""
import homeassistant.config as config_util
parser = argparse.ArgumentParser(
@@ -87,8 +88,7 @@ def get_arguments():
parser.add_argument(
'--debug',
action='store_true',
help='Start Home Assistant in debug mode. Runs in single process to '
'enable use of interactive debuggers.')
help='Start Home Assistant in debug mode')
parser.add_argument(
'--open-ui',
action='store_true',
@@ -112,30 +112,27 @@ def get_arguments():
default=None,
help='Enables daily log rotation and keeps up to the specified days')
parser.add_argument(
'--install-osx',
'--runner',
action='store_true',
help='Installs as a service on OS X and loads on boot.')
help='On restart exit with code {}'.format(RESTART_EXIT_CODE))
parser.add_argument(
'--uninstall-osx',
action='store_true',
help='Uninstalls from OS X.')
parser.add_argument(
'--restart-osx',
action='store_true',
help='Restarts on OS X.')
if os.name != "nt":
'--script',
nargs=argparse.REMAINDER,
help='Run one of the embedded scripts')
if os.name == "posix":
parser.add_argument(
'--daemon',
action='store_true',
help='Run Home Assistant as daemon')
arguments = parser.parse_args()
if os.name == "nt":
arguments.daemon = False
if os.name != "posix" or arguments.debug or arguments.runner:
setattr(arguments, 'daemon', False)
return arguments
def daemonize():
def daemonize() -> None:
"""Move current process to daemon process."""
# Create first fork
pid = os.fork()
@@ -144,15 +141,23 @@ def daemonize():
# Decouple fork
os.setsid()
os.umask(0)
# Create second fork
pid = os.fork()
if pid > 0:
sys.exit(0)
# redirect standard file descriptors to devnull
infd = open(os.devnull, 'r')
outfd = open(os.devnull, 'a+')
sys.stdout.flush()
sys.stderr.flush()
os.dup2(infd.fileno(), sys.stdin.fileno())
os.dup2(outfd.fileno(), sys.stdout.fileno())
os.dup2(outfd.fileno(), sys.stderr.fileno())
def check_pid(pid_file):
def check_pid(pid_file: str) -> None:
"""Check that HA is not already running."""
# Check pid file
try:
@@ -161,6 +166,10 @@ def check_pid(pid_file):
# PID File does not exist
return
# If we just restarted, we just found our own pidfile.
if pid == os.getpid():
return
try:
os.kill(pid, 0)
except OSError:
@@ -170,7 +179,7 @@ def check_pid(pid_file):
sys.exit(1)
def write_pid(pid_file):
def write_pid(pid_file: str) -> None:
"""Create a PID File."""
pid = os.getpid()
try:
@@ -180,72 +189,65 @@ def write_pid(pid_file):
sys.exit(1)
def install_osx():
"""Setup to run via launchd on OS X."""
with os.popen('which hass') as inp:
hass_path = inp.read().strip()
def closefds_osx(min_fd: int, max_fd: int) -> None:
"""Make sure file descriptors get closed when we restart.
with os.popen('whoami') as inp:
user = inp.read().strip()
cwd = os.path.dirname(__file__)
template_path = os.path.join(cwd, 'startup', 'launchd.plist')
with open(template_path, 'r', encoding='utf-8') as inp:
plist = inp.read()
plist = plist.replace("$HASS_PATH$", hass_path)
plist = plist.replace("$USER$", user)
path = os.path.expanduser("~/Library/LaunchAgents/org.homeassistant.plist")
try:
with open(path, 'w', encoding='utf-8') as outp:
outp.write(plist)
except IOError as err:
print('Unable to write to ' + path, err)
return
os.popen('launchctl load -w -F ' + path)
print("Home Assistant has been installed. \
Open it here: http://localhost:8123")
def uninstall_osx():
"""Unload from launchd on OS X."""
path = os.path.expanduser("~/Library/LaunchAgents/org.homeassistant.plist")
os.popen('launchctl unload ' + path)
print("Home Assistant has been uninstalled.")
def 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.
We cannot call close on guarded fds, and we cannot easily test which fds
are guarded. But we can set the close-on-exec flag on everything we want to
get rid of.
"""
from fcntl import fcntl, F_GETFD, F_SETFD, FD_CLOEXEC
for _fd in range(min_fd, max_fd):
try:
val = fcntl(_fd, F_GETFD)
if not val & FD_CLOEXEC:
fcntl(_fd, F_SETFD, val | FD_CLOEXEC)
except IOError:
pass
def cmdline() -> List[str]:
"""Collect path and arguments to re-execute the current hass instance."""
if sys.argv[0].endswith('/__main__.py'):
modulepath = os.path.dirname(sys.argv[0])
os.environ['PYTHONPATH'] = os.path.dirname(modulepath)
return [sys.executable] + [arg for arg in sys.argv if arg != '--daemon']
def setup_and_run_hass(config_dir: str,
args: argparse.Namespace) -> Optional[int]:
"""Setup HASS and run."""
from homeassistant import bootstrap
# Run a simple daemon runner process on Windows to handle restarts
if os.name == 'nt' and '--runner' not in sys.argv:
nt_args = cmdline() + ['--runner']
while True:
try:
subprocess.check_call(nt_args)
sys.exit(0)
except subprocess.CalledProcessError as exc:
if exc.returncode != RESTART_EXIT_CODE:
sys.exit(exc.returncode)
if args.demo_mode:
config = {
'frontend': {},
'demo': {}
}
hass = bootstrap.from_config_dict(
config, config_dir=config_dir, daemon=args.daemon,
verbose=args.verbose, skip_pip=args.skip_pip,
log_rotate_days=args.log_rotate_days)
config, config_dir=config_dir, verbose=args.verbose,
skip_pip=args.skip_pip, log_rotate_days=args.log_rotate_days)
else:
config_file = ensure_config_file(config_dir)
print('Config directory:', config_dir)
hass = bootstrap.from_config_file(
config_file, daemon=args.daemon, verbose=args.verbose,
skip_pip=args.skip_pip, log_rotate_days=args.log_rotate_days)
config_file, verbose=args.verbose, skip_pip=args.skip_pip,
log_rotate_days=args.log_rotate_days)
if hass is None:
return
return None
if args.open_ui:
def open_browser(event):
@@ -259,64 +261,64 @@ def setup_and_run_hass(config_dir, args, top_process=False):
hass.start()
exit_code = int(hass.block_till_stopped())
if not top_process:
sys.exit(exit_code)
return exit_code
def run_hass_process(hass_proc):
"""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."""
requested_stop.set()
hass_proc.terminate()
def try_to_restart() -> None:
"""Attempt to clean up state and start a new homeassistant instance."""
# Things should be mostly shut down already at this point, now just try
# to clean up things that may have been left behind.
sys.stderr.write('Home Assistant attempting to restart.\n')
# Count remaining threads, ideally there should only be one non-daemonized
# thread left (which is us). Nothing we really do with it, but it might be
# useful when debugging shutdown/restart issues.
try:
signal.signal(signal.SIGTERM, request_stop)
nthreads = sum(thread.isAlive() and not thread.isDaemon()
for thread in threading.enumerate())
if nthreads > 1:
sys.stderr.write(
"Found {} non-daemonic threads.\n".format(nthreads))
# Somehow we sometimes seem to trigger an assertion in the python threading
# module. It seems we find threads that have no associated OS level thread
# which are not marked as stopped at the python level.
except AssertionError:
sys.stderr.write("Failed to count non-daemonic threads.\n")
# Try to not leave behind open filedescriptors with the emphasis on try.
try:
max_fd = os.sysconf("SC_OPEN_MAX")
except ValueError:
print('Could not bind to SIGTERM. Are you running in a thread?')
max_fd = 256
hass_proc.start()
try:
hass_proc.join()
except KeyboardInterrupt:
request_stop()
try:
hass_proc.join()
except KeyboardInterrupt:
return False
if platform.system() == 'Darwin':
closefds_osx(3, max_fd)
else:
os.closerange(3, max_fd)
return (not requested_stop.isSet() and
hass_proc.exitcode == RESTART_EXIT_CODE,
hass_proc.exitcode)
# Now launch into a new instance of Home-Assistant. If this fails we
# fall through and exit with error 100 (RESTART_EXIT_CODE) in which case
# systemd will restart us when RestartForceExitStatus=100 is set in the
# systemd.service file.
sys.stderr.write("Restarting Home-Assistant\n")
args = cmdline()
os.execv(args[0], args)
def main():
def main() -> int:
"""Start Home Assistant."""
validate_python()
args = get_arguments()
if args.script is not None:
from homeassistant import scripts
return scripts.run(args.script)
config_dir = os.path.join(os.getcwd(), args.config)
ensure_config_path(config_dir)
# OS X launchd functions
if args.install_osx:
install_osx()
return 0
if args.uninstall_osx:
uninstall_osx()
return 0
if args.restart_osx:
uninstall_osx()
# A small delay is needed on some systems to let the unload finish.
time.sleep(0.5)
install_osx()
return 0
# Daemon functions
if args.pid_file:
check_pid(args.pid_file)
@@ -325,21 +327,10 @@ def main():
if args.pid_file:
write_pid(args.pid_file)
# Run hass in debug mode if requested
if args.debug:
sys.stderr.write('Running in debug mode. '
'Home Assistant will not be able to restart.\n')
exit_code = setup_and_run_hass(config_dir, args, top_process=True)
if exit_code == RESTART_EXIT_CODE:
sys.stderr.write('Home Assistant requested a '
'restart in debug mode.\n')
return exit_code
exit_code = setup_and_run_hass(config_dir, args)
if exit_code == RESTART_EXIT_CODE and not args.runner:
try_to_restart()
# Run hass as child process. Restart if necessary.
keep_running = True
while keep_running:
hass_proc = Process(target=setup_and_run_hass, args=(config_dir, args))
keep_running, exit_code = run_hass_process(hass_proc)
return exit_code

View File

@@ -3,29 +3,26 @@
import logging
import logging.handlers
import os
import shutil
import sys
from collections import defaultdict
from threading import RLock
from types import ModuleType
from typing import Any, Optional, Dict
import voluptuous as vol
import homeassistant.components as core_components
import homeassistant.components.group as group
import homeassistant.config as config_util
from homeassistant.components import group, persistent_notification
import homeassistant.config as conf_util
import homeassistant.core as core
import homeassistant.helpers.config_validation as cv
import homeassistant.loader as loader
import homeassistant.util.dt as date_util
import homeassistant.util.location as loc_util
import homeassistant.util.package as pkg_util
from homeassistant.const import (
CONF_CUSTOMIZE, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME,
CONF_TEMPERATURE_UNIT, CONF_TIME_ZONE, EVENT_COMPONENT_LOADED,
TEMP_CELCIUS, TEMP_FAHRENHEIT, PLATFORM_FORMAT, __version__)
from homeassistant.const import EVENT_COMPONENT_LOADED, PLATFORM_FORMAT
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import (
event_decorators, service, config_per_platform, extract_domain_configs)
from homeassistant.helpers.entity import Entity
_LOGGER = logging.getLogger(__name__)
_SETUP_LOCK = RLock()
@@ -36,7 +33,8 @@ ATTR_COMPONENT = 'component'
ERROR_LOG_FILENAME = 'home-assistant.log'
def setup_component(hass, domain, config=None):
def setup_component(hass: core.HomeAssistant, domain: str,
config: Optional[Dict]=None) -> bool:
"""Setup a component and all its dependencies."""
if domain in hass.config.components:
return True
@@ -59,13 +57,14 @@ def setup_component(hass, domain, config=None):
return True
def _handle_requirements(hass, component, name):
def _handle_requirements(hass: core.HomeAssistant, component,
name: str) -> bool:
"""Install the requirements for a component."""
if hass.config.skip_pip or not hasattr(component, 'REQUIREMENTS'):
return True
for req in component.REQUIREMENTS:
if not pkg_util.install_package(req, target=hass.config.path('lib')):
if not pkg_util.install_package(req, target=hass.config.path('deps')):
_LOGGER.error('Not initializing %s because could not install '
'dependency %s', name, req)
return False
@@ -73,9 +72,10 @@ def _handle_requirements(hass, component, name):
return True
def _setup_component(hass, domain, config):
def _setup_component(hass: core.HomeAssistant, domain: str, config) -> bool:
"""Setup a component for Home Assistant."""
# pylint: disable=too-many-return-statements,too-many-branches
# pylint: disable=too-many-statements
if domain in hass.config.components:
return True
@@ -103,7 +103,7 @@ def _setup_component(hass, domain, config):
try:
config = component.CONFIG_SCHEMA(config)
except vol.MultipleInvalid as ex:
_LOGGER.error('Invalid config for [%s]: %s', domain, ex)
cv.log_exception(_LOGGER, ex, domain, config)
return False
elif hasattr(component, 'PLATFORM_SCHEMA'):
@@ -113,12 +113,11 @@ def _setup_component(hass, domain, config):
try:
p_validated = component.PLATFORM_SCHEMA(p_config)
except vol.MultipleInvalid as ex:
_LOGGER.error('Invalid platform config for [%s]: %s. %s',
domain, ex, p_config)
cv.log_exception(_LOGGER, ex, domain, p_config)
return False
# Not all platform components follow same pattern for platforms
# Sof if p_name is None we are not going to validate platform
# So if p_name is None we are not going to validate platform
# (the automation component is one of them)
if p_name is None:
platforms.append(p_validated)
@@ -135,9 +134,8 @@ def _setup_component(hass, domain, config):
try:
p_validated = platform.PLATFORM_SCHEMA(p_validated)
except vol.MultipleInvalid as ex:
_LOGGER.error(
'Invalid platform config for [%s.%s]: %s. %s',
domain, p_name, ex, p_config)
cv.log_exception(_LOGGER, ex, '{}.{}'
.format(domain, p_name), p_validated)
return False
platforms.append(p_validated)
@@ -155,9 +153,15 @@ def _setup_component(hass, domain, config):
_CURRENT_SETUP.append(domain)
try:
if not component.setup(hass, config):
result = component.setup(hass, config)
if result is False:
_LOGGER.error('component %s failed to initialize', domain)
return False
elif result is not True:
_LOGGER.error('component %s did not return boolean if setup '
'was successful. Disabling component.', domain)
loader.set_component(domain, None)
return False
except Exception: # pylint: disable=broad-except
_LOGGER.exception('Error during setup of component %s', domain)
return False
@@ -177,7 +181,8 @@ def _setup_component(hass, domain, config):
return True
def prepare_setup_platform(hass, config, domain, platform_name):
def prepare_setup_platform(hass: core.HomeAssistant, config, domain: str,
platform_name: str) -> Optional[ModuleType]:
"""Load a platform and makes sure dependencies are setup."""
_ensure_loader_prepared(hass)
@@ -209,15 +214,15 @@ def prepare_setup_platform(hass, config, domain, platform_name):
return platform
def mount_local_lib_path(config_dir):
"""Add local library to Python Path."""
sys.path.insert(0, os.path.join(config_dir, 'lib'))
# pylint: disable=too-many-branches, too-many-statements, too-many-arguments
def from_config_dict(config, hass=None, config_dir=None, enable_log=True,
verbose=False, daemon=False, skip_pip=False,
log_rotate_days=None):
def from_config_dict(config: Dict[str, Any],
hass: Optional[core.HomeAssistant]=None,
config_dir: Optional[str]=None,
enable_log: bool=True,
verbose: bool=False,
skip_pip: bool=False,
log_rotate_days: Any=None) \
-> Optional[core.HomeAssistant]:
"""Try to configure Home Assistant from a config dict.
Dynamically loads required components and its dependencies.
@@ -227,19 +232,20 @@ def from_config_dict(config, hass=None, config_dir=None, enable_log=True,
if config_dir is not None:
config_dir = os.path.abspath(config_dir)
hass.config.config_dir = config_dir
mount_local_lib_path(config_dir)
_mount_local_lib_path(config_dir)
core_config = config.get(core.DOMAIN, {})
try:
process_ha_core_config(hass, config_util.CORE_CONFIG_SCHEMA(
config.get(core.DOMAIN, {})))
except vol.MultipleInvalid as ex:
_LOGGER.error('Invalid config for [homeassistant]: %s', ex)
conf_util.process_ha_core_config(hass, core_config)
except vol.Invalid as ex:
cv.log_exception(_LOGGER, ex, 'homeassistant', core_config)
return None
process_ha_config_upgrade(hass)
conf_util.process_ha_config_upgrade(hass)
if enable_log:
enable_logging(hass, verbose, daemon, log_rotate_days)
enable_logging(hass, verbose, log_rotate_days)
hass.config.skip_pip = skip_pip
if skip_pip:
@@ -261,9 +267,10 @@ def from_config_dict(config, hass=None, config_dir=None, enable_log=True,
if not core_components.setup(hass, config):
_LOGGER.error('Home Assistant core failed to initialize. '
'Further initialization aborted.')
return hass
persistent_notification.setup(hass, config)
_LOGGER.info('Home Assistant core initialized')
# Give event decorators access to HASS
@@ -277,8 +284,11 @@ def from_config_dict(config, hass=None, config_dir=None, enable_log=True,
return hass
def from_config_file(config_path, hass=None, verbose=False, daemon=False,
skip_pip=True, log_rotate_days=None):
def from_config_file(config_path: str,
hass: Optional[core.HomeAssistant]=None,
verbose: bool=False,
skip_pip: bool=True,
log_rotate_days: Any=None):
"""Read the configuration file and try to start all the functionality.
Will add functionality to 'hass' parameter if given,
@@ -290,12 +300,12 @@ def from_config_file(config_path, hass=None, verbose=False, daemon=False,
# Set config dir to directory holding config file
config_dir = os.path.abspath(os.path.dirname(config_path))
hass.config.config_dir = config_dir
mount_local_lib_path(config_dir)
_mount_local_lib_path(config_dir)
enable_logging(hass, verbose, daemon, log_rotate_days)
enable_logging(hass, verbose, log_rotate_days)
try:
config_dict = config_util.load_yaml_config_file(config_path)
config_dict = conf_util.load_yaml_config_file(config_path)
except HomeAssistantError:
return None
@@ -303,28 +313,28 @@ def from_config_file(config_path, hass=None, verbose=False, daemon=False,
skip_pip=skip_pip)
def enable_logging(hass, verbose=False, daemon=False, log_rotate_days=None):
def enable_logging(hass: core.HomeAssistant, verbose: bool=False,
log_rotate_days=None) -> None:
"""Setup the logging."""
if not daemon:
logging.basicConfig(level=logging.INFO)
fmt = ("%(log_color)s%(asctime)s %(levelname)s (%(threadName)s) "
"[%(name)s] %(message)s%(reset)s")
try:
from colorlog import ColoredFormatter
logging.getLogger().handlers[0].setFormatter(ColoredFormatter(
fmt,
datefmt='%y-%m-%d %H:%M:%S',
reset=True,
log_colors={
'DEBUG': 'cyan',
'INFO': 'green',
'WARNING': 'yellow',
'ERROR': 'red',
'CRITICAL': 'red',
}
))
except ImportError:
pass
logging.basicConfig(level=logging.INFO)
fmt = ("%(log_color)s%(asctime)s %(levelname)s (%(threadName)s) "
"[%(name)s] %(message)s%(reset)s")
try:
from colorlog import ColoredFormatter
logging.getLogger().handlers[0].setFormatter(ColoredFormatter(
fmt,
datefmt='%y-%m-%d %H:%M:%S',
reset=True,
log_colors={
'DEBUG': 'cyan',
'INFO': 'green',
'WARNING': 'yellow',
'ERROR': 'red',
'CRITICAL': 'red',
}
))
except ImportError:
pass
# Log errors to a file if we have write access to file or config dir
err_log_path = hass.config.path(ERROR_LOG_FILENAME)
@@ -355,95 +365,12 @@ def enable_logging(hass, verbose=False, daemon=False, log_rotate_days=None):
'Unable to setup error log %s (access denied)', err_log_path)
def process_ha_config_upgrade(hass):
"""Upgrade config if necessary."""
version_path = hass.config.path('.HA_VERSION')
try:
with open(version_path, 'rt') as inp:
conf_version = inp.readline().strip()
except FileNotFoundError:
# Last version to not have this file
conf_version = '0.7.7'
if conf_version == __version__:
return
_LOGGER.info('Upgrading config directory from %s to %s', conf_version,
__version__)
lib_path = hass.config.path('lib')
if os.path.isdir(lib_path):
shutil.rmtree(lib_path)
with open(version_path, 'wt') as outp:
outp.write(__version__)
def process_ha_core_config(hass, config):
"""Process the [homeassistant] section from the config."""
hac = hass.config
def set_time_zone(time_zone_str):
"""Helper method to set time zone."""
if time_zone_str is None:
return
time_zone = date_util.get_time_zone(time_zone_str)
if time_zone:
hac.time_zone = time_zone
date_util.set_default_time_zone(time_zone)
else:
_LOGGER.error('Received invalid time zone %s', time_zone_str)
for key, attr in ((CONF_LATITUDE, 'latitude'),
(CONF_LONGITUDE, 'longitude'),
(CONF_NAME, 'location_name')):
if key in config:
setattr(hac, attr, config[key])
if CONF_TIME_ZONE in config:
set_time_zone(config.get(CONF_TIME_ZONE))
for entity_id, attrs in config.get(CONF_CUSTOMIZE).items():
Entity.overwrite_attribute(entity_id, attrs.keys(), attrs.values())
if CONF_TEMPERATURE_UNIT in config:
hac.temperature_unit = config[CONF_TEMPERATURE_UNIT]
# If we miss some of the needed values, auto detect them
if None not in (
hac.latitude, hac.longitude, hac.temperature_unit, hac.time_zone):
return
_LOGGER.warning('Incomplete core config. Auto detecting location and '
'temperature unit')
info = loc_util.detect_location_info()
if info is None:
_LOGGER.error('Could not detect location information')
return
if hac.latitude is None and hac.longitude is None:
hac.latitude = info.latitude
hac.longitude = info.longitude
if hac.temperature_unit is None:
if info.use_fahrenheit:
hac.temperature_unit = TEMP_FAHRENHEIT
else:
hac.temperature_unit = TEMP_CELCIUS
if hac.location_name is None:
hac.location_name = info.city
if hac.time_zone is None:
set_time_zone(info.time_zone)
def _ensure_loader_prepared(hass):
def _ensure_loader_prepared(hass: core.HomeAssistant) -> None:
"""Ensure Home Assistant loader is prepared."""
if not loader.PREPARED:
loader.prepare(hass)
def _mount_local_lib_path(config_dir: str) -> None:
"""Add local library to Python Path."""
sys.path.insert(0, os.path.join(config_dir, 'deps'))

View File

@@ -19,6 +19,8 @@ from homeassistant.const import (
_LOGGER = logging.getLogger(__name__)
SERVICE_RELOAD_CORE_CONFIG = 'reload_core_config'
def is_on(hass, entity_id=None):
"""Load up the module to call the is_on method.
@@ -73,6 +75,11 @@ def toggle(hass, entity_id=None, **service_data):
hass.services.call(ha.DOMAIN, SERVICE_TOGGLE, service_data)
def reload_core_config(hass):
"""Reload the core config."""
hass.services.call(ha.DOMAIN, SERVICE_RELOAD_CORE_CONFIG)
def setup(hass, config):
"""Setup general services related to Home Assistant."""
def handle_turn_service(service):
@@ -111,4 +118,21 @@ def setup(hass, config):
hass.services.register(ha.DOMAIN, SERVICE_TURN_ON, handle_turn_service)
hass.services.register(ha.DOMAIN, SERVICE_TOGGLE, handle_turn_service)
def handle_reload_config(call):
"""Service handler for reloading core config."""
from homeassistant.exceptions import HomeAssistantError
from homeassistant import config as conf_util
try:
path = conf_util.find_config_file(hass.config.config_dir)
conf = conf_util.load_yaml_config_file(path)
except HomeAssistantError as err:
_LOGGER.error(err)
return
conf_util.process_ha_core_config(hass, conf.get(ha.DOMAIN) or {})
hass.services.register(ha.DOMAIN, SERVICE_RELOAD_CORE_CONFIG,
handle_reload_config)
return True

View File

@@ -7,12 +7,14 @@ https://home-assistant.io/components/alarm_control_panel/
import logging
import os
from homeassistant.components import verisure
import voluptuous as vol
from homeassistant.const import (
ATTR_CODE, ATTR_CODE_FORMAT, ATTR_ENTITY_ID, SERVICE_ALARM_TRIGGER,
SERVICE_ALARM_DISARM, SERVICE_ALARM_ARM_HOME, SERVICE_ALARM_ARM_AWAY)
from homeassistant.config import load_yaml_config_file
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
@@ -21,11 +23,6 @@ SCAN_INTERVAL = 30
ENTITY_ID_FORMAT = DOMAIN + '.{}'
# Maps discovered services to their platforms
DISCOVERY_PLATFORMS = {
verisure.DISCOVER_ALARMS: 'verisure'
}
SERVICE_TO_METHOD = {
SERVICE_ALARM_DISARM: 'alarm_disarm',
SERVICE_ALARM_ARM_HOME: 'alarm_arm_home',
@@ -38,12 +35,16 @@ ATTR_TO_PROPERTY = [
ATTR_CODE_FORMAT
]
ALARM_SERVICE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional(ATTR_CODE): cv.string,
})
def setup(hass, config):
"""Track states and offer events for sensors."""
component = EntityComponent(
logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL,
DISCOVERY_PLATFORMS)
logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL)
component.setup(config)
@@ -51,10 +52,7 @@ def setup(hass, config):
"""Map services to methods on Alarm."""
target_alarms = component.extract_from_service(service)
if ATTR_CODE not in service.data:
code = None
else:
code = service.data[ATTR_CODE]
code = service.data.get(ATTR_CODE)
method = SERVICE_TO_METHOD[service.service]
@@ -68,8 +66,8 @@ def setup(hass, config):
for service in SERVICE_TO_METHOD:
hass.services.register(DOMAIN, service, alarm_service_handler,
descriptions.get(service))
descriptions.get(service),
schema=ALARM_SERVICE_SCHEMA)
return True

View File

@@ -0,0 +1,105 @@
"""
Support for Envisalink-based alarm control panels (Honeywell/DSC).
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.envisalink/
"""
import logging
import homeassistant.components.alarm_control_panel as alarm
from homeassistant.components.envisalink import (EVL_CONTROLLER,
EnvisalinkDevice,
PARTITION_SCHEMA,
CONF_CODE,
CONF_PARTITIONNAME,
SIGNAL_PARTITION_UPDATE,
SIGNAL_KEYPAD_UPDATE)
from homeassistant.const import (
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED,
STATE_UNKNOWN, STATE_ALARM_TRIGGERED)
DEPENDENCIES = ['envisalink']
_LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Perform the setup for Envisalink alarm panels."""
_configured_partitions = discovery_info['partitions']
_code = discovery_info[CONF_CODE]
for part_num in _configured_partitions:
_device_config_data = PARTITION_SCHEMA(
_configured_partitions[part_num])
_device = EnvisalinkAlarm(
part_num,
_device_config_data[CONF_PARTITIONNAME],
_code,
EVL_CONTROLLER.alarm_state['partition'][part_num],
EVL_CONTROLLER)
add_devices_callback([_device])
return True
class EnvisalinkAlarm(EnvisalinkDevice, alarm.AlarmControlPanel):
"""Represents the Envisalink-based alarm panel."""
# pylint: disable=too-many-arguments
def __init__(self, partition_number, alarm_name, code, info, controller):
"""Initialize the alarm panel."""
from pydispatch import dispatcher
self._partition_number = partition_number
self._code = code
_LOGGER.debug('Setting up alarm: ' + alarm_name)
EnvisalinkDevice.__init__(self, alarm_name, info, controller)
dispatcher.connect(self._update_callback,
signal=SIGNAL_PARTITION_UPDATE,
sender=dispatcher.Any)
dispatcher.connect(self._update_callback,
signal=SIGNAL_KEYPAD_UPDATE,
sender=dispatcher.Any)
def _update_callback(self, partition):
"""Update HA state, if needed."""
if partition is None or int(partition) == self._partition_number:
self.update_ha_state()
@property
def code_format(self):
"""The characters if code is defined."""
return self._code
@property
def state(self):
"""Return the state of the device."""
if self._info['status']['alarm']:
return STATE_ALARM_TRIGGERED
elif self._info['status']['armed_away']:
return STATE_ALARM_ARMED_AWAY
elif self._info['status']['armed_stay']:
return STATE_ALARM_ARMED_HOME
elif self._info['status']['alpha']:
return STATE_ALARM_DISARMED
else:
return STATE_UNKNOWN
def alarm_disarm(self, code=None):
"""Send disarm command."""
if self._code:
EVL_CONTROLLER.disarm_partition(str(code),
self._partition_number)
def alarm_arm_home(self, code=None):
"""Send arm home command."""
if self._code:
EVL_CONTROLLER.arm_stay_partition(str(code),
self._partition_number)
def alarm_arm_away(self, code=None):
"""Send arm away command."""
if self._code:
EVL_CONTROLLER.arm_away_partition(str(code),
self._partition_number)
def alarm_trigger(self, code=None):
"""Alarm trigger command. Not possible for us."""
raise NotImplementedError()

View File

@@ -0,0 +1,124 @@
"""
Interfaces with SimpliSafe alarm control panel.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.simplisafe/
"""
import logging
import homeassistant.components.alarm_control_panel as alarm
from homeassistant.const import (
CONF_PASSWORD, CONF_USERNAME, STATE_UNKNOWN,
STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_AWAY)
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['https://github.com/w1ll1am23/simplisafe-python/archive/'
'586fede0e85fd69e56e516aaa8e97eb644ca8866.zip#'
'simplisafe-python==0.0.1']
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the SimpliSafe platform."""
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
if username is None or password is None:
_LOGGER.error('Must specify username and password!')
return False
add_devices([SimpliSafeAlarm(
config.get('name', "SimpliSafe"),
username,
password,
config.get('code'))])
# pylint: disable=abstract-method
class SimpliSafeAlarm(alarm.AlarmControlPanel):
"""Representation a SimpliSafe alarm."""
def __init__(self, name, username, password, code):
"""Initialize the SimpliSafe alarm."""
from simplisafe import SimpliSafe
self.simplisafe = SimpliSafe(username, password)
self._name = name
self._code = str(code) if code else None
self._id = self.simplisafe.get_id()
status = self.simplisafe.get_state()
if status == 'Off':
self._state = STATE_ALARM_DISARMED
elif status == 'Home':
self._state = STATE_ALARM_ARMED_HOME
elif status == 'Away':
self._state = STATE_ALARM_ARMED_AWAY
else:
self._state = STATE_UNKNOWN
@property
def should_poll(self):
"""Poll the SimpliSafe API."""
return True
@property
def name(self):
"""Return the name of the device."""
if self._name is not None:
return self._name
else:
return 'Alarm {}'.format(self._id)
@property
def code_format(self):
"""One or more characters if code is defined."""
return None if self._code is None else '.+'
@property
def state(self):
"""Return the state of the device."""
return self._state
def update(self):
"""Update alarm status."""
self.simplisafe.get_location()
status = self.simplisafe.get_state()
if status == 'Off':
self._state = STATE_ALARM_DISARMED
elif status == 'Home':
self._state = STATE_ALARM_ARMED_HOME
elif status == 'Away':
self._state = STATE_ALARM_ARMED_AWAY
else:
self._state = STATE_UNKNOWN
def alarm_disarm(self, code=None):
"""Send disarm command."""
if not self._validate_code(code, 'disarming'):
return
self.simplisafe.set_state('off')
_LOGGER.info('SimpliSafe alarm disarming')
self.update()
def alarm_arm_home(self, code=None):
"""Send arm home command."""
if not self._validate_code(code, 'arming home'):
return
self.simplisafe.set_state('home')
_LOGGER.info('SimpliSafe alarm arming home')
self.update()
def alarm_arm_away(self, code=None):
"""Send arm away command."""
if not self._validate_code(code, 'arming away'):
return
self.simplisafe.set_state('away')
_LOGGER.info('SimpliSafe alarm arming away')
self.update()
def _validate_code(self, code, state):
"""Validate given code."""
check = self._code is None or code == self._code
if not check:
_LOGGER.warning('Wrong code entered for %s', state)
return check

View File

@@ -48,6 +48,11 @@ class VerisureAlarm(alarm.AlarmControlPanel):
"""Return the state of the device."""
return self._state
@property
def available(self):
"""Return True if entity is available."""
return hub.available
@property
def code_format(self):
"""The code format as regex."""

View File

@@ -7,15 +7,14 @@ https://home-assistant.io/components/alexa/
import enum
import logging
from homeassistant.const import HTTP_OK, HTTP_UNPROCESSABLE_ENTITY
from homeassistant.helpers.service import call_from_config
from homeassistant.helpers import template
from homeassistant.const import HTTP_BAD_REQUEST
from homeassistant.helpers import template, script
from homeassistant.components.http import HomeAssistantView
DOMAIN = 'alexa'
DEPENDENCIES = ['http']
_LOGGER = logging.getLogger(__name__)
_CONFIG = {}
API_ENDPOINT = '/api/alexa'
@@ -27,73 +26,88 @@ CONF_ACTION = 'action'
def setup(hass, config):
"""Activate Alexa component."""
_CONFIG.update(config[DOMAIN].get(CONF_INTENTS, {}))
hass.http.register_path('POST', API_ENDPOINT, _handle_alexa, True)
hass.wsgi.register_view(AlexaView(hass,
config[DOMAIN].get(CONF_INTENTS, {})))
return True
def _handle_alexa(handler, path_match, data):
"""Handle Alexa."""
_LOGGER.debug('Received Alexa request: %s', data)
class AlexaView(HomeAssistantView):
"""Handle Alexa requests."""
req = data.get('request')
url = API_ENDPOINT
name = 'api:alexa'
if req is None:
_LOGGER.error('Received invalid data from Alexa: %s', data)
handler.write_json_message(
"Invalid value received for port", HTTP_UNPROCESSABLE_ENTITY)
return
def __init__(self, hass, intents):
"""Initialize Alexa view."""
super().__init__(hass)
req_type = req['type']
for name, intent in intents.items():
if CONF_ACTION in intent:
intent[CONF_ACTION] = script.Script(
hass, intent[CONF_ACTION], "Alexa intent {}".format(name))
if req_type == 'SessionEndedRequest':
handler.send_response(HTTP_OK)
handler.end_headers()
return
self.intents = intents
intent = req.get('intent')
response = AlexaResponse(handler.server.hass, intent)
def post(self, request):
"""Handle Alexa."""
data = request.json
if req_type == 'LaunchRequest':
response.add_speech(
SpeechType.plaintext,
"Hello, and welcome to the future. How may I help?")
handler.write_json(response.as_dict())
return
_LOGGER.debug('Received Alexa request: %s', data)
if req_type != 'IntentRequest':
_LOGGER.warning('Received unsupported request: %s', req_type)
return
req = data.get('request')
intent_name = intent['name']
config = _CONFIG.get(intent_name)
if req is None:
_LOGGER.error('Received invalid data from Alexa: %s', data)
return self.json_message('Expected request value not received',
HTTP_BAD_REQUEST)
if config is None:
_LOGGER.warning('Received unknown intent %s', intent_name)
response.add_speech(
SpeechType.plaintext,
"This intent is not yet configured within Home Assistant.")
handler.write_json(response.as_dict())
return
req_type = req['type']
speech = config.get(CONF_SPEECH)
card = config.get(CONF_CARD)
action = config.get(CONF_ACTION)
if req_type == 'SessionEndedRequest':
return None
# pylint: disable=unsubscriptable-object
if speech is not None:
response.add_speech(SpeechType[speech['type']], speech['text'])
intent = req.get('intent')
response = AlexaResponse(self.hass, intent)
if card is not None:
response.add_card(CardType[card['type']], card['title'],
card['content'])
if req_type == 'LaunchRequest':
response.add_speech(
SpeechType.plaintext,
"Hello, and welcome to the future. How may I help?")
return self.json(response)
if action is not None:
call_from_config(handler.server.hass, action, True)
if req_type != 'IntentRequest':
_LOGGER.warning('Received unsupported request: %s', req_type)
return self.json_message(
'Received unsupported request: {}'.format(req_type),
HTTP_BAD_REQUEST)
handler.write_json(response.as_dict())
intent_name = intent['name']
config = self.intents.get(intent_name)
if config is None:
_LOGGER.warning('Received unknown intent %s', intent_name)
response.add_speech(
SpeechType.plaintext,
"This intent is not yet configured within Home Assistant.")
return self.json(response)
speech = config.get(CONF_SPEECH)
card = config.get(CONF_CARD)
action = config.get(CONF_ACTION)
if action is not None:
action.run(response.variables)
# pylint: disable=unsubscriptable-object
if speech is not None:
response.add_speech(SpeechType[speech['type']], speech['text'])
if card is not None:
response.add_card(CardType[card['type']], card['title'],
card['content'])
return self.json(response)
class SpeechType(enum.Enum):

View File

@@ -6,22 +6,23 @@ https://home-assistant.io/developers/api/
"""
import json
import logging
import re
import threading
import queue
import homeassistant.core as ha
import homeassistant.remote as rem
from homeassistant.bootstrap import ERROR_LOG_FILENAME
from homeassistant.const import (
CONTENT_TYPE_TEXT_PLAIN, EVENT_HOMEASSISTANT_STOP, EVENT_TIME_CHANGED,
HTTP_BAD_REQUEST, HTTP_CREATED, HTTP_HEADER_CONTENT_TYPE, HTTP_NOT_FOUND,
HTTP_OK, HTTP_UNPROCESSABLE_ENTITY, MATCH_ALL, URL_API, URL_API_COMPONENTS,
URL_API_CONFIG, URL_API_ERROR_LOG, URL_API_EVENT_FORWARD, URL_API_EVENTS,
URL_API_LOG_OUT, URL_API_SERVICES, URL_API_STATES, URL_API_STATES_ENTITY,
URL_API_STREAM, URL_API_TEMPLATE)
EVENT_HOMEASSISTANT_STOP, EVENT_TIME_CHANGED,
HTTP_BAD_REQUEST, HTTP_CREATED, HTTP_NOT_FOUND,
HTTP_UNPROCESSABLE_ENTITY, MATCH_ALL, URL_API, URL_API_COMPONENTS,
URL_API_CONFIG, URL_API_DISCOVERY_INFO, URL_API_ERROR_LOG,
URL_API_EVENT_FORWARD, URL_API_EVENTS, URL_API_SERVICES,
URL_API_STATES, URL_API_STATES_ENTITY, URL_API_STREAM, URL_API_TEMPLATE,
__version__)
from homeassistant.exceptions import TemplateError
from homeassistant.helpers.state import TrackStates
from homeassistant.helpers import template
from homeassistant.components.http import HomeAssistantView
DOMAIN = 'api'
DEPENDENCIES = ['http']
@@ -34,356 +35,351 @@ _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)
# /api/stream
hass.http.register_path('GET', URL_API_STREAM, _handle_get_api_stream)
# /api/config
hass.http.register_path('GET', URL_API_CONFIG, _handle_get_api_config)
# /states
hass.http.register_path('GET', URL_API_STATES, _handle_get_api_states)
hass.http.register_path(
'GET', re.compile(r'/api/states/(?P<entity_id>[a-zA-Z\._0-9]+)'),
_handle_get_api_states_entity)
hass.http.register_path(
'POST', re.compile(r'/api/states/(?P<entity_id>[a-zA-Z\._0-9]+)'),
_handle_post_state_entity)
hass.http.register_path(
'PUT', re.compile(r'/api/states/(?P<entity_id>[a-zA-Z\._0-9]+)'),
_handle_post_state_entity)
hass.http.register_path(
'DELETE', re.compile(r'/api/states/(?P<entity_id>[a-zA-Z\._0-9]+)'),
_handle_delete_state_entity)
# /events
hass.http.register_path('GET', URL_API_EVENTS, _handle_get_api_events)
hass.http.register_path(
'POST', re.compile(r'/api/events/(?P<event_type>[a-zA-Z\._0-9]+)'),
_handle_api_post_events_event)
# /services
hass.http.register_path('GET', URL_API_SERVICES, _handle_get_api_services)
hass.http.register_path(
'POST',
re.compile((r'/api/services/'
r'(?P<domain>[a-zA-Z\._0-9]+)/'
r'(?P<service>[a-zA-Z\._0-9]+)')),
_handle_post_api_services_domain_service)
# /event_forwarding
hass.http.register_path(
'POST', URL_API_EVENT_FORWARD, _handle_post_api_event_forward)
hass.http.register_path(
'DELETE', URL_API_EVENT_FORWARD, _handle_delete_api_event_forward)
# /components
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)
hass.wsgi.register_view(APIStatusView)
hass.wsgi.register_view(APIEventStream)
hass.wsgi.register_view(APIConfigView)
hass.wsgi.register_view(APIDiscoveryView)
hass.wsgi.register_view(APIStatesView)
hass.wsgi.register_view(APIEntityStateView)
hass.wsgi.register_view(APIEventListenersView)
hass.wsgi.register_view(APIEventView)
hass.wsgi.register_view(APIServicesView)
hass.wsgi.register_view(APIDomainServicesView)
hass.wsgi.register_view(APIEventForwardingView)
hass.wsgi.register_view(APIComponentsView)
hass.wsgi.register_view(APIErrorLogView)
hass.wsgi.register_view(APITemplateView)
return True
def _handle_get_api(handler, path_match, data):
"""Render the debug interface."""
handler.write_json_message("API running.")
class APIStatusView(HomeAssistantView):
"""View to handle Status requests."""
url = URL_API
name = "api:status"
def _handle_get_api_stream(handler, path_match, data):
"""Provide a streaming interface for the event bus."""
gracefully_closed = False
hass = handler.server.hass
wfile = handler.wfile
write_lock = threading.Lock()
block = threading.Event()
session_id = None
def get(self, request):
"""Retrieve if API is running."""
return self.json_message('API running.')
restrict = data.get('restrict')
if restrict:
restrict = restrict.split(',')
def write_message(payload):
"""Write a message to the output."""
with write_lock:
msg = "data: {}\n\n".format(payload)
class APIEventStream(HomeAssistantView):
"""View to handle EventStream requests."""
try:
wfile.write(msg.encode("UTF-8"))
wfile.flush()
except (IOError, ValueError):
# IOError: socket errors
# ValueError: raised when 'I/O operation on closed file'
block.set()
url = URL_API_STREAM
name = "api:stream"
def forward_events(event):
"""Forward events to the open request."""
nonlocal gracefully_closed
def get(self, request):
"""Provide a streaming interface for the event bus."""
stop_obj = object()
to_write = queue.Queue()
if block.is_set() or event.event_type == EVENT_TIME_CHANGED:
return
elif event.event_type == EVENT_HOMEASSISTANT_STOP:
gracefully_closed = True
block.set()
return
restrict = request.args.get('restrict')
if restrict:
restrict = restrict.split(',') + [EVENT_HOMEASSISTANT_STOP]
handler.server.sessions.extend_validation(session_id)
write_message(json.dumps(event, cls=rem.JSONEncoder))
def forward_events(event):
"""Forward events to the open request."""
if event.event_type == EVENT_TIME_CHANGED:
return
handler.send_response(HTTP_OK)
handler.send_header('Content-type', 'text/event-stream')
session_id = handler.set_session_cookie_header()
handler.end_headers()
if restrict and event.event_type not in restrict:
return
if restrict:
for event in restrict:
hass.bus.listen(event, forward_events)
else:
hass.bus.listen(MATCH_ALL, forward_events)
_LOGGER.debug('STREAM %s FORWARDING %s', id(stop_obj), event)
while True:
write_message(STREAM_PING_PAYLOAD)
if event.event_type == EVENT_HOMEASSISTANT_STOP:
data = stop_obj
else:
data = json.dumps(event, cls=rem.JSONEncoder)
block.wait(STREAM_PING_INTERVAL)
to_write.put(data)
if block.is_set():
break
def stream():
"""Stream events to response."""
self.hass.bus.listen(MATCH_ALL, forward_events)
if not gracefully_closed:
_LOGGER.info("Found broken event stream to %s, cleaning up",
handler.client_address[0])
_LOGGER.debug('STREAM %s ATTACHED', id(stop_obj))
if restrict:
for event in restrict:
hass.bus.remove_listener(event, forward_events)
else:
hass.bus.remove_listener(MATCH_ALL, forward_events)
# Fire off one message right away to have browsers fire open event
to_write.put(STREAM_PING_PAYLOAD)
while True:
try:
payload = to_write.get(timeout=STREAM_PING_INTERVAL)
def _handle_get_api_config(handler, path_match, data):
"""Return the Home Assistant configuration."""
handler.write_json(handler.server.hass.config.as_dict())
if payload is stop_obj:
break
msg = "data: {}\n\n".format(payload)
_LOGGER.debug('STREAM %s WRITING %s', id(stop_obj),
msg.strip())
yield msg.encode("UTF-8")
except queue.Empty:
to_write.put(STREAM_PING_PAYLOAD)
except GeneratorExit:
break
def _handle_get_api_states(handler, path_match, data):
"""Return a dict containing all entity ids and their state."""
handler.write_json(handler.server.hass.states.all())
_LOGGER.debug('STREAM %s RESPONSE CLOSED', id(stop_obj))
self.hass.bus.remove_listener(MATCH_ALL, forward_events)
return self.Response(stream(), mimetype='text/event-stream')
def _handle_get_api_states_entity(handler, path_match, data):
"""Return the state of a specific entity."""
entity_id = path_match.group('entity_id')
state = handler.server.hass.states.get(entity_id)
class APIConfigView(HomeAssistantView):
"""View to handle Config requests."""
if state:
handler.write_json(state)
else:
handler.write_json_message("State does not exist.", HTTP_NOT_FOUND)
url = URL_API_CONFIG
name = "api:config"
def get(self, request):
"""Get current configuration."""
return self.json(self.hass.config.as_dict())
def _handle_post_state_entity(handler, path_match, data):
"""Handle updating the state of an entity.
This handles the following paths:
/api/states/<entity_id>
"""
entity_id = path_match.group('entity_id')
class APIDiscoveryView(HomeAssistantView):
"""View to provide discovery info."""
try:
new_state = data['state']
except KeyError:
handler.write_json_message("state not specified", HTTP_BAD_REQUEST)
return
requires_auth = False
url = URL_API_DISCOVERY_INFO
name = "api:discovery"
attributes = data['attributes'] if 'attributes' in data else None
def get(self, request):
"""Get discovery info."""
needs_auth = self.hass.config.api.api_password is not None
return self.json({
'base_url': self.hass.config.api.base_url,
'location_name': self.hass.config.location_name,
'requires_api_password': needs_auth,
'version': __version__
})
is_new_state = handler.server.hass.states.get(entity_id) is None
# Write state
handler.server.hass.states.set(entity_id, new_state, attributes)
class APIStatesView(HomeAssistantView):
"""View to handle States requests."""
state = handler.server.hass.states.get(entity_id)
url = URL_API_STATES
name = "api:states"
status_code = HTTP_CREATED if is_new_state else HTTP_OK
def get(self, request):
"""Get current states."""
return self.json(self.hass.states.all())
handler.write_json(
state.as_dict(),
status_code=status_code,
location=URL_API_STATES_ENTITY.format(entity_id))
class APIEntityStateView(HomeAssistantView):
"""View to handle EntityState requests."""
def _handle_delete_state_entity(handler, path_match, data):
"""Handle request to delete an entity from state machine.
url = "/api/states/<entity(exist=False):entity_id>"
name = "api:entity-state"
This handles the following paths:
/api/states/<entity_id>
"""
entity_id = path_match.group('entity_id')
def get(self, request, entity_id):
"""Retrieve state of entity."""
state = self.hass.states.get(entity_id)
if state:
return self.json(state)
else:
return self.json_message('Entity not found', HTTP_NOT_FOUND)
if handler.server.hass.states.remove(entity_id):
handler.write_json_message(
"Entity not found", HTTP_NOT_FOUND)
else:
handler.write_json_message(
"Entity removed", HTTP_OK)
def post(self, request, entity_id):
"""Update state of entity."""
try:
new_state = request.json['state']
except KeyError:
return self.json_message('No state specified', HTTP_BAD_REQUEST)
attributes = request.json.get('attributes')
force_update = request.json.get('force_update', False)
def _handle_get_api_events(handler, path_match, data):
"""Handle getting overview of event listeners."""
handler.write_json(events_json(handler.server.hass))
is_new_state = self.hass.states.get(entity_id) is None
# Write state
self.hass.states.set(entity_id, new_state, attributes, force_update)
def _handle_api_post_events_event(handler, path_match, event_data):
"""Handle firing of an event.
# Read the state back for our response
resp = self.json(self.hass.states.get(entity_id))
This handles the following paths: /api/events/<event_type>
if is_new_state:
resp.status_code = HTTP_CREATED
Events from /api are threated as remote events.
"""
event_type = path_match.group('event_type')
resp.headers.add('Location', URL_API_STATES_ENTITY.format(entity_id))
if event_data is not None and not isinstance(event_data, dict):
handler.write_json_message(
"event_data should be an object", HTTP_UNPROCESSABLE_ENTITY)
return
return resp
event_origin = ha.EventOrigin.remote
def delete(self, request, entity_id):
"""Remove entity."""
if self.hass.states.remove(entity_id):
return self.json_message('Entity removed')
else:
return self.json_message('Entity not found', HTTP_NOT_FOUND)
# Special case handling for event STATE_CHANGED
# We will try to convert state dicts back to State objects
if event_type == ha.EVENT_STATE_CHANGED and event_data:
for key in ('old_state', 'new_state'):
state = ha.State.from_dict(event_data.get(key))
if state:
event_data[key] = state
class APIEventListenersView(HomeAssistantView):
"""View to handle EventListeners requests."""
handler.server.hass.bus.fire(event_type, event_data, event_origin)
url = URL_API_EVENTS
name = "api:event-listeners"
handler.write_json_message("Event {} fired.".format(event_type))
def get(self, request):
"""Get event listeners."""
return self.json(events_json(self.hass))
def _handle_get_api_services(handler, path_match, data):
"""Handle getting overview of services."""
handler.write_json(services_json(handler.server.hass))
class APIEventView(HomeAssistantView):
"""View to handle Event requests."""
url = '/api/events/<event_type>'
name = "api:event"
# pylint: disable=invalid-name
def _handle_post_api_services_domain_service(handler, path_match, data):
"""Handle calling a service.
def post(self, request, event_type):
"""Fire events."""
event_data = request.json
This handles the following paths: /api/services/<domain>/<service>
"""
domain = path_match.group('domain')
service = path_match.group('service')
if event_data is not None and not isinstance(event_data, dict):
return self.json_message('Event data should be a JSON object',
HTTP_BAD_REQUEST)
with TrackStates(handler.server.hass) as changed_states:
handler.server.hass.services.call(domain, service, data, True)
# Special case handling for event STATE_CHANGED
# We will try to convert state dicts back to State objects
if event_type == ha.EVENT_STATE_CHANGED and event_data:
for key in ('old_state', 'new_state'):
state = ha.State.from_dict(event_data.get(key))
handler.write_json(changed_states)
if state:
event_data[key] = state
self.hass.bus.fire(event_type, event_data, ha.EventOrigin.remote)
# pylint: disable=invalid-name
def _handle_post_api_event_forward(handler, path_match, data):
"""Handle adding an event forwarding target."""
try:
host = data['host']
api_password = data['api_password']
except KeyError:
handler.write_json_message(
"No host or api_password received.", HTTP_BAD_REQUEST)
return
return self.json_message("Event {} fired.".format(event_type))
try:
port = int(data['port']) if 'port' in data else None
except ValueError:
handler.write_json_message(
"Invalid value received for port", HTTP_UNPROCESSABLE_ENTITY)
return
api = rem.API(host, api_password, port)
class APIServicesView(HomeAssistantView):
"""View to handle Services requests."""
if not api.validate_api():
handler.write_json_message(
"Unable to validate API", HTTP_UNPROCESSABLE_ENTITY)
return
url = URL_API_SERVICES
name = "api:services"
if handler.server.event_forwarder is None:
handler.server.event_forwarder = \
rem.EventForwarder(handler.server.hass)
def get(self, request):
"""Get registered services."""
return self.json(services_json(self.hass))
handler.server.event_forwarder.connect(api)
handler.write_json_message("Event forwarding setup.")
class APIDomainServicesView(HomeAssistantView):
"""View to handle DomainServices requests."""
url = "/api/services/<domain>/<service>"
name = "api:domain-services"
def _handle_delete_api_event_forward(handler, path_match, data):
"""Handle deleting an event forwarding target."""
try:
host = data['host']
except KeyError:
handler.write_json_message("No host received.", HTTP_BAD_REQUEST)
return
def post(self, request, domain, service):
"""Call a service.
try:
port = int(data['port']) if 'port' in data else None
except ValueError:
handler.write_json_message(
"Invalid value received for port", HTTP_UNPROCESSABLE_ENTITY)
return
Returns a list of changed states.
"""
with TrackStates(self.hass) as changed_states:
self.hass.services.call(domain, service, request.json, True)
if handler.server.event_forwarder is not None:
api = rem.API(host, None, port)
return self.json(changed_states)
handler.server.event_forwarder.disconnect(api)
handler.write_json_message("Event forwarding cancelled.")
class APIEventForwardingView(HomeAssistantView):
"""View to handle EventForwarding requests."""
url = URL_API_EVENT_FORWARD
name = "api:event-forward"
event_forwarder = None
def _handle_get_api_components(handler, path_match, data):
"""Return all the loaded components."""
handler.write_json(handler.server.hass.config.components)
def post(self, request):
"""Setup an event forwarder."""
data = request.json
if data is None:
return self.json_message("No data received.", HTTP_BAD_REQUEST)
try:
host = data['host']
api_password = data['api_password']
except KeyError:
return self.json_message("No host or api_password received.",
HTTP_BAD_REQUEST)
try:
port = int(data['port']) if 'port' in data else None
except ValueError:
return self.json_message("Invalid value received for port.",
HTTP_UNPROCESSABLE_ENTITY)
def _handle_get_api_error_log(handler, path_match, data):
"""Return the logged errors for this session."""
handler.write_file(handler.server.hass.config.path(ERROR_LOG_FILENAME),
False)
api = rem.API(host, api_password, port)
if not api.validate_api():
return self.json_message("Unable to validate API.",
HTTP_UNPROCESSABLE_ENTITY)
def _handle_post_api_log_out(handler, path_match, data):
"""Log user out."""
handler.send_response(HTTP_OK)
handler.destroy_session()
handler.end_headers()
if self.event_forwarder is None:
self.event_forwarder = rem.EventForwarder(self.hass)
self.event_forwarder.connect(api)
def _handle_post_api_template(handler, path_match, data):
"""Log user out."""
template_string = data.get('template', '')
return self.json_message("Event forwarding setup.")
try:
rendered = template.render(handler.server.hass, template_string)
def delete(self, request):
"""Remove event forwarer."""
data = request.json
if data is None:
return self.json_message("No data received.", HTTP_BAD_REQUEST)
handler.send_response(HTTP_OK)
handler.send_header(HTTP_HEADER_CONTENT_TYPE, CONTENT_TYPE_TEXT_PLAIN)
handler.end_headers()
handler.wfile.write(rendered.encode('utf-8'))
except TemplateError as e:
handler.write_json_message(str(e), HTTP_UNPROCESSABLE_ENTITY)
return
try:
host = data['host']
except KeyError:
return self.json_message("No host received.", HTTP_BAD_REQUEST)
try:
port = int(data['port']) if 'port' in data else None
except ValueError:
return self.json_message("Invalid value received for port.",
HTTP_UNPROCESSABLE_ENTITY)
if self.event_forwarder is not None:
api = rem.API(host, None, port)
self.event_forwarder.disconnect(api)
return self.json_message("Event forwarding cancelled.")
class APIComponentsView(HomeAssistantView):
"""View to handle Components requests."""
url = URL_API_COMPONENTS
name = "api:components"
def get(self, request):
"""Get current loaded components."""
return self.json(self.hass.config.components)
class APIErrorLogView(HomeAssistantView):
"""View to handle ErrorLog requests."""
url = URL_API_ERROR_LOG
name = "api:error-log"
def get(self, request):
"""Serve error log."""
return self.file(request, self.hass.config.path(ERROR_LOG_FILENAME))
class APITemplateView(HomeAssistantView):
"""View to handle requests."""
url = URL_API_TEMPLATE
name = "api:template"
def post(self, request):
"""Render a template."""
try:
return template.render(self.hass, request.json['template'],
request.json.get('variables'))
except TemplateError as ex:
return self.json_message('Error rendering template: {}'.format(ex),
HTTP_BAD_REQUEST)
def services_json(hass):

View File

@@ -11,7 +11,7 @@ from homeassistant.const import (
from homeassistant.helpers import validate_config
DOMAIN = "arduino"
REQUIREMENTS = ['PyMata==2.07a']
REQUIREMENTS = ['PyMata==2.12']
BOARD = None
_LOGGER = logging.getLogger(__name__)
@@ -69,7 +69,7 @@ class ArduinoBoard(object):
self._board.ANALOG)
elif mode == 'digital' and direction == 'in':
self._board.set_pin_mode(pin,
self._board.OUTPUT,
self._board.INPUT,
self._board.DIGITAL)
elif mode == 'digital' and direction == 'out':
self._board.set_pin_mode(pin,

View File

@@ -9,10 +9,10 @@ import logging
import voluptuous as vol
from homeassistant.bootstrap import prepare_setup_platform
from homeassistant.const import CONF_PLATFORM
from homeassistant.const import ATTR_ENTITY_ID, CONF_PLATFORM
from homeassistant.components import logbook
from homeassistant.helpers import extract_domain_configs
from homeassistant.helpers.service import call_from_config
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import extract_domain_configs, script, condition
from homeassistant.loader import get_platform
import homeassistant.helpers.config_validation as cv
@@ -51,8 +51,6 @@ def _platform_validator(method, schema):
if not hasattr(platform, schema):
return config
print('validating config', method, config)
return getattr(platform, schema)(config)
return validator
@@ -76,10 +74,11 @@ _CONDITION_SCHEMA = vol.Any(
[
vol.All(
vol.Schema({
vol.Required(CONF_PLATFORM): cv.platform_validator(DOMAIN),
CONF_PLATFORM: str,
CONF_CONDITION: str,
}, extra=vol.ALLOW_EXTRA),
_platform_validator(METHOD_IF_ACTION, 'IF_ACTION_SCHEMA'),
)
cv.has_at_least_one_key(CONF_PLATFORM, CONF_CONDITION),
),
]
)
)
@@ -90,21 +89,23 @@ PLATFORM_SCHEMA = vol.Schema({
vol.Required(CONF_CONDITION_TYPE, default=DEFAULT_CONDITION_TYPE):
vol.All(vol.Lower, vol.Any(CONDITION_TYPE_AND, CONDITION_TYPE_OR)),
CONF_CONDITION: _CONDITION_SCHEMA,
vol.Required(CONF_ACTION): cv.SERVICE_SCHEMA,
vol.Required(CONF_ACTION): cv.SCRIPT_SCHEMA,
})
def setup(hass, config):
"""Setup the automation."""
success = False
for config_key in extract_domain_configs(config, DOMAIN):
conf = config[config_key]
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)
success = (_setup_automation(hass, config_block, name, config) or
success)
return True
return success
def _setup_automation(hass, config_block, name, config):
@@ -124,12 +125,13 @@ def _setup_automation(hass, config_block, name, config):
def _get_action(hass, config, name):
"""Return an action based on a configuration."""
def action():
script_obj = script.Script(hass, config, name)
def action(variables=None):
"""Action to be executed."""
_LOGGER.info('Executing %s', name)
logbook.log_entry(hass, name, 'has been triggered', DOMAIN)
call_from_config(hass, config)
script_obj.run(variables)
return action
@@ -139,6 +141,11 @@ def _process_if(hass, config, p_config, action):
cond_type = p_config.get(CONF_CONDITION_TYPE,
DEFAULT_CONDITION_TYPE).lower()
# Deprecated since 0.19 - 5/5/2016
if cond_type != DEFAULT_CONDITION_TYPE:
_LOGGER.warning('Using condition_type: "or" is deprecated. Please use '
'"condition: or" instead.')
if_configs = p_config.get(CONF_CONDITION)
use_trigger = if_configs == CONDITION_USE_TRIGGER_VALUES
@@ -147,38 +154,47 @@ def _process_if(hass, config, p_config, action):
checks = []
for if_config in if_configs:
platform = _resolve_platform(METHOD_IF_ACTION, hass, config,
if_config.get(CONF_PLATFORM))
if platform is None:
continue
# Deprecated except for used by use_trigger_values
# since 0.19 - 5/5/2016
if CONF_PLATFORM in if_config:
if not use_trigger:
_LOGGER.warning("Please switch your condition configuration "
"to use 'condition' instead of 'platform'.")
if_config = dict(if_config)
if_config[CONF_CONDITION] = if_config.pop(CONF_PLATFORM)
check = platform.if_action(hass, if_config)
# To support use_trigger_values with state trigger accepting
# multiple entity_ids to monitor.
if_entity_id = if_config.get(ATTR_ENTITY_ID)
if isinstance(if_entity_id, list) and len(if_entity_id) == 1:
if_config[ATTR_ENTITY_ID] = if_entity_id[0]
# Invalid conditions are allowed if we base it on trigger
if check is None and not use_trigger:
return None
checks.append(check)
try:
checks.append(condition.from_config(if_config))
except HomeAssistantError as ex:
# Invalid conditions are allowed if we base it on trigger
if use_trigger:
_LOGGER.warning('Ignoring invalid condition: %s', ex)
else:
_LOGGER.warning('Invalid condition: %s', ex)
return None
if cond_type == CONDITION_TYPE_AND:
def if_action():
def if_action(variables=None):
"""AND all conditions."""
if all(check() for check in checks):
action()
if all(check(hass, variables) for check in checks):
action(variables)
else:
def if_action():
def if_action(variables=None):
"""OR all conditions."""
if any(check() for check in checks):
action()
if any(check(hass, variables) for check in checks):
action(variables)
return if_action
def _process_trigger(hass, config, trigger_configs, name, action):
"""Setup the triggers."""
if isinstance(trigger_configs, dict):
trigger_configs = [trigger_configs]
for conf in trigger_configs:
platform = _resolve_platform(METHOD_TRIGGER, hass, config,
conf.get(CONF_PLATFORM))

View File

@@ -6,27 +6,38 @@ at https://home-assistant.io/components/automation/#event-trigger
"""
import logging
import voluptuous as vol
from homeassistant.const import CONF_PLATFORM
from homeassistant.helpers import config_validation as cv
CONF_EVENT_TYPE = "event_type"
CONF_EVENT_DATA = "event_data"
_LOGGER = logging.getLogger(__name__)
TRIGGER_SCHEMA = vol.Schema({
vol.Required(CONF_PLATFORM): 'event',
vol.Required(CONF_EVENT_TYPE): cv.string,
vol.Optional(CONF_EVENT_DATA): dict,
})
def trigger(hass, config, action):
"""Listen for events based on configuration."""
event_type = config.get(CONF_EVENT_TYPE)
if event_type is None:
_LOGGER.error("Missing configuration key %s", CONF_EVENT_TYPE)
return False
event_data = config.get(CONF_EVENT_DATA)
def handle_event(event):
"""Listen for events and calls the action when data matches."""
if not event_data or all(val == event.data.get(key) for key, val
in event_data.items()):
action()
action({
'trigger': {
'platform': 'event',
'event': event,
},
})
hass.bus.listen(event_type, handle_event)
return True

View File

@@ -30,7 +30,14 @@ def trigger(hass, config, action):
def mqtt_automation_listener(msg_topic, msg_payload, qos):
"""Listen for MQTT messages."""
if payload is None or payload == msg_payload:
action()
action({
'trigger': {
'platform': 'mqtt',
'topic': msg_topic,
'payload': msg_payload,
'qos': qos,
}
})
mqtt.subscribe(hass, topic, mqtt_automation_listener)

View File

@@ -5,101 +5,65 @@ For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#numeric-state-trigger
"""
import logging
from functools import partial
from homeassistant.const import CONF_VALUE_TEMPLATE
import voluptuous as vol
from homeassistant.const import (
CONF_VALUE_TEMPLATE, CONF_PLATFORM, CONF_ENTITY_ID,
CONF_BELOW, CONF_ABOVE)
from homeassistant.helpers.event import track_state_change
from homeassistant.helpers import template
from homeassistant.helpers import condition, config_validation as cv
CONF_ENTITY_ID = "entity_id"
CONF_BELOW = "below"
CONF_ABOVE = "above"
TRIGGER_SCHEMA = vol.All(vol.Schema({
vol.Required(CONF_PLATFORM): 'numeric_state',
vol.Required(CONF_ENTITY_ID): cv.entity_ids,
CONF_BELOW: vol.Coerce(float),
CONF_ABOVE: vol.Coerce(float),
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
}), cv.has_at_least_one_key(CONF_BELOW, CONF_ABOVE))
_LOGGER = logging.getLogger(__name__)
def _renderer(hass, value_template, state):
"""Render the state value."""
if value_template is None:
return state.state
return template.render(hass, value_template, {'state': state})
def trigger(hass, config, action):
"""Listen for state changes based on configuration."""
entity_id = config.get(CONF_ENTITY_ID)
if entity_id is None:
_LOGGER.error("Missing configuration key %s", CONF_ENTITY_ID)
return False
below = config.get(CONF_BELOW)
above = config.get(CONF_ABOVE)
value_template = config.get(CONF_VALUE_TEMPLATE)
if below is None and above is None:
_LOGGER.error("Missing configuration key."
" One of %s or %s is required",
CONF_BELOW, CONF_ABOVE)
return False
renderer = partial(_renderer, hass, value_template)
# pylint: disable=unused-argument
def state_automation_listener(entity, from_s, to_s):
"""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))):
action()
if to_s is None:
return
variables = {
'trigger': {
'platform': 'numeric_state',
'entity_id': entity,
'below': below,
'above': above,
}
}
# If new one doesn't match, nothing to do
if not condition.numeric_state(
hass, to_s, below, above, value_template, variables):
return
# Only match if old didn't exist or existed but didn't match
# Written as: skip if old one did exist and matched
if from_s is not None and condition.numeric_state(
hass, from_s, below, above, value_template, variables):
return
variables['trigger']['from_state'] = from_s
variables['trigger']['to_state'] = to_s
action(variables)
track_state_change(
hass, entity_id, state_automation_listener)
return True
def if_action(hass, config):
"""Wrap action method with state based condition."""
entity_id = config.get(CONF_ENTITY_ID)
if entity_id is None:
_LOGGER.error("Missing configuration key %s", CONF_ENTITY_ID)
return None
below = config.get(CONF_BELOW)
above = config.get(CONF_ABOVE)
value_template = config.get(CONF_VALUE_TEMPLATE)
if below is None and above is None:
_LOGGER.error("Missing configuration key."
" One of %s or %s is required",
CONF_BELOW, CONF_ABOVE)
return None
renderer = partial(_renderer, hass, value_template)
def if_numeric_state():
"""Test numeric state condition."""
state = hass.states.get(entity_id)
return state is not None and _in_range(above, below, renderer(state))
return if_numeric_state
def _in_range(range_start, range_end, value):
"""Check if value is inside the range."""
try:
value = float(value)
except ValueError:
_LOGGER.warning("Value returned from template is not a number: %s",
value)
return False
if range_start is not None and range_end is not None:
return float(range_start) <= value < float(range_end)
elif range_end is not None:
return value < float(range_end)
else:
return float(range_start) <= value

View File

@@ -4,15 +4,11 @@ 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
"""
from datetime import timedelta
import voluptuous as vol
import homeassistant.util.dt as dt_util
from homeassistant.const import (
EVENT_STATE_CHANGED, EVENT_TIME_CHANGED, MATCH_ALL, CONF_PLATFORM)
from homeassistant.components.automation.time import (
CONF_HOURS, CONF_MINUTES, CONF_SECONDS)
from homeassistant.helpers.event import track_state_change, track_point_in_time
import homeassistant.helpers.config_validation as cv
@@ -22,46 +18,19 @@ CONF_TO = "to"
CONF_STATE = "state"
CONF_FOR = "for"
BASE_SCHEMA = vol.Schema({
vol.Required(CONF_PLATFORM): 'state',
vol.Required(CONF_ENTITY_ID): cv.entity_id,
# These are str on purpose. Want to catch YAML conversions
CONF_STATE: str,
CONF_FOR: vol.All(vol.Schema({
CONF_HOURS: vol.Coerce(int),
CONF_MINUTES: vol.Coerce(int),
CONF_SECONDS: vol.Coerce(int),
}), cv.has_at_least_one_key(CONF_HOURS, CONF_MINUTES, CONF_SECONDS)),
})
TRIGGER_SCHEMA = vol.Schema(vol.All(
BASE_SCHEMA.extend({
TRIGGER_SCHEMA = vol.All(
vol.Schema({
vol.Required(CONF_PLATFORM): 'state',
vol.Required(CONF_ENTITY_ID): cv.entity_ids,
# These are str on purpose. Want to catch YAML conversions
CONF_FROM: str,
CONF_TO: str,
CONF_STATE: str,
CONF_FOR: vol.All(cv.time_period, cv.positive_timedelta),
}),
vol.Any(cv.key_dependency(CONF_FOR, CONF_TO),
cv.key_dependency(CONF_FOR, CONF_STATE))
))
IF_ACTION_SCHEMA = vol.Schema(vol.All(
BASE_SCHEMA,
cv.key_dependency(CONF_FOR, CONF_STATE)
))
def get_time_config(config):
"""Helper function to extract the time specified in the configuration."""
if CONF_FOR not in config:
return None
hours = config[CONF_FOR].get(CONF_HOURS)
minutes = config[CONF_FOR].get(CONF_MINUTES)
seconds = config[CONF_FOR].get(CONF_SECONDS)
return timedelta(hours=(hours or 0.0),
minutes=(minutes or 0.0),
seconds=(seconds or 0.0))
)
def trigger(hass, config, action):
@@ -69,52 +38,48 @@ def trigger(hass, config, action):
entity_id = config.get(CONF_ENTITY_ID)
from_state = config.get(CONF_FROM, MATCH_ALL)
to_state = config.get(CONF_TO) or config.get(CONF_STATE) or MATCH_ALL
time_delta = get_time_config(config)
time_delta = config.get(CONF_FOR)
def state_automation_listener(entity, from_s, to_s):
"""Listen for state changes and calls action."""
def call_action():
"""Call action with right context."""
action({
'trigger': {
'platform': 'state',
'entity_id': entity,
'from_state': from_s,
'to_state': to_s,
'for': time_delta,
}
})
if time_delta is None:
call_action()
return
def state_for_listener(now):
"""Fire on state changes after a delay and calls action."""
hass.bus.remove_listener(
EVENT_STATE_CHANGED, for_state_listener)
action()
EVENT_STATE_CHANGED, attached_state_for_cancel)
call_action()
def state_for_cancel_listener(entity, inner_from_s, inner_to_s):
"""Fire on changes and cancel for listener if changed."""
if inner_to_s == to_s:
if inner_to_s.state == to_s.state:
return
hass.bus.remove_listener(EVENT_TIME_CHANGED, for_time_listener)
hass.bus.remove_listener(
EVENT_STATE_CHANGED, for_state_listener)
hass.bus.remove_listener(EVENT_TIME_CHANGED,
attached_state_for_listener)
hass.bus.remove_listener(EVENT_STATE_CHANGED,
attached_state_for_cancel)
if time_delta is not None:
target_tm = dt_util.utcnow() + time_delta
for_time_listener = track_point_in_time(
hass, state_for_listener, target_tm)
for_state_listener = track_state_change(
hass, entity_id, state_for_cancel_listener,
MATCH_ALL, MATCH_ALL)
else:
action()
attached_state_for_listener = track_point_in_time(
hass, state_for_listener, dt_util.utcnow() + time_delta)
attached_state_for_cancel = track_state_change(
hass, entity, state_for_cancel_listener)
track_state_change(
hass, entity_id, state_automation_listener, from_state, to_state)
return True
def if_action(hass, config):
"""Wrap action method with state based condition."""
entity_id = config.get(CONF_ENTITY_ID)
state = config.get(CONF_STATE)
time_delta = get_time_config(config)
def if_state():
"""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
dt_util.utcnow() - time_delta >
hass.states.get(entity_id).last_changed)
return if_state

View File

@@ -9,108 +9,41 @@ import logging
import voluptuous as vol
from homeassistant.const import CONF_PLATFORM
import homeassistant.util.dt as dt_util
from homeassistant.components import sun
from homeassistant.const import (
CONF_EVENT, CONF_OFFSET, CONF_PLATFORM, SUN_EVENT_SUNRISE)
from homeassistant.helpers.event import track_sunrise, track_sunset
import homeassistant.helpers.config_validation as cv
DEPENDENCIES = ['sun']
CONF_OFFSET = 'offset'
CONF_EVENT = 'event'
CONF_BEFORE = "before"
CONF_BEFORE_OFFSET = "before_offset"
CONF_AFTER = "after"
CONF_AFTER_OFFSET = "after_offset"
EVENT_SUNSET = 'sunset'
EVENT_SUNRISE = 'sunrise'
_LOGGER = logging.getLogger(__name__)
_SUN_EVENT = vol.All(vol.Lower, vol.Any(EVENT_SUNRISE, EVENT_SUNSET))
TRIGGER_SCHEMA = vol.Schema({
vol.Required(CONF_PLATFORM): 'sun',
vol.Required(CONF_EVENT): _SUN_EVENT,
vol.Required(CONF_OFFSET, default=timedelta(0)): cv.time_offset,
vol.Required(CONF_EVENT): cv.sun_event,
vol.Required(CONF_OFFSET, default=timedelta(0)): cv.time_period,
})
IF_ACTION_SCHEMA = vol.All(
vol.Schema({
vol.Required(CONF_PLATFORM): 'sun',
CONF_BEFORE: _SUN_EVENT,
CONF_AFTER: _SUN_EVENT,
vol.Required(CONF_BEFORE_OFFSET, default=timedelta(0)): cv.time_offset,
vol.Required(CONF_AFTER_OFFSET, default=timedelta(0)): cv.time_offset,
}),
cv.has_at_least_one_key(CONF_BEFORE, CONF_AFTER),
)
def trigger(hass, config, action):
"""Listen for events based on configuration."""
event = config.get(CONF_EVENT)
offset = config.get(CONF_OFFSET)
def call_action():
"""Call action with right context."""
action({
'trigger': {
'platform': 'sun',
'event': event,
'offset': offset,
},
})
# Do something to call action
if event == EVENT_SUNRISE:
track_sunrise(hass, action, offset)
if event == SUN_EVENT_SUNRISE:
track_sunrise(hass, call_action, offset)
else:
track_sunset(hass, action, offset)
track_sunset(hass, call_action, offset)
return True
def if_action(hass, config):
"""Wrap action method with sun based condition."""
before = config.get(CONF_BEFORE)
after = config.get(CONF_AFTER)
before_offset = config.get(CONF_BEFORE_OFFSET)
after_offset = config.get(CONF_AFTER_OFFSET)
if before is None:
def before_func():
"""Return no point in time."""
return None
elif before == EVENT_SUNRISE:
def before_func():
"""Return time before sunrise."""
return sun.next_rising(hass) + before_offset
else:
def before_func():
"""Return time before sunset."""
return sun.next_setting(hass) + before_offset
if after is None:
def after_func():
"""Return no point in time."""
return None
elif after == EVENT_SUNRISE:
def after_func():
"""Return time after sunrise."""
return sun.next_rising(hass) + after_offset
else:
def after_func():
"""Return time after sunset."""
return sun.next_setting(hass) + after_offset
def time_if():
"""Validate time based if-condition."""
now = dt_util.now()
before = before_func()
after = after_func()
if before is not None and now > now.replace(hour=before.hour,
minute=before.minute):
return False
if after is not None and now < now.replace(hour=after.hour,
minute=after.minute):
return False
return True
return time_if

View File

@@ -9,9 +9,9 @@ import logging
import voluptuous as vol
from homeassistant.const import (
CONF_VALUE_TEMPLATE, EVENT_STATE_CHANGED, CONF_PLATFORM)
from homeassistant.exceptions import TemplateError
from homeassistant.helpers import template
CONF_VALUE_TEMPLATE, CONF_PLATFORM, MATCH_ALL)
from homeassistant.helpers import condition
from homeassistant.helpers.event import track_state_change
import homeassistant.helpers.config_validation as cv
@@ -30,40 +30,24 @@ def trigger(hass, config, action):
# Local variable to keep track of if the action has already been triggered
already_triggered = False
def event_listener(event):
def state_changed_listener(entity_id, from_s, to_s):
"""Listen for state changes and calls action."""
nonlocal already_triggered
template_result = _check_template(hass, value_template)
template_result = condition.template(hass, value_template)
# Check to see if template returns true
if template_result and not already_triggered:
already_triggered = True
action()
action({
'trigger': {
'platform': 'template',
'entity_id': entity_id,
'from_state': from_s,
'to_state': to_s,
},
})
elif not template_result:
already_triggered = False
hass.bus.listen(EVENT_STATE_CHANGED, event_listener)
track_state_change(hass, MATCH_ALL, state_changed_listener)
return True
def if_action(hass, config):
"""Wrap action method with state based condition."""
value_template = config.get(CONF_VALUE_TEMPLATE)
return lambda: _check_template(hass, value_template)
def _check_template(hass, value_template):
"""Check if result of template is true."""
try:
value = template.render(hass, value_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'

View File

@@ -6,99 +6,48 @@ at https://home-assistant.io/components/automation/#time-trigger
"""
import logging
import homeassistant.util.dt as dt_util
import voluptuous as vol
from homeassistant.const import CONF_AFTER, CONF_PLATFORM
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.event import track_time_change
CONF_HOURS = "hours"
CONF_MINUTES = "minutes"
CONF_SECONDS = "seconds"
CONF_BEFORE = "before"
CONF_AFTER = "after"
CONF_WEEKDAY = "weekday"
WEEKDAYS = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']
_LOGGER = logging.getLogger(__name__)
TRIGGER_SCHEMA = vol.All(vol.Schema({
vol.Required(CONF_PLATFORM): 'time',
CONF_AFTER: cv.time,
CONF_HOURS: vol.Any(vol.Coerce(int), vol.Coerce(str)),
CONF_MINUTES: vol.Any(vol.Coerce(int), vol.Coerce(str)),
CONF_SECONDS: vol.Any(vol.Coerce(int), vol.Coerce(str)),
}), cv.has_at_least_one_key(CONF_HOURS, CONF_MINUTES,
CONF_SECONDS, CONF_AFTER))
def trigger(hass, config, action):
"""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:
_error_time(config[CONF_AFTER], CONF_AFTER)
return False
after = config.get(CONF_AFTER)
hours, minutes, seconds = after.hour, after.minute, after.second
elif (CONF_HOURS in config or CONF_MINUTES in config or
CONF_SECONDS in config):
else:
hours = config.get(CONF_HOURS)
minutes = config.get(CONF_MINUTES)
seconds = config.get(CONF_SECONDS)
else:
_LOGGER.error('One of %s, %s, %s OR %s needs to be specified',
CONF_HOURS, CONF_MINUTES, CONF_SECONDS, CONF_AFTER)
return False
def time_automation_listener(now):
"""Listen for time changes and calls action."""
action()
action({
'trigger': {
'platform': 'time',
'now': now,
},
})
track_time_change(hass, time_automation_listener,
hour=hours, minute=minutes, second=seconds)
return True
def if_action(hass, config):
"""Wrap action method with time based condition."""
before = config.get(CONF_BEFORE)
after = config.get(CONF_AFTER)
weekday = config.get(CONF_WEEKDAY)
if before is None and after is None and weekday is None:
_LOGGER.error(
"Missing if-condition configuration key %s, %s or %s",
CONF_BEFORE, CONF_AFTER, CONF_WEEKDAY)
return None
if before is not None:
before = dt_util.parse_time_str(before)
if before is None:
_error_time(before, CONF_BEFORE)
return None
if after is not None:
after = dt_util.parse_time_str(after)
if after is None:
_error_time(after, CONF_AFTER)
return None
def time_if():
"""Validate time based if-condition."""
now = dt_util.now()
if before is not None and now > now.replace(hour=before.hour,
minute=before.minute):
return False
if after is not None and now < now.replace(hour=after.hour,
minute=after.minute):
return False
if weekday is not None:
now_weekday = WEEKDAYS[now.weekday()]
if isinstance(weekday, str) and weekday != now_weekday or \
now_weekday not in weekday:
return False
return True
return time_if
def _error_time(value, key):
"""Helper method to print error."""
_LOGGER.error(
"Received invalid value for '%s': %s", key, value)
if isinstance(value, int):
_LOGGER.error('Make sure you wrap time values in quotes')

View File

@@ -6,33 +6,24 @@ at https://home-assistant.io/components/automation/#zone-trigger
"""
import voluptuous as vol
from homeassistant.components import zone
from homeassistant.const import (
ATTR_GPS_ACCURACY, ATTR_LATITUDE, ATTR_LONGITUDE, MATCH_ALL, CONF_PLATFORM)
CONF_EVENT, CONF_ENTITY_ID, CONF_ZONE, MATCH_ALL, CONF_PLATFORM)
from homeassistant.helpers.event import track_state_change
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers import (
condition, config_validation as cv, location)
CONF_ENTITY_ID = "entity_id"
CONF_ZONE = "zone"
CONF_EVENT = "event"
EVENT_ENTER = "enter"
EVENT_LEAVE = "leave"
DEFAULT_EVENT = EVENT_ENTER
TRIGGER_SCHEMA = vol.Schema({
vol.Required(CONF_PLATFORM): 'zone',
vol.Required(CONF_ENTITY_ID): cv.entity_id,
vol.Required(CONF_ENTITY_ID): cv.entity_ids,
vol.Required(CONF_ZONE): cv.entity_id,
vol.Required(CONF_EVENT, default=DEFAULT_EVENT):
vol.Any(EVENT_ENTER, EVENT_LEAVE),
})
IF_ACTION_SCHEMA = vol.Schema({
vol.Required(CONF_PLATFORM): 'zone',
vol.Required(CONF_ENTITY_ID): cv.entity_id,
vol.Required(CONF_ZONE): cv.entity_id,
})
def trigger(hass, config, action):
"""Listen for state changes based on configuration."""
@@ -42,46 +33,32 @@ def trigger(hass, config, action):
def zone_automation_listener(entity, from_s, to_s):
"""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),
to_s.attributes.get(ATTR_LONGITUDE)):
if from_s and not location.has_location(from_s) or \
not location.has_location(to_s):
return
from_match = _in_zone(hass, zone_entity_id, from_s) if from_s else None
to_match = _in_zone(hass, zone_entity_id, to_s)
zone_state = hass.states.get(zone_entity_id)
if from_s:
from_match = condition.zone(hass, zone_state, from_s)
else:
from_match = False
to_match = condition.zone(hass, zone_state, to_s)
# pylint: disable=too-many-boolean-expressions
if event == EVENT_ENTER and not from_match and to_match or \
event == EVENT_LEAVE and from_match and not to_match:
action()
action({
'trigger': {
'platform': 'zone',
'entity_id': entity,
'from_state': from_s,
'to_state': to_s,
'zone': zone_state,
'event': event,
},
})
track_state_change(
hass, entity_id, zone_automation_listener, MATCH_ALL, MATCH_ALL)
return True
def if_action(hass, config):
"""Wrap action method with zone based condition."""
entity_id = config.get(CONF_ENTITY_ID)
zone_entity_id = config.get(CONF_ZONE)
def if_in_zone():
"""Test if condition."""
return _in_zone(hass, zone_entity_id, hass.states.get(entity_id))
return if_in_zone
def _in_zone(hass, zone_entity_id, state):
"""Check if state is in zone."""
if not state or None in (state.attributes.get(ATTR_LATITUDE),
state.attributes.get(ATTR_LONGITUDE)):
return False
zone_state = hass.states.get(zone_entity_id)
return zone_state and zone.in_zone(
zone_state, state.attributes.get(ATTR_LATITUDE),
state.attributes.get(ATTR_LONGITUDE),
state.attributes.get(ATTR_GPS_ACCURACY, 0))

View File

@@ -9,8 +9,6 @@ import logging
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity import Entity
from homeassistant.const import (STATE_ON, STATE_OFF)
from homeassistant.components import (
bloomsky, mysensors, zwave, vera, wemo, wink)
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
DOMAIN = 'binary_sensor'
@@ -35,22 +33,11 @@ SENSOR_CLASSES = [
'vibration', # On means vibration detected, Off means no vibration
]
# Maps discovered services to their platforms
DISCOVERY_PLATFORMS = {
bloomsky.DISCOVER_BINARY_SENSORS: 'bloomsky',
mysensors.DISCOVER_BINARY_SENSORS: 'mysensors',
zwave.DISCOVER_BINARY_SENSORS: 'zwave',
vera.DISCOVER_BINARY_SENSORS: 'vera',
wemo.DISCOVER_BINARY_SENSORS: 'wemo',
wink.DISCOVER_BINARY_SENSORS: 'wink'
}
def setup(hass, config):
"""Track states and offer events for binary sensors."""
component = EntityComponent(
logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL,
DISCOVERY_PLATFORMS)
logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL)
component.setup(config)

View File

@@ -1,13 +1,14 @@
"""
Support for custom shell commands to to retrieve values.
Support for custom shell commands to retrieve values.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.command/
https://home-assistant.io/components/binary_sensor.command_line/
"""
import logging
from datetime import timedelta
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.binary_sensor import (BinarySensorDevice,
SENSOR_CLASSES)
from homeassistant.components.sensor.command_line import CommandSensorData
from homeassistant.const import CONF_VALUE_TEMPLATE
from homeassistant.helpers import template
@@ -15,6 +16,7 @@ from homeassistant.helpers import template
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = "Binary Command Sensor"
DEFAULT_SENSOR_CLASS = None
DEFAULT_PAYLOAD_ON = 'ON'
DEFAULT_PAYLOAD_OFF = 'OFF'
@@ -29,28 +31,35 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
_LOGGER.error('Missing required variable: "command"')
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 = DEFAULT_SENSOR_CLASS
data = CommandSensorData(config.get('command'))
add_devices([CommandBinarySensor(
hass,
data,
config.get('name', DEFAULT_NAME),
sensor_class,
config.get('payload_on', DEFAULT_PAYLOAD_ON),
config.get('payload_off', DEFAULT_PAYLOAD_OFF),
config.get(CONF_VALUE_TEMPLATE)
)])
# pylint: disable=too-many-arguments
# pylint: disable=too-many-arguments, too-many-instance-attributes
class CommandBinarySensor(BinarySensorDevice):
"""Represent a command line binary sensor."""
def __init__(self, hass, data, name, payload_on,
def __init__(self, hass, data, name, sensor_class, payload_on,
payload_off, value_template):
"""Initialize the Command line binary sensor."""
self._hass = hass
self.data = data
self._name = name
self._sensor_class = sensor_class
self._state = False
self._payload_on = payload_on
self._payload_off = payload_off
@@ -67,6 +76,11 @@ class CommandBinarySensor(BinarySensorDevice):
"""Return true if the binary sensor is on."""
return self._state
@ property
def sensor_class(self):
"""Return the class of the binary sensor."""
return self._sensor_class
def update(self):
"""Get the latest data and updates the state."""
self.data.update()

View File

@@ -0,0 +1,63 @@
"""
Support for EnOcean binary sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.enocean/
"""
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components import enocean
from homeassistant.const import CONF_NAME
DEPENDENCIES = ["enocean"]
CONF_ID = "id"
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Binary Sensor platform fo EnOcean."""
dev_id = config.get(CONF_ID, None)
devname = config.get(CONF_NAME, "EnOcean binary sensor")
add_devices([EnOceanBinarySensor(dev_id, devname)])
class EnOceanBinarySensor(enocean.EnOceanDevice, BinarySensorDevice):
"""Representation of EnOcean binary sensors such as wall switches."""
def __init__(self, dev_id, devname):
"""Initialize the EnOcean binary sensor."""
enocean.EnOceanDevice.__init__(self)
self.stype = "listener"
self.dev_id = dev_id
self.which = -1
self.onoff = -1
self.devname = devname
@property
def name(self):
"""The default name for the binary sensor."""
return self.devname
def value_changed(self, value, value2):
"""Fire an event with the data that have changed.
This method is called when there is an incoming packet associated
with this platform.
"""
self.update_ha_state()
if value2 == 0x70:
self.which = 0
self.onoff = 0
elif value2 == 0x50:
self.which = 0
self.onoff = 1
elif value2 == 0x30:
self.which = 1
self.onoff = 0
elif value2 == 0x10:
self.which = 1
self.onoff = 1
self.hass.bus.fire('button_pressed', {"id": self.dev_id,
'pushed': value,
'which': self.which,
'onoff': self.onoff})

View File

@@ -0,0 +1,71 @@
"""
Support for Envisalink zone states- represented as binary sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.envisalink/
"""
import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.envisalink import (EVL_CONTROLLER,
ZONE_SCHEMA,
CONF_ZONENAME,
CONF_ZONETYPE,
EnvisalinkDevice,
SIGNAL_ZONE_UPDATE)
from homeassistant.const import ATTR_LAST_TRIP_TIME
DEPENDENCIES = ['envisalink']
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Setup Envisalink binary sensor devices."""
_configured_zones = discovery_info['zones']
for zone_num in _configured_zones:
_device_config_data = ZONE_SCHEMA(_configured_zones[zone_num])
_device = EnvisalinkBinarySensor(
zone_num,
_device_config_data[CONF_ZONENAME],
_device_config_data[CONF_ZONETYPE],
EVL_CONTROLLER.alarm_state['zone'][zone_num],
EVL_CONTROLLER)
add_devices_callback([_device])
class EnvisalinkBinarySensor(EnvisalinkDevice, BinarySensorDevice):
"""Representation of an Envisalink binary sensor."""
# pylint: disable=too-many-arguments
def __init__(self, zone_number, zone_name, zone_type, info, controller):
"""Initialize the binary_sensor."""
from pydispatch import dispatcher
self._zone_type = zone_type
self._zone_number = zone_number
_LOGGER.debug('Setting up zone: ' + zone_name)
EnvisalinkDevice.__init__(self, zone_name, info, controller)
dispatcher.connect(self._update_callback,
signal=SIGNAL_ZONE_UPDATE,
sender=dispatcher.Any)
@property
def device_state_attributes(self):
"""Return the state attributes."""
attr = {}
attr[ATTR_LAST_TRIP_TIME] = self._info['last_fault']
return attr
@property
def is_on(self):
"""Return true if sensor is on."""
return self._info['status']['open']
@property
def sensor_class(self):
"""Return the class of this sensor, from SENSOR_CLASSES."""
return self._zone_type
def _update_callback(self, zone):
"""Update the zone's state, if needed."""
if zone is None or int(zone) == self._zone_number:
self.update_ha_state()

View File

@@ -0,0 +1,100 @@
"""
Support for Homematic binary sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.homematic/
"""
import logging
from homeassistant.const import STATE_UNKNOWN
from homeassistant.components.binary_sensor import BinarySensorDevice
import homeassistant.components.homematic as homematic
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['homematic']
SENSOR_TYPES_CLASS = {
"Remote": None,
"ShutterContact": "opening",
"Smoke": "smoke",
"SmokeV2": "smoke",
"Motion": "motion",
"MotionV2": "motion",
"RemoteMotion": None
}
def setup_platform(hass, config, add_callback_devices, discovery_info=None):
"""Setup the Homematic binary sensor platform."""
if discovery_info is None:
return
return homematic.setup_hmdevice_discovery_helper(HMBinarySensor,
discovery_info,
add_callback_devices)
class HMBinarySensor(homematic.HMDevice, BinarySensorDevice):
"""Representation of a binary Homematic device."""
@property
def is_on(self):
"""Return true if switch is on."""
if not self.available:
return False
return bool(self._hm_get_state())
@property
def sensor_class(self):
"""Return the class of this sensor, from SENSOR_CLASSES."""
if not self.available:
return None
# If state is MOTION (RemoteMotion works only)
if self._state == "MOTION":
return "motion"
return SENSOR_TYPES_CLASS.get(self._hmdevice.__class__.__name__, None)
def _check_hm_to_ha_object(self):
"""Check if possible to use the HM Object as this HA type."""
from pyhomematic.devicetypes.sensors import HMBinarySensor\
as pyHMBinarySensor
# Check compatibility from HMDevice
if not super()._check_hm_to_ha_object():
return False
# check if the Homematic device correct for this HA device
if not isinstance(self._hmdevice, pyHMBinarySensor):
_LOGGER.critical("This %s can't be use as binary", self._name)
return False
# if exists user value?
if self._state and self._state not in self._hmdevice.BINARYNODE:
_LOGGER.critical("This %s have no binary with %s", self._name,
self._state)
return False
# only check and give a warning to the user
if self._state is None and len(self._hmdevice.BINARYNODE) > 1:
_LOGGER.critical("%s have multiple binary params. It use all "
"binary nodes as one. Possible param values: %s",
self._name, str(self._hmdevice.BINARYNODE))
return False
return True
def _init_data_struct(self):
"""Generate a data struct (self._data) from the Homematic metadata."""
super()._init_data_struct()
# object have 1 binary
if self._state is None and len(self._hmdevice.BINARYNODE) == 1:
for value in self._hmdevice.BINARYNODE:
self._state = value
# add state to data struct
if self._state:
_LOGGER.debug("%s init datastruct with main node '%s'", self._name,
self._state)
self._data.update({self._state: STATE_UNKNOWN})

View File

@@ -0,0 +1,24 @@
"""
Contains functionality to use a KNX group address as a binary.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.knx/
"""
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.knx import (
KNXConfig, KNXGroupAddress)
DEPENDENCIES = ["knx"]
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Setup the KNX binary sensor platform."""
add_entities([
KNXSwitch(hass, KNXConfig(config))
])
class KNXSwitch(KNXGroupAddress, BinarySensorDevice):
"""Representation of a KNX binary sensor device."""
pass

View File

@@ -6,11 +6,10 @@ https://home-assistant.io/components/binary_sensor.mysensors/
"""
import logging
from homeassistant.const import (
ATTR_BATTERY_LEVEL, STATE_OFF, STATE_ON)
from homeassistant.components.binary_sensor import (
BinarySensorDevice, SENSOR_CLASSES)
from homeassistant.loader import get_component
from homeassistant.components import mysensors
from homeassistant.components.binary_sensor import (SENSOR_CLASSES,
BinarySensorDevice)
from homeassistant.const import STATE_ON
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = []
@@ -23,8 +22,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
if discovery_info is None:
return
mysensors = get_component('mysensors')
for gateway in mysensors.GATEWAYS.values():
# Define the S_TYPES and V_TYPES that the platform should handle as
# states. Map them in a dict of lists.
@@ -49,76 +46,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
map_sv_types, devices, add_devices, MySensorsBinarySensor))
class MySensorsBinarySensor(BinarySensorDevice):
"""Represent the value of a MySensors child node."""
# pylint: disable=too-many-arguments,too-many-instance-attributes
def __init__(
self, gateway, node_id, child_id, name, value_type, child_type):
"""
Setup class attributes on instantiation.
Args:
gateway (GatewayWrapper): Gateway object.
node_id (str): Id of node.
child_id (str): Id of child.
name (str): Entity name.
value_type (str): Value type of child. Value is entity state.
child_type (str): Child type of child.
Attributes:
gateway (GatewayWrapper): Gateway object.
node_id (str): Id of node.
child_id (str): Id of child.
_name (str): Entity name.
value_type (str): Value type of child. Value is entity state.
child_type (str): Child type of child.
battery_level (int): Node battery level.
_values (dict): Child values. Non state values set as state attributes.
mysensors (module): Mysensors main component module.
"""
self.gateway = gateway
self.node_id = node_id
self.child_id = child_id
self._name = name
self.value_type = value_type
self.child_type = child_type
self.battery_level = 0
self._values = {}
self.mysensors = get_component('mysensors')
@property
def should_poll(self):
"""Mysensor gateway pushes its state to HA."""
return False
@property
def name(self):
"""The name of this entity."""
return self._name
@property
def device_state_attributes(self):
"""Return device specific state attributes."""
attr = {
self.mysensors.ATTR_PORT: self.gateway.port,
self.mysensors.ATTR_NODE_ID: self.node_id,
self.mysensors.ATTR_CHILD_ID: self.child_id,
ATTR_BATTERY_LEVEL: self.battery_level,
}
set_req = self.gateway.const.SetReq
for value_type, value in self._values.items():
if value_type != self.value_type:
try:
attr[set_req(value_type).name] = value
except ValueError:
_LOGGER.error('value_type %s is not valid for mysensors '
'version %s', value_type,
self.gateway.version)
return attr
class MySensorsBinarySensor(
mysensors.MySensorsDeviceEntity, BinarySensorDevice):
"""Represent the value of a MySensors Binary Sensor child node."""
@property
def is_on(self):
@@ -146,23 +76,3 @@ class MySensorsBinarySensor(BinarySensorDevice):
})
if class_map.get(self.child_type) in SENSOR_CLASSES:
return class_map.get(self.child_type)
@property
def available(self):
"""Return True if entity is available."""
return self.value_type in self._values
def update(self):
"""Update the controller with the latest values from a sensor."""
node = self.gateway.sensors[self.node_id]
child = node.children[self.child_id]
for value_type, value in child.values.items():
_LOGGER.debug(
"%s: value_type %s, value = %s", self._name, value_type, value)
if value_type == self.gateway.const.SetReq.V_TRIPPED:
self._values[value_type] = STATE_ON if int(
value) == 1 else STATE_OFF
else:
self._values[value_type] = value
self.battery_level = node.battery_level

View File

@@ -4,12 +4,14 @@ Support for Nest Thermostat Binary Sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.nest/
"""
import logging
import socket
import voluptuous as vol
import homeassistant.components.nest as nest
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.sensor.nest import NestSensor
from homeassistant.const import (
CONF_PLATFORM, CONF_SCAN_INTERVAL, CONF_MONITORED_CONDITIONS
)
DEPENDENCIES = ['nest']
BINARY_TYPES = ['fan',
@@ -23,25 +25,19 @@ BINARY_TYPES = ['fan',
'hvac_emer_heat_state',
'online']
PLATFORM_SCHEMA = vol.Schema({
vol.Required(CONF_PLATFORM): nest.DOMAIN,
vol.Optional(CONF_SCAN_INTERVAL):
vol.All(vol.Coerce(int), vol.Range(min=1)),
vol.Required(CONF_MONITORED_CONDITIONS): [vol.In(BINARY_TYPES)],
})
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:
for device in structure.devices:
for variable in config['monitored_conditions']:
if variable in BINARY_TYPES:
add_devices([NestBinarySensor(structure,
device,
variable)])
else:
logger.error('Nest sensor type: "%s" does not exist',
variable)
except socket.error:
logger.error(
"Connection error logging into the nest web service."
)
for structure, device in nest.devices():
add_devices([NestBinarySensor(structure, device, variable)
for variable in config[CONF_MONITORED_CONDITIONS]])
class NestBinarySensor(NestSensor, BinarySensorDevice):

View File

@@ -18,7 +18,7 @@ _LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup nx584 sensors."""
"""Setup nx584 binary sensor platform."""
from nx584 import client as nx584_client
host = config.get('host', 'localhost:5007')

View File

@@ -0,0 +1,109 @@
"""
Support for monitoring OctoPrint binary sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.octoprint/
"""
import logging
import requests
from homeassistant.const import CONF_NAME, STATE_ON, STATE_OFF
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.loader import get_component
DEPENDENCIES = ["octoprint"]
SENSOR_TYPES = {
# API Endpoint, Group, Key, unit
"Printing": ["printer", "state", "printing", None],
"Printing Error": ["printer", "state", "error", None]
}
_LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the available OctoPrint binary sensors."""
octoprint = get_component('octoprint')
name = config.get(CONF_NAME, "OctoPrint")
monitored_conditions = config.get("monitored_conditions",
SENSOR_TYPES.keys())
devices = []
for octo_type in monitored_conditions:
if octo_type in SENSOR_TYPES:
new_sensor = OctoPrintBinarySensor(octoprint.OCTOPRINT,
octo_type,
SENSOR_TYPES[octo_type][2],
name,
SENSOR_TYPES[octo_type][3],
SENSOR_TYPES[octo_type][0],
SENSOR_TYPES[octo_type][1],
"flags")
devices.append(new_sensor)
else:
_LOGGER.error("Unknown OctoPrint sensor type: %s", octo_type)
add_devices(devices)
# pylint: disable=too-many-instance-attributes
class OctoPrintBinarySensor(BinarySensorDevice):
"""Representation an OctoPrint binary sensor."""
# pylint: disable=too-many-arguments
def __init__(self, api, condition, sensor_type, sensor_name,
unit, endpoint, group, tool=None):
"""Initialize a new OctoPrint sensor."""
self.sensor_name = sensor_name
if tool is None:
self._name = sensor_name + ' ' + condition
else:
self._name = sensor_name + ' ' + condition
self.sensor_type = sensor_type
self.api = api
self._state = False
self._unit_of_measurement = unit
self.api_endpoint = endpoint
self.api_group = group
self.api_tool = tool
# Set initial state
self.update()
_LOGGER.debug("Created OctoPrint binary sensor %r", self)
@property
def name(self):
"""Return the name of the sensor."""
return self._name
@property
def state(self):
"""Return the state of the sensor."""
return self.is_on
@property
def is_on(self):
"""Return true if binary sensor is on."""
if self._state:
return STATE_ON
else:
return STATE_OFF
@property
def sensor_class(self):
"""Return the class of this sensor, from SENSOR_CLASSES."""
return None
def update(self):
"""Update state of sensor."""
try:
self._state = self.api.update(self.sensor_type,
self.api_endpoint,
self.api_group,
self.api_tool)
except requests.exceptions.ConnectionError:
# Error calling the api, already logged in api.update()
return
if self._state is None:
_LOGGER.warning("Unable to locate value for %s", self.sensor_type)

View File

@@ -35,7 +35,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
rest.update()
if rest.data is None:
_LOGGER.error('Unable to fetch Rest data')
_LOGGER.error('Unable to fetch REST data')
return False
add_devices([RestBinarySensor(
@@ -57,6 +57,7 @@ class RestBinarySensor(BinarySensorDevice):
self._name = name
self._sensor_class = sensor_class
self._state = False
self._previous_data = None
self._value_template = value_template
self.update()
@@ -77,9 +78,14 @@ class RestBinarySensor(BinarySensorDevice):
return False
if self._value_template is not None:
self.rest.data = template.render_with_possible_json_value(
response = template.render_with_possible_json_value(
self._hass, self._value_template, self.rest.data, False)
return bool(int(self.rest.data))
try:
return bool(int(response))
except ValueError:
return {"true": True, "on": True, "open": True,
"yes": True}.get(response.lower(), False)
def update(self):
"""Get the latest data from REST API and updates the state."""

View File

@@ -9,11 +9,12 @@ import logging
from homeassistant.components.binary_sensor import (BinarySensorDevice,
ENTITY_ID_FORMAT,
SENSOR_CLASSES)
from homeassistant.const import ATTR_FRIENDLY_NAME, CONF_VALUE_TEMPLATE
from homeassistant.core import EVENT_STATE_CHANGED
from homeassistant.const import (ATTR_FRIENDLY_NAME, CONF_VALUE_TEMPLATE,
ATTR_ENTITY_ID, MATCH_ALL)
from homeassistant.exceptions import TemplateError
from homeassistant.helpers.entity import generate_entity_id
from homeassistant.helpers import template
from homeassistant.helpers.event import track_state_change
from homeassistant.util import slugify
CONF_SENSORS = 'sensors'
@@ -52,13 +53,16 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
'Missing %s for sensor %s', CONF_VALUE_TEMPLATE, device)
continue
entity_ids = device_config.get(ATTR_ENTITY_ID, MATCH_ALL)
sensors.append(
BinarySensorTemplate(
hass,
device,
friendly_name,
sensor_class,
value_template)
value_template,
entity_ids)
)
if not sensors:
_LOGGER.error('No sensors added')
@@ -73,7 +77,7 @@ class BinarySensorTemplate(BinarySensorDevice):
# pylint: disable=too-many-arguments
def __init__(self, hass, device, friendly_name, sensor_class,
value_template):
value_template, entity_ids):
"""Initialize the Template binary sensor."""
self.hass = hass
self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, device,
@@ -85,12 +89,12 @@ class BinarySensorTemplate(BinarySensorDevice):
self.update()
def template_bsensor_event_listener(event):
def template_bsensor_state_listener(entity, old_state, new_state):
"""Called when the target device changes state."""
self.update_ha_state(True)
hass.bus.listen(EVENT_STATE_CHANGED,
template_bsensor_event_listener)
track_state_change(hass, entity_ids,
template_bsensor_state_listener)
@property
def name(self):

View File

@@ -6,9 +6,6 @@ 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 (
@@ -34,31 +31,6 @@ class VeraBinarySensor(VeraDevice, BinarySensorDevice):
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."""

View File

@@ -5,24 +5,28 @@ For more details about this platform, please refer to the documentation at
at https://home-assistant.io/components/sensor.wink/
"""
import logging
import json
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.sensor.wink import WinkDevice
from homeassistant.const import CONF_ACCESS_TOKEN
from homeassistant.helpers.entity import Entity
from homeassistant.loader import get_component
REQUIREMENTS = ['python-wink==0.6.4']
REQUIREMENTS = ['python-wink==0.7.11', 'pubnub==3.8.2']
# These are the available sensors mapped to binary_sensor class
SENSOR_TYPES = {
"opened": "opening",
"brightness": "light",
"vibration": "vibration",
"loudness": "sound"
"loudness": "sound",
"liquid_detected": "moisture"
}
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Wink platform."""
"""Setup the Wink binary sensor platform."""
import pywink
if discovery_info is None:
@@ -40,16 +44,28 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
if sensor.capability() in SENSOR_TYPES:
add_devices([WinkBinarySensorDevice(sensor)])
for key in pywink.get_keys():
add_devices([WinkBinarySensorDevice(key)])
class WinkBinarySensorDevice(BinarySensorDevice, Entity):
"""Representation of a Wink sensor."""
class WinkBinarySensorDevice(WinkDevice, BinarySensorDevice, Entity):
"""Representation of a Wink binary sensor."""
def __init__(self, wink):
"""Initialize the Wink binary sensor."""
self.wink = wink
super().__init__(wink)
wink = get_component('wink')
self._unit_of_measurement = self.wink.UNIT
self.capability = self.wink.capability()
def _pubnub_update(self, message, channel):
if 'data' in message:
json_data = json.dumps(message.get('data'))
else:
json_data = message
self.wink.pubnub_update(json.loads(json_data))
self.update_ha_state()
@property
def is_on(self):
"""Return true if the binary sensor is on."""
@@ -59,6 +75,8 @@ class WinkBinarySensorDevice(BinarySensorDevice, Entity):
return self.wink.vibration_boolean()
elif self.capability == "brightness":
return self.wink.brightness_boolean()
elif self.capability == "liquid_detected":
return self.wink.liquid_boolean()
else:
return self.wink.state()
@@ -66,22 +84,3 @@ class WinkBinarySensorDevice(BinarySensorDevice, Entity):
def sensor_class(self):
"""Return the class of this sensor, from SENSOR_CLASSES."""
return SENSOR_TYPES.get(self.capability)
@property
def unique_id(self):
"""Return the ID of this wink sensor."""
return "{}.{}".format(self.__class__, self.wink.device_id())
@property
def name(self):
"""Return the name of the sensor if any."""
return self.wink.name()
@property
def available(self):
"""True if connection == True."""
return self.wink.available
def update(self):
"""Update state of the sensor."""
self.wink.update_state()

View File

@@ -12,7 +12,7 @@ DEPENDENCIES = ["zigbee"]
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Create and add an entity based on the configuration."""
"""Setup the ZigBee binary sensor platform."""
add_entities([
ZigBeeBinarySensor(hass, ZigBeeDigitalInConfig(config))
])

View File

@@ -8,11 +8,8 @@ import logging
import datetime
import homeassistant.util.dt as dt_util
from homeassistant.helpers.event import track_point_in_time
from homeassistant.components.zwave import (
ATTR_NODE_ID, ATTR_VALUE_ID,
COMMAND_CLASS_SENSOR_BINARY, NETWORK,
ZWaveDeviceEntity, get_config_value)
from homeassistant.helpers.entity import Entity
from homeassistant.components import zwave
from homeassistant.components.binary_sensor import (
DOMAIN,
BinarySensorDevice)
@@ -35,12 +32,12 @@ DEVICE_MAPPINGS = {
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:
"""Setup the Z-Wave platform for binary sensors."""
if discovery_info is None or zwave.NETWORK is None:
return
node = NETWORK.nodes[discovery_info[ATTR_NODE_ID]]
value = node.values[discovery_info[ATTR_VALUE_ID]]
node = zwave.NETWORK.nodes[discovery_info[zwave.ATTR_NODE_ID]]
value = node.values[discovery_info[zwave.ATTR_VALUE_ID]]
value.set_change_verified(False)
# Make sure that we have values for the key before converting to int
@@ -53,18 +50,19 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
if specific_sensor_key in DEVICE_MAPPINGS:
if DEVICE_MAPPINGS[specific_sensor_key] == WORKAROUND_NO_OFF_EVENT:
# Default the multiplier to 4
re_arm_multiplier = (get_config_value(value.node, 9) or 4)
re_arm_multiplier = (zwave.get_config_value(value.node,
9) or 4)
add_devices([
ZWaveTriggerSensor(value, "motion",
hass, re_arm_multiplier * 8)
])
return
if value.command_class == COMMAND_CLASS_SENSOR_BINARY:
if value.command_class == zwave.COMMAND_CLASS_SENSOR_BINARY:
add_devices([ZWaveBinarySensor(value, None)])
class ZWaveBinarySensor(BinarySensorDevice, ZWaveDeviceEntity):
class ZWaveBinarySensor(BinarySensorDevice, zwave.ZWaveDeviceEntity, Entity):
"""Representation of a binary sensor within Z-Wave."""
def __init__(self, value, sensor_class):
@@ -74,7 +72,7 @@ class ZWaveBinarySensor(BinarySensorDevice, ZWaveDeviceEntity):
from openzwave.network import ZWaveNetwork
from pydispatch import dispatcher
ZWaveDeviceEntity.__init__(self, value, DOMAIN)
zwave.ZWaveDeviceEntity.__init__(self, value, DOMAIN)
dispatcher.connect(
self.value_changed, ZWaveNetwork.SIGNAL_VALUE_CHANGED)
@@ -96,11 +94,12 @@ class ZWaveBinarySensor(BinarySensorDevice, ZWaveDeviceEntity):
def value_changed(self, value):
"""Called when a value has changed on the network."""
if self._value.value_id == value.value_id:
if self._value.value_id == value.value_id or \
self._value.node == value.node:
self.update_ha_state()
class ZWaveTriggerSensor(ZWaveBinarySensor):
class ZWaveTriggerSensor(ZWaveBinarySensor, Entity):
"""Representation of a stateless sensor within Z-Wave."""
def __init__(self, sensor_value, sensor_class, hass, re_arm_sec=60):

View File

@@ -9,9 +9,8 @@ from datetime import timedelta
import requests
from homeassistant.components import discovery
from homeassistant.const import CONF_API_KEY
from homeassistant.helpers import validate_config
from homeassistant.helpers import validate_config, discovery
from homeassistant.util import Throttle
DOMAIN = "bloomsky"
@@ -23,10 +22,6 @@ _LOGGER = logging.getLogger(__name__)
# no point in polling the API more frequently
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=300)
DISCOVER_SENSORS = 'bloomsky.sensors'
DISCOVER_BINARY_SENSORS = 'bloomsky.binary_sensor'
DISCOVER_CAMERAS = 'bloomsky.camera'
# pylint: disable=unused-argument,too-few-public-methods
def setup(hass, config):
@@ -45,11 +40,8 @@ def setup(hass, config):
except RuntimeError:
return False
for component, discovery_service in (
('camera', DISCOVER_CAMERAS), ('sensor', DISCOVER_SENSORS),
('binary_sensor', DISCOVER_BINARY_SENSORS)):
discovery.discover(hass, discovery_service, component=component,
hass_config=config)
for component in 'camera', 'binary_sensor', 'sensor':
discovery.load_platform(hass, component, DOMAIN, {}, config)
return True

View File

@@ -4,10 +4,19 @@ Provides functionality to launch a web browser on the host machine.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/browser/
"""
import voluptuous as vol
DOMAIN = "browser"
SERVICE_BROWSE_URL = "browse_url"
ATTR_URL = 'url'
ATTR_URL_DEFAULT = 'https://www.google.com'
SERVICE_BROWSE_URL_SCHEMA = vol.Schema({
# pylint: disable=no-value-for-parameter
vol.Required(ATTR_URL, default=ATTR_URL_DEFAULT): vol.Url(),
})
def setup(hass, config):
"""Listen for browse_url events."""
@@ -15,8 +24,7 @@ def setup(hass, config):
hass.services.register(DOMAIN, SERVICE_BROWSE_URL,
lambda service:
webbrowser.open(
service.data.get(
'url', 'https://www.google.com')))
webbrowser.open(service.data[ATTR_URL]),
schema=SERVICE_BROWSE_URL_SCHEMA)
return True

View File

@@ -6,96 +6,36 @@ For more details about this component, please refer to the documentation at
https://home-assistant.io/components/camera/
"""
import logging
import re
import time
import requests
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.components import bloomsky
from homeassistant.const import HTTP_OK, HTTP_NOT_FOUND, ATTR_ENTITY_ID
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
from homeassistant.components.http import HomeAssistantView
DOMAIN = 'camera'
DEPENDENCIES = ['http']
SCAN_INTERVAL = 30
ENTITY_ID_FORMAT = DOMAIN + '.{}'
# Maps discovered services to their platforms
DISCOVERY_PLATFORMS = {
bloomsky.DISCOVER_CAMERAS: 'bloomsky',
}
STATE_RECORDING = 'recording'
STATE_STREAMING = 'streaming'
STATE_IDLE = 'idle'
ENTITY_IMAGE_URL = '/api/camera_proxy/{0}'
MULTIPART_BOUNDARY = '--jpgboundary'
MJPEG_START_HEADER = 'Content-type: {0}\r\n\r\n'
ENTITY_IMAGE_URL = '/api/camera_proxy/{0}?token={1}'
# pylint: disable=too-many-branches
def setup(hass, config):
"""Setup the camera component."""
component = EntityComponent(
logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL,
DISCOVERY_PLATFORMS)
logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL)
hass.wsgi.register_view(CameraImageView(hass, component.entities))
hass.wsgi.register_view(CameraMjpegStream(hass, component.entities))
component.setup(config)
def _proxy_camera_image(handler, path_match, data):
"""Serve the camera image via the HA server."""
entity_id = path_match.group(ATTR_ENTITY_ID)
camera = component.entities.get(entity_id)
if camera is None:
handler.send_response(HTTP_NOT_FOUND)
handler.end_headers()
return
response = camera.camera_image()
if response is None:
handler.send_response(HTTP_NOT_FOUND)
handler.end_headers()
return
handler.send_response(HTTP_OK)
handler.write_content(response)
hass.http.register_path(
'GET',
re.compile(r'/api/camera_proxy/(?P<entity_id>[a-zA-Z\._0-9]+)'),
_proxy_camera_image)
def _proxy_camera_mjpeg_stream(handler, path_match, data):
"""Proxy the camera image as an mjpeg stream via the HA server."""
entity_id = path_match.group(ATTR_ENTITY_ID)
camera = component.entities.get(entity_id)
if camera is None:
handler.send_response(HTTP_NOT_FOUND)
handler.end_headers()
return
try:
camera.is_streaming = True
camera.update_ha_state()
camera.mjpeg_stream(handler)
except (requests.RequestException, IOError):
camera.is_streaming = False
camera.update_ha_state()
hass.http.register_path(
'GET',
re.compile(r'/api/camera_proxy_stream/(?P<entity_id>[a-zA-Z\._0-9]+)'),
_proxy_camera_mjpeg_stream)
return True
@@ -106,6 +46,11 @@ class Camera(Entity):
"""Initialize a camera."""
self.is_streaming = False
@property
def access_token(self):
"""Access token for this camera."""
return str(id(self))
@property
def should_poll(self):
"""No need to poll cameras."""
@@ -114,7 +59,7 @@ class Camera(Entity):
@property
def entity_picture(self):
"""Return a link to the camera feed as entity picture."""
return ENTITY_IMAGE_URL.format(self.entity_id)
return ENTITY_IMAGE_URL.format(self.entity_id, self.access_token)
@property
def is_recording(self):
@@ -135,32 +80,33 @@ class Camera(Entity):
"""Return bytes of camera image."""
raise NotImplementedError()
def mjpeg_stream(self, handler):
def mjpeg_stream(self, response):
"""Generate an HTTP MJPEG stream from camera images."""
def write_string(text):
"""Helper method to write a string to the stream."""
handler.request.sendall(bytes(text + '\r\n', 'utf-8'))
def stream():
"""Stream images as mjpeg stream."""
try:
last_image = None
while True:
img_bytes = self.camera_image()
write_string('HTTP/1.1 200 OK')
write_string('Content-type: multipart/x-mixed-replace; '
'boundary={}'.format(MULTIPART_BOUNDARY))
write_string('')
write_string(MULTIPART_BOUNDARY)
if img_bytes is not None and img_bytes != last_image:
yield bytes(
'--jpegboundary\r\n'
'Content-Type: image/jpeg\r\n'
'Content-Length: {}\r\n\r\n'.format(
len(img_bytes)), 'utf-8') + img_bytes + b'\r\n'
while True:
img_bytes = self.camera_image()
last_image = img_bytes
if img_bytes is None:
continue
time.sleep(0.5)
except GeneratorExit:
pass
write_string('Content-length: {}'.format(len(img_bytes)))
write_string('Content-type: image/jpeg')
write_string('')
handler.request.sendall(img_bytes)
write_string('')
write_string(MULTIPART_BOUNDARY)
time.sleep(0.5)
return response(
stream(),
content_type=('multipart/x-mixed-replace; '
'boundary=--jpegboundary')
)
@property
def state(self):
@@ -175,7 +121,9 @@ class Camera(Entity):
@property
def state_attributes(self):
"""Camera state attributes."""
attr = {}
attr = {
'access_token': self.access_token,
}
if self.model:
attr['model_name'] = self.model
@@ -184,3 +132,60 @@ class Camera(Entity):
attr['brand'] = self.brand
return attr
class CameraView(HomeAssistantView):
"""Base CameraView."""
requires_auth = False
def __init__(self, hass, entities):
"""Initialize a basic camera view."""
super().__init__(hass)
self.entities = entities
def get(self, request, entity_id):
"""Start a get request."""
camera = self.entities.get(entity_id)
if camera is None:
return self.Response(status=404)
authenticated = (request.authenticated or
request.args.get('token') == camera.access_token)
if not authenticated:
return self.Response(status=401)
return self.handle(camera)
def handle(self, camera):
"""Hanlde the camera request."""
raise NotImplementedError()
class CameraImageView(CameraView):
"""Camera view to serve an image."""
url = "/api/camera_proxy/<entity(domain=camera):entity_id>"
name = "api:camera:image"
def handle(self, camera):
"""Serve camera image."""
response = camera.camera_image()
if response is None:
return self.Response(status=500)
return self.Response(response)
class CameraMjpegStream(CameraView):
"""Camera View to serve an MJPEG stream."""
url = "/api/camera_proxy_stream/<entity(domain=camera):entity_id>"
name = "api:camera:stream"
def handle(self, camera):
"""Serve camera image."""
return camera.mjpeg_stream(self.Response)

View File

@@ -18,7 +18,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class DemoCamera(Camera):
"""A Demo camera."""
"""The representation of a Demo camera."""
def __init__(self, name):
"""Initialize demo camera component."""

View File

@@ -49,7 +49,7 @@ class FoscamCamera(Camera):
def camera_image(self):
"""Return a still image reponse from the camera."""
# Send the request to snap a picture and return raw jpg data
response = requests.get(self._snap_picture_url)
response = requests.get(self._snap_picture_url, timeout=10)
return response.content

View File

@@ -43,13 +43,14 @@ class GenericCamera(Camera):
try:
response = requests.get(
self._still_image_url,
auth=HTTPBasicAuth(self._username, self._password))
auth=HTTPBasicAuth(self._username, self._password),
timeout=10)
except requests.exceptions.RequestException as error:
_LOGGER.error('Error getting camera image: %s', error)
return None
else:
try:
response = requests.get(self._still_image_url)
response = requests.get(self._still_image_url, timeout=10)
except requests.exceptions.RequestException as error:
_LOGGER.error('Error getting camera image: %s', error)
return None

View File

@@ -0,0 +1,53 @@
"""Camera that loads a picture from a local file."""
import logging
import os
from homeassistant.components.camera import Camera
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Camera."""
# check for missing required configuration variable
if config.get("file_path") is None:
_LOGGER.error("Missing required variable: file_path")
return False
setup_config = (
{
"name": config.get("name", "Local File"),
"file_path": config.get("file_path")
}
)
# check filepath given is readable
if not os.access(setup_config["file_path"], os.R_OK):
_LOGGER.error("file path is not readable")
return False
add_devices([
LocalFile(setup_config)
])
class LocalFile(Camera):
"""Local camera."""
def __init__(self, device_info):
"""Initialize Local File Camera component."""
super().__init__()
self._name = device_info["name"]
self._config = device_info
def camera_image(self):
"""Return image response."""
with open(self._config["file_path"], 'rb') as file:
return file.read()
@property
def name(self):
"""Return the name of this camera."""
return self._name

View File

@@ -11,7 +11,6 @@ import requests
from requests.auth import HTTPBasicAuth
from homeassistant.components.camera import DOMAIN, Camera
from homeassistant.const import HTTP_OK
from homeassistant.helpers import validate_config
CONTENT_TYPE_HEADER = 'Content-Type'
@@ -47,10 +46,9 @@ class MjpegCamera(Camera):
return requests.get(self._mjpeg_url,
auth=HTTPBasicAuth(self._username,
self._password),
stream=True)
stream=True, timeout=10)
else:
return requests.get(self._mjpeg_url,
stream=True)
return requests.get(self._mjpeg_url, stream=True, timeout=10)
def camera_image(self):
"""Return a still image response from the camera."""
@@ -68,19 +66,14 @@ class MjpegCamera(Camera):
with closing(self.camera_stream()) as response:
return process_response(response)
def mjpeg_stream(self, handler):
def mjpeg_stream(self, response):
"""Generate an HTTP MJPEG stream from the camera."""
response = self.camera_stream()
content_type = response.headers[CONTENT_TYPE_HEADER]
handler.send_response(HTTP_OK)
handler.send_header(CONTENT_TYPE_HEADER, content_type)
handler.end_headers()
for chunk in response.iter_content(chunk_size=1024):
if not chunk:
break
handler.wfile.write(chunk)
stream = self.camera_stream()
return response(
stream.iter_content(chunk_size=1024),
mimetype=stream.headers[CONTENT_TYPE_HEADER],
direct_passthrough=True
)
@property
def name(self):

View File

@@ -0,0 +1,104 @@
"""
Support for the Netatmo Welcome camera.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.netatmo/
"""
import logging
from datetime import timedelta
import requests
from homeassistant.util import Throttle
from homeassistant.components.camera import Camera
from homeassistant.loader import get_component
DEPENDENCIES = ["netatmo"]
_LOGGER = logging.getLogger(__name__)
CONF_HOME = 'home'
ATTR_CAMERAS = 'cameras'
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=10)
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Setup access to Netatmo Welcome cameras."""
netatmo = get_component('netatmo')
home = config.get(CONF_HOME, None)
data = WelcomeData(netatmo.NETATMO_AUTH, home)
for camera_name in data.get_camera_names():
if ATTR_CAMERAS in config:
if camera_name not in config[ATTR_CAMERAS]:
continue
add_devices_callback([WelcomeCamera(data, camera_name, home)])
class WelcomeCamera(Camera):
"""Representation of the images published from Welcome camera."""
def __init__(self, data, camera_name, home):
"""Setup for access to the BloomSky camera images."""
super(WelcomeCamera, self).__init__()
self._data = data
self._camera_name = camera_name
if home:
self._name = home + ' / ' + camera_name
else:
self._name = camera_name
self._vpnurl, self._localurl = self._data.welcomedata.cameraUrls(
camera=camera_name
)
def camera_image(self):
"""Return a still image response from the camera."""
try:
if self._localurl:
response = requests.get('{0}/live/snapshot_720.jpg'.format(
self._localurl), timeout=10)
else:
response = requests.get('{0}/live/snapshot_720.jpg'.format(
self._vpnurl), timeout=10)
except requests.exceptions.RequestException as error:
_LOGGER.error('Welcome VPN url changed: %s', error)
self._data.update()
(self._vpnurl, self._localurl) = \
self._data.welcomedata.cameraUrls(camera=self._camera_name)
return None
return response.content
@property
def name(self):
"""Return the name of this Netatmo Welcome device."""
return self._name
class WelcomeData(object):
"""Get the latest data from NetAtmo."""
def __init__(self, auth, home=None):
"""Initialize the data object."""
self.auth = auth
self.welcomedata = None
self.camera_names = []
self.home = home
def get_camera_names(self):
"""Return all module available on the API as a list."""
self.update()
if not self.home:
for home in self.welcomedata.cameras:
for camera in self.welcomedata.cameras[home].values():
self.camera_names.append(camera['name'])
else:
for camera in self.welcomedata.cameras[self.home].values():
self.camera_names.append(camera['name'])
return self.camera_names
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Call the NetAtmo API to update the data."""
import lnetatmo
self.welcomedata = lnetatmo.WelcomeData(self.auth)

View File

@@ -1,5 +1,9 @@
"""Camera platform that has a Raspberry Pi camera."""
"""
Camera platform that has a Raspberry Pi camera.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.rpi_camera/
"""
import os
import subprocess
import logging
@@ -43,7 +47,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class RaspberryCamera(Camera):
"""Raspberry Pi camera."""
"""Representation of a Raspberry Pi camera."""
def __init__(self, device_info):
"""Initialize Raspberry Pi camera component."""

View File

@@ -12,7 +12,7 @@ import requests
from homeassistant.components.camera import DOMAIN, Camera
from homeassistant.helpers import validate_config
REQUIREMENTS = ['uvcclient==0.8']
REQUIREMENTS = ['uvcclient==0.9.0']
_LOGGER = logging.getLogger(__name__)
@@ -45,13 +45,15 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
_LOGGER.error('Unable to connect to NVR: %s', str(ex))
return False
identifier = nvrconn.server_version >= (3, 2, 0) and 'id' or 'uuid'
# Filter out airCam models, which are not supported in the latest
# version of UnifiVideo and which are EOL by Ubiquiti
cameras = [camera for camera in cameras
if 'airCam' not in nvrconn.get_camera(camera['uuid'])['model']]
cameras = [
camera for camera in cameras
if 'airCam' not in nvrconn.get_camera(camera[identifier])['model']]
add_devices([UnifiVideoCamera(nvrconn,
camera['uuid'],
camera[identifier],
camera['name'])
for camera in cameras])
return True
@@ -110,12 +112,17 @@ class UnifiVideoCamera(Camera):
dict(name=self._name))
password = 'ubnt'
if self._nvr.server_version >= (3, 2, 0):
client_cls = uvc_camera.UVCCameraClientV320
else:
client_cls = uvc_camera.UVCCameraClient
camera = None
for addr in addrs:
try:
camera = uvc_camera.UVCCameraClient(addr,
caminfo['username'],
password)
camera = client_cls(addr,
caminfo['username'],
password)
camera.login()
_LOGGER.debug('Logged into UVC camera %(name)s via %(addr)s',
dict(name=self._name, addr=addr))

View File

@@ -8,7 +8,7 @@ the user has submitted configuration information.
"""
import logging
from homeassistant.const import EVENT_TIME_CHANGED
from homeassistant.const import EVENT_TIME_CHANGED, ATTR_FRIENDLY_NAME
from homeassistant.helpers.entity import generate_entity_id
DOMAIN = "configurator"
@@ -19,6 +19,8 @@ SERVICE_CONFIGURE = "configure"
STATE_CONFIGURE = "configure"
STATE_CONFIGURED = "configured"
ATTR_LINK_NAME = "link_name"
ATTR_LINK_URL = "link_url"
ATTR_CONFIGURE_ID = "configure_id"
ATTR_DESCRIPTION = "description"
ATTR_DESCRIPTION_IMAGE = "description_image"
@@ -34,7 +36,7 @@ _LOGGER = logging.getLogger(__name__)
# pylint: disable=too-many-arguments
def request_config(
hass, name, callback, description=None, description_image=None,
submit_caption=None, fields=None):
submit_caption=None, fields=None, link_name=None, link_url=None):
"""Create a new request for configuration.
Will return an ID to be used for sequent calls.
@@ -43,7 +45,8 @@ def request_config(
request_id = instance.request_config(
name, callback,
description, description_image, submit_caption, fields)
description, description_image, submit_caption,
fields, link_name, link_url)
_REQUESTS[request_id] = instance
@@ -100,7 +103,8 @@ class Configurator(object):
# pylint: disable=too-many-arguments
def request_config(
self, name, callback,
description, description_image, submit_caption, fields):
description, description_image, submit_caption,
fields, link_name, link_url):
"""Setup a request for configuration."""
entity_id = generate_entity_id(ENTITY_ID_FORMAT, name, hass=self.hass)
@@ -114,6 +118,7 @@ class Configurator(object):
data = {
ATTR_CONFIGURE_ID: request_id,
ATTR_FIELDS: fields,
ATTR_FRIENDLY_NAME: name,
}
data.update({
@@ -121,6 +126,8 @@ class Configurator(object):
(ATTR_DESCRIPTION, description),
(ATTR_DESCRIPTION_IMAGE, description_image),
(ATTR_SUBMIT_CAPTION, submit_caption),
(ATTR_LINK_NAME, link_name),
(ATTR_LINK_URL, link_url),
] if value is not None
})

View File

@@ -8,9 +8,12 @@ import logging
import re
import warnings
import voluptuous as vol
from homeassistant import core
from homeassistant.const import (
ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON)
import homeassistant.helpers.config_validation as cv
DOMAIN = "conversation"
@@ -18,9 +21,13 @@ SERVICE_PROCESS = "process"
ATTR_TEXT = "text"
SERVICE_PROCESS_SCHEMA = vol.Schema({
vol.Required(ATTR_TEXT): vol.All(cv.string, vol.Lower),
})
REGEX_TURN_COMMAND = re.compile(r'turn (?P<name>(?: |\w)+) (?P<command>\w+)')
REQUIREMENTS = ['fuzzywuzzy==0.8.0']
REQUIREMENTS = ['fuzzywuzzy==0.11.0']
def setup(hass, config):
@@ -32,11 +39,7 @@ def setup(hass, config):
def process(service):
"""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()
text = service.data[ATTR_TEXT]
match = REGEX_TURN_COMMAND.match(text)
if not match:
@@ -64,9 +67,9 @@ def setup(hass, config):
}, blocking=True)
else:
logger.error(
'Got unsupported command %s from text %s', command, text)
hass.services.register(DOMAIN, SERVICE_PROCESS, process)
logger.error('Got unsupported command %s from text %s',
command, text)
hass.services.register(DOMAIN, SERVICE_PROCESS, process,
schema=SERVICE_PROCESS_SCHEMA)
return True

View File

@@ -21,6 +21,7 @@ COMPONENTS_WITH_DEMO_PLATFORM = [
'camera',
'device_tracker',
'garage_door',
'hvac',
'light',
'lock',
'media_player',
@@ -36,6 +37,7 @@ def setup(hass, config):
"""Setup a demo environment."""
group = loader.get_component('group')
configurator = loader.get_component('configurator')
persistent_notification = loader.get_component('persistent_notification')
config.setdefault(ha.DOMAIN, {})
config.setdefault(DOMAIN, {})
@@ -58,6 +60,11 @@ def setup(hass, config):
demo_config[component] = {CONF_PLATFORM: 'demo'}
bootstrap.setup_component(hass, component, demo_config)
# Setup example persistent notification
persistent_notification.create(
hass, 'This is an example of a persistent notification.',
title='Example Notification')
# Setup room groups
lights = sorted(hass.states.entity_ids('light'))
switches = sorted(hass.states.entity_ids('switch'))
@@ -66,7 +73,9 @@ def setup(hass, config):
lights[1], switches[0], 'input_select.living_room_preset',
'rollershutter.living_room_window', media_players[1],
'scene.romantic_lights'])
group.Group(hass, 'bedroom', [lights[0], switches[1], media_players[0]])
group.Group(hass, 'bedroom', [
lights[0], switches[1], media_players[0],
'input_slider.noise_allowance'])
group.Group(hass, 'kitchen', [
lights[2], 'rollershutter.kitchen_window', 'lock.kitchen_door'])
group.Group(hass, 'doors', [
@@ -144,6 +153,17 @@ def setup(hass, config):
{'input_boolean': {'notify': {'icon': 'mdi:car',
'initial': False,
'name': 'Notify Anne Therese is home'}}})
# Set up input boolean
bootstrap.setup_component(
hass, 'input_slider',
{'input_slider': {
'noise_allowance': {'icon': 'mdi:bell-ring',
'min': 0,
'max': 10,
'name': 'Allowed Noise',
'unit_of_measurement': 'dB'}}})
# Set up weblink
bootstrap.setup_component(
hass, 'weblink',

View File

@@ -12,12 +12,13 @@ import os
import threading
from homeassistant.bootstrap import prepare_setup_platform
from homeassistant.components import discovery, group, zone
from homeassistant.components import group, zone
from homeassistant.components.discovery import SERVICE_NETGEAR
from homeassistant.config import load_yaml_config_file
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_per_platform
from homeassistant.helpers import config_per_platform, discovery
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
import homeassistant.helpers.config_validation as cv
import homeassistant.util as util
import homeassistant.util.dt as dt_util
@@ -26,6 +27,7 @@ from homeassistant.const import (
ATTR_GPS_ACCURACY, ATTR_LATITUDE, ATTR_LONGITUDE,
DEVICE_DEFAULT_NAME, STATE_HOME, STATE_NOT_HOME)
PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA
DOMAIN = "device_tracker"
DEPENDENCIES = ['zone']
@@ -61,7 +63,7 @@ ATTR_GPS = 'gps'
ATTR_BATTERY = 'battery'
DISCOVERY_PLATFORMS = {
discovery.SERVICE_NETGEAR: 'netgear',
SERVICE_NETGEAR: 'netgear',
}
_LOGGER = logging.getLogger(__name__)
@@ -94,8 +96,11 @@ def setup(hass, config):
yaml_path = hass.config.path(YAML_DEVICES)
conf = config.get(DOMAIN, {})
# Config can be an empty list. In that case, substitute a dict
if isinstance(conf, list):
conf = conf[0]
conf = conf[0] if len(conf) > 0 else {}
consider_home = timedelta(
seconds=util.convert(conf.get(CONF_CONSIDER_HOME), int,
DEFAULT_CONSIDER_HOME))
@@ -193,7 +198,7 @@ class DeviceTracker(object):
if not device:
dev_id = util.slugify(host_name or '') or util.slugify(mac)
else:
dev_id = str(dev_id).lower()
dev_id = cv.slug(str(dev_id).lower())
device = self.devices.get(dev_id)
if device:
@@ -372,12 +377,16 @@ def load_config(path, hass, consider_home, home_range):
"""Load devices from YAML configuration file."""
if not os.path.isfile(path):
return []
return [
Device(hass, consider_home, home_range, device.get('track', False),
str(dev_id).lower(), str(device.get('mac')).upper(),
device.get('name'), device.get('picture'),
device.get(CONF_AWAY_HIDE, DEFAULT_AWAY_HIDE))
for dev_id, device in load_yaml_config_file(path).items()]
try:
return [
Device(hass, consider_home, home_range, device.get('track', False),
str(dev_id).lower(), str(device.get('mac')).upper(),
device.get('name'), device.get('picture'),
device.get(CONF_AWAY_HIDE, DEFAULT_AWAY_HIDE))
for dev_id, device in load_yaml_config_file(path).items()]
except HomeAssistantError:
# When YAML file could not be loaded/did not contain a dict
return []
def setup_scanner_platform(hass, config, scanner, see_device):

View File

@@ -6,8 +6,10 @@ https://home-assistant.io/components/device_tracker.asuswrt/
"""
import logging
import re
import socket
import telnetlib
import threading
from collections import namedtuple
from datetime import timedelta
from homeassistant.components.device_tracker import DOMAIN
@@ -19,13 +21,31 @@ from homeassistant.util import Throttle
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['pexpect==4.0.1']
_LEASES_CMD = 'cat /var/lib/misc/dnsmasq.leases'
_LEASES_REGEX = re.compile(
r'\w+\s' +
r'(?P<mac>(([0-9a-f]{2}[:-]){5}([0-9a-f]{2})))\s' +
r'(?P<ip>([0-9]{1,3}[\.]){3}[0-9]{1,3})\s' +
r'(?P<host>([^\s]+))')
# command to get both 5GHz and 2.4GHz clients
_WL_CMD = '{ wl -i eth2 assoclist & wl -i eth1 assoclist ; }'
_WL_REGEX = re.compile(
r'\w+\s' +
r'(?P<mac>(([0-9A-F]{2}[:-]){5}([0-9A-F]{2})))')
_ARP_CMD = 'arp -n'
_ARP_REGEX = re.compile(
r'.+\s' +
r'\((?P<ip>([0-9]{1,3}[\.]){3}[0-9]{1,3})\)\s' +
r'.+\s' +
r'(?P<mac>(([0-9a-f]{2}[:-]){5}([0-9a-f]{2})))' +
r'\s' +
r'.*')
_IP_NEIGH_CMD = 'ip neigh'
_IP_NEIGH_REGEX = re.compile(
r'(?P<ip>([0-9]{1,3}[\.]){3}[0-9]{1,3})\s' +
r'\w+\s' +
@@ -38,23 +58,36 @@ _IP_NEIGH_REGEX = re.compile(
def get_scanner(hass, config):
"""Validate the configuration and return an ASUS-WRT scanner."""
if not validate_config(config,
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
{DOMAIN: [CONF_HOST, CONF_USERNAME]},
_LOGGER):
return None
elif CONF_PASSWORD not in config[DOMAIN] and \
'ssh_key' not in config[DOMAIN] and \
'pub_key' not in config[DOMAIN]:
_LOGGER.error('Either a private key or password must be provided')
return None
scanner = AsusWrtDeviceScanner(config[DOMAIN])
return scanner if scanner.success_init else None
AsusWrtResult = namedtuple('AsusWrtResult', 'neighbors leases arp')
class AsusWrtDeviceScanner(object):
"""This class queries a router running ASUSWRT firmware."""
# pylint: disable=too-many-instance-attributes, too-many-branches
# Eighth attribute needed for mode (AP mode vs router mode)
def __init__(self, config):
"""Initialize the scanner."""
self.host = config[CONF_HOST]
self.username = str(config[CONF_USERNAME])
self.password = str(config[CONF_PASSWORD])
self.password = str(config.get(CONF_PASSWORD, ''))
self.ssh_key = str(config.get('ssh_key', config.get('pub_key', '')))
self.protocol = config.get('protocol')
self.mode = config.get('mode')
self.lock = threading.Lock()
@@ -88,7 +121,7 @@ class AsusWrtDeviceScanner(object):
return False
with self.lock:
_LOGGER.info("Checking ARP")
_LOGGER.info('Checking ARP')
data = self.get_asuswrt_data()
if not data:
return False
@@ -100,8 +133,45 @@ class AsusWrtDeviceScanner(object):
self.last_results = active_clients
return True
def get_asuswrt_data(self):
"""Retrieve data from ASUSWRT and return parsed result."""
def ssh_connection(self):
"""Retrieve data from ASUSWRT via the ssh protocol."""
from pexpect import pxssh, exceptions
try:
ssh = pxssh.pxssh()
if self.ssh_key:
ssh.login(self.host, self.username, ssh_key=self.ssh_key)
elif self.password:
ssh.login(self.host, self.username, self.password)
else:
_LOGGER.error('No password or private key specified')
return None
ssh.sendline(_IP_NEIGH_CMD)
ssh.prompt()
neighbors = ssh.before.split(b'\n')[1:-1]
if self.mode == 'ap':
ssh.sendline(_ARP_CMD)
ssh.prompt()
arp_result = ssh.before.split(b'\n')[1:-1]
ssh.sendline(_WL_CMD)
ssh.prompt()
leases_result = ssh.before.split(b'\n')[1:-1]
else:
arp_result = ['']
ssh.sendline(_LEASES_CMD)
ssh.prompt()
leases_result = ssh.before.split(b'\n')[1:-1]
ssh.logout()
return AsusWrtResult(neighbors, leases_result, arp_result)
except pxssh.ExceptionPxssh as exc:
_LOGGER.error('Unexpected response from router: %s', exc)
return None
except exceptions.EOF:
_LOGGER.error('Connection refused or no route to host')
return None
def telnet_connection(self):
"""Retrieve data from ASUSWRT via the telnet protocol."""
try:
telnet = telnetlib.Telnet(self.host)
telnet.read_until(b'login: ')
@@ -109,44 +179,104 @@ class AsusWrtDeviceScanner(object):
telnet.read_until(b'Password: ')
telnet.write((self.password + '\n').encode('ascii'))
prompt_string = telnet.read_until(b'#').split(b'\n')[-1]
telnet.write('ip neigh\n'.encode('ascii'))
telnet.write('{}\n'.format(_IP_NEIGH_CMD).encode('ascii'))
neighbors = telnet.read_until(prompt_string).split(b'\n')[1:-1]
telnet.write('cat /var/lib/misc/dnsmasq.leases\n'.encode('ascii'))
leases_result = telnet.read_until(prompt_string).split(b'\n')[1:-1]
if self.mode == 'ap':
telnet.write('{}\n'.format(_ARP_CMD).encode('ascii'))
arp_result = (telnet.read_until(prompt_string).
split(b'\n')[1:-1])
telnet.write('{}\n'.format(_WL_CMD).encode('ascii'))
leases_result = (telnet.read_until(prompt_string).
split(b'\n')[1:-1])
else:
arp_result = ['']
telnet.write('{}\n'.format(_LEASES_CMD).encode('ascii'))
leases_result = (telnet.read_until(prompt_string).
split(b'\n')[1:-1])
telnet.write('exit\n'.encode('ascii'))
return AsusWrtResult(neighbors, leases_result, arp_result)
except EOFError:
_LOGGER.exception("Unexpected response from router")
return
_LOGGER.error('Unexpected response from router')
return None
except ConnectionRefusedError:
_LOGGER.exception("Connection refused by router," +
" is telnet enabled?")
return
_LOGGER.error('Connection refused by router, is telnet enabled?')
return None
except socket.gaierror as exc:
_LOGGER.error('Socket exception: %s', exc)
return None
except OSError as exc:
_LOGGER.error('OSError: %s', exc)
return None
def get_asuswrt_data(self):
"""Retrieve data from ASUSWRT and return parsed result."""
if self.protocol == 'ssh':
result = self.ssh_connection()
elif self.protocol == 'telnet':
result = self.telnet_connection()
else:
# autodetect protocol
result = self.ssh_connection()
if result:
self.protocol = 'ssh'
else:
result = self.telnet_connection()
if result:
self.protocol = 'telnet'
if not result:
return {}
devices = {}
for lease in leases_result:
match = _LEASES_REGEX.search(lease.decode('utf-8'))
if self.mode == 'ap':
for lease in result.leases:
match = _WL_REGEX.search(lease.decode('utf-8'))
if not match:
_LOGGER.warning("Could not parse lease row: %s", lease)
continue
if not match:
_LOGGER.warning('Could not parse wl row: %s', lease)
continue
# For leases where the client doesn't set a hostname, ensure it is
# blank and not '*', which breaks the entity_id down the line.
host = match.group('host')
if host == '*':
host = ''
devices[match.group('ip')] = {
'host': host,
'status': '',
'ip': match.group('ip'),
'mac': match.group('mac').upper(),
}
# match mac addresses to IP addresses in ARP table
for arp in result.arp:
if match.group('mac').lower() in arp.decode('utf-8'):
arp_match = _ARP_REGEX.search(arp.decode('utf-8'))
if not arp_match:
_LOGGER.warning('Could not parse arp row: %s', arp)
continue
for neighbor in neighbors:
devices[arp_match.group('ip')] = {
'host': host,
'status': '',
'ip': arp_match.group('ip'),
'mac': match.group('mac').upper(),
}
else:
for lease in result.leases:
match = _LEASES_REGEX.search(lease.decode('utf-8'))
if not match:
_LOGGER.warning('Could not parse lease row: %s', lease)
continue
# For leases where the client doesn't set a hostname, ensure it
# is blank and not '*', which breaks entity_id down the line.
host = match.group('host')
if host == '*':
host = ''
devices[match.group('ip')] = {
'host': host,
'status': '',
'ip': match.group('ip'),
'mac': match.group('mac').upper(),
}
for neighbor in result.neighbors:
match = _IP_NEIGH_REGEX.search(neighbor.decode('utf-8'))
if not match:
_LOGGER.warning("Could not parse neighbor row: %s", neighbor)
_LOGGER.warning('Could not parse neighbor row: %s', neighbor)
continue
if match.group('ip') in devices:
devices[match.group('ip')]['status'] = match.group('status')

View File

@@ -0,0 +1,91 @@
"""Tracking for bluetooth devices."""
import logging
from datetime import timedelta
from homeassistant.helpers.event import track_point_in_utc_time
from homeassistant.components.device_tracker import (
YAML_DEVICES,
CONF_TRACK_NEW,
CONF_SCAN_INTERVAL,
DEFAULT_SCAN_INTERVAL,
load_config,
)
import homeassistant.util as util
import homeassistant.util.dt as dt_util
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['pybluez==0.22']
BT_PREFIX = 'BT_'
def setup_scanner(hass, config, see):
"""Setup the Bluetooth Scanner."""
# pylint: disable=import-error
import bluetooth
def see_device(device):
"""Mark a device as seen."""
see(mac=BT_PREFIX + device[0], host_name=device[1])
def discover_devices():
"""Discover bluetooth devices."""
result = bluetooth.discover_devices(duration=8,
lookup_names=True,
flush_cache=True,
lookup_class=False)
_LOGGER.debug("Bluetooth devices discovered = " + str(len(result)))
return result
yaml_path = hass.config.path(YAML_DEVICES)
devs_to_track = []
devs_donot_track = []
# Load all known devices.
# We just need the devices so set consider_home and home range
# to 0
for device in load_config(yaml_path, hass, 0, 0):
# check if device is a valid bluetooth device
if device.mac and device.mac[:3].upper() == BT_PREFIX:
if device.track:
devs_to_track.append(device.mac[3:])
else:
devs_donot_track.append(device.mac[3:])
# if track new devices is true discover new devices
# on startup.
track_new = util.convert(config.get(CONF_TRACK_NEW), bool,
len(devs_to_track) == 0)
if track_new:
for dev in discover_devices():
if dev[0] not in devs_to_track and \
dev[0] not in devs_donot_track:
devs_to_track.append(dev[0])
see_device(dev)
if not devs_to_track:
_LOGGER.warning("No bluetooth devices to track!")
return False
interval = util.convert(config.get(CONF_SCAN_INTERVAL), int,
DEFAULT_SCAN_INTERVAL)
def update_bluetooth(now):
"""Lookup bluetooth device and update status."""
try:
for mac in devs_to_track:
_LOGGER.debug("Scanning " + mac)
result = bluetooth.lookup_name(mac, timeout=5)
if not result:
# Could not lookup device name
continue
see_device((mac, result))
except bluetooth.BluetoothError:
_LOGGER.exception('Error looking up bluetooth device!')
track_point_in_utc_time(hass, update_bluetooth,
now + timedelta(seconds=interval))
update_bluetooth(dt_util.utcnow())
return True

View File

@@ -0,0 +1,141 @@
"""
Support for BT Home Hub 5.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.bt_home_hub_5/
"""
import logging
import re
import threading
from datetime import timedelta
import xml.etree.ElementTree as ET
import json
from urllib.parse import unquote
import requests
from homeassistant.helpers import validate_config
from homeassistant.components.device_tracker import DOMAIN
from homeassistant.const import CONF_HOST
from homeassistant.util import Throttle
# Return cached results if last scan was less then this time ago.
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
_LOGGER = logging.getLogger(__name__)
_MAC_REGEX = re.compile(r'(([0-9A-Fa-f]{1,2}\:){5}[0-9A-Fa-f]{1,2})')
# pylint: disable=unused-argument
def get_scanner(hass, config):
"""Return a BT Home Hub 5 scanner if successful."""
if not validate_config(config,
{DOMAIN: [CONF_HOST]},
_LOGGER):
return None
scanner = BTHomeHub5DeviceScanner(config[DOMAIN])
return scanner if scanner.success_init else None
class BTHomeHub5DeviceScanner(object):
"""This class queries a BT Home Hub 5."""
def __init__(self, config):
"""Initialise the scanner."""
_LOGGER.info("Initialising BT Home Hub 5")
self.host = config.get(CONF_HOST, '192.168.1.254')
self.lock = threading.Lock()
self.last_results = {}
self.url = 'http://{}/nonAuth/home_status.xml'.format(self.host)
# Test the router is accessible
data = _get_homehub_data(self.url)
self.success_init = data is not None
def scan_devices(self):
"""Scan for new devices and return a list with found device IDs."""
self._update_info()
return (device for device in self.last_results)
def get_device_name(self, device):
"""Return the name of the given device or None if we don't know."""
with self.lock:
# If not initialised and not already scanned and not found.
if device not in self.last_results:
self._update_info()
if not self.last_results:
return None
return self.last_results.get(device)
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
"""Ensure the information from the BT Home Hub 5 is up to date.
Return boolean if scanning successful.
"""
if not self.success_init:
return False
with self.lock:
_LOGGER.info("Scanning")
data = _get_homehub_data(self.url)
if not data:
_LOGGER.warning('Error scanning devices')
return False
self.last_results = data
return True
def _get_homehub_data(url):
"""Retrieve data from BT Home Hub 5 and return parsed result."""
try:
response = requests.get(url, timeout=5)
except requests.exceptions.Timeout:
_LOGGER.exception("Connection to the router timed out")
return
if response.status_code == 200:
return _parse_homehub_response(response.text)
else:
_LOGGER.error("Invalid response from Home Hub: %s", response)
def _parse_homehub_response(data_str):
"""Parse the BT Home Hub 5 data format."""
root = ET.fromstring(data_str)
dirty_json = root.find('known_device_list').get('value')
# Normalise the JavaScript data to JSON.
clean_json = unquote(dirty_json.replace('\'', '\"')
.replace('{', '{\"')
.replace(':\"', '\":\"')
.replace('\",', '\",\"'))
known_devices = [x for x in json.loads(clean_json) if x]
devices = {}
for device in known_devices:
name = device.get('name')
mac = device.get('mac')
if _MAC_REGEX.match(mac) or ',' in mac:
for mac_addr in mac.split(','):
if _MAC_REGEX.match(mac_addr):
devices[mac_addr] = name
else:
devices[mac] = name
return devices

View File

@@ -12,7 +12,9 @@ from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.helpers import validate_config
from homeassistant.util import Throttle
REQUIREMENTS = ['fritzconnection==0.4.6']
REQUIREMENTS = ['https://github.com/deisi/fritzconnection/archive/'
'b5c14515e1c8e2652b06b6316a7f3913df942841.zip'
'#fritzconnection==0.4.6']
# Return cached results if last scan was less then this time ago.
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)

View File

@@ -5,17 +5,28 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.icloud/
"""
import logging
import re
import voluptuous as vol
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.const import (CONF_PASSWORD, CONF_USERNAME,
EVENT_HOMEASSISTANT_START)
from homeassistant.helpers.event import track_utc_time_change
from homeassistant.util import slugify
from homeassistant.components.device_tracker import (ENTITY_ID_FORMAT,
PLATFORM_SCHEMA)
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['pyicloud==0.7.2']
REQUIREMENTS = ['pyicloud==0.9.1']
CONF_INTERVAL = 'interval'
DEFAULT_INTERVAL = 8
KEEPALIVE_INTERVAL = 4
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_USERNAME): vol.Coerce(str),
vol.Required(CONF_PASSWORD): vol.Coerce(str),
vol.Optional(CONF_INTERVAL, default=8): vol.All(vol.Coerce(int),
vol.Range(min=1))
})
def setup_scanner(hass, config, see):
@@ -23,63 +34,67 @@ def setup_scanner(hass, config, see):
from pyicloud import PyiCloudService
from pyicloud.exceptions import PyiCloudFailedLoginException
from pyicloud.exceptions import PyiCloudNoDevicesException
logging.getLogger("pyicloud.base").setLevel(logging.WARNING)
# Get the username and password from the configuration.
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
if username is None or password is None:
_LOGGER.error('Must specify a username and password')
return False
username = config[CONF_USERNAME]
password = config[CONF_PASSWORD]
try:
_LOGGER.info('Logging into iCloud Account')
# Attempt the login to iCloud
api = PyiCloudService(username,
password,
verify=True)
api = PyiCloudService(username, password, verify=True)
except PyiCloudFailedLoginException as error:
_LOGGER.exception('Error logging into iCloud Service: %s', error)
return False
def keep_alive(now):
"""Keep authenticating iCloud connection."""
"""Keep authenticating iCloud connection.
The session timeouts if we are not using it so we
have to re-authenticate & this will send an email.
"""
api.authenticate()
_LOGGER.info("Authenticate against iCloud")
track_utc_time_change(hass, keep_alive, second=0)
seen_devices = {}
def update_icloud(now):
"""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.
api.authenticate()
keep_alive(None)
# Loop through every device registered with the iCloud account
for device in api.devices:
status = device.status()
dev_id = slugify(status['name'].replace(' ', '', 99))
# An entity will not be created by see() when track=false in
# 'known_devices.yaml', but we need to see() it at least once
entity = hass.states.get(ENTITY_ID_FORMAT.format(dev_id))
if entity is None and dev_id in seen_devices:
continue
seen_devices[dev_id] = True
location = device.location()
# If the device has a location add it. If not do nothing
if location:
see(
dev_id=re.sub(r"(\s|\W|')",
'',
status['name']),
dev_id=dev_id,
host_name=status['name'],
gps=(location['latitude'], location['longitude']),
battery=status['batteryLevel']*100,
gps_accuracy=location['horizontalAccuracy']
)
else:
# No location found for the device so continue
continue
except PyiCloudNoDevicesException:
_LOGGER.info('No iCloud Devices found!')
track_utc_time_change(
hass, update_icloud,
minute=range(0, 60, config.get(CONF_INTERVAL, DEFAULT_INTERVAL)),
second=0
)
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, update_icloud)
update_minutes = list(range(0, 60, config[CONF_INTERVAL]))
# Schedule keepalives between the updates
keepalive_minutes = list(x for x in range(0, 60, KEEPALIVE_INTERVAL)
if x not in update_minutes)
track_utc_time_change(hass, update_icloud, second=0, minute=update_minutes)
track_utc_time_change(hass, keep_alive, second=0, minute=keepalive_minutes)
return True

View File

@@ -5,95 +5,92 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.locative/
"""
import logging
from functools import partial
from homeassistant.components.device_tracker import DOMAIN
from homeassistant.const import HTTP_UNPROCESSABLE_ENTITY, STATE_NOT_HOME
from homeassistant.components.http import HomeAssistantView
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['http']
URL_API_LOCATIVE_ENDPOINT = "/api/locative"
def setup_scanner(hass, config, see):
"""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))
hass.wsgi.register_view(LocativeView(hass, see))
return True
def _handle_get_api_locative(hass, see, handler, path_match, data):
"""Locative message received."""
if not _check_data(handler, data):
return
class LocativeView(HomeAssistantView):
"""View to handle locative requests."""
device = data['device'].replace('-', '')
location_name = data['id'].lower()
direction = data['trigger']
url = "/api/locative"
name = "api:locative"
if direction == 'enter':
see(dev_id=device, location_name=location_name)
handler.write_text("Setting location to {}".format(location_name))
def __init__(self, hass, see):
"""Initialize Locative url endpoints."""
super().__init__(hass)
self.see = see
elif direction == 'exit':
current_state = hass.states.get("{}.{}".format(DOMAIN, device))
def get(self, request):
"""Locative message received as GET."""
return self.post(request)
def post(self, request):
"""Locative message received."""
# pylint: disable=too-many-return-statements
data = request.values
if 'latitude' not in data or 'longitude' not in data:
return ("Latitude and longitude not specified.",
HTTP_UNPROCESSABLE_ENTITY)
if 'device' not in data:
_LOGGER.error("Device id not specified.")
return ("Device id not specified.",
HTTP_UNPROCESSABLE_ENTITY)
if 'id' not in data:
_LOGGER.error("Location id not specified.")
return ("Location id not specified.",
HTTP_UNPROCESSABLE_ENTITY)
if 'trigger' not in data:
_LOGGER.error("Trigger is not specified.")
return ("Trigger is not specified.",
HTTP_UNPROCESSABLE_ENTITY)
device = data['device'].replace('-', '')
location_name = data['id'].lower()
direction = data['trigger']
if direction == 'enter':
self.see(dev_id=device, location_name=location_name)
return "Setting location to {}".format(location_name)
elif direction == 'exit':
current_state = self.hass.states.get(
"{}.{}".format(DOMAIN, device))
if current_state is None or current_state.state == location_name:
self.see(dev_id=device, location_name=STATE_NOT_HOME)
return "Setting location to not home"
else:
# Ignore the message if it is telling us to exit a zone that we
# aren't currently in. This occurs when a zone is entered
# before the previous zone was exited. The enter message will
# be sent first, then the exit message will be sent second.
return 'Ignoring exit from {} (already in {})'.format(
location_name, current_state)
elif direction == 'test':
# In the app, a test message can be sent. Just return something to
# the user to let them know that it works.
return "Received test message."
if current_state is None or current_state.state == location_name:
see(dev_id=device, location_name=STATE_NOT_HOME)
handler.write_text("Setting location to not home")
else:
# Ignore the message if it is telling us to exit a zone that we
# aren't currently in. This occurs when a zone is entered before
# the previous zone was exited. The enter message will be sent
# first, then the exit message will be sent second.
handler.write_text(
'Ignoring exit from {} (already in {})'.format(
location_name, current_state))
elif direction == 'test':
# In the app, a test message can be sent. Just return something to
# the user to let them know that it works.
handler.write_text("Received test message.")
else:
handler.write_text(
"Received unidentified message: {}".format(direction),
HTTP_UNPROCESSABLE_ENTITY)
_LOGGER.error("Received unidentified message from Locative: %s",
direction)
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)
_LOGGER.error("Latitude and longitude not specified.")
return False
if 'device' not in data:
handler.write_text("Device id not specified.",
HTTP_UNPROCESSABLE_ENTITY)
_LOGGER.error("Device id not specified.")
return False
if 'id' not in data:
handler.write_text("Location id not specified.",
HTTP_UNPROCESSABLE_ENTITY)
_LOGGER.error("Location id not specified.")
return False
if 'trigger' not in data:
handler.write_text("Trigger is not specified.",
HTTP_UNPROCESSABLE_ENTITY)
_LOGGER.error("Trigger is not specified.")
return False
return True
_LOGGER.error("Received unidentified message from Locative: %s",
direction)
return ("Received unidentified message: {}".format(direction),
HTTP_UNPROCESSABLE_ENTITY)

View File

@@ -9,14 +9,15 @@ import threading
from datetime import timedelta
from homeassistant.components.device_tracker import DOMAIN
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME, \
CONF_PORT
from homeassistant.util import Throttle
# Return cached results if last scan was less then this time ago.
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['pynetgear==0.3.2']
REQUIREMENTS = ['pynetgear==0.3.3']
def get_scanner(hass, config):
@@ -25,12 +26,13 @@ def get_scanner(hass, config):
host = info.get(CONF_HOST)
username = info.get(CONF_USERNAME)
password = info.get(CONF_PASSWORD)
port = info.get(CONF_PORT)
if password is not None and host is None:
_LOGGER.warning('Found username or password but no host')
return None
scanner = NetgearDeviceScanner(host, username, password)
scanner = NetgearDeviceScanner(host, username, password, port)
return scanner if scanner.success_init else None
@@ -38,7 +40,7 @@ def get_scanner(hass, config):
class NetgearDeviceScanner(object):
"""Queries a Netgear wireless router using the SOAP-API."""
def __init__(self, host, username, password):
def __init__(self, host, username, password, port):
"""Initialize the scanner."""
import pynetgear
@@ -49,8 +51,10 @@ class NetgearDeviceScanner(object):
self._api = pynetgear.Netgear()
elif username is None:
self._api = pynetgear.Netgear(password, host)
else:
elif port is None:
self._api = pynetgear.Netgear(password, host, username)
else:
self._api = pynetgear.Netgear(password, host, username, port)
_LOGGER.info("Logging in")

View File

@@ -11,7 +11,7 @@ from collections import defaultdict
import homeassistant.components.mqtt as mqtt
from homeassistant.const import STATE_HOME
from homeassistant.util import convert
from homeassistant.util import convert, slugify
DEPENDENCIES = ['mqtt']
@@ -34,21 +34,39 @@ def setup_scanner(hass, config, see):
"""Setup an OwnTracks tracker."""
max_gps_accuracy = config.get(CONF_MAX_GPS_ACCURACY)
def owntracks_location_update(topic, payload, qos):
"""MQTT message received."""
# Docs on available data:
# http://owntracks.org/booklet/tech/json/#_typelocation
def validate_payload(payload, data_type):
"""Validate OwnTracks payload."""
try:
data = json.loads(payload)
except ValueError:
# If invalid JSON
_LOGGER.error(
'Unable to parse payload as JSON: %s', payload)
return
_LOGGER.error('Unable to parse payload as JSON: %s', payload)
return None
if not isinstance(data, dict) or data.get('_type') != data_type:
_LOGGER.debug('Skipping %s update for following data '
'because of missing or malformatted data: %s',
data_type, data)
return None
if max_gps_accuracy is not None and \
convert(data.get('acc'), float, 0.0) > max_gps_accuracy:
_LOGGER.debug('Skipping %s update because expected GPS '
'accuracy %s is not met: %s',
data_type, max_gps_accuracy, data)
return None
if convert(data.get('acc'), float, 1.0) == 0.0:
_LOGGER.debug('Skipping %s update because GPS accuracy'
'is zero',
data_type)
return None
if (not isinstance(data, dict) or data.get('_type') != 'location') or (
max_gps_accuracy is not None and
convert(data.get('acc'), float, 0.0) > max_gps_accuracy):
return data
def owntracks_location_update(topic, payload, qos):
"""MQTT message received."""
# Docs on available data:
# http://owntracks.org/booklet/tech/json/#_typelocation
data = validate_payload(payload, 'location')
if not data:
return
dev_id, kwargs = _parse_see_args(topic, data)
@@ -65,44 +83,36 @@ def setup_scanner(hass, config, see):
see_beacons(dev_id, kwargs)
def owntracks_event_update(topic, payload, qos):
# pylint: disable=too-many-branches, too-many-statements
"""MQTT event (geofences) received."""
# Docs on available data:
# http://owntracks.org/booklet/tech/json/#_typetransition
try:
data = json.loads(payload)
except ValueError:
# If invalid JSON
_LOGGER.error(
'Unable to parse payload as JSON: %s', payload)
return
if not isinstance(data, dict) or data.get('_type') != 'transition':
data = validate_payload(payload, 'transition')
if not data:
return
if data.get('desc') is None:
_LOGGER.error(
"Location missing from `enter/exit` message - "
"Location missing from `Entering/Leaving` message - "
"please turn `Share` on in OwnTracks app")
return
# OwnTracks uses - at the start of a beacon zone
# to switch on 'hold mode' - ignore this
location = data['desc'].lstrip("-")
location = slugify(data['desc'].lstrip("-"))
if location.lower() == 'home':
location = STATE_HOME
dev_id, kwargs = _parse_see_args(topic, data)
if data['event'] == 'enter':
def enter_event():
"""Execute enter event."""
zone = hass.states.get("zone.{}".format(location))
with LOCK:
if zone is None:
if data['t'] == 'b':
# Not a HA zone, and a beacon so assume mobile
beacons = MOBILE_BEACONS_ACTIVE[dev_id]
if location not in beacons:
beacons.append(location)
_LOGGER.info("Added beacon %s", location)
if zone is None and data.get('t') == 'b':
# Not a HA zone, and a beacon so assume mobile
beacons = MOBILE_BEACONS_ACTIVE[dev_id]
if location not in beacons:
beacons.append(location)
_LOGGER.info("Added beacon %s", location)
else:
# Normal region
regions = REGIONS_ENTERED[dev_id]
@@ -114,7 +124,8 @@ def setup_scanner(hass, config, see):
see(**kwargs)
see_beacons(dev_id, kwargs)
elif data['event'] == 'leave':
def leave_event():
"""Execute leave event."""
with LOCK:
regions = REGIONS_ENTERED[dev_id]
if location in regions:
@@ -146,6 +157,10 @@ def setup_scanner(hass, config, see):
beacons.remove(location)
_LOGGER.info("Remove beacon %s", location)
if data['event'] == 'enter':
enter_event()
elif data['event'] == 'leave':
leave_event()
else:
_LOGGER.error(
'Misformatted mqtt msgs, _type=transition, event=%s',
@@ -171,7 +186,7 @@ def setup_scanner(hass, config, see):
def _parse_see_args(topic, data):
"""Parse the OwnTracks location parameters, into the format see expects."""
parts = topic.split('/')
dev_id = '{}_{}'.format(parts[1], parts[2])
dev_id = slugify('{}_{}'.format(parts[1], parts[2]))
host_name = parts[1]
kwargs = {
'dev_id': dev_id,

View File

@@ -18,7 +18,7 @@ from homeassistant.util import Throttle
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['pysnmp==4.2.5']
REQUIREMENTS = ['pysnmp==4.3.2']
CONF_COMMUNITY = "community"
CONF_BASEOID = "baseoid"
@@ -72,7 +72,7 @@ class SnmpScanner(object):
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
"""Ensure the information from the WAP is up to date.
"""Ensure the information from the device is up to date.
Return boolean if scanning successful.
"""
@@ -88,7 +88,7 @@ class SnmpScanner(object):
return True
def get_snmp_data(self):
"""Fetch MAC addresses from WAP via SNMP."""
"""Fetch MAC addresses from access point via SNMP."""
devices = []
errindication, errstatus, errindex, restable = self.snmp.nextCmd(
@@ -97,9 +97,10 @@ class SnmpScanner(object):
if errindication:
_LOGGER.error("SNMPLIB error: %s", errindication)
return
# pylint: disable=no-member
if errstatus:
_LOGGER.error('SNMP error: %s at %s', errstatus.prettyPrint(),
errindex and restable[-1][int(errindex)-1] or '?')
errindex and restable[int(errindex) - 1][0] or '?')
return
for resrow in restable:

View File

@@ -12,9 +12,11 @@ from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD
from homeassistant.helpers import validate_config
# Unifi package doesn't list urllib3 as a requirement
REQUIREMENTS = ['urllib3', 'unifi==1.2.4']
REQUIREMENTS = ['urllib3', 'unifi==1.2.5']
_LOGGER = logging.getLogger(__name__)
CONF_PORT = 'port'
CONF_SITE_ID = 'site_id'
def get_scanner(hass, config):
@@ -31,6 +33,7 @@ def get_scanner(hass, config):
host = this_config.get(CONF_HOST, 'localhost')
username = this_config.get(CONF_USERNAME)
password = this_config.get(CONF_PASSWORD)
site_id = this_config.get(CONF_SITE_ID, 'default')
try:
port = int(this_config.get(CONF_PORT, 8443))
@@ -39,7 +42,7 @@ def get_scanner(hass, config):
return False
try:
ctrl = Controller(host, username, password, port, 'v4')
ctrl = Controller(host, username, password, port, 'v4', site_id)
except urllib.error.HTTPError as ex:
_LOGGER.error('Failed to connect to unifi: %s', ex)
return False

View File

@@ -9,70 +9,31 @@ loaded before the EVENT_PLATFORM_DISCOVERED is fired.
import logging
import threading
from homeassistant import bootstrap
from homeassistant.const import (
ATTR_DISCOVERED, ATTR_SERVICE, EVENT_HOMEASSISTANT_START,
EVENT_PLATFORM_DISCOVERED)
from homeassistant.const import EVENT_HOMEASSISTANT_START
from homeassistant.helpers.discovery import load_platform, discover
DOMAIN = "discovery"
REQUIREMENTS = ['netdisco==0.6.1']
REQUIREMENTS = ['netdisco==0.7.0']
SCAN_INTERVAL = 300 # seconds
SERVICE_WEMO = 'belkin_wemo'
SERVICE_HUE = 'philips_hue'
SERVICE_CAST = 'google_cast'
SERVICE_NETGEAR = 'netgear_router'
SERVICE_SONOS = 'sonos'
SERVICE_PLEX = 'plex_mediaserver'
SERVICE_SQUEEZEBOX = 'logitech_mediaserver'
SERVICE_PANASONIC_VIERA = 'panasonic_viera'
SERVICE_HANDLERS = {
SERVICE_WEMO: "wemo",
SERVICE_CAST: "media_player",
SERVICE_HUE: "light",
SERVICE_NETGEAR: 'device_tracker',
SERVICE_SONOS: 'media_player',
SERVICE_PLEX: 'media_player',
SERVICE_SQUEEZEBOX: 'media_player',
SERVICE_PANASONIC_VIERA: 'media_player',
SERVICE_NETGEAR: ('device_tracker', None),
SERVICE_WEMO: ('wemo', None),
'philips_hue': ('light', 'hue'),
'google_cast': ('media_player', 'cast'),
'panasonic_viera': ('media_player', 'panasonic_viera'),
'plex_mediaserver': ('media_player', 'plex'),
'roku': ('media_player', 'roku'),
'sonos': ('media_player', 'sonos'),
'logitech_mediaserver': ('media_player', 'squeezebox'),
'directv': ('media_player', 'directv'),
}
def listen(hass, service, callback):
"""Setup listener for discovery of specific service.
Service can be a string or a list/tuple.
"""
if isinstance(service, str):
service = (service,)
else:
service = tuple(service)
def discovery_event_listener(event):
"""Listen for discovery events."""
if event.data[ATTR_SERVICE] in service:
callback(event.data[ATTR_SERVICE], event.data.get(ATTR_DISCOVERED))
hass.bus.listen(EVENT_PLATFORM_DISCOVERED, discovery_event_listener)
def discover(hass, service, discovered=None, component=None, hass_config=None):
"""Fire discovery event. Can ensure a component is loaded."""
if component is not None:
bootstrap.setup_component(hass, component, hass_config)
data = {
ATTR_SERVICE: service
}
if discovered is not None:
data[ATTR_DISCOVERED] = discovered
hass.bus.fire(EVENT_PLATFORM_DISCOVERED, data)
def setup(hass, config):
"""Start a discovery service."""
logger = logging.getLogger(__name__)
@@ -89,20 +50,18 @@ def setup(hass, config):
with lock:
logger.info("Found new service: %s %s", service, info)
component = SERVICE_HANDLERS.get(service)
comp_plat = SERVICE_HANDLERS.get(service)
# We do not know how to handle this service.
if not component:
if not comp_plat:
return
# This component cannot be setup.
if not bootstrap.setup_component(hass, component, config):
return
component, platform = comp_plat
hass.bus.fire(EVENT_PLATFORM_DISCOVERED, {
ATTR_SERVICE: service,
ATTR_DISCOVERED: info
})
if platform is None:
discover(hass, service, info, component, config)
else:
load_platform(hass, component, platform, info, config)
# pylint: disable=unused-argument
def start_discovery(event):

View File

@@ -10,8 +10,10 @@ import re
import threading
import requests
import voluptuous as vol
from homeassistant.helpers import validate_config
import homeassistant.helpers.config_validation as cv
from homeassistant.util import sanitize_filename
DOMAIN = "downloader"
@@ -21,6 +23,12 @@ SERVICE_DOWNLOAD_FILE = "download_file"
ATTR_URL = "url"
ATTR_SUBDIR = "subdir"
SERVICE_DOWNLOAD_FILE_SCHEMA = vol.Schema({
# pylint: disable=no-value-for-parameter
vol.Required(ATTR_URL): vol.Url(),
vol.Optional(ATTR_SUBDIR): cv.string,
})
CONF_DOWNLOAD_DIR = 'download_dir'
@@ -48,10 +56,6 @@ def setup(hass, config):
def download_file(service):
"""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():
"""Download the file."""
try:
@@ -127,7 +131,7 @@ def setup(hass, config):
threading.Thread(target=do_download).start()
hass.services.register(DOMAIN, SERVICE_DOWNLOAD_FILE,
download_file)
hass.services.register(DOMAIN, SERVICE_DOWNLOAD_FILE, download_file,
schema=SERVICE_DOWNLOAD_FILE_SCHEMA)
return True

View File

@@ -0,0 +1,73 @@
"""
A component which allows you to send data to Dweet.io.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/dweet/
"""
import logging
from datetime import timedelta
import voluptuous as vol
from homeassistant.const import EVENT_STATE_CHANGED, STATE_UNKNOWN
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers import state as state_helper
from homeassistant.util import Throttle
_LOGGER = logging.getLogger(__name__)
DOMAIN = "dweet"
DEPENDENCIES = []
REQUIREMENTS = ['dweepy==0.2.0']
CONF_NAME = 'name'
CONF_WHITELIST = 'whitelist'
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=1)
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_NAME): cv.string,
vol.Required(CONF_WHITELIST): cv.string,
}),
}, extra=vol.ALLOW_EXTRA)
# pylint: disable=too-many-locals
def setup(hass, config):
"""Setup the Dweet.io component."""
conf = config[DOMAIN]
name = conf[CONF_NAME]
whitelist = conf.get(CONF_WHITELIST, [])
json_body = {}
def dweet_event_listener(event):
"""Listen for new messages on the bus and sends them to Dweet.io."""
state = event.data.get('new_state')
if state is None or state.state in (STATE_UNKNOWN, '') \
or state.entity_id not in whitelist:
return
try:
_state = state_helper.state_as_number(state)
except ValueError:
_state = state.state
json_body[state.attributes.get('friendly_name')] = _state
send_data(name, json_body)
hass.bus.listen(EVENT_STATE_CHANGED, dweet_event_listener)
return True
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def send_data(name, msg):
"""Send the collected data to Dweet.io."""
import dweepy
try:
dweepy.dweet_for(name, msg)
except dweepy.DweepyError:
_LOGGER.error("Error saving data '%s' to Dweet.io", msg)

View File

@@ -8,21 +8,18 @@ import logging
import os
from datetime import timedelta
from homeassistant import bootstrap
from homeassistant.const import (
ATTR_DISCOVERED, ATTR_SERVICE, CONF_API_KEY, EVENT_PLATFORM_DISCOVERED)
from homeassistant.helpers import discovery
from homeassistant.const import CONF_API_KEY
from homeassistant.loader import get_component
from homeassistant.util import Throttle
DOMAIN = "ecobee"
DISCOVER_THERMOSTAT = "ecobee.thermostat"
DISCOVER_SENSORS = "ecobee.sensor"
NETWORK = None
HOLD_TEMP = 'hold_temp'
REQUIREMENTS = [
'https://github.com/nkgilley/python-ecobee-api/archive/'
'92a2f330cbaf601d0618456fdd97e5a8c42c1c47.zip#python-ecobee==0.0.4']
'4856a704670c53afe1882178a89c209b5f98533d.zip#python-ecobee==0.0.6']
_LOGGER = logging.getLogger(__name__)
@@ -70,23 +67,11 @@ def setup_ecobee(hass, network, config):
configurator = get_component('configurator')
configurator.request_done(_CONFIGURING.pop('ecobee'))
# Ensure component is loaded
bootstrap.setup_component(hass, 'thermostat', config)
bootstrap.setup_component(hass, 'sensor', config)
hold_temp = config[DOMAIN].get(HOLD_TEMP, False)
# Fire thermostat discovery event
hass.bus.fire(EVENT_PLATFORM_DISCOVERED, {
ATTR_SERVICE: DISCOVER_THERMOSTAT,
ATTR_DISCOVERED: {'hold_temp': hold_temp}
})
# Fire sensor discovery event
hass.bus.fire(EVENT_PLATFORM_DISCOVERED, {
ATTR_SERVICE: DISCOVER_SENSORS,
ATTR_DISCOVERED: {}
})
discovery.load_platform(hass, 'thermostat', DOMAIN,
{'hold_temp': hold_temp}, config)
discovery.load_platform(hass, 'sensor', DOMAIN, {}, config)
# pylint: disable=too-few-public-methods

View File

@@ -0,0 +1,117 @@
"""
EnOcean Component.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/EnOcean/
"""
DOMAIN = "enocean"
REQUIREMENTS = ['enocean==0.31']
CONF_DEVICE = "device"
ENOCEAN_DONGLE = None
def setup(hass, config):
"""Setup the EnOcean component."""
global ENOCEAN_DONGLE
serial_dev = config[DOMAIN].get(CONF_DEVICE, "/dev/ttyUSB0")
ENOCEAN_DONGLE = EnOceanDongle(hass, serial_dev)
return True
class EnOceanDongle:
"""Representation of an EnOcean dongle."""
def __init__(self, hass, ser):
"""Initialize the EnOcean dongle."""
from enocean.communicators.serialcommunicator import SerialCommunicator
self.__communicator = SerialCommunicator(port=ser,
callback=self.callback)
self.__communicator.start()
self.__devices = []
def register_device(self, dev):
"""Register another device."""
self.__devices.append(dev)
def send_command(self, command):
"""Send a command from the EnOcean dongle."""
self.__communicator.send(command)
def _combine_hex(self, data): # pylint: disable=no-self-use
"""Combine list of integer values to one big integer."""
output = 0x00
for i, j in enumerate(reversed(data)):
output |= (j << i * 8)
return output
# pylint: disable=too-many-branches
def callback(self, temp):
"""Callback function for EnOcean Device.
This is the callback function called by
python-enocan whenever there is an incoming
packet.
"""
from enocean.protocol.packet import RadioPacket
if isinstance(temp, RadioPacket):
rxtype = None
value = None
if temp.data[6] == 0x30:
rxtype = "wallswitch"
value = 1
elif temp.data[6] == 0x20:
rxtype = "wallswitch"
value = 0
elif temp.data[4] == 0x0c:
rxtype = "power"
value = temp.data[3] + (temp.data[2] << 8)
elif temp.data[2] == 0x60:
rxtype = "switch_status"
if temp.data[3] == 0xe4:
value = 1
elif temp.data[3] == 0x80:
value = 0
elif temp.data[0] == 0xa5 and temp.data[1] == 0x02:
rxtype = "dimmerstatus"
value = temp.data[2]
for device in self.__devices:
if rxtype == "wallswitch" and device.stype == "listener":
if temp.sender == self._combine_hex(device.dev_id):
device.value_changed(value, temp.data[1])
if rxtype == "power" and device.stype == "powersensor":
if temp.sender == self._combine_hex(device.dev_id):
device.value_changed(value)
if rxtype == "power" and device.stype == "switch":
if temp.sender == self._combine_hex(device.dev_id):
if value > 10:
device.value_changed(1)
if rxtype == "switch_status" and device.stype == "switch":
if temp.sender == self._combine_hex(device.dev_id):
device.value_changed(value)
if rxtype == "dimmerstatus" and device.stype == "dimmer":
if temp.sender == self._combine_hex(device.dev_id):
device.value_changed(value)
# pylint: disable=too-few-public-methods
class EnOceanDevice():
"""Parent class for all devices associated with the EnOcean component."""
def __init__(self):
"""Initialize the device."""
ENOCEAN_DONGLE.register_device(self)
self.stype = ""
self.sensorid = [0x00, 0x00, 0x00, 0x00]
# pylint: disable=no-self-use
def send_command(self, data, optional, packet_type):
"""Send a command via the EnOcean dongle."""
from enocean.protocol.packet import Packet
packet = Packet(packet_type, data=data, optional=optional)
ENOCEAN_DONGLE.send_command(packet)

View File

@@ -0,0 +1,210 @@
"""
Support for Envisalink devices.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/envisalink/
"""
import logging
import time
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.helpers.entity import Entity
from homeassistant.components.discovery import load_platform
REQUIREMENTS = ['pyenvisalink==1.0', 'pydispatcher==2.0.5']
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'envisalink'
EVL_CONTROLLER = None
CONF_EVL_HOST = 'host'
CONF_EVL_PORT = 'port'
CONF_PANEL_TYPE = 'panel_type'
CONF_EVL_VERSION = 'evl_version'
CONF_CODE = 'code'
CONF_USERNAME = 'user_name'
CONF_PASS = 'password'
CONF_EVL_KEEPALIVE = 'keepalive_interval'
CONF_ZONEDUMP_INTERVAL = 'zonedump_interval'
CONF_ZONES = 'zones'
CONF_PARTITIONS = 'partitions'
CONF_ZONENAME = 'name'
CONF_ZONETYPE = 'type'
CONF_PARTITIONNAME = 'name'
DEFAULT_PORT = 4025
DEFAULT_EVL_VERSION = 3
DEFAULT_KEEPALIVE = 60
DEFAULT_ZONEDUMP_INTERVAL = 30
DEFAULT_ZONETYPE = 'opening'
SIGNAL_ZONE_UPDATE = 'zones_updated'
SIGNAL_PARTITION_UPDATE = 'partition_updated'
SIGNAL_KEYPAD_UPDATE = 'keypad_updated'
ZONE_SCHEMA = vol.Schema({
vol.Required(CONF_ZONENAME): cv.string,
vol.Optional(CONF_ZONETYPE, default=DEFAULT_ZONETYPE): cv.string})
PARTITION_SCHEMA = vol.Schema({
vol.Required(CONF_PARTITIONNAME): cv.string})
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_EVL_HOST): cv.string,
vol.Required(CONF_PANEL_TYPE):
vol.All(cv.string, vol.In(['HONEYWELL', 'DSC'])),
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASS): cv.string,
vol.Required(CONF_CODE): cv.string,
vol.Optional(CONF_ZONES): {vol.Coerce(int): ZONE_SCHEMA},
vol.Optional(CONF_PARTITIONS): {vol.Coerce(int): PARTITION_SCHEMA},
vol.Optional(CONF_EVL_PORT, default=DEFAULT_PORT):
vol.All(vol.Coerce(int), vol.Range(min=1, max=65535)),
vol.Optional(CONF_EVL_VERSION, default=DEFAULT_EVL_VERSION):
vol.All(vol.Coerce(int), vol.Range(min=3, max=4)),
vol.Optional(CONF_EVL_KEEPALIVE, default=DEFAULT_KEEPALIVE):
vol.All(vol.Coerce(int), vol.Range(min=15)),
vol.Optional(CONF_ZONEDUMP_INTERVAL,
default=DEFAULT_ZONEDUMP_INTERVAL):
vol.All(vol.Coerce(int), vol.Range(min=15)),
}),
}, extra=vol.ALLOW_EXTRA)
# pylint: disable=unused-argument, too-many-function-args, too-many-locals
# pylint: disable=too-many-return-statements
def setup(hass, base_config):
"""Common setup for Envisalink devices."""
from pyenvisalink import EnvisalinkAlarmPanel
from pydispatch import dispatcher
global EVL_CONTROLLER
config = base_config.get(DOMAIN)
_host = config.get(CONF_EVL_HOST)
_port = config.get(CONF_EVL_PORT)
_code = config.get(CONF_CODE)
_panel_type = config.get(CONF_PANEL_TYPE)
_version = config.get(CONF_EVL_VERSION)
_user = config.get(CONF_USERNAME)
_pass = config.get(CONF_PASS)
_keep_alive = config.get(CONF_EVL_KEEPALIVE)
_zone_dump = config.get(CONF_ZONEDUMP_INTERVAL)
_zones = config.get(CONF_ZONES)
_partitions = config.get(CONF_PARTITIONS)
_connect_status = {}
EVL_CONTROLLER = EnvisalinkAlarmPanel(_host,
_port,
_panel_type,
_version,
_user,
_pass,
_zone_dump,
_keep_alive)
def login_fail_callback(data):
"""Callback for when the evl rejects our login."""
_LOGGER.error("The envisalink rejected your credentials.")
_connect_status['fail'] = 1
def connection_fail_callback(data):
"""Network failure callback."""
_LOGGER.error("Could not establish a connection with the envisalink.")
_connect_status['fail'] = 1
def connection_success_callback(data):
"""Callback for a successful connection."""
_LOGGER.info("Established a connection with the envisalink.")
_connect_status['success'] = 1
def zones_updated_callback(data):
"""Handle zone timer updates."""
_LOGGER.info("Envisalink sent a zone update event. Updating zones...")
dispatcher.send(signal=SIGNAL_ZONE_UPDATE,
sender=None,
zone=data)
def alarm_data_updated_callback(data):
"""Handle non-alarm based info updates."""
_LOGGER.info("Envisalink sent new alarm info. Updating alarms...")
dispatcher.send(signal=SIGNAL_KEYPAD_UPDATE,
sender=None,
partition=data)
def partition_updated_callback(data):
"""Handle partition changes thrown by evl (including alarms)."""
_LOGGER.info("The envisalink sent a partition update event.")
dispatcher.send(signal=SIGNAL_PARTITION_UPDATE,
sender=None,
partition=data)
def stop_envisalink(event):
"""Shutdown envisalink connection and thread on exit."""
_LOGGER.info("Shutting down envisalink.")
EVL_CONTROLLER.stop()
def start_envisalink(event):
"""Startup process for the Envisalink."""
EVL_CONTROLLER.start()
for _ in range(10):
if 'success' in _connect_status:
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_envisalink)
return True
elif 'fail' in _connect_status:
return False
else:
time.sleep(1)
_LOGGER.error("Timeout occurred while establishing evl connection.")
return False
EVL_CONTROLLER.callback_zone_timer_dump = zones_updated_callback
EVL_CONTROLLER.callback_zone_state_change = zones_updated_callback
EVL_CONTROLLER.callback_partition_state_change = partition_updated_callback
EVL_CONTROLLER.callback_keypad_update = alarm_data_updated_callback
EVL_CONTROLLER.callback_login_failure = login_fail_callback
EVL_CONTROLLER.callback_login_timeout = connection_fail_callback
EVL_CONTROLLER.callback_login_success = connection_success_callback
_result = start_envisalink(None)
if not _result:
return False
# Load sub-components for Envisalink
if _partitions:
load_platform(hass, 'alarm_control_panel', 'envisalink',
{'partitions': _partitions,
'code': _code}, config)
load_platform(hass, 'sensor', 'envisalink',
{'partitions': _partitions,
'code': _code}, config)
if _zones:
load_platform(hass, 'binary_sensor', 'envisalink',
{'zones': _zones}, config)
return True
class EnvisalinkDevice(Entity):
"""Representation of an Envisalink device."""
def __init__(self, name, info, controller):
"""Initialize the device."""
self._controller = controller
self._info = info
self._name = name
@property
def name(self):
"""Return the name of the device."""
return self._name
@property
def should_poll(self):
"""No polling needed."""
return False

View File

@@ -0,0 +1,174 @@
"""
Support for RSS/Atom feed.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/feedreader/
"""
from datetime import datetime
from logging import getLogger
from os.path import exists
from threading import Lock
import pickle
import voluptuous as vol
from homeassistant.const import EVENT_HOMEASSISTANT_START
from homeassistant.helpers.event import track_utc_time_change
REQUIREMENTS = ['feedparser==5.2.1']
_LOGGER = getLogger(__name__)
DOMAIN = "feedreader"
EVENT_FEEDREADER = "feedreader"
# pylint: disable=no-value-for-parameter
CONFIG_SCHEMA = vol.Schema({
DOMAIN: {
'urls': [vol.Url()],
}
}, extra=vol.ALLOW_EXTRA)
MAX_ENTRIES = 20
# pylint: disable=too-few-public-methods
class FeedManager(object):
"""Abstraction over feedparser module."""
def __init__(self, url, hass, storage):
"""Initialize the FeedManager object, poll every hour."""
self._url = url
self._feed = None
self._hass = hass
self._firstrun = True
self._storage = storage
self._last_entry_timestamp = None
self._has_published_parsed = False
hass.bus.listen_once(EVENT_HOMEASSISTANT_START,
lambda _: self._update())
track_utc_time_change(hass, lambda now: self._update(),
minute=0, second=0)
def _log_no_entries(self):
"""Send no entries log at debug level."""
_LOGGER.debug('No new entries to be published in feed "%s"', self._url)
def _update(self):
"""Update the feed and publish new entries to the event bus."""
import feedparser
_LOGGER.info('Fetching new data from feed "%s"', self._url)
self._feed = feedparser.parse(self._url,
etag=None if not self._feed
else self._feed.get('etag'),
modified=None if not self._feed
else self._feed.get('modified'))
if not self._feed:
_LOGGER.error('Error fetching feed data from "%s"', self._url)
else:
if self._feed.bozo != 0:
_LOGGER.error('Error parsing feed "%s"', self._url)
# Using etag and modified, if there's no new data available,
# the entries list will be empty
elif len(self._feed.entries) > 0:
_LOGGER.debug('%s entri(es) available in feed "%s"',
len(self._feed.entries),
self._url)
if len(self._feed.entries) > MAX_ENTRIES:
_LOGGER.debug('Processing only the first %s entries '
'in feed "%s"', MAX_ENTRIES, self._url)
self._feed.entries = self._feed.entries[0:MAX_ENTRIES]
self._publish_new_entries()
if self._has_published_parsed:
self._storage.put_timestamp(self._url,
self._last_entry_timestamp)
else:
self._log_no_entries()
_LOGGER.info('Fetch from feed "%s" completed', self._url)
def _update_and_fire_entry(self, entry):
"""Update last_entry_timestamp and fire entry."""
# We are lucky, `published_parsed` data available,
# let's make use of it to publish only new available
# entries since the last run
if 'published_parsed' in entry.keys():
self._has_published_parsed = True
self._last_entry_timestamp = max(entry.published_parsed,
self._last_entry_timestamp)
else:
self._has_published_parsed = False
_LOGGER.debug('No `published_parsed` info available '
'for entry "%s"', entry.title)
entry.update({'feed_url': self._url})
self._hass.bus.fire(EVENT_FEEDREADER, entry)
def _publish_new_entries(self):
"""Publish new entries to the event bus."""
new_entries = False
self._last_entry_timestamp = self._storage.get_timestamp(self._url)
if self._last_entry_timestamp:
self._firstrun = False
else:
# Set last entry timestamp as epoch time if not available
self._last_entry_timestamp = \
datetime.utcfromtimestamp(0).timetuple()
for entry in self._feed.entries:
if self._firstrun or (
'published_parsed' in entry.keys() and
entry.published_parsed > self._last_entry_timestamp):
self._update_and_fire_entry(entry)
new_entries = True
else:
_LOGGER.debug('Entry "%s" already processed', entry.title)
if not new_entries:
self._log_no_entries()
self._firstrun = False
class StoredData(object):
"""Abstraction over pickle data storage."""
def __init__(self, data_file):
"""Initialize pickle data storage."""
self._data_file = data_file
self._lock = Lock()
self._cache_outdated = True
self._data = {}
self._fetch_data()
def _fetch_data(self):
"""Fetch data stored into pickle file."""
if self._cache_outdated and exists(self._data_file):
try:
_LOGGER.debug('Fetching data from file %s', self._data_file)
with self._lock, open(self._data_file, 'rb') as myfile:
self._data = pickle.load(myfile) or {}
self._cache_outdated = False
# pylint: disable=bare-except
except:
_LOGGER.error('Error loading data from pickled file %s',
self._data_file)
def get_timestamp(self, url):
"""Return stored timestamp for given url."""
self._fetch_data()
return self._data.get(url)
def put_timestamp(self, url, timestamp):
"""Update timestamp for given url."""
self._fetch_data()
with self._lock, open(self._data_file, 'wb') as myfile:
self._data.update({url: timestamp})
_LOGGER.debug('Overwriting feed "%s" timestamp in storage file %s',
url, self._data_file)
try:
pickle.dump(self._data, myfile)
# pylint: disable=bare-except
except:
_LOGGER.error('Error saving pickled data to %s',
self._data_file)
self._cache_outdated = True
def setup(hass, config):
"""Setup the feedreader component."""
urls = config.get(DOMAIN)['urls']
data_file = hass.config.path("{}.pickle".format(DOMAIN))
storage = StoredData(data_file)
feeds = [FeedManager(url, hass, storage) for url in urls]
return len(feeds) > 0

View File

@@ -1,121 +1,200 @@
"""Handle the frontend for Home Assistant."""
import re
import os
import hashlib
import logging
import os
from . import version, mdi_version
import homeassistant.util as util
from homeassistant.const import URL_ROOT, HTTP_OK
from homeassistant.const import EVENT_HOMEASSISTANT_START
from homeassistant.components import api
from homeassistant.components.http import HomeAssistantView
from .version import FINGERPRINTS
DOMAIN = 'frontend'
DEPENDENCIES = ['api']
URL_PANEL_COMPONENT = '/frontend/panels/{}.html'
URL_PANEL_COMPONENT_FP = '/frontend/panels/{}-{}.html'
STATIC_PATH = os.path.join(os.path.dirname(__file__), 'www_static')
PANELS = {}
INDEX_PATH = os.path.join(os.path.dirname(__file__), 'index.html.template')
# To keep track we don't register a component twice (gives a warning)
_REGISTERED_COMPONENTS = set()
_LOGGER = logging.getLogger(__name__)
FRONTEND_URLS = [
URL_ROOT, '/logbook', '/history', '/map', '/devService', '/devState',
'/devEvent', '/devInfo', '/devTemplate',
re.compile(r'/states(/([a-zA-Z\._\-0-9/]+)|)'),
]
URL_API_BOOTSTRAP = "/api/bootstrap"
def register_built_in_panel(hass, component_name, title=None, icon=None,
url_name=None, config=None):
"""Register a built-in panel."""
# pylint: disable=too-many-arguments
path = 'panels/ha-panel-{}.html'.format(component_name)
_FINGERPRINT = re.compile(r'^(\w+)-[a-z0-9]{32}\.(\w+)$', re.IGNORECASE)
if hass.wsgi.development:
url = ('/static/home-assistant-polymer/panels/'
'{0}/ha-panel-{0}.html'.format(component_name))
else:
url = None # use default url generate mechanism
register_panel(hass, component_name, os.path.join(STATIC_PATH, path),
FINGERPRINTS[path], title, icon, url_name, url, config)
def register_panel(hass, component_name, path, md5=None, title=None, icon=None,
url_name=None, url=None, config=None):
"""Register a panel for the frontend.
component_name: name of the web component
path: path to the HTML of the web component
md5: the md5 hash of the web component (for versioning, optional)
title: title to show in the sidebar (optional)
icon: icon to show next to title in sidebar (optional)
url_name: name to use in the url (defaults to component_name)
url: for the web component (for dev environment, optional)
config: config to be passed into the web component
Warning: this API will probably change. Use at own risk.
"""
# pylint: disable=too-many-arguments
if url_name is None:
url_name = component_name
if url_name in PANELS:
_LOGGER.warning('Overwriting component %s', url_name)
if not os.path.isfile(path):
_LOGGER.error('Panel %s component does not exist: %s',
component_name, path)
return
if md5 is None:
with open(path) as fil:
md5 = hashlib.md5(fil.read().encode('utf-8')).hexdigest()
data = {
'url_name': url_name,
'component_name': component_name,
}
if title:
data['title'] = title
if icon:
data['icon'] = icon
if config is not None:
data['config'] = config
if url is not None:
data['url'] = url
else:
url = URL_PANEL_COMPONENT.format(component_name)
if url not in _REGISTERED_COMPONENTS:
hass.wsgi.register_static_path(url, path)
_REGISTERED_COMPONENTS.add(url)
fprinted_url = URL_PANEL_COMPONENT_FP.format(component_name, md5)
data['url'] = fprinted_url
PANELS[url_name] = data
def setup(hass, config):
"""Setup serving the frontend."""
for url in FRONTEND_URLS:
hass.http.register_path('GET', url, _handle_get_root, False)
hass.wsgi.register_view(BootstrapView)
hass.http.register_path('GET', '/service_worker.js',
_handle_get_service_worker, False)
# Bootstrap API
hass.http.register_path(
'GET', URL_API_BOOTSTRAP, _handle_get_api_bootstrap)
# Static files
hass.http.register_path(
'GET', re.compile(r'/static/(?P<file>[a-zA-Z\._\-0-9/]+)'),
_handle_get_static, False)
hass.http.register_path(
'HEAD', re.compile(r'/static/(?P<file>[a-zA-Z\._\-0-9/]+)'),
_handle_get_static, False)
hass.http.register_path(
'GET', re.compile(r'/local/(?P<file>[a-zA-Z\._\-0-9/]+)'),
_handle_get_local, False)
return True
def _handle_get_api_bootstrap(handler, path_match, data):
"""Return all data needed to bootstrap Home Assistant."""
hass = handler.server.hass
handler.write_json({
'config': hass.config.as_dict(),
'states': hass.states.all(),
'events': api.events_json(hass),
'services': api.services_json(hass),
})
def _handle_get_root(handler, path_match, data):
"""Render the frontend."""
if handler.server.development:
app_url = "home-assistant-polymer/src/home-assistant.html"
else:
app_url = "frontend-{}.html".format(version.VERSION)
# auto login if no password was set, else check api_password param
auth = ('no_password_set' if handler.server.api_password is None
else data.get('api_password', ''))
with open(INDEX_PATH) as template_file:
template_html = template_file.read()
template_html = template_html.replace('{{ app_url }}', app_url)
template_html = template_html.replace('{{ auth }}', auth)
template_html = template_html.replace('{{ icons }}', mdi_version.VERSION)
handler.send_response(HTTP_OK)
handler.write_content(template_html.encode("UTF-8"),
'text/html; charset=utf-8')
def _handle_get_service_worker(handler, path_match, data):
"""Return service worker for the frontend."""
if handler.server.development:
if hass.wsgi.development:
sw_path = "home-assistant-polymer/build/service_worker.js"
else:
sw_path = "service_worker.js"
handler.write_file(os.path.join(os.path.dirname(__file__), 'www_static',
sw_path))
hass.wsgi.register_static_path("/service_worker.js",
os.path.join(STATIC_PATH, sw_path), 0)
hass.wsgi.register_static_path("/robots.txt",
os.path.join(STATIC_PATH, "robots.txt"))
hass.wsgi.register_static_path("/static", STATIC_PATH)
hass.wsgi.register_static_path("/local", hass.config.path('www'))
register_built_in_panel(hass, 'map', 'Map', 'mdi:account-location')
for panel in ('dev-event', 'dev-info', 'dev-service', 'dev-state',
'dev-template'):
register_built_in_panel(hass, panel)
def register_frontend_index(event):
"""Register the frontend index urls.
Done when Home Assistant is started so that all panels are known.
"""
hass.wsgi.register_view(IndexView(
hass, ['/{}'.format(name) for name in PANELS]))
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, register_frontend_index)
return True
def _handle_get_static(handler, path_match, data):
"""Return a static file for the frontend."""
req_file = util.sanitize_path(path_match.group('file'))
class BootstrapView(HomeAssistantView):
"""View to bootstrap frontend with all needed data."""
# Strip md5 hash out
fingerprinted = _FINGERPRINT.match(req_file)
if fingerprinted:
req_file = "{}.{}".format(*fingerprinted.groups())
url = "/api/bootstrap"
name = "api:bootstrap"
path = os.path.join(os.path.dirname(__file__), 'www_static', req_file)
handler.write_file(path)
def get(self, request):
"""Return all data needed to bootstrap Home Assistant."""
return self.json({
'config': self.hass.config.as_dict(),
'states': self.hass.states.all(),
'events': api.events_json(self.hass),
'services': api.services_json(self.hass),
'panels': PANELS,
})
def _handle_get_local(handler, path_match, data):
"""Return a static file from the hass.config.path/www for the frontend."""
req_file = util.sanitize_path(path_match.group('file'))
class IndexView(HomeAssistantView):
"""Serve the frontend."""
path = handler.server.hass.config.path('www', req_file)
url = '/'
name = "frontend:index"
requires_auth = False
extra_urls = ['/states', '/states/<entity:entity_id>']
handler.write_file(path)
def __init__(self, hass, extra_urls):
"""Initialize the frontend view."""
super().__init__(hass)
from jinja2 import FileSystemLoader, Environment
self.extra_urls = self.extra_urls + extra_urls
self.templates = Environment(
loader=FileSystemLoader(
os.path.join(os.path.dirname(__file__), 'templates/')
)
)
def get(self, request, entity_id=None):
"""Serve the index view."""
if self.hass.wsgi.development:
core_url = '/static/home-assistant-polymer/build/core.js'
ui_url = '/static/home-assistant-polymer/src/home-assistant.html'
else:
core_url = '/static/core-{}.js'.format(
FINGERPRINTS['core.js'])
ui_url = '/static/frontend-{}.html'.format(
FINGERPRINTS['frontend.html'])
if request.path == '/':
panel = 'states'
else:
panel = request.path.split('/')[1]
panel_url = PANELS[panel]['url'] if panel != 'states' else ''
# auto login if no password was set
no_auth = 'false' if self.hass.config.api.api_password else 'true'
icons_url = '/static/mdi-{}.html'.format(FINGERPRINTS['mdi.html'])
template = self.templates.get_template('index.html')
# pylint is wrong
# pylint: disable=no-member
resp = template.render(
core_url=core_url, ui_url=ui_url, no_auth=no_auth,
icons_url=icons_url, icons=FINGERPRINTS['mdi.html'],
panel_url=panel_url)
return self.Response(resp, mimetype='text/html')

View File

@@ -1,51 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Home Assistant</title>
<link rel='manifest' href='/static/manifest.json'>
<link rel='icon' href='/static/favicon.ico'>
<link rel='apple-touch-icon' sizes='180x180'
href='/static/favicon-apple-180x180.png'>
<meta name='apple-mobile-web-app-capable' content='yes'>
<meta name='mobile-web-app-capable' content='yes'>
<meta name='viewport' content='width=device-width, user-scalable=no'>
<meta name='theme-color' content='#03a9f4'>
<style>
#ha-init-skeleton {
display: -webkit-flex;
display: flex;
-webkit-flex-direction: column;
-webkit-justify-content: center;
-webkit-align-items: center;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin-bottom: 123px;
}
</style>
<link rel='import' href='/static/{{ app_url }}' async>
</head>
<body fullbleed>
<div id='ha-init-skeleton'><img src='/static/favicon-192x192.png' height='192'></div>
<script>
var webComponentsSupported = ('registerElement' in document &&
'import' in document.createElement('link') &&
'content' in document.createElement('template'))
if (!webComponentsSupported) {
var script = document.createElement('script')
script.async = true
script.src = '/static/webcomponents-lite.min.js'
document.head.appendChild(script)
}
</script>
<home-assistant auth='{{ auth }}' icons='{{ icons }}'></home-assistant>
</body>
</html>

View File

@@ -1,2 +0,0 @@
"""DO NOT MODIFY. Auto-generated by update_mdi script."""
VERSION = "df49e6b7c930eb39b42ff1909712e95e"

View File

@@ -0,0 +1,107 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Home Assistant</title>
<link rel='manifest' href='/static/manifest.json'>
<link rel='icon' href='/static/icons/favicon.ico'>
<link rel='apple-touch-icon' sizes='180x180'
href='/static/icons/favicon-apple-180x180.png'>
<meta name='apple-mobile-web-app-capable' content='yes'>
<meta name="msapplication-square70x70logo" content="/static/icons/tile-win-70x70.png"/>
<meta name="msapplication-square150x150logo" content="/static/icons/tile-win-150x150.png"/>
<meta name="msapplication-wide310x150logo" content="/static/icons/tile-win-310x150.png"/>
<meta name="msapplication-square310x310logo" content="/static/icons/tile-win-310x310.png"/>
<meta name="msapplication-TileColor" content="#3fbbf4ff"/>
<meta name='mobile-web-app-capable' content='yes'>
<meta name='viewport' content='width=device-width, user-scalable=no'>
<meta name='theme-color' content='#03a9f4'>
<style>
body {
font-family: 'Roboto', 'Noto', sans-serif;
font-weight: 300;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
#ha-init-skeleton {
display: -webkit-flex;
display: flex;
-webkit-flex-direction: column;
-webkit-justify-content: center;
-webkit-align-items: center;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin-bottom: 83px;
font-family: Roboto, sans-serif;
font-size: 0pt;
transition: font-size 2s;
}
#ha-init-skeleton paper-spinner {
height: 28px;
margin-top: 16px;
}
#ha-init-skeleton a {
color: #03A9F4;
text-decoration: none;
font-weight: bold;
}
#ha-init-skeleton.error {
font-size: 16px;
}
#ha-init-skeleton.error img,
#ha-init-skeleton.error paper-spinner {
display: none;
}
</style>
<script>
function initError() {
document
.getElementById('ha-init-skeleton')
.classList.add('error');
};
window.noAuth = {{ no_auth }};
window.Polymer = {lazyRegister: true, useNativeCSSProperties: true, dom: 'shady'};
</script>
</head>
<body>
<div id='ha-init-skeleton'>
<img src='/static/icons/favicon-192x192.png' height='192'>
<paper-spinner active></paper-spinner>
Home Assistant had trouble<br>connecting to the server.<br><br><a href='/'>TRY AGAIN</a>
</div>
<home-assistant icons='{{ icons }}'></home-assistant>
{# <script src='/static/home-assistant-polymer/build/_demo_data_compiled.js'></script> #}
<script src='{{ core_url }}'></script>
<link rel='import' href='{{ ui_url }}' onerror='initError()'>
{% if panel_url %}
<link rel='import' href='{{ panel_url }}' onerror='initError()' async>
{% endif %}
<link rel='import' href='{{ icons_url }}' async>
<script>
var webComponentsSupported = (
'registerElement' in document &&
'import' in document.createElement('link') &&
'content' in document.createElement('template'));
if (!webComponentsSupported) {
var e = document.createElement('script');
e.async = true;
e.onerror = initError;
e.src = '/static/webcomponents-lite.min.js';
document.head.appendChild(e);
}
</script>
</body>
</html>

View File

@@ -1,2 +1,16 @@
"""DO NOT MODIFY. Auto-generated by build_frontend script."""
VERSION = "c2932592a6946e955ddc46f31409b81f"
"""DO NOT MODIFY. Auto-generated by script/fingerprint_frontend."""
FINGERPRINTS = {
"core.js": "bc78f21f5280217aa2c78dfc5848134f",
"frontend.html": "6c52e8cb797bafa3124d936af5ce1fcc",
"mdi.html": "f6c6cc64c2ec38a80e91f801b41119b3",
"panels/ha-panel-dev-event.html": "20327fbd4fb0370aec9be4db26fd723f",
"panels/ha-panel-dev-info.html": "28e0a19ceb95aa714fd53228d9983a49",
"panels/ha-panel-dev-service.html": "85fd5b48600418bb5a6187539a623c38",
"panels/ha-panel-dev-state.html": "25d84d7b7aea779bb3bb3cd6c155f8d9",
"panels/ha-panel-dev-template.html": "d079abf61cff9690f828cafb0d29b7e7",
"panels/ha-panel-history.html": "7e051b5babf5653b689e0107ea608acb",
"panels/ha-panel-iframe.html": "7bdb564a8f37971d7b89b718935810a1",
"panels/ha-panel-logbook.html": "9b285357b0b2d82ee282e634f4e1cab2",
"panels/ha-panel-map.html": "dfe141a3fa5fd403be554def1dd039a9"
}

File diff suppressed because one or more lines are too long

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