Compare commits

..

205 Commits

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
245 changed files with 6445 additions and 2565 deletions
+17 -1
View File
@@ -3,6 +3,7 @@ source = homeassistant
omit =
homeassistant/__main__.py
homeassistant/scripts/*.py
# omit pieces of code that rely on external devices being present
homeassistant/components/apcupsd.py
@@ -87,8 +88,14 @@ omit =
homeassistant/components/homematic.py
homeassistant/components/*/homematic.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
@@ -120,23 +127,28 @@ omit =
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
@@ -144,6 +156,7 @@ omit =
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
@@ -154,8 +167,8 @@ omit =
homeassistant/components/notify/aws_sqs.py
homeassistant/components/notify/free_mobile.py
homeassistant/components/notify/gntp.py
homeassistant/components/notify/googlevoice.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
@@ -185,6 +198,7 @@ omit =
homeassistant/components/sensor/glances.py
homeassistant/components/sensor/google_travel_time.py
homeassistant/components/sensor/gtfs.py
homeassistant/components/sensor/imap.py
homeassistant/components/sensor/lastfm.py
homeassistant/components/sensor/loopenergy.py
homeassistant/components/sensor/neurio_energy.py
@@ -209,6 +223,7 @@ 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
@@ -220,6 +235,7 @@ omit =
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
+1 -4
View File
@@ -15,7 +15,7 @@
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:
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]).
@@ -26,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/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
+3 -2
View File
@@ -7,9 +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/deps
tests/config/home-assistant.log
tests/testing_config/deps
tests/testing_config/home-assistant.log
# Hide sublime text stuff
*.sublime-project
+5
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
+1 -75
View File
@@ -9,79 +9,5 @@ The process is straight-forward.
- Ensure tests work.
- 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/home-assistant/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/home-assistant/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/home-assistant/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/home-assistant/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 `script/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/home-assistant/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/home-assistant/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.
+2 -1
View File
@@ -20,7 +20,8 @@ RUN script/build_python_openzwave && \
COPY requirements_all.txt requirements_all.txt
# certifi breaks Debian based installs
RUN pip3 install --no-cache-dir -r requirements_all.txt && pip3 uninstall -y certifi
RUN pip3 install --no-cache-dir -r requirements_all.txt && pip3 uninstall -y certifi && \
pip3 install mysqlclient psycopg2
# Copy source
COPY . .
+7 -7
View File
@@ -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,10 +75,10 @@ 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/>`__
and the `section on creating your own
+32 -41
View File
@@ -7,6 +7,9 @@ homeassistant:
latitude: 32.87336
longitude: 117.22743
# Impacts weather/sunrise data
elevation: 665
# C for Celsius, F for Fahrenheit
temperature_unit: C
@@ -22,8 +25,8 @@ http:
# Set to 1 to enable development mode
# development: 1
# Enable the frontend
frontend:
# enable the frontend
light:
# platform: hue
@@ -33,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:
@@ -74,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:
@@ -105,6 +104,7 @@ browser:
keyboard:
# https://home-assistant.io/getting-started/automation/
automation:
- alias: 'Rule 1 Light on in the evening'
trigger:
@@ -126,7 +126,6 @@ automation:
entity_id: group.living_room
- alias: 'Rule 2 - Away Mode'
trigger:
- platform: state
entity_id: group.all_devices
@@ -139,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
@@ -149,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
@@ -166,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
@@ -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
@@ -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>
+27 -83
View File
@@ -7,7 +7,8 @@ import platform
import subprocess
import sys
import threading
import time
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,7 +29,7 @@ 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, 'deps')
@@ -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(
@@ -110,22 +111,14 @@ def get_arguments():
type=int,
default=None,
help='Enables daily log rotation and keeps up to the specified days')
parser.add_argument(
'--install-osx',
action='store_true',
help='Installs as a service on OS X and loads on boot.')
parser.add_argument(
'--uninstall-osx',
action='store_true',
help='Uninstalls from OS X.')
parser.add_argument(
'--restart-osx',
action='store_true',
help='Restarts on OS X.')
parser.add_argument(
'--runner',
action='store_true',
help='On restart exit with code {}'.format(RESTART_EXIT_CODE))
parser.add_argument(
'--script',
nargs=argparse.REMAINDER,
help='Run one of the embedded scripts')
if os.name == "posix":
parser.add_argument(
'--daemon',
@@ -134,12 +127,12 @@ def get_arguments():
arguments = parser.parse_args()
if os.name != "posix" or arguments.debug or arguments.runner:
arguments.daemon = False
setattr(arguments, 'daemon', False)
return arguments
def daemonize():
def daemonize() -> None:
"""Move current process to daemon process."""
# Create first fork
pid = os.fork()
@@ -164,7 +157,7 @@ def daemonize():
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:
@@ -186,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:
@@ -196,47 +189,7 @@ def write_pid(pid_file):
sys.exit(1)
def install_osx():
"""Setup to run via launchd on OS X."""
with os.popen('which hass') as inp:
hass_path = inp.read().strip()
with os.popen('whoami') as inp:
user = inp.read().strip()
cwd = os.path.dirname(__file__)
template_path = os.path.join(cwd, 'startup', 'launchd.plist')
with open(template_path, 'r', encoding='utf-8') as inp:
plist = inp.read()
plist = plist.replace("$HASS_PATH$", hass_path)
plist = plist.replace("$USER$", user)
path = os.path.expanduser("~/Library/LaunchAgents/org.homeassistant.plist")
try:
with open(path, 'w', encoding='utf-8') as outp:
outp.write(plist)
except IOError as err:
print('Unable to write to ' + path, err)
return
os.popen('launchctl load -w -F ' + path)
print("Home Assistant has been installed. \
Open it here: http://localhost:8123")
def uninstall_osx():
"""Unload from launchd on OS X."""
path = os.path.expanduser("~/Library/LaunchAgents/org.homeassistant.plist")
os.popen('launchctl unload ' + path)
print("Home Assistant has been uninstalled.")
def closefds_osx(min_fd, max_fd):
def closefds_osx(min_fd: int, max_fd: int) -> None:
"""Make sure file descriptors get closed when we restart.
We cannot call close on guarded fds, and we cannot easily test which fds
@@ -254,7 +207,7 @@ def closefds_osx(min_fd, max_fd):
pass
def cmdline():
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])
@@ -262,16 +215,17 @@ def cmdline():
return [sys.executable] + [arg for arg in sys.argv if arg != '--daemon']
def setup_and_run_hass(config_dir, args):
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:
args = cmdline() + ['--runner']
nt_args = cmdline() + ['--runner']
while True:
try:
subprocess.check_call(args)
subprocess.check_call(nt_args)
sys.exit(0)
except subprocess.CalledProcessError as exc:
if exc.returncode != RESTART_EXIT_CODE:
@@ -293,7 +247,7 @@ def setup_and_run_hass(config_dir, args):
log_rotate_days=args.log_rotate_days)
if hass is None:
return
return None
if args.open_ui:
def open_browser(event):
@@ -310,7 +264,7 @@ def setup_and_run_hass(config_dir, args):
return exit_code
def try_to_restart():
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.
@@ -352,29 +306,19 @@ def try_to_restart():
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)
+35 -13
View File
@@ -7,6 +7,9 @@ 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
@@ -30,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
@@ -53,7 +57,8 @@ 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
@@ -67,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
@@ -147,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
@@ -169,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)
@@ -202,9 +215,14 @@ def prepare_setup_platform(hass, config, domain, platform_name):
# pylint: disable=too-many-branches, too-many-statements, too-many-arguments
def from_config_dict(config, hass=None, config_dir=None, enable_log=True,
verbose=False, skip_pip=False,
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.
@@ -266,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, 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,
@@ -292,7 +313,8 @@ def from_config_file(config_path, hass=None, verbose=False, skip_pip=True,
skip_pip=skip_pip)
def enable_logging(hass, verbose=False, log_rotate_days=None):
def enable_logging(hass: core.HomeAssistant, verbose: bool=False,
log_rotate_days=None) -> None:
"""Setup the logging."""
logging.basicConfig(level=logging.INFO)
fmt = ("%(log_color)s%(asctime)s %(levelname)s (%(threadName)s) "
@@ -343,12 +365,12 @@ def enable_logging(hass, verbose=False, log_rotate_days=None):
'Unable to setup error log %s (access denied)', err_log_path)
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):
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'))
@@ -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
@@ -19,7 +19,7 @@ _LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Perform the setup for Envisalink sensor devices."""
"""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])
@@ -33,7 +33,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
class EnvisalinkBinarySensor(EnvisalinkDevice, BinarySensorDevice):
"""Representation of an envisalink Binary Sensor."""
"""Representation of an Envisalink binary sensor."""
# pylint: disable=too-many-arguments
def __init__(self, zone_number, zone_name, zone_type, info, controller):
@@ -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
@@ -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')
@@ -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,30 +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] = utc_time.isoformat()
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."""
+19 -4
View File
@@ -5,6 +5,7 @@ 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
@@ -12,19 +13,20 @@ from homeassistant.const import CONF_ACCESS_TOKEN
from homeassistant.helpers.entity import Entity
from homeassistant.loader import get_component
REQUIREMENTS = ['python-wink==0.7.8', 'pubnub==3.7.8']
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:
@@ -42,9 +44,12 @@ 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(WinkDevice, BinarySensorDevice, Entity):
"""Representation of a Wink sensor."""
"""Representation of a Wink binary sensor."""
def __init__(self, wink):
"""Initialize the Wink binary sensor."""
@@ -53,6 +58,14 @@ class WinkBinarySensorDevice(WinkDevice, BinarySensorDevice, Entity):
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."""
@@ -62,6 +75,8 @@ class WinkBinarySensorDevice(WinkDevice, 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()
@@ -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))
])
@@ -8,6 +8,7 @@ import logging
import datetime
import homeassistant.util.dt as dt_util
from homeassistant.helpers.event import track_point_in_time
from homeassistant.helpers.entity import Entity
from homeassistant.components import zwave
from homeassistant.components.binary_sensor import (
DOMAIN,
@@ -31,7 +32,7 @@ DEVICE_MAPPINGS = {
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Z-Wave platform for sensors."""
"""Setup the Z-Wave platform for binary sensors."""
if discovery_info is None or zwave.NETWORK is None:
return
@@ -61,7 +62,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
add_devices([ZWaveBinarySensor(value, None)])
class ZWaveBinarySensor(BinarySensorDevice, zwave.ZWaveDeviceEntity):
class ZWaveBinarySensor(BinarySensorDevice, zwave.ZWaveDeviceEntity, Entity):
"""Representation of a binary sensor within Z-Wave."""
def __init__(self, value, sensor_class):
@@ -93,11 +94,12 @@ class ZWaveBinarySensor(BinarySensorDevice, zwave.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):
+2 -1
View File
@@ -13,7 +13,8 @@ ATTR_URL = 'url'
ATTR_URL_DEFAULT = 'https://www.google.com'
SERVICE_BROWSE_URL_SCHEMA = vol.Schema({
vol.Required(ATTR_URL, default=ATTR_URL_DEFAULT): vol.Url,
# pylint: disable=no-value-for-parameter
vol.Required(ATTR_URL, default=ATTR_URL_DEFAULT): vol.Url(),
})
+1 -1
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."""
+1 -1
View File
@@ -89,7 +89,7 @@ class WelcomeData(object):
"""Return all module available on the API as a list."""
self.update()
if not self.home:
for home in self.welcomedata.cameras.keys():
for home in self.welcomedata.cameras:
for camera in self.welcomedata.cameras[home].values():
self.camera_names.append(camera['name'])
else:
+3 -3
View File
@@ -27,7 +27,7 @@ SERVICE_PROCESS_SCHEMA = vol.Schema({
REGEX_TURN_COMMAND = re.compile(r'turn (?P<name>(?: |\w)+) (?P<command>\w+)')
REQUIREMENTS = ['fuzzywuzzy==0.10.0']
REQUIREMENTS = ['fuzzywuzzy==0.11.0']
def setup(hass, config):
@@ -67,8 +67,8 @@ def setup(hass, config):
}, blocking=True)
else:
logger.error(
'Got unsupported command %s from text %s', command, text)
logger.error('Got unsupported command %s from text %s',
command, text)
hass.services.register(DOMAIN, SERVICE_PROCESS, process,
schema=SERVICE_PROCESS_SCHEMA)
@@ -377,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):
@@ -62,8 +62,9 @@ def get_scanner(hass, config):
_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 public key or password must be provided")
_LOGGER.error('Either a private key or password must be provided')
return None
scanner = AsusWrtDeviceScanner(config[DOMAIN])
@@ -83,8 +84,8 @@ class AsusWrtDeviceScanner(object):
"""Initialize the scanner."""
self.host = config[CONF_HOST]
self.username = str(config[CONF_USERNAME])
self.password = str(config.get(CONF_PASSWORD, ""))
self.pub_key = str(config.get('pub_key', ""))
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')
@@ -120,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
@@ -138,12 +139,12 @@ class AsusWrtDeviceScanner(object):
try:
ssh = pxssh.pxssh()
if self.pub_key:
ssh.login(self.host, self.username, ssh_key=self.pub_key)
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 public key specified')
_LOGGER.error('No password or private key specified')
return None
ssh.sendline(_IP_NEIGH_CMD)
ssh.prompt()
@@ -195,16 +196,16 @@ class AsusWrtDeviceScanner(object):
telnet.write('exit\n'.encode('ascii'))
return AsusWrtResult(neighbors, leases_result, arp_result)
except EOFError:
_LOGGER.error("Unexpected response from router")
_LOGGER.error('Unexpected response from router')
return None
except ConnectionRefusedError:
_LOGGER.error("Connection refused by router, is telnet enabled?")
_LOGGER.error('Connection refused by router, is telnet enabled?')
return None
except socket.gaierror as exc:
_LOGGER.error("Socket exception: %s", exc)
_LOGGER.error('Socket exception: %s', exc)
return None
except OSError as exc:
_LOGGER.error("OSError: %s", exc)
_LOGGER.error('OSError: %s', exc)
return None
def get_asuswrt_data(self):
@@ -232,7 +233,7 @@ class AsusWrtDeviceScanner(object):
match = _WL_REGEX.search(lease.decode('utf-8'))
if not match:
_LOGGER.warning("Could not parse wl row: %s", lease)
_LOGGER.warning('Could not parse wl row: %s', lease)
continue
host = ''
@@ -242,7 +243,7 @@ class AsusWrtDeviceScanner(object):
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)
_LOGGER.warning('Could not parse arp row: %s', arp)
continue
devices[arp_match.group('ip')] = {
@@ -256,7 +257,7 @@ class AsusWrtDeviceScanner(object):
match = _LEASES_REGEX.search(lease.decode('utf-8'))
if not match:
_LOGGER.warning("Could not parse lease row: %s", lease)
_LOGGER.warning('Could not parse lease row: %s', lease)
continue
# For leases where the client doesn't set a hostname, ensure it
@@ -275,7 +276,7 @@ class AsusWrtDeviceScanner(object):
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')
@@ -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.8.3']
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
@@ -16,6 +16,7 @@ REQUIREMENTS = ['urllib3', 'unifi==1.2.5']
_LOGGER = logging.getLogger(__name__)
CONF_PORT = 'port'
CONF_SITE_ID = 'site_id'
def get_scanner(hass, config):
@@ -32,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))
@@ -40,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
+2 -1
View File
@@ -13,7 +13,7 @@ from homeassistant.const import EVENT_HOMEASSISTANT_START
from homeassistant.helpers.discovery import load_platform, discover
DOMAIN = "discovery"
REQUIREMENTS = ['netdisco==0.6.7']
REQUIREMENTS = ['netdisco==0.7.0']
SCAN_INTERVAL = 300 # seconds
@@ -30,6 +30,7 @@ SERVICE_HANDLERS = {
'roku': ('media_player', 'roku'),
'sonos': ('media_player', 'sonos'),
'logitech_mediaserver': ('media_player', 'squeezebox'),
'directv': ('media_player', 'directv'),
}
+2 -1
View File
@@ -24,7 +24,8 @@ ATTR_URL = "url"
ATTR_SUBDIR = "subdir"
SERVICE_DOWNLOAD_FILE_SCHEMA = vol.Schema({
vol.Required(ATTR_URL): vol.Url,
# pylint: disable=no-value-for-parameter
vol.Required(ATTR_URL): vol.Url(),
vol.Optional(ATTR_SUBDIR): cv.string,
})
+3 -3
View File
@@ -149,7 +149,7 @@ def setup(hass, base_config):
EVL_CONTROLLER.stop()
def start_envisalink(event):
"""Startup process for the envisalink."""
"""Startup process for the Envisalink."""
EVL_CONTROLLER.start()
for _ in range(10):
if 'success' in _connect_status:
@@ -175,7 +175,7 @@ def setup(hass, base_config):
if not _result:
return False
# Load sub-components for envisalink
# Load sub-components for Envisalink
if _partitions:
load_platform(hass, 'alarm_control_panel', 'envisalink',
{'partitions': _partitions,
@@ -191,7 +191,7 @@ def setup(hass, base_config):
class EnvisalinkDevice(Entity):
"""Representation of an envisalink devicetity."""
"""Representation of an Envisalink device."""
def __init__(self, name, info, controller):
"""Initialize the device."""
+128 -29
View File
@@ -1,37 +1,130 @@
"""Handle the frontend for Home Assistant."""
import hashlib
import logging
import os
from . import version, mdi_version
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 = {}
# To keep track we don't register a component twice (gives a warning)
_REGISTERED_COMPONENTS = set()
_LOGGER = logging.getLogger(__name__)
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)
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."""
hass.wsgi.register_view(IndexView)
hass.wsgi.register_view(BootstrapView)
www_static_path = os.path.join(os.path.dirname(__file__), 'www_static')
if hass.wsgi.development:
sw_path = "home-assistant-polymer/build/service_worker.js"
else:
sw_path = "service_worker.js"
hass.wsgi.register_static_path(
"/service_worker.js",
os.path.join(www_static_path, sw_path),
0
)
hass.wsgi.register_static_path(
"/robots.txt",
os.path.join(www_static_path, "robots.txt")
)
hass.wsgi.register_static_path("/static", www_static_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
@@ -48,6 +141,7 @@ class BootstrapView(HomeAssistantView):
'states': self.hass.states.all(),
'events': api.events_json(self.hass),
'services': api.services_json(self.hass),
'panels': PANELS,
})
@@ -57,16 +151,15 @@ class IndexView(HomeAssistantView):
url = '/'
name = "frontend:index"
requires_auth = False
extra_urls = ['/logbook', '/history', '/map', '/devService', '/devState',
'/devEvent', '/devInfo', '/devTemplate',
'/states', '/states/<entity:entity_id>']
extra_urls = ['/states', '/states/<entity:entity_id>']
def __init__(self, hass):
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/')
@@ -76,26 +169,32 @@ class IndexView(HomeAssistantView):
def get(self, request, entity_id=None):
"""Serve the index view."""
if self.hass.wsgi.development:
core_url = 'home-assistant-polymer/build/_core_compiled.js'
ui_url = 'home-assistant-polymer/src/home-assistant.html'
core_url = '/static/home-assistant-polymer/build/core.js'
ui_url = '/static/home-assistant-polymer/src/home-assistant.html'
else:
core_url = 'core-{}.js'.format(version.CORE)
ui_url = 'frontend-{}.html'.format(version.UI)
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
if self.hass.config.api.api_password is None:
auth = 'true'
else:
auth = 'false'
icons_url = 'mdi-{}.html'.format(mdi_version.VERSION)
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, auth=auth,
icons_url=icons_url, icons=mdi_version.VERSION)
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')
@@ -1,2 +0,0 @@
"""DO NOT MODIFY. Auto-generated by update_mdi script."""
VERSION = "9ee3d4466a65bef35c2c8974e91b37c0"
@@ -5,19 +5,26 @@
<title>Home Assistant</title>
<link rel='manifest' href='/static/manifest.json'>
<link rel='icon' href='/static/favicon.ico'>
<link rel='icon' href='/static/icons/favicon.ico'>
<link rel='apple-touch-icon' sizes='180x180'
href='/static/favicon-apple-180x180.png'>
href='/static/icons/favicon-apple-180x180.png'>
<meta name='apple-mobile-web-app-capable' content='yes'>
<meta name="msapplication-square70x70logo" content="/static/tile-win-70x70.png"/>
<meta name="msapplication-square150x150logo" content="/static/tile-win-150x150.png"/>
<meta name="msapplication-wide310x150logo" content="/static/tile-win-310x150.png"/>
<meta name="msapplication-square310x310logo" content="/static/tile-win-310x310.png"/>
<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;
@@ -64,32 +71,36 @@
document
.getElementById('ha-init-skeleton')
.classList.add('error');
}
window.noAuth = {{ auth }}
};
window.noAuth = {{ no_auth }};
window.Polymer = {lazyRegister: true, useNativeCSSProperties: true, dom: 'shady'};
</script>
</head>
<body fullbleed>
<body>
<div id='ha-init-skeleton'>
<img src='/static/favicon-192x192.png' height='192'>
<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='/static/{{ core_url }}'></script>
<link rel='import' href='/static/{{ ui_url }}' onerror='initError()' async>
<link rel='import' href='/static/{{ icons_url }}' async>
<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 script = document.createElement('script')
script.async = true
script.onerror = initError;
script.src = '/static/webcomponents-lite.min.js'
document.head.appendChild(script)
var e = document.createElement('script');
e.async = true;
e.onerror = initError;
e.src = '/static/webcomponents-lite.min.js';
document.head.appendChild(e);
}
</script>
</body>
+16 -3
View File
@@ -1,3 +1,16 @@
"""DO NOT MODIFY. Auto-generated by build_frontend script."""
CORE = "db0bb387f4d3bcace002d62b94baa348"
UI = "5b306b7e7d36799b7b67f592cbe94703"
"""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
File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 93 KiB

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 9.2 KiB

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

@@ -7,22 +7,22 @@
"background_color": "#FFFFFF",
"icons": [
{
"src": "/static/favicon-192x192.png",
"src": "/static/icons/favicon-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/static/favicon-384x384.png",
"src": "/static/icons/favicon-384x384.png",
"sizes": "384x384",
"type": "image/png"
},
{
"src": "/static/favicon-512x512.png",
"src": "/static/icons/favicon-512x512.png",
"sizes": "512x512",
"type": "image/png"
},
{
"src": "/static/favicon-1024x1024.png",
"src": "/static/icons/favicon-1024x1024.png",
"sizes": "1024x1024",
"type": "image/png"
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,2 @@
<html><head><meta charset="UTF-8"></head><body><dom-module id="ha-panel-dev-info"><template><style is="custom-style" include="iron-positioning"></style><style>.content{margin-top:64px;padding:24px;background-color:#fff;-ms-user-select:initial;-webkit-user-select:initial;-moz-user-select:initial}.about{text-align:center;line-height:2em}.version{@apply(--paper-font-headline)}.develop{@apply(--paper-font-subhead)}.about a{color:var(--dark-primary-color)}.error-log-intro{margin-top:16px;border-top:1px solid var(--light-primary-color);padding-top:16px}paper-icon-button{float:right}.error-log{@apply(--paper-font-code1)
clear: both;white-space:pre-wrap}</style><partial-base narrow="[[narrow]]" show-menu="[[showMenu]]"><span header-title="">About</span><div class="content fit"><div class="about"><p class="version"><a href="https://home-assistant.io"><img src="/static/icons/favicon-192x192.png" height="192"></a><br>Home Assistant<br>[[hassVersion]]</p><p class="develop"><a href="https://home-assistant.io/developers/credits/" target="_blank">Developed by a bunch of awesome people.</a></p><p>Published under the MIT license<br>Source: <a href="https://github.com/balloob/home-assistant" target="_blank">server</a><a href="https://github.com/balloob/home-assistant-polymer" target="_blank">frontend-ui</a><a href="https://github.com/balloob/home-assistant-js" target="_blank">frontend-core</a></p><p>Built using <a href="https://www.python.org">Python 3</a>, <a href="https://www.polymer-project.org" target="_blank">Polymer [[polymerVersion]]</a>, <a href="https://optimizely.github.io/nuclear-js/" target="_blank">NuclearJS [[nuclearVersion]]</a><br>Icons by <a href="https://www.google.com/design/icons/" target="_blank">Google</a> and <a href="https://MaterialDesignIcons.com" target="_blank">MaterialDesignIcons.com</a>.</p></div><p class="error-log-intro">The following errors have been logged this session:<paper-icon-button icon="mdi:refresh" on-tap="refreshErrorLog"></paper-icon-button></p><div class="error-log">[[errorLog]]</div></div></partial-base></template></dom-module><script>Polymer({is:"ha-panel-dev-info",behaviors:[window.hassBehavior],properties:{hass:{type:Object},narrow:{type:Boolean,value:!1},showMenu:{type:Boolean,value:!1},hassVersion:{type:String,bindNuclear:function(r){return r.configGetters.serverVersion}},polymerVersion:{type:String,value:Polymer.version},nuclearVersion:{type:String,value:"1.3.0"},errorLog:{type:String,value:""}},attached:function(){this.refreshErrorLog()},refreshErrorLog:function(r){r&&r.preventDefault(),this.errorLog="Loading error log…",this.hass.errorLogActions.fetchErrorLog().then(function(r){this.errorLog=r||"No errors have been reported."}.bind(this))}})</script></body></html>
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
<html><head><meta charset="UTF-8"></head><body><dom-module id="ha-panel-iframe"><template><style>iframe{border:0;width:100%;height:100%}</style><partial-base narrow="[[narrow]]" show-menu="[[showMenu]]"><span header-title="">[[panel.title]]</span><iframe src="[[panel.config.url]]" sandbox="allow-forms allow-popups allow-pointer-lock allow-same-origin allow-scripts"></iframe></partial-base></template></dom-module><script>Polymer({is:"ha-panel-iframe",properties:{panel:{type:Object},narrow:{type:Boolean},showMenu:{type:Boolean}}})</script></body></html>
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -57,7 +57,7 @@ class RPiGPIOGarageDoor(GarageDoorDevice):
self._relay_pin = relay_pin
self._state_pin = state_pin
rpi_gpio.setup_output(self._relay_pin)
rpi_gpio.setup_input(self._state_pin, 'DOWN')
rpi_gpio.setup_input(self._state_pin, 'UP')
rpi_gpio.write_output(self._relay_pin, True)
@property
+1 -1
View File
@@ -10,7 +10,7 @@ from homeassistant.components.garage_door import GarageDoorDevice
from homeassistant.components.wink import WinkDevice
from homeassistant.const import CONF_ACCESS_TOKEN
REQUIREMENTS = ['python-wink==0.7.8', 'pubnub==3.7.8']
REQUIREMENTS = ['python-wink==0.7.11', 'pubnub==3.8.2']
def setup_platform(hass, config, add_devices, discovery_info=None):
@@ -44,7 +44,6 @@ class ZwaveGarageDoor(zwave.ZWaveDeviceEntity, GarageDoorDevice):
from openzwave.network import ZWaveNetwork
from pydispatch import dispatcher
ZWaveDeviceEntity.__init__(self, value, DOMAIN)
self._node = value.node
self._state = value.data
dispatcher.connect(
self.value_changed, ZWaveNetwork.SIGNAL_VALUE_CHANGED)
@@ -53,7 +52,7 @@ class ZwaveGarageDoor(zwave.ZWaveDeviceEntity, GarageDoorDevice):
"""Called when a value has changed on the network."""
if self._value.value_id == value.value_id:
self._state = value.data
self.update_ha_state(True)
self.update_ha_state()
_LOGGER.debug("Value changed on network %s", value)
@property
+6 -5
View File
@@ -12,8 +12,8 @@ import voluptuous as vol
import homeassistant.core as ha
from homeassistant.const import (
ATTR_ENTITY_ID, CONF_ICON, CONF_NAME, STATE_CLOSED, STATE_HOME,
STATE_NOT_HOME, STATE_OFF, STATE_ON, STATE_OPEN, STATE_UNKNOWN,
ATTR_ASSUMED_STATE, )
STATE_NOT_HOME, STATE_OFF, STATE_ON, STATE_OPEN, STATE_LOCKED,
STATE_UNLOCKED, STATE_UNKNOWN, ATTR_ASSUMED_STATE)
from homeassistant.helpers.entity import (
Entity, generate_entity_id, split_entity_id)
from homeassistant.helpers.event import track_state_change
@@ -64,7 +64,7 @@ CONFIG_SCHEMA = vol.Schema({
# List of ON/OFF state tuples for groupable states
_GROUP_TYPES = [(STATE_ON, STATE_OFF), (STATE_HOME, STATE_NOT_HOME),
(STATE_OPEN, STATE_CLOSED)]
(STATE_OPEN, STATE_CLOSED), (STATE_LOCKED, STATE_UNLOCKED)]
def _get_group_on_off(state):
@@ -304,8 +304,9 @@ class Group(Entity):
if gr_on is None:
return
if tr_state is None or (gr_state == gr_on and
tr_state.state == gr_off):
if tr_state is None or ((gr_state == gr_on and
tr_state.state == gr_off) or
tr_state.state not in (gr_on, gr_off)):
if states is None:
states = self._tracking_states
+50 -56
View File
@@ -4,13 +4,13 @@ Provide pre-made queries on top of the recorder component.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/history/
"""
import re
from collections import defaultdict
from datetime import timedelta
from itertools import groupby
from homeassistant.components import recorder, script
import homeassistant.util.dt as dt_util
from homeassistant.components import recorder, script
from homeassistant.components.frontend import register_built_in_panel
from homeassistant.components.http import HomeAssistantView
DOMAIN = 'history'
@@ -19,21 +19,17 @@ DEPENDENCIES = ['recorder', 'http']
SIGNIFICANT_DOMAINS = ('thermostat',)
IGNORE_DOMAINS = ('zone', 'scene',)
URL_HISTORY_PERIOD = re.compile(
r'/api/history/period(?:/(?P<date>\d{4}-\d{1,2}-\d{1,2})|)')
def last_5_states(entity_id):
"""Return the last 5 states for entity_id."""
entity_id = entity_id.lower()
query = """
SELECT * FROM states WHERE entity_id=? AND
last_changed=last_updated
ORDER BY state_id DESC LIMIT 0, 5
"""
return recorder.query_states(query, (entity_id, ))
states = recorder.get_model('States')
return recorder.execute(
recorder.query('States').filter(
(states.entity_id == entity_id) &
(states.last_changed == states.last_updated)
).order_by(states.state_id.desc()).limit(5))
def get_significant_states(start_time, end_time=None, entity_id=None):
@@ -44,48 +40,42 @@ def get_significant_states(start_time, end_time=None, entity_id=None):
as well as all states from certain domains (for instance
thermostat so that we get current temperature in our graphs).
"""
where = """
(domain IN ({}) OR last_changed=last_updated)
AND domain NOT IN ({}) AND last_updated > ?
""".format(",".join("'%s'" % x for x in SIGNIFICANT_DOMAINS),
",".join("'%s'" % x for x in IGNORE_DOMAINS))
data = [start_time]
states = recorder.get_model('States')
query = recorder.query('States').filter(
(states.domain.in_(SIGNIFICANT_DOMAINS) |
(states.last_changed == states.last_updated)) &
((~states.domain.in_(IGNORE_DOMAINS)) &
(states.last_updated > start_time)))
if end_time is not None:
where += "AND last_updated < ? "
data.append(end_time)
query = query.filter(states.last_updated < end_time)
if entity_id is not None:
where += "AND entity_id = ? "
data.append(entity_id.lower())
query = query.filter_by(entity_id=entity_id.lower())
query = ("SELECT * FROM states WHERE {} "
"ORDER BY entity_id, last_updated ASC").format(where)
states = (state for state in recorder.query_states(query, data)
if _is_significant(state))
states = (
state for state in recorder.execute(
query.order_by(states.entity_id, states.last_updated))
if _is_significant(state))
return states_to_json(states, start_time, entity_id)
def state_changes_during_period(start_time, end_time=None, entity_id=None):
"""Return states changes during UTC period start_time - end_time."""
where = "last_changed=last_updated AND last_changed > ? "
data = [start_time]
states = recorder.get_model('States')
query = recorder.query('States').filter(
(states.last_changed == states.last_updated) &
(states.last_changed > start_time))
if end_time is not None:
where += "AND last_changed < ? "
data.append(end_time)
query = query.filter(states.last_updated < end_time)
if entity_id is not None:
where += "AND entity_id = ? "
data.append(entity_id.lower())
query = query.filter_by(entity_id=entity_id.lower())
query = ("SELECT * FROM states WHERE {} "
"ORDER BY entity_id, last_changed ASC").format(where)
states = recorder.query_states(query, data)
states = recorder.execute(
query.order_by(states.entity_id, states.last_updated))
return states_to_json(states, start_time, entity_id)
@@ -99,24 +89,27 @@ def get_states(utc_point_in_time, entity_ids=None, run=None):
if run is None:
return []
where = run.where_after_start_run + "AND created < ? "
where_data = [utc_point_in_time]
from sqlalchemy import and_, func
states = recorder.get_model('States')
most_recent_state_ids = recorder.query(
func.max(states.state_id).label('max_state_id')
).filter(
(states.created >= run.start) &
(states.created < utc_point_in_time)
)
if entity_ids is not None:
where += "AND entity_id IN ({}) ".format(
",".join(['?'] * len(entity_ids)))
where_data.extend(entity_ids)
most_recent_state_ids = most_recent_state_ids.filter(
states.entity_id.in_(entity_ids))
query = """
SELECT * FROM states
INNER JOIN (
SELECT max(state_id) AS max_state_id
FROM states WHERE {}
GROUP BY entity_id)
WHERE state_id = max_state_id
""".format(where)
most_recent_state_ids = most_recent_state_ids.group_by(
states.entity_id).subquery()
return recorder.query_states(query, where_data)
query = recorder.query('States').join(most_recent_state_ids, and_(
states.state_id == most_recent_state_ids.c.max_state_id))
return recorder.execute(query)
def states_to_json(states, start_time, entity_id):
@@ -157,6 +150,7 @@ def setup(hass, config):
"""Setup the history hooks."""
hass.wsgi.register_view(Last5StatesView)
hass.wsgi.register_view(HistoryPeriodView)
register_built_in_panel(hass, 'history', 'History', 'mdi:poll-box')
return True
@@ -177,14 +171,14 @@ class HistoryPeriodView(HomeAssistantView):
url = '/api/history/period'
name = 'api:history:view-period'
extra_urls = ['/api/history/period/<date:date>']
extra_urls = ['/api/history/period/<datetime:datetime>']
def get(self, request, date=None):
def get(self, request, datetime=None):
"""Return history over a period of time."""
one_day = timedelta(days=1)
if date:
start_time = dt_util.as_utc(dt_util.start_of_local_day(date))
if datetime:
start_time = dt_util.as_utc(datetime)
else:
start_time = dt_util.utcnow() - one_day
+139 -48
View File
@@ -4,71 +4,120 @@ Support for Homematic devices.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/homematic/
"""
import os
import time
import logging
from functools import partial
from homeassistant.const import EVENT_HOMEASSISTANT_STOP, STATE_UNKNOWN
from homeassistant.helpers import discovery
import voluptuous as vol
from homeassistant.const import (EVENT_HOMEASSISTANT_STOP, STATE_UNKNOWN,
CONF_USERNAME, CONF_PASSWORD)
from homeassistant.helpers.entity import Entity
from homeassistant.helpers import discovery
from homeassistant.config import load_yaml_config_file
DOMAIN = 'homematic'
REQUIREMENTS = ['pyhomematic==0.1.8']
REQUIREMENTS = ["pyhomematic==0.1.10"]
HOMEMATIC = None
HOMEMATIC_LINK_DELAY = 0.5
DISCOVER_SWITCHES = "homematic.switch"
DISCOVER_LIGHTS = "homematic.light"
DISCOVER_SENSORS = "homematic.sensor"
DISCOVER_BINARY_SENSORS = "homematic.binary_sensor"
DISCOVER_ROLLERSHUTTER = "homematic.rollershutter"
DISCOVER_THERMOSTATS = "homematic.thermostat"
DISCOVER_SWITCHES = 'homematic.switch'
DISCOVER_LIGHTS = 'homematic.light'
DISCOVER_SENSORS = 'homematic.sensor'
DISCOVER_BINARY_SENSORS = 'homematic.binary_sensor'
DISCOVER_ROLLERSHUTTER = 'homematic.rollershutter'
DISCOVER_THERMOSTATS = 'homematic.thermostat'
ATTR_DISCOVER_DEVICES = "devices"
ATTR_PARAM = "param"
ATTR_CHANNEL = "channel"
ATTR_NAME = "name"
ATTR_ADDRESS = "address"
ATTR_DISCOVER_DEVICES = 'devices'
ATTR_PARAM = 'param'
ATTR_CHANNEL = 'channel'
ATTR_NAME = 'name'
ATTR_ADDRESS = 'address'
EVENT_KEYPRESS = "homematic.keypress"
EVENT_KEYPRESS = 'homematic.keypress'
EVENT_IMPULSE = 'homematic.impulse'
SERVICE_VIRTUALKEY = 'virtualkey'
HM_DEVICE_TYPES = {
DISCOVER_SWITCHES: ["Switch", "SwitchPowermeter"],
DISCOVER_LIGHTS: ["Dimmer"],
DISCOVER_SENSORS: ["SwitchPowermeter", "Motion", "MotionV2",
"RemoteMotion", "ThermostatWall", "AreaThermostat",
"RotaryHandleSensor", "WaterSensor"],
DISCOVER_THERMOSTATS: ["Thermostat", "ThermostatWall", "MAXThermostat"],
DISCOVER_BINARY_SENSORS: ["ShutterContact", "Smoke", "SmokeV2",
"Motion", "MotionV2", "RemoteMotion"],
DISCOVER_ROLLERSHUTTER: ["Blind"]
DISCOVER_SWITCHES: ['Switch', 'SwitchPowermeter'],
DISCOVER_LIGHTS: ['Dimmer'],
DISCOVER_SENSORS: ['SwitchPowermeter', 'Motion', 'MotionV2',
'RemoteMotion', 'ThermostatWall', 'AreaThermostat',
'RotaryHandleSensor', 'WaterSensor', 'PowermeterGas',
'LuxSensor'],
DISCOVER_THERMOSTATS: ['Thermostat', 'ThermostatWall', 'MAXThermostat'],
DISCOVER_BINARY_SENSORS: ['ShutterContact', 'Smoke', 'SmokeV2', 'Motion',
'MotionV2', 'RemoteMotion'],
DISCOVER_ROLLERSHUTTER: ['Blind']
}
HM_IGNORE_DISCOVERY_NODE = [
"ACTUAL_TEMPERATURE"
'ACTUAL_TEMPERATURE'
]
HM_ATTRIBUTE_SUPPORT = {
"LOWBAT": ["Battery", {0: "High", 1: "Low"}],
"ERROR": ["Sabotage", {0: "No", 1: "Yes"}],
"RSSI_DEVICE": ["RSSI", {}],
"VALVE_STATE": ["Valve", {}],
"BATTERY_STATE": ["Battery", {}],
"CONTROL_MODE": ["Mode", {0: "Auto", 1: "Manual", 2: "Away", 3: "Boost"}],
"POWER": ["Power", {}],
"CURRENT": ["Current", {}],
"VOLTAGE": ["Voltage", {}]
'LOWBAT': ['Battery', {0: 'High', 1: 'Low'}],
'ERROR': ['Sabotage', {0: 'No', 1: 'Yes'}],
'RSSI_DEVICE': ['RSSI', {}],
'VALVE_STATE': ['Valve', {}],
'BATTERY_STATE': ['Battery', {}],
'CONTROL_MODE': ['Mode', {0: 'Auto', 1: 'Manual', 2: 'Away', 3: 'Boost'}],
'POWER': ['Power', {}],
'CURRENT': ['Current', {}],
'VOLTAGE': ['Voltage', {}]
}
HM_PRESS_EVENTS = [
"PRESS_SHORT",
"PRESS_LONG",
"PRESS_CONT",
"PRESS_LONG_RELEASE"
'PRESS_SHORT',
'PRESS_LONG',
'PRESS_CONT',
'PRESS_LONG_RELEASE'
]
HM_IMPULSE_EVENTS = [
'SEQUENCE_OK'
]
_LOGGER = logging.getLogger(__name__)
CONF_RESOLVENAMES_OPTIONS = [
'metadata',
'json',
'xml',
False
]
CONF_LOCAL_IP = 'local_ip'
CONF_LOCAL_PORT = 'local_port'
CONF_REMOTE_IP = 'remote_ip'
CONF_REMOTE_PORT = 'remote_port'
CONF_RESOLVENAMES = 'resolvenames'
CONF_DELAY = 'delay'
PLATFORM_SCHEMA = vol.Schema({
vol.Required(CONF_LOCAL_IP): vol.Coerce(str),
vol.Optional(CONF_LOCAL_PORT, default=8943):
vol.All(vol.Coerce(int),
vol.Range(min=1, max=65535)),
vol.Required(CONF_REMOTE_IP): vol.Coerce(str),
vol.Optional(CONF_REMOTE_PORT, default=2001):
vol.All(vol.Coerce(int),
vol.Range(min=1, max=65535)),
vol.Optional(CONF_RESOLVENAMES, default=False):
vol.In(CONF_RESOLVENAMES_OPTIONS),
vol.Optional(CONF_USERNAME, default="Admin"): vol.Coerce(str),
vol.Optional(CONF_PASSWORD, default=""): vol.Coerce(str),
vol.Optional(CONF_DELAY, default=0.5): vol.Coerce(float)
})
SCHEMA_SERVICE_VIRTUALKEY = vol.Schema({
vol.Required(ATTR_ADDRESS): vol.Coerce(str),
vol.Required(ATTR_CHANNEL): vol.Coerce(int),
vol.Required(ATTR_PARAM): vol.Coerce(str)
})
# pylint: disable=unused-argument
def setup(hass, config):
@@ -77,14 +126,14 @@ def setup(hass, config):
from pyhomematic import HMConnection
local_ip = config[DOMAIN].get("local_ip", None)
local_port = config[DOMAIN].get("local_port", 8943)
remote_ip = config[DOMAIN].get("remote_ip", None)
remote_port = config[DOMAIN].get("remote_port", 2001)
resolvenames = config[DOMAIN].get("resolvenames", False)
username = config[DOMAIN].get("username", "Admin")
password = config[DOMAIN].get("password", "")
HOMEMATIC_LINK_DELAY = config[DOMAIN].get("delay", 0.5)
local_ip = config[DOMAIN][0].get(CONF_LOCAL_IP)
local_port = config[DOMAIN][0].get(CONF_LOCAL_PORT)
remote_ip = config[DOMAIN][0].get(CONF_REMOTE_IP)
remote_port = config[DOMAIN][0].get(CONF_REMOTE_PORT)
resolvenames = config[DOMAIN][0].get(CONF_RESOLVENAMES)
username = config[DOMAIN][0].get(CONF_USERNAME)
password = config[DOMAIN][0].get(CONF_PASSWORD)
HOMEMATIC_LINK_DELAY = config[DOMAIN][0].get(CONF_DELAY)
if remote_ip is None or local_ip is None:
_LOGGER.error("Missing remote CCU/Homegear or local address")
@@ -109,6 +158,15 @@ def setup(hass, config):
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, HOMEMATIC.stop)
hass.config.components.append(DOMAIN)
# regeister homematic services
descriptions = load_yaml_config_file(
os.path.join(os.path.dirname(__file__), 'services.yaml'))
hass.services.register(DOMAIN, SERVICE_VIRTUALKEY,
_hm_service_virtualkey,
descriptions[DOMAIN][SERVICE_VIRTUALKEY],
SCHEMA_SERVICE_VIRTUALKEY)
return True
@@ -302,7 +360,7 @@ def _hm_event_handler(hass, device, caller, attribute, value):
_LOGGER.debug("Event %s for %s channel %i", attribute,
hmdevice.NAME, channel)
# a keypress event
# keypress event
if attribute in HM_PRESS_EVENTS:
hass.bus.fire(EVENT_KEYPRESS, {
ATTR_NAME: hmdevice.NAME,
@@ -311,9 +369,42 @@ def _hm_event_handler(hass, device, caller, attribute, value):
})
return
# impulse event
if attribute in HM_IMPULSE_EVENTS:
hass.bus.fire(EVENT_KEYPRESS, {
ATTR_NAME: hmdevice.NAME,
ATTR_CHANNEL: channel
})
return
_LOGGER.warning("Event is unknown and not forwarded to HA")
def _hm_service_virtualkey(call):
"""Callback for handle virtualkey services."""
address = call.data.get(ATTR_ADDRESS)
channel = call.data.get(ATTR_CHANNEL)
param = call.data.get(ATTR_PARAM)
if address not in HOMEMATIC.devices:
_LOGGER.error("%s not found for service virtualkey!", address)
return
hmdevice = HOMEMATIC.devices.get(address)
# if param exists for this device
if param not in hmdevice.ACTIONNODE:
_LOGGER.error("%s not datapoint in hm device %s", param, address)
return
# channel exists?
if channel > hmdevice.ELEMENT:
_LOGGER.error("%i is not a channel in hm device %s", channel, address)
return
# call key
hmdevice.actionNodeData(param, 1, channel)
class HMDevice(Entity):
"""The Homematic device base object."""
@@ -465,7 +556,7 @@ class HMDevice(Entity):
channel = self._channel
# Prepare for subscription
try:
if int(channel) > 0:
if int(channel) >= 0:
channels_to_sub.update({int(channel): True})
except (ValueError, TypeError):
_LOGGER("Invalid channel in metadata from %s",
+24 -2
View File
@@ -25,7 +25,7 @@ import homeassistant.util.dt as dt_util
import homeassistant.helpers.config_validation as cv
DOMAIN = "http"
REQUIREMENTS = ("cherrypy==6.0.2", "static3==0.7.0", "Werkzeug==0.11.10")
REQUIREMENTS = ("cherrypy==6.1.1", "static3==0.7.0", "Werkzeug==0.11.10")
CONF_API_PASSWORD = "api_password"
CONF_SERVER_HOST = "server_host"
@@ -41,7 +41,9 @@ DATA_API_PASSWORD = 'api_password'
# specified here: https://wiki.mozilla.org/Security/Server_Side_TLS
# Intermediate guidelines are followed.
SSL_VERSION = ssl.PROTOCOL_SSLv23
SSL_OPTS = ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 | ssl.OP_NO_COMPRESSION
SSL_OPTS = ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3
if hasattr(ssl, 'OP_NO_COMPRESSION'):
SSL_OPTS |= ssl.OP_NO_COMPRESSION
CIPHERS = "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:" \
"ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:" \
"ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:" \
@@ -214,9 +216,29 @@ def routing_map(hass):
"""Convert date to url value."""
return value.isoformat()
class DateTimeValidator(BaseConverter):
"""Validate datetimes in urls formatted per ISO 8601."""
regex = r'\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d' \
r'\.\d+([+-][0-2]\d:[0-5]\d|Z)'
def to_python(self, value):
"""Validate and convert date."""
parsed = dt_util.parse_datetime(value)
if parsed is None:
raise ValidationError()
return parsed
def to_url(self, value):
"""Convert date to url value."""
return value.isoformat()
return Map(converters={
'entity': EntityValidator,
'date': DateValidator,
'datetime': DateTimeValidator,
})
+5 -4
View File
@@ -98,9 +98,10 @@ class ZWaveHvac(ZWaveDeviceEntity, HvacDevice):
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_properties()
self.update_ha_state(True)
self.update_ha_state()
_LOGGER.debug("Value changed on network %s", value)
def update_properties(self):
@@ -135,7 +136,7 @@ class ZWaveHvac(ZWaveDeviceEntity, HvacDevice):
class_id=COMMAND_CLASS_CONFIGURATION).values():
if value.command_class == 112 and value.index == 33:
self._current_swing_mode = value.data
self._swing_list = [0, 1]
self._swing_list = list(value.data_items)
_LOGGER.debug("self._swing_list=%s", self._swing_list)
@property
@@ -235,5 +236,5 @@ class ZWaveHvac(ZWaveDeviceEntity, HvacDevice):
for value in self._node.get_values(
class_id=COMMAND_CLASS_CONFIGURATION).values():
if value.command_class == 112 and value.index == 33:
value.data = int(swing_mode)
value.data = bytes(swing_mode, 'utf-8')
break
+5
View File
@@ -33,6 +33,7 @@ CONF_PASSWORD = 'password'
CONF_SSL = 'ssl'
CONF_VERIFY_SSL = 'verify_ssl'
CONF_BLACKLIST = 'blacklist'
CONF_TAGS = 'tags'
# pylint: disable=too-many-locals
@@ -56,6 +57,7 @@ def setup(hass, config):
verify_ssl = util.convert(conf.get(CONF_VERIFY_SSL), bool,
DEFAULT_VERIFY_SSL)
blacklist = conf.get(CONF_BLACKLIST, [])
tags = conf.get(CONF_TAGS, {})
try:
influx = InfluxDBClient(host=host, port=port, username=username,
@@ -99,6 +101,9 @@ def setup(hass, config):
}
]
for tag in tags:
json_body[0]['tags'][tag] = tags[tag]
try:
influx.write_points(json_body)
except exceptions.InfluxDBClientError:
+2 -2
View File
@@ -34,7 +34,7 @@ SERVICE_SELECT_VALUE = 'select_value'
SERVICE_SELECT_VALUE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Required(ATTR_VALUE): vol.Coerce(int),
vol.Required(ATTR_VALUE): vol.Coerce(float),
})
@@ -152,7 +152,7 @@ class InputSlider(Entity):
def select_value(self, value):
"""Select new value."""
num_value = int(value)
num_value = float(value)
if num_value < self._minimum or num_value > self._maximum:
_LOGGER.warning('Invalid value: %s (range %s - %s)',
num_value, self._minimum, self._maximum)
+87
View File
@@ -0,0 +1,87 @@
"""
Component for Joaoapps Join services.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/join/
"""
import logging
import voluptuous as vol
from homeassistant.const import CONF_NAME, CONF_API_KEY
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = [
'https://github.com/nkgilley/python-join-api/archive/'
'3e1e849f1af0b4080f551b62270c6d244d5fbcbd.zip#python-join-api==0.0.1']
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'joaoapps_join'
CONF_DEVICE_ID = 'device_id'
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.All(cv.ensure_list, [{
vol.Required(CONF_DEVICE_ID): cv.string,
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_API_KEY): cv.string
}])
}, extra=vol.ALLOW_EXTRA)
# pylint: disable=too-many-locals
def register_device(hass, device_id, api_key, name):
"""Method to register services for each join device listed."""
from pyjoin import (ring_device, set_wallpaper, send_sms,
send_file, send_url, send_notification)
def ring_service(service):
"""Service to ring devices."""
ring_device(device_id, api_key=api_key)
def set_wallpaper_service(service):
"""Service to set wallpaper on devices."""
set_wallpaper(device_id, url=service.data.get('url'), api_key=api_key)
def send_file_service(service):
"""Service to send files to devices."""
send_file(device_id, url=service.data.get('url'), api_key=api_key)
def send_url_service(service):
"""Service to open url on devices."""
send_url(device_id, url=service.data.get('url'), api_key=api_key)
def send_tasker_service(service):
"""Service to open url on devices."""
send_notification(device_id=device_id,
text=service.data.get('command'),
api_key=api_key)
def send_sms_service(service):
"""Service to send sms from devices."""
send_sms(device_id=device_id,
sms_number=service.data.get('number'),
sms_text=service.data.get('message'),
api_key=api_key)
hass.services.register(DOMAIN, name + 'ring', ring_service)
hass.services.register(DOMAIN, name + 'set_wallpaper',
set_wallpaper_service)
hass.services.register(DOMAIN, name + 'send_sms', send_sms_service)
hass.services.register(DOMAIN, name + 'send_file', send_file_service)
hass.services.register(DOMAIN, name + 'send_url', send_url_service)
hass.services.register(DOMAIN, name + 'send_tasker', send_tasker_service)
def setup(hass, config):
"""Setup Join services."""
from pyjoin import get_devices
for device in config[DOMAIN]:
device_id = device.get(CONF_DEVICE_ID)
api_key = device.get(CONF_API_KEY)
name = device.get(CONF_NAME)
name = name.lower().replace(" ", "_") + "_" if name else ""
if api_key:
if not get_devices(api_key):
_LOGGER.error("Error connecting to Join, check API key")
return False
register_device(hass, device_id, api_key, name)
return True
+330
View File
@@ -0,0 +1,330 @@
"""
Support for KNX components.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/knx/
"""
import logging
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.helpers.entity import Entity
DOMAIN = "knx"
REQUIREMENTS = ['knxip==0.3.2']
EVENT_KNX_FRAME_RECEIVED = "knx_frame_received"
CONF_HOST = "host"
CONF_PORT = "port"
DEFAULT_PORT = "3671"
KNXTUNNEL = None
_LOGGER = logging.getLogger(__name__)
def setup(hass, config):
"""Setup the connection to the KNX IP interface."""
global KNXTUNNEL
from knxip.ip import KNXIPTunnel
from knxip.core import KNXException
host = config[DOMAIN].get(CONF_HOST, None)
if host is None:
_LOGGER.debug("Will try to auto-detect KNX/IP gateway")
host = "0.0.0.0"
try:
port = int(config[DOMAIN].get(CONF_PORT, DEFAULT_PORT))
except ValueError:
_LOGGER.exception("Can't parse KNX IP interface port")
return False
KNXTUNNEL = KNXIPTunnel(host, port)
try:
res = KNXTUNNEL.connect()
_LOGGER.debug("Res = %s", res)
if not res:
_LOGGER.exception("Could not connect to KNX/IP interface %s", host)
return False
except KNXException as ex:
_LOGGER.exception("Can't connect to KNX/IP interface: %s", ex)
KNXTUNNEL = None
return False
_LOGGER.info("KNX IP tunnel to %s:%i established", host, port)
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, close_tunnel)
return True
def close_tunnel(_data):
"""Close the NKX tunnel connection on shutdown."""
global KNXTUNNEL
KNXTUNNEL.disconnect()
KNXTUNNEL = None
class KNXConfig(object):
"""Handle the fetching of configuration from the config file."""
def __init__(self, config):
"""Initialize the configuration."""
from knxip.core import parse_group_address
self.config = config
self.should_poll = config.get("poll", True)
if config.get("address"):
self._address = parse_group_address(config.get("address"))
else:
self._address = None
if self.config.get("state_address"):
self._state_address = parse_group_address(
self.config.get("state_address"))
else:
self._state_address = None
@property
def name(self):
"""The name given to the entity."""
return self.config["name"]
@property
def address(self):
"""The address of the device as an integer value.
3 types of addresses are supported:
integer - 0-65535
2 level - a/b
3 level - a/b/c
"""
return self._address
@property
def state_address(self):
"""The group address the device sends its current state to.
Some KNX devices can send the current state to a seperate
group address. This makes send e.g. when an actuator can
be switched but also have a timer functionality.
"""
return self._state_address
class KNXGroupAddress(Entity):
"""Representation of devices connected to a KNX group address."""
def __init__(self, hass, config):
"""Initialize the device."""
self._config = config
self._state = False
self._data = None
_LOGGER.debug("Initalizing KNX group address %s", self.address)
def handle_knx_message(addr, data):
"""Handle an incoming KNX frame.
Handle an incoming frame and update our status if it contains
information relating to this device.
"""
if (addr == self.state_address) or (addr == self.address):
self._state = data
self.update_ha_state()
KNXTUNNEL.register_listener(self.address, handle_knx_message)
if self.state_address:
KNXTUNNEL.register_listener(self.state_address, handle_knx_message)
@property
def name(self):
"""The entity's display name."""
return self._config.name
@property
def config(self):
"""The entity's configuration."""
return self._config
@property
def should_poll(self):
"""Return the state of the polling, if needed."""
return self._config.should_poll
@property
def is_on(self):
"""Return True if the value is not 0 is on, else False."""
if self.should_poll:
self.update()
return self._state != 0
@property
def address(self):
"""Return the KNX group address."""
return self._config.address
@property
def state_address(self):
"""Return the KNX group address."""
return self._config.state_address
@property
def cache(self):
"""The name given to the entity."""
return self._config.config.get("cache", True)
def group_write(self, value):
"""Write to the group address."""
KNXTUNNEL.group_write(self.address, [value])
def update(self):
"""Get the state from KNX bus or cache."""
from knxip.core import KNXException
try:
if self.state_address:
res = KNXTUNNEL.group_read(self.state_address,
use_cache=self.cache)
else:
res = KNXTUNNEL.group_read(self.address,
use_cache=self.cache)
if res:
self._state = res[0]
self._data = res
else:
_LOGGER.debug("Unable to read from KNX address: %s (None)",
self.address)
except KNXException:
_LOGGER.exception("Unable to read from KNX address: %s",
self.address)
return False
class KNXMultiAddressDevice(Entity):
"""Representation of devices connected to a multiple KNX group address.
This is needed for devices like dimmers or shutter actuators as they have
to be controlled by multiple group addresses.
"""
names = {}
values = {}
def __init__(self, hass, config, required, optional=None):
"""Initialize the device.
The namelist argument lists the required addresses. E.g. for a dimming
actuators, the namelist might look like:
onoff_address: 0/0/1
brightness_address: 0/0/2
"""
from knxip.core import parse_group_address, KNXException
self._config = config
self._state = False
self._data = None
_LOGGER.debug("Initalizing KNX multi address device")
# parse required addresses
for name in required:
_LOGGER.info(name)
paramname = name + "_address"
addr = self._config.config.get(paramname)
if addr is None:
_LOGGER.exception("Required KNX group address %s missing",
paramname)
raise KNXException("Group address for %s missing "
"in configuration", paramname)
addr = parse_group_address(addr)
self.names[addr] = name
# parse optional addresses
for name in optional:
paramname = name + "_address"
addr = self._config.config.get(paramname)
if addr:
try:
addr = parse_group_address(addr)
except KNXException:
_LOGGER.exception("Cannot parse group address %s", addr)
self.names[addr] = name
@property
def name(self):
"""The entity's display name."""
return self._config.name
@property
def config(self):
"""The entity's configuration."""
return self._config
@property
def should_poll(self):
"""Return the state of the polling, if needed."""
return self._config.should_poll
@property
def cache(self):
"""The name given to the entity."""
return self._config.config.get("cache", True)
def has_attribute(self, name):
"""Check if the attribute with the given name is defined.
This is mostly important for optional addresses.
"""
for attributename, dummy_attribute in self.names.items():
if attributename == name:
return True
return False
def value(self, name):
"""Return the value to a given named attribute."""
from knxip.core import KNXException
addr = None
for attributeaddress, attributename in self.names.items():
if attributename == name:
addr = attributeaddress
if addr is None:
_LOGGER.exception("Attribute %s undefined", name)
return False
try:
res = KNXTUNNEL.group_read(addr, use_cache=self.cache)
except KNXException:
_LOGGER.exception("Unable to read from KNX address: %s",
addr)
return False
return res
def set_value(self, name, value):
"""Set the value of a given named attribute."""
from knxip.core import KNXException
addr = None
for attributeaddress, attributename in self.names.items():
if attributename == name:
addr = attributeaddress
if addr is None:
_LOGGER.exception("Attribute %s undefined", name)
return False
try:
KNXTUNNEL.group_write(addr, value)
except KNXException:
_LOGGER.exception("Unable to write to KNX address: %s",
addr)
return False
return True
+2 -1
View File
@@ -67,12 +67,13 @@ PROP_TO_ATTR = {
# Service call validation schemas
VALID_TRANSITION = vol.All(vol.Coerce(int), vol.Clamp(min=0, max=900))
VALID_BRIGHTNESS = vol.All(vol.Coerce(int), vol.Clamp(min=0, max=255))
LIGHT_TURN_ON_SCHEMA = vol.Schema({
ATTR_ENTITY_ID: cv.entity_ids,
ATTR_PROFILE: str,
ATTR_TRANSITION: VALID_TRANSITION,
ATTR_BRIGHTNESS: cv.byte,
ATTR_BRIGHTNESS: VALID_BRIGHTNESS,
ATTR_COLOR_NAME: str,
ATTR_RGB_COLOR: vol.All(vol.ExactSequence((cv.byte, cv.byte, cv.byte)),
vol.Coerce(tuple)),
+2 -2
View File
@@ -18,7 +18,7 @@ LIGHT_TEMPS = [240, 380]
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Setup demo light platform."""
"""Setup the demo light platform."""
add_devices_callback([
DemoLight("Bed Light", False),
DemoLight("Ceiling Lights", True, LIGHT_COLORS[0], LIGHT_TEMPS[1]),
@@ -27,7 +27,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
class DemoLight(Light):
"""Provide a demo light."""
"""Represenation of a demo light."""
# pylint: disable=too-many-arguments
def __init__(self, name, state, rgb=None, ct=None, brightness=180):
+109
View File
@@ -0,0 +1,109 @@
"""
Support for Flux lights.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.flux_led/
"""
import logging
import socket
import voluptuous as vol
from homeassistant.components.light import Light
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['https://github.com/Danielhiversen/flux_led/archive/0.3.zip'
'#flux_led==0.3']
_LOGGER = logging.getLogger(__name__)
DOMAIN = "flux_led"
ATTR_NAME = 'name'
DEVICE_SCHEMA = vol.Schema({
vol.Optional(ATTR_NAME): cv.string,
})
PLATFORM_SCHEMA = vol.Schema({
vol.Required('platform'): DOMAIN,
vol.Optional('devices', default={}): {cv.string: DEVICE_SCHEMA},
vol.Optional('automatic_add', default=False): cv.boolean,
}, extra=vol.ALLOW_EXTRA)
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Setup the Flux lights."""
import flux_led
lights = []
light_ips = []
for ipaddr, device_config in config["devices"].items():
device = {}
device['id'] = device_config[ATTR_NAME]
device['ipaddr'] = ipaddr
light = FluxLight(device)
if light.is_valid:
lights.append(light)
light_ips.append(ipaddr)
if not config['automatic_add']:
add_devices_callback(lights)
return
# Find the bulbs on the LAN
scanner = flux_led.BulbScanner()
scanner.scan(timeout=20)
for device in scanner.getBulbInfo():
light = FluxLight(device)
ipaddr = device['ipaddr']
if light.is_valid and ipaddr not in light_ips:
lights.append(light)
light_ips.append(ipaddr)
add_devices_callback(lights)
class FluxLight(Light):
"""Representation of a Flux light."""
# pylint: disable=too-many-arguments
def __init__(self, device):
"""Initialize the light."""
import flux_led
self._name = device['id']
self._ipaddr = device['ipaddr']
self.is_valid = True
self._bulb = None
try:
self._bulb = flux_led.WifiLedBulb(self._ipaddr)
except socket.error:
self.is_valid = False
_LOGGER.error("Failed to connect to bulb %s, %s",
self._ipaddr, self._name)
@property
def unique_id(self):
"""Return the ID of this light."""
return "{}.{}".format(
self.__class__, self._ipaddr)
@property
def name(self):
"""Return the name of the device if any."""
return self._name
@property
def is_on(self):
"""Return true if device is on."""
return self._bulb.isOn()
def turn_on(self, **kwargs):
"""Turn the specified or all lights on."""
self._bulb.turnOn()
def turn_off(self, **kwargs):
"""Turn the specified or all lights off."""
self._bulb.turnOff()
def update(self):
"""Synchronize state with bulb."""
self._bulb.refreshState()
+1 -1
View File
@@ -129,7 +129,7 @@ def setup_bridge(host, hass, add_devices_callback, filename,
new_lights = []
api_name = api.get('config').get('name')
if api_name == 'RaspBee-GW':
if api_name in ('RaspBee-GW', 'deCONZ-GW'):
bridge_type = 'deconz'
else:
bridge_type = 'hue'
+26 -26
View File
@@ -19,24 +19,24 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Setup a Hyperion server remote."""
host = config.get(CONF_HOST, None)
port = config.get("port", 19444)
device = Hyperion(config.get('name', host), host, port)
default_color = config.get("default_color", [255, 255, 255])
device = Hyperion(config.get('name', host), host, port, default_color)
if device.setup():
add_devices_callback([device])
return True
else:
return False
return False
class Hyperion(Light):
"""Representation of a Hyperion remote."""
def __init__(self, name, host, port):
def __init__(self, name, host, port, default_color):
"""Initialize the light."""
self._host = host
self._port = port
self._name = name
self._is_available = True
self._rgb_color = [255, 255, 255]
self._default_color = default_color
self._rgb_color = [0, 0, 0]
@property
def name(self):
@@ -50,38 +50,43 @@ class Hyperion(Light):
@property
def is_on(self):
"""Return true if the device is online."""
return self._is_available
"""Return true if not black."""
return self._rgb_color != [0, 0, 0]
def turn_on(self, **kwargs):
"""Turn the lights on."""
if self._is_available:
if ATTR_RGB_COLOR in kwargs:
self._rgb_color = kwargs[ATTR_RGB_COLOR]
if ATTR_RGB_COLOR in kwargs:
self._rgb_color = kwargs[ATTR_RGB_COLOR]
else:
self._rgb_color = self._default_color
self.json_request({"command": "color", "priority": 128,
"color": self._rgb_color})
self.json_request({"command": "color", "priority": 128,
"color": self._rgb_color})
def turn_off(self, **kwargs):
"""Disconnect the remote."""
"""Disconnect all remotes."""
self.json_request({"command": "clearall"})
self._rgb_color = [0, 0, 0]
def update(self):
"""Ping the remote."""
# just see if the remote port is open
self._is_available = self.json_request()
"""Get the remote's active color."""
response = self.json_request({"command": "serverinfo"})
if response:
if response["info"]["activeLedColor"] == []:
self._rgb_color = [0, 0, 0]
else:
self._rgb_color =\
response["info"]["activeLedColor"][0]["RGB Value"]
def setup(self):
"""Get the hostname of the remote."""
response = self.json_request({"command": "serverinfo"})
if response:
if self._name == self._host:
self._name = response["info"]["hostname"]
self._name = response["info"]["hostname"]
return True
return False
def json_request(self, request=None, wait_for_response=False):
def json_request(self, request, wait_for_response=False):
"""Communicate with the JSON server."""
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(5)
@@ -92,11 +97,6 @@ class Hyperion(Light):
sock.close()
return False
if not request:
# No communication needed, simple presence detection returns True
sock.close()
return True
sock.send(bytearray(json.dumps(request) + "\n", "utf-8"))
try:
buf = sock.recv(4096)
+21 -35
View File
@@ -1,35 +1,21 @@
"""
Support for Qwikswitch Relays and Dimmers.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.qwikswitch/
"""
import logging
import homeassistant.components.qwikswitch as qwikswitch
from homeassistant.components.light import Light
DEPENDENCIES = ['qwikswitch']
class QSLight(qwikswitch.QSToggleEntity, Light):
"""Light based on a Qwikswitch relay/dimmer module."""
pass
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Store add_devices for the light components."""
if discovery_info is None or 'qsusb_id' not in discovery_info:
logging.getLogger(__name__).error(
'Configure main Qwikswitch component')
return False
qsusb = qwikswitch.QSUSB[discovery_info['qsusb_id']]
for item in qsusb.ha_devices:
if item['type'] not in ['dim', 'rel']:
continue
dev = QSLight(item, qsusb)
add_devices([dev])
qsusb.ha_objects[item['id']] = dev
"""
Support for Qwikswitch Relays and Dimmers.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.qwikswitch/
"""
import logging
import homeassistant.components.qwikswitch as qwikswitch
DEPENDENCIES = ['qwikswitch']
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Add lights from the main Qwikswitch component."""
if discovery_info is None:
logging.getLogger(__name__).error('Configure Qwikswitch Component.')
return False
add_devices(qwikswitch.QSUSB['light'])
return True
-27
View File
@@ -6,10 +6,8 @@ https://home-assistant.io/components/light.vera/
"""
import logging
import homeassistant.util.dt as dt_util
from homeassistant.components.light import ATTR_BRIGHTNESS, Light
from homeassistant.const import (
ATTR_ARMED, ATTR_BATTERY_LEVEL, ATTR_LAST_TRIP_TIME, ATTR_TRIPPED,
STATE_OFF, STATE_ON)
from homeassistant.components.vera import (
VeraDevice, VERA_DEVICES, VERA_CONTROLLER)
@@ -56,31 +54,6 @@ class VeraLight(VeraDevice, Light):
self._state = STATE_OFF
self.update_ha_state()
@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] = utc_time.isoformat()
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 device is on."""
+1 -1
View File
@@ -14,7 +14,7 @@ from homeassistant.util import color as color_util
from homeassistant.util.color import \
color_temperature_mired_to_kelvin as mired_to_kelvin
REQUIREMENTS = ['python-wink==0.7.8', 'pubnub==3.7.8']
REQUIREMENTS = ['python-wink==0.7.11', 'pubnub==3.8.2']
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
+80
View File
@@ -0,0 +1,80 @@
"""
Support for X10 lights.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.x10/
"""
import logging
from subprocess import check_output, CalledProcessError, STDOUT
from homeassistant.components.light import ATTR_BRIGHTNESS, Light
_LOGGER = logging.getLogger(__name__)
def x10_command(command):
"""Execute X10 command and check output."""
return check_output(["heyu"] + command.split(' '), stderr=STDOUT)
def get_status():
"""Get on/off status for all x10 units in default housecode."""
output = check_output("heyu info | grep monitored", shell=True)
return output.decode('utf-8').split(' ')[-1].strip('\n()')
def get_unit_status(code):
"""Get on/off status for given unit."""
unit = int(code[1])
return get_status()[16 - int(unit)] == '1'
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the x10 Light platform."""
try:
x10_command("info")
except CalledProcessError as err:
_LOGGER.error(err.output)
return False
add_devices(X10Light(light) for light in config['lights'])
class X10Light(Light):
"""Representation of an X10 Light."""
def __init__(self, light):
"""Initialize an X10 Light."""
self._name = light['name']
self._id = light['id']
self._is_on = False
self._brightness = 0
@property
def name(self):
"""Return the display name of this light."""
return self._name
@property
def brightness(self):
"""Brightness of the light (an integer in the range 1-255)."""
return self._brightness
@property
def is_on(self):
"""Return true if light is on."""
return self._is_on
def turn_on(self, **kwargs):
"""Instruct the light to turn on."""
x10_command("on " + self._id)
self._brightness = kwargs.get(ATTR_BRIGHTNESS, 255)
self._is_on = True
def turn_off(self, **kwargs):
"""Instruct the light to turn off."""
x10_command("off " + self._id)
self._is_on = False
def update(self):
"""Fetch new state data for this light."""
self._is_on = get_unit_status(self._id)
+48 -57
View File
@@ -8,22 +8,32 @@ import logging
# Because we do not compile openzwave on CI
# pylint: disable=import-error
from threading import Timer
from homeassistant.components.light import ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, \
ATTR_RGB_COLOR, DOMAIN, Light
from homeassistant.components import zwave
from homeassistant.const import STATE_OFF, STATE_ON
from homeassistant.util.color import HASS_COLOR_MAX, HASS_COLOR_MIN, \
color_temperature_mired_to_kelvin, color_temperature_to_rgb
color_temperature_mired_to_kelvin, color_temperature_to_rgb, \
color_rgb_to_rgbw, color_rgbw_to_rgb
_LOGGER = logging.getLogger(__name__)
AEOTEC = 0x86
AEOTEC_ZW098_LED_BULB = 0x62
AEOTEC_ZW098_LED_BULB_LIGHT = (AEOTEC, AEOTEC_ZW098_LED_BULB)
COLOR_CHANNEL_WARM_WHITE = 0x01
COLOR_CHANNEL_COLD_WHITE = 0x02
COLOR_CHANNEL_RED = 0x04
COLOR_CHANNEL_GREEN = 0x08
COLOR_CHANNEL_BLUE = 0x10
WORKAROUND_ZW098 = 'zw098'
DEVICE_MAPPINGS = {
AEOTEC_ZW098_LED_BULB_LIGHT: WORKAROUND_ZW098
}
# Generate midpoint color temperatures for bulbs that have limited
# support for white light colors
TEMP_MID_HASS = (HASS_COLOR_MAX - HASS_COLOR_MIN) / 2 + HASS_COLOR_MIN
@@ -96,25 +106,10 @@ class ZwaveDimmer(zwave.ZWaveDeviceEntity, Light):
def _value_changed(self, value):
"""Called when a value has changed on the network."""
if self._value.value_id != value.value_id:
return
if self._refreshing:
self._refreshing = False
if self._value.value_id == value.value_id or \
self._value.node == value.node:
self.update_properties()
else:
def _refresh_value():
"""Used timer callback for delayed value refresh."""
self._refreshing = True
self._value.refresh()
if self._timer is not None and self._timer.isAlive():
self._timer.cancel()
self._timer = Timer(2, _refresh_value)
self._timer.start()
self.update_ha_state()
self.update_ha_state()
@property
def brightness(self):
@@ -161,6 +156,7 @@ class ZwaveColorLight(ZwaveDimmer):
self._color_channels = None
self._rgb = None
self._ct = None
self._zw098 = None
# Here we attempt to find a zwave color value with the same instance
# id as the dimmer value. Currently zwave nodes that change colors
@@ -182,6 +178,17 @@ class ZwaveColorLight(ZwaveDimmer):
if self._value_color_channels is None:
raise ValueError("Color Channels not found.")
# Make sure that we have values for the key before converting to int
if (value.node.manufacturer_id.strip() and
value.node.product_id.strip()):
specific_sensor_key = (int(value.node.manufacturer_id, 16),
int(value.node.product_id, 16))
if specific_sensor_key in DEVICE_MAPPINGS:
if DEVICE_MAPPINGS[specific_sensor_key] == WORKAROUND_ZW098:
_LOGGER.debug("AEOTEC ZW098 workaround enabled")
self._zw098 = 1
super().__init__(value)
def update_properties(self):
@@ -218,11 +225,10 @@ class ZwaveColorLight(ZwaveDimmer):
else:
cold_white = 0
# Color temperature. With two white channels, only two color
# temperatures are supported for the bulb. The channel values
# Color temperature. With the AEOTEC ZW098 bulb, only two color
# temperatures are supported. The warm and cold channel values
# indicate brightness for warm/cold color temperature.
if (self._color_channels & COLOR_CHANNEL_WARM_WHITE and
self._color_channels & COLOR_CHANNEL_COLD_WHITE):
if self._zw098:
if warm_white > 0:
self._ct = TEMP_WARM_HASS
self._rgb = ct_to_rgb(self._ct)
@@ -233,17 +239,11 @@ class ZwaveColorLight(ZwaveDimmer):
# RGB color is being used. Just report midpoint.
self._ct = TEMP_MID_HASS
# If only warm white is reported 0-255 is color temperature.
elif self._color_channels & COLOR_CHANNEL_WARM_WHITE:
self._ct = HASS_COLOR_MIN + (HASS_COLOR_MAX - HASS_COLOR_MIN) * (
warm_white / 255)
self._rgb = ct_to_rgb(self._ct)
self._rgb = list(color_rgbw_to_rgb(*self._rgb, w=warm_white))
# If only cold white is reported 0-255 is negative color temperature.
elif self._color_channels & COLOR_CHANNEL_COLD_WHITE:
self._ct = HASS_COLOR_MIN + (HASS_COLOR_MAX - HASS_COLOR_MIN) * (
(255 - cold_white) / 255)
self._rgb = ct_to_rgb(self._ct)
self._rgb = list(color_rgbw_to_rgb(*self._rgb, w=cold_white))
# If no rgb channels supported, report None.
if not (self._color_channels & COLOR_CHANNEL_RED or
@@ -266,10 +266,10 @@ class ZwaveColorLight(ZwaveDimmer):
rgbw = None
if ATTR_COLOR_TEMP in kwargs:
# With two white channels, only two color temperatures are
# supported for the bulb.
if (self._color_channels & COLOR_CHANNEL_WARM_WHITE and
self._color_channels & COLOR_CHANNEL_COLD_WHITE):
# Color temperature. With the AEOTEC ZW098 bulb, only two color
# temperatures are supported. The warm and cold channel values
# indicate brightness for warm/cold color temperature.
if self._zw098:
if kwargs[ATTR_COLOR_TEMP] > TEMP_MID_HASS:
self._ct = TEMP_WARM_HASS
rgbw = b'#000000FF00'
@@ -277,29 +277,20 @@ class ZwaveColorLight(ZwaveDimmer):
self._ct = TEMP_COLD_HASS
rgbw = b'#00000000FF'
# If only warm white is reported 0-255 is color temperature
elif self._color_channels & COLOR_CHANNEL_WARM_WHITE:
rgbw = b'#000000'
temp = (
(kwargs[ATTR_COLOR_TEMP] - HASS_COLOR_MIN) /
(HASS_COLOR_MAX - HASS_COLOR_MIN) * 255)
rgbw += format(int(temp)).encode('utf-8')
# If only cold white is reported 0-255 is negative color temp
elif self._color_channels & COLOR_CHANNEL_COLD_WHITE:
rgbw = b'#000000'
temp = (
255 - (kwargs[ATTR_COLOR_TEMP] - HASS_COLOR_MIN) /
(HASS_COLOR_MAX - HASS_COLOR_MIN) * 255)
rgbw += format(int(temp)).encode('utf-8')
elif ATTR_RGB_COLOR in kwargs:
self._rgb = kwargs[ATTR_RGB_COLOR]
rgbw = b'#'
for colorval in self._rgb:
rgbw += format(colorval, '02x').encode('utf-8')
rgbw += b'0000'
if (not self._zw098 and (
self._color_channels & COLOR_CHANNEL_WARM_WHITE or
self._color_channels & COLOR_CHANNEL_COLD_WHITE)):
rgbw = b'#'
for colorval in color_rgb_to_rgbw(*self._rgb):
rgbw += format(colorval, '02x').encode('utf-8')
rgbw += b'00'
else:
rgbw = b'#'
for colorval in self._rgb:
rgbw += format(colorval, '02x').encode('utf-8')
rgbw += b'0000'
if rgbw is None:
_LOGGER.warning("rgbw string was not generated for turn_on")
+1 -11
View File
@@ -8,7 +8,7 @@ import logging
from homeassistant.components.lock import LockDevice
from homeassistant.const import (
ATTR_BATTERY_LEVEL, STATE_LOCKED, STATE_UNLOCKED)
STATE_LOCKED, STATE_UNLOCKED)
from homeassistant.components.vera import (
VeraDevice, VERA_DEVICES, VERA_CONTROLLER)
@@ -32,16 +32,6 @@ class VeraLock(VeraDevice, LockDevice):
self._state = None
VeraDevice.__init__(self, vera_device, controller)
@property
def device_state_attributes(self):
"""Return the state attributes of the device."""
attr = {}
if self.vera_device.has_battery:
attr[ATTR_BATTERY_LEVEL] = self.vera_device.battery_level + '%'
attr['Vera Device Id'] = self.vera_device.vera_device_id
return attr
def lock(self, **kwargs):
"""Lock the device."""
self.vera_device.lock()
+1 -1
View File
@@ -39,7 +39,7 @@ class VerisureDoorlock(LockDevice):
@property
def name(self):
"""Return the name of the lock."""
return 'Lock {}'.format(self._id)
return '{}'.format(hub.lock_status[self._id].location)
@property
def state(self):
+1 -1
View File
@@ -10,7 +10,7 @@ from homeassistant.components.lock import LockDevice
from homeassistant.components.wink import WinkDevice
from homeassistant.const import CONF_ACCESS_TOKEN
REQUIREMENTS = ['python-wink==0.7.8', 'pubnub==3.7.8']
REQUIREMENTS = ['python-wink==0.7.11', 'pubnub==3.8.2']
def setup_platform(hass, config, add_devices, discovery_info=None):
+2 -1
View File
@@ -46,7 +46,8 @@ class ZwaveLock(zwave.ZWaveDeviceEntity, LockDevice):
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._state = value.data
self.update_ha_state()

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