Compare commits

..

481 Commits
0.39 ... 0.42.1

Author SHA1 Message Date
Paulus Schoutsen
9c386c68dd Merge pull request #6993 from home-assistant/release-0-42-1
0.42.1
2017-04-09 01:06:10 -07:00
Paulus Schoutsen
f51d705ac7 Make discovery not block start (#6991)
* Make discovery not block start

* Fix tests
2017-04-09 01:06:16 -07:00
Paulus Schoutsen
62d0df4f73 Upgrade to aiohttp 2.0.6 (#6992) 2017-04-08 18:30:02 -07:00
Paulus Schoutsen
d675804119 Version bump to 0.42.1 2017-04-08 18:29:39 -07:00
Paulus Schoutsen
cd8723f742 Merge pull request #6956 from home-assistant/release-0-42
0.42
2017-04-08 15:40:30 -07:00
Marcelo Moreira de Mello
50cc2ed97c Bump Amcrest module to 1.1.8 (#6990)
Fixed traceback when calculating SD card percent storage

   self._state = self._camera.percent(sd_used[0], sd_total[0])
AttributeError: 'Http' object has no attribute 'percent'
2017-04-08 14:55:12 -07:00
Paulus Schoutsen
dea9aec268 Warn if start takes a long time. (#6975)
* Warn if start takes a long time.

* ps - cleanup

* Tweak message

* Add tests

* Tweak messagE
2017-04-08 14:55:12 -07:00
Teemu R
5a2ab3167b switch.tplink: bump pyhs100 version requirement (#6986) 2017-04-08 06:33:59 -07:00
John Mihalic
660b1b616b Update Emby for aiohttp v2 (#6981) 2017-04-08 04:51:47 -07:00
Paulus Schoutsen
d8558ad173 Fix control+c quitting HASS (#6974) 2017-04-08 04:50:52 -07:00
Pascal Vizeli
a93c01788d Bugfix time and task coro (#6968)
* Bugfix time and task coro

* fix also other create_task

* fix tests

* fix lint in test
2017-04-08 04:50:52 -07:00
Adam Mills
d3c1a48475 Update kodi for aiohttp2 (#6967) 2017-04-08 04:50:52 -07:00
happyleavesaoc
01672e63ea Crime Reports sensor (#6966)
* add crimereports

* add crimereports metadata

* implicit interval

* remove zone support
2017-04-08 04:50:52 -07:00
viswa-swami
382519e082 Foscam Camera: Adding exception handling when fetching the camera image to avoid python exception errors when host is not reachable or rather any url error to camera (#6964)
* Adding exception handling when fetching the camera image to avoid python errors when host is not reachable or any url errors to camera

* Added exception as ConnectionError instead of plain except

* Added exception as ConnectionError instead of plain except. Removed the unused error handle
2017-04-08 04:50:52 -07:00
Andrey
5d1dbd61b2 Preserve customize glob order. (#6963)
* Preserve customize glob order.

* add tests
2017-04-08 04:50:52 -07:00
aufano
69dee168a1 Fix current_temperature is rounded (#6960)
* Fix current_temperature is rounded

* fix  Unnecessary parens after 'if'
2017-04-08 04:50:52 -07:00
Teemu R
6d8af58891 light.yeelight: catch i/o related exceptions from the backend lib (#6952)
Fixes/mitigates problems with #5949 and #6624
2017-04-08 04:50:52 -07:00
Pascal Vizeli
0bb224d8c7 Initial import for HassIO (#6935)
* Initial import for HassIO

* Cleanup api code for views

* First unittest for view

* Add test for edit view

* Finish unittest

* fix addons test

* cleanup service.yaml

* Address first round with ping command

* handle timeout dynamic

* fix lint
2017-04-08 04:50:52 -07:00
Fabian Affolter
eb55fc8e77 Update for 0.42 2017-04-06 09:36:57 +02:00
Fabian Affolter
37246449f1 Upgrade sqlalchemy to 1.1.9 (#6955) 2017-04-06 00:27:49 -07:00
Diogo Soares
2551bf8645 Added average temperature for the day before and the current period (#6883)
* Added average temperature for the day before and the current period

* Fixed "line too long" warnings

* Fixed "indentation contains tabs" and "indentation contains mixed spaces and tabs" warnings

* Fixed "trailing whitespace" warnings

* upgrade pyhydroquebec requirements to version 1.1.0
2017-04-06 00:26:26 -07:00
Martin Hjelmare
749f79e813 Upgrade mysensors dep and callbacks (#6950) 2017-04-06 00:21:21 -07:00
Pascal Vizeli
86568b443c Fix startup of sonos / snapshot handling / error handling (#6945)
* Fix startup of sonos / snapshot handling / error handling

* Use decorator for coordinator relay

* fix lint

* Fix unittest

* Move subscribe into executor
2017-04-05 23:24:30 -07:00
Paulus Schoutsen
29f385ea76 Fix automations listening to HOMEASSISTANT_START (#6936)
* Fire EVENT_HOMEASSISTANT_START automations off right away while starting

* Actually have core state be set to 'starting' during boot

* Fix correct start implementation

* Test and deprecate event automation platform on start

* Fix doc strings

* Remove shutting down exception

* More strict when to mark an instance as finished

* Add automation platform to listen for start/shutdown

* When we stop we should wait till it's all done

* Fix testing

* Fix async bugs in tests

* Only set UVLOOP when hass starts from CLI

* This hangs normal asyncio event loop

* Clean up Z-Wave node entity test
2017-04-05 23:23:02 -07:00
Fabian Affolter
289d6b6605 Upgrade py-cpuinfo to 3.0.0 (#6948) 2017-04-05 21:29:59 +02:00
Fabian Affolter
73f69085d9 Upgrade Sphinx to 1.5.5 (#6947) 2017-04-05 21:05:50 +02:00
Thibault Cohen
118bd34d74 Add multi phone numbers support (#6605)
* Add multi phone numbers support

* Update fido.py
2017-04-05 17:18:02 +02:00
Jeff Wilson
f1f033e5d2 Report proper features in mqtt_json light (#6941)
* Add tests for supported features in mqtt_json (it fails)

* Fix supported features in mqtt_json
2017-04-05 14:39:19 +02:00
Adam Mills
75a3747f61 Rename zwave nodes by node ID instead of entity ID (#6938) 2017-04-05 08:11:37 -04:00
Marcelo Moreira de Mello
a5f77d5f46 Clean artifacts after running Ring tests. (#6944)
* Clean artifacts after running Ring tests.

* Clean artifacts from top level test_ring.py
2017-04-05 11:26:56 +02:00
Greg Dowling
534187f4cd Bump pywemo version. Fixes Osram/Sylvania Lightify tunable white bulbs. (#6946)
Add an optional extended description…
2017-04-05 11:26:03 +02:00
Pascal Vizeli
8f4fd951e5 Add android ip webcam support for aiohttp2 (#6940) 2017-04-04 21:40:19 -07:00
citruz
e4e7141ae7 Eddystone Beacon Temperature Sensor (#6789)
* Added eddystone_temperature platform.

* Fixed style issues.

* Fixed style issues #2.

* Fixed style issues #3.

* Added new platform to .coveragerc

* Refactored platform to use the beacontools package.

* Fixed style issues and added beacontools to excluded requirements.

* Removed obsolete constants and added pylint exception.

* Added blank line

* Updated beacontools to version 1.0.0

* Updated beacontools to version 1.0.1

* Forgot to regenerate requirements_all

* Minor changes
2017-04-04 23:57:19 +02:00
Paulus Schoutsen
c4e1255a84 Initial state over restore state (#6924)
* Input Boolean: initial state > restore state

* Input select: initial state overrules restored state

* Input slider: initial state overrule restore state

* Lint

* Lint
2017-04-04 09:29:49 -07:00
Craig J. Ward
c5574c2684 total connect alarm support (#6887)
* total connect alarm support

* linting fixes

* linting fixes

* docstring

* docstring

* use sync and update coveragerc

* remove unused import

* changes as per notes

* Update HA code style
2017-04-04 12:21:53 +02:00
Greg Dowling
dcbc0b490c WIP - Fix bug in state handling in Vera Switch and Light (#6931)
* Fix bug in state handling.

* Tidy.
2017-04-04 12:02:09 +02:00
Klaas Hoekema
57a00c1fbf Allow token authentication for 'hook' switch component (#6922)
The Hook switch component currently uses username/password to get a token
from the Hook web app to use for subsequent requests. This adds an option
to specify the token directly in config.

Makes the 'password' and 'token' options mutually exclusive since
otherwise one would have to take precedence, and it seems worth
preventing people from filling their config with passwords that don't
even need to be there.
2017-04-04 10:55:43 +02:00
Fabian Affolter
aff8c0f695 Upgrade Sphinx to 1.5.4 (#6927) 2017-04-04 10:50:04 +02:00
Fabian Affolter
542e430c1c Upgrade distro to 1.0.4 (#6926) 2017-04-04 10:44:08 +02:00
Fabian Affolter
26e9e59a5b Upgrade paho-mqtt to 1.2.1 (#6928) 2017-04-04 10:43:41 +02:00
Daniel Høyer Iversen
86d265d407 Upgrade flux_led to 0.17 (#6929)
* Update flux_led lib
2017-04-04 10:42:51 +02:00
Paulus Schoutsen
23645da74c Automation: initial state > restore state (#6911)
* Automation: initial state > restore state

* Clean up code

* Ensure MQTT defaults are used.

* Ensure failed platforms always return None

* Automation: write state to state machine after start
2017-04-03 23:11:39 -07:00
Paulus Schoutsen
3895979e39 Update frontend 2017-04-03 23:02:15 -07:00
Greg Dowling
5b9d9954c5 Update vera cover refresh logic (#6897)
* Update cover update logic to fix compatibility bug. Bump vera library.

* Tidy.

* Add missed file.
2017-04-03 22:44:52 -07:00
John Mihalic
4c7ec4932c Bump pyHik library version to support more cameras (#6921) 2017-04-04 06:54:33 +02:00
Adam Mills
06e1c21b1f Support for zwave light transitions (#6868)
* Support for zwave light transitions

* Dimming duration is optional

* Updated supported_features to show transition
2017-04-03 14:56:48 -04:00
Mitesh Patel
01e581aced Adds support for the PlugInDimmer hardware (#6915) 2017-04-03 08:52:02 -07:00
John Arild Berentsen
a107a592de Fix for #6691 Neato Connection error handling (#6731)
* Responsiveness

* Delay was not needed as commands does not return until done.

* Offline error catch

* Remove unneeded code
2017-04-03 14:42:21 +02:00
Dan
134b21dfea Onkyo update (#6906)
* Update onkyo req, change volume to int

* Update Onkyo

Updates onkyo component. Pulls added sources (Bluetooth, built-in
streaming, etc.)

* Regenerated requirements_all.txt via script

* Update onkyo.py

* Update requirements_all.txt
2017-04-03 00:35:36 -07:00
David McNett
c27a526f5b Eliminate needless async_add_job invocation of async_add_devices (#6864) 2017-04-03 00:01:53 -07:00
Marcelo Moreira de Mello
f4d2ece2fe Make sensor.ring to handle scan_interval option as expected. (#6886)
* Make sensor.ring to handle scan_interval option as expected.

* Fix lint

* Fixed lint timedelta
2017-04-02 23:46:54 -07:00
Marcelo Moreira de Mello
5b8f1850fa Makes amcrest.sensor to handle properly the scan_interval option. (#6885)
* Makes amcrest.sensor to handle scan_interval option as expected.

* Added _LOGGER.debug statement for troubleshooting.

* Fixed lint
2017-04-02 23:46:18 -07:00
Paulus Schoutsen
ce42648a51 Update README.rst 2017-04-02 17:35:03 -07:00
Paulus Schoutsen
36e5878b2e Move examples out (#6908)
* Remove examples from main repo

* Simplify README

* Point screenshot for components at dev branch for now
2017-04-02 17:01:51 -07:00
Dan
f0027e3cc1 Fox UMP volume set (#6904)
async needs consistant paramater namming accross platforms. This fixes
UMP's volume set method by renaming the paramater.
2017-04-02 15:31:28 -07:00
Wolfgang Malgadey
864b57d42c Fix Tado climate set off mode (#6848) 2017-04-02 12:47:15 -04:00
Daniel Høyer Iversen
8806265e99 Fluxled (#6892)
* Update flux_led lib
2017-04-02 14:12:38 +02:00
ChristianKuehnel
2413d97415 added support for Fibaro FGR-222 (similar to FGRM-222) (#6890) 2017-04-02 12:41:53 +03:00
Fabian Affolter
395f9b6548 Upgrade sqlalchemy to 1.1.8 (#6873) 2017-04-01 12:36:46 +02:00
Fabian Affolter
7afe694cc7 Upgrade aiohttp_cors to 0.5.2 (#6874) 2017-04-01 12:36:35 +02:00
Fabian Affolter
ec2df2ca0f Upgrade pytz to 2017.02 (#6875) 2017-04-01 12:36:24 +02:00
Marcelo Moreira de Mello
65b9383e04 Bumped amcrest module to 1.1.5 (#6872) 2017-04-01 12:36:04 +02:00
Fabian Affolter
a0bb554f8a Upgrade speedtest-cli to 1.0.3 (#6867) 2017-03-31 22:57:29 +02:00
Jacob Tomlinson
2d6b09586d Added Met Office weather and sensor components (#6742)
* Added Met Office weather and sensor components

* Removed unnecessary dependancy

* Generated requirements

* Fix time interval

* Updated coverage

* Some review changes

* Allow user to specify lat and lon in component

* Added missing import

* Fixed unit

* Fixed import indent

* Updated condition to use CONDITION_CLASSES
2017-03-31 22:03:27 +02:00
Fabian Affolter
573b2a11c0 Upgrade sphinx-autodoc-typehints to 1.2.0 (#6865) 2017-03-31 21:39:22 +02:00
Fabian Affolter
ac25eff2d0 Upgrade sendgrid to 3.6.5 (#6866) 2017-03-31 12:37:34 -07:00
Marcelo Moreira de Mello
05398a9dff Introduced Ring binary sensors and refactored Ring component (#6520)
* - Introduced Ring binary_sensor.

- Added unittest for Ring binary_sensor.

- Bumped ring_doorbell 3rd party module.

* Updated requirements

* Added correct file for unittest

* - Introduced Ring binary_sensor.

- Added unittest for Ring binary_sensor.

- Bumped ring_doorbell 3rd party module.

* Updated requirements

* Added correct file for unittest

* Added extra sensors last_ding and last_motion

* Modified Ring binary_sensor and sensor to inherit DOMAIN configuration

* Moved static to top ring.py

* Fixed requirements

* Bump version ring_doorbell to 0.1.2

* testing unittests

* Use hass.data dict instead GLOBALS

* Fixed unittests

* Bump ring_doorbell to 0.1.3

* Updated unittest and coverted to use decorator @requests_mock.Mocker()

* Updated ring_session with lower case
2017-03-31 08:53:56 -07:00
Paulus Schoutsen
8c97bccaaa Handle aiohttp task cancellation better (#6862) 2017-03-31 11:55:22 +02:00
Craig J. Ward
5bb201c7fc use change light level to avoid variable ramp speeds (#6860) 2017-03-30 23:53:10 -07:00
Pascal Vizeli
72db4a80dd Update aioHTTP to 2.0.5 (#6856) 2017-03-30 08:27:53 -07:00
Johan Bloemberg
816b1891b5 Add option to disable automatic add for lights and sensors. (#6852) 2017-03-30 08:02:03 -07:00
Beat
ee8701b560 Fix configuration setup (#6853)
When the user exceeds the request limit for google APIs, the response status stays at 200 but the response body is different:
```
{
   "error_message" : "You have exceeded your daily request quota for this API. We recommend registering for a key at the Google Developers Console: https://console.developers.google.com/apis/credentials?project=_",
   "results" : [],
   "status" : "OVER_QUERY_LIMIT"
}
```
This prevents HA from creating the initial configuration
2017-03-30 15:26:11 +02:00
Paulus Schoutsen
714b516176 aiohttp 2 (#6835)
* Upgrade aiohttp2

* Fix resource caching

* Fix helpers.aiohttp_client

* Lint

* Use static path for api error_log

* Fix ClientErrors import

* Remove not needed DisconnectError

* Remove releasing responses code

* Add timeout if stream starts non responding

* More async_aiohttp_proxy_stream cleanup

* Fix references to ClientError

* Fix fingerprinting

* Fix aiohttp stream tests

* Rename aiohttp_proxy_stream

* Remove impossible darksky test

* Fix sleepiq requests escaping mocker

* Lint

* Remove deprecated parameter

* Break up aiohttp_proxy_stream in 2 methods

* Lint

* Upgrade to aiohttp 2.0.4

* Convert connector close to a callback

* Fix static fingerprinted links
2017-03-30 00:50:53 -07:00
Anders Melchiorsen
7b83a836f3 Lifx legacy (#6847)
* Add legacy LIFX platform for Windows support

The async platform introduced in 9ef084d903 has
turned out to use Python functionality that is not available in Windows.

This commit restores the previous implementation, now named lifx_legacy.

* Add a comment about the platform being a legacy implementation

* Warn when using unsupported lifx platform on Windows

* Update .coveragerc
2017-03-29 23:00:22 -07:00
Johan Bloemberg
ead00e956f Handle initial event after entity is instantiated. (#6760) 2017-03-29 22:50:32 -07:00
Paulus Schoutsen
556dba4020 Convert Alexa tests to use aiohttp test utils (#6839) 2017-03-29 22:21:39 -07:00
Paulus Schoutsen
bfe0aee468 Locative tests to use aiohttp test utils (#6838) 2017-03-29 22:19:58 -07:00
Lewis Juggins
9de4c2b056 [switch.wemo] Fix today_energy_kwh calculation. (#6846)
* [switch.wemo] Fix today_energy_kwh calculation.

* Blank line fail

* Round to two decimal places.
2017-03-29 21:49:28 +02:00
Anubhaw Arya
c935bfce2a Integration with lockitron (#6805)
* Integration with lockitron

* Let super class deal with polling and updating
2017-03-29 17:25:23 +02:00
Martin Hjelmare
7c614a6738 Add voluptuous config validation to scenes (#6830)
* Add platform schema to scene component and homeassistant platform.
* Clean up code and add constants.
* Add unit test and clean up tests.
2017-03-28 23:39:53 -07:00
Xorso
d1b519a418 Updating Alarm.com Component for async and no Selenium (#6752)
* Updating Alarm.com Component for async and no Selenium

* Fixed gen_all_requirements
2017-03-28 23:21:40 -07:00
Johan Bloemberg
e1ed076015 Rflink group commands (#5969)
* Add support for group commands (allon/alloff).
Add 'group_aliasses' config attribute that only respond to group commands.
Add nogroup_aliases that only respond to 'on' 'off' commands.
Allow settings device id group behaviour.

* Fix linting.

* Fix lint.
2017-03-29 01:04:25 -04:00
Oleksii Serdiuk
63c15e997a history_stats: Fix schema, as state can be arbitrary string (#6753) 2017-03-29 00:58:59 -04:00
Andrey
fb8323f48d Remove zwave cover invert workaround. Use config instead. (#6832) 2017-03-28 23:01:29 +03:00
William Scanlon
b5336ed04e Updated pubnubsub-handler version (#6829) 2017-03-28 14:13:45 -04:00
Teemu R
429367409c yeelight: adjust supported features on update() (#6799)
* yeelight: adjust supported features on update()

Earlier we checked for the type only during the initialization,
but this won't work when the bulb is disconnected during the init,
causing failures to adjust rgb&color temperature even if those should be supported.

fixes #6692

* Use reassign instead of OR for updating the supported features
2017-03-28 17:26:43 +02:00
Lewis Juggins
6dba05c79f [switch.wemo] Fix mW to kW conversion. (#6826)
* Fix mW to kW conversion.

* Line length.
2017-03-28 17:24:33 +02:00
Janne Grunau
5c80da6a8f lights/hue: use device class for on/off devices like the osram lightify plug (#6817)
* hue: remove duplicate SUPPORT_FLASH flag

* hue: use correct device class for the Osram Lightify Plug
2017-03-28 00:04:28 +02:00
Fabian Affolter
d027df5a89 Allow to monitor Windows hosts (#6803) 2017-03-27 22:11:15 +02:00
goto100
b8c1bc9542 fix WOL in docker/jail (#6810)
* fix WOL in docker/jail

add ip_address parameter to send_magic_packet if host specific.

in docker/jail, WOL doesn't works due to subnet broadcast issues.

* Update wake_on_lan.py

lint
2017-03-27 14:02:43 +02:00
Greg Dowling
c53de19246 Update Insight parameters using the subscription data. (#6782)
* Update Insight parameters using the subscription dta.

* Minor refactor.

* Tidy.
2017-03-27 12:43:43 +02:00
John Mihalic
f242ad26ca Add NVR support to Hikvision Binary Sensors (#6807)
* Add NVR support to hikvision

* Only append channel for nvr devices

* Descriptor cleanup

* Update requirements
2017-03-27 12:41:57 +02:00
tantecky
a70af62e60 Make get_snmp_data more robust (#6798) 2017-03-27 12:22:59 +02:00
Fabian Affolter
be04ef7be1 Upgrade matrix-client to 0.0.6 (#6808) 2017-03-27 10:35:40 +02:00
Fabian Affolter
f4f72e420a Fix typo and update name (#6809) 2017-03-27 10:35:27 +02:00
Fabian Affolter
84287872bb Use string formatting and remove already global disabled pylint issue (#6801) 2017-03-26 21:13:38 +02:00
David Straub
78b5eb7aac Platform for Munich public transport departure times (#6704)
* Add MVGLive (Munich public transport real-time departure) sensor platform

* Move update on startup to add_devices; rewrite dictionary filtering

* Fix lint error

* Updated requirement to PyMVGLive 1.1.3 (PyPI version)

* Refactor and clean up MVGLiveData.update method

* Shorten line
2017-03-26 19:06:40 +02:00
Fabian Affolter
6e44ccf683 Upgrade pysnmp to 4.3.5 (#6793) 2017-03-26 15:53:53 +02:00
Fabian Affolter
ad649009cd Upgrade zeroconf to 0.19.0 (#6792) 2017-03-26 15:53:42 +02:00
Fabian Affolter
7782e7e948 Add optional unit of measurement (#6796) 2017-03-26 15:52:59 +02:00
Fabian Affolter
5d5547cdb6 Update docstrings (#6795)
* Add link to docs and remove comments which are obvious

* Update docstrings

* Repleace conf details with link to docs

* Add link to docs

* Update docstrings

* Update import

* Update ordering

* Update ordering

* Update docstring

* Update ordering

* Update ordering
2017-03-26 15:50:40 +02:00
Fabian Affolter
22b28d85db Add switch to MQTT discovery (#6733) 2017-03-26 15:48:28 +02:00
Teemu R
f5d4f853ba switch.tplink: upgrade to the newest upstream release which adds support for plugs using the newer communication protocol (#6790) 2017-03-26 10:55:17 +02:00
Fabian Affolter
0f098df232 Merge branch 'master' into dev 2017-03-26 00:07:25 +01:00
Fabian Affolter
1f046972d9 Merge pull request #6756 from home-assistant/release-0-41
0.41
2017-03-26 00:01:49 +01:00
John Arild Berentsen
5a7155fc4a Wrong info in discovery schema (#6779) 2017-03-25 19:39:03 +01:00
Nick Sabinske
c817ab08b7 Fix bridge-led support in limitlessled.py (#6776)
Addressing #6382 . Feedback from github & forums is the bridge_led feature never worked and defining the bridge LED as another group+bulb type is the right way to do this in the limitlessled component.
2017-03-25 15:32:57 +01:00
William Scanlon
f8005153c9 Fix wink siren (#6775)
* Fix siren/switch attributes and update python-wink

* Updated requirements_all.txt
2017-03-25 15:28:16 +01:00
Daniel Perna
447048701c Homematic Fixes (#6769)
* Added missing operational modes for thermostats

* Added attributes

* Updated requirements

* Bumped dependency
2017-03-25 09:48:05 +01:00
Pascal Vizeli
f4e9466394 Bugfix automation fire on bootstrap (#6770)
* Bugfix automation fire on bootstrap

* Add test & fix bug

* fix lint
2017-03-24 15:52:14 -07:00
Adam Mills
cffc6c7bea Tests for zwave workaround detection (#6761)
* Tests for zwave workaround detection

* Remove unused code

* Revert "Remove unused code"

This reverts commit e06cce0abe.

* Tests for empty manufacturer ID
2017-03-24 15:31:46 -07:00
Fabian Affolter
8d606f8d16 Upgrade sleekxmpp to 1.3.2 (#6773) 2017-03-24 21:44:04 +01:00
Fabian Affolter
7ae814357a Upgrade psutil to 5.2.1 (#6771) 2017-03-24 21:43:48 +01:00
Fabian Affolter
1be2706de3 Upgrade pydroid-ipcam to 0.7 (#6772) 2017-03-24 21:42:00 +01:00
geekofweek
06d3889e1b Wink Aros Fixes (#6726)
Wink Aros only supports 3 Fan Modes:

Low
Medium
High

Fan Mode had a Typo and wasn't represented in the UI
2017-03-24 16:13:14 -04:00
John Arild Berentsen
ee6c9ab6a9 Typing error and update test (#6757) 2017-03-23 23:53:59 -07:00
John Arild Berentsen
82c599a749 current temp could be none (#6759) 2017-03-23 23:50:36 -07:00
Paulus Schoutsen
0b7f873120 Merge branch 'release-0-41' into dev 2017-03-23 23:34:08 -07:00
Paulus Schoutsen
9f2f0c5566 Version bump to 0.42.0.dev0 2017-03-23 23:33:49 -07:00
Paulus Schoutsen
53f8828181 Merge branch 'master' into release-0-41 2017-03-23 23:32:50 -07:00
Adam Mills
22613d8e2e Repair zwave sensor coverage (#6764) 2017-03-23 20:57:15 -07:00
Adam Mills
efbd66bca1 Fix flaky template test (#6768) 2017-03-23 20:56:39 -07:00
Tim Soderstrom
5dfdb9e481 New indexes for states and recording_runs tables (#6688)
* New indexes for states table

* Added recorder_runs indexes

* Created a new function for compound indexes.

A new function was created because it makes it a little cleaner when creating
a single-field index since one doesn't have to create a list. This is mostly
when creating the name of the index so with a bit more logic it's possible
to combine it into one function. Given how often migration changes are run,
I thought that code bloat was probably a worthy trade-off for now.

* Adjusted indexes, POC for ref indexes by name.

* Corrected lint errors

* Fixed pydocstyle error

* Moved create_index function outside apply_update

* Moved to single line (just barely)
2017-03-23 20:48:31 -07:00
micw
6c5989895a Adding expire_after to mqtt sensor to expire outdated values (#6708)
* Adding expire_after to mqtt sensor to expire outdated values

* Extending test case

* mqtt: refactoring expire_after to use timed events instead of polling; lint

* refactor to reset unused trigger

* Fix: handler must be set to None after execution or removal to avoid warning

* Commenting out non-working test

* Fix lint

* Commit to trigger new build

* Commit to trigger new build

* Make testcase work

* Undo unnecessary change

* Remove default value, add extra check
2017-03-23 17:55:07 -04:00
Daniel Høyer Iversen
3acd926d29 Flux led update lib (#6763)
* Update flux_led lib
2017-03-23 21:58:22 +01:00
Paulus Schoutsen
b6b40286ef Version bump to 0.41 2017-03-23 08:42:01 -07:00
Andrey
8a86ec5b74 Add zwave per-node entity. (#6690)
* Add zwave per-node entity.

* Disable lint import error

* Add tests for base class

* More tests

* More tests

* Sort .coveragerc

* more tests

* Move location, battery and wakeup to node entity

* More tests

* Cleanup

* Make zwave node entity visible by default

* Remove battery sensor

* Fix tests
2017-03-23 08:37:20 -07:00
Dan Ports
20c5f9de4b Add sensor for Lyft time and price (based on Uber sensor) (#6711)
* Add sensor for Lyft time and price (based on Uber sensor)

* Minor fixes to lyft sensor
 - use add_devices(...,True) instead of explicitly calling update
 - move sensor name check into constructor

* lyft sensor: disable sandbox mode
2017-03-23 08:15:52 -07:00
Mitesh Patel
61730012d8 Adds Support for Lutron Caseta devices. (#6631)
* Adds support for the Lutron Caseta family of devices

* Added external requirement

* Removes unuse import

* Adds requirement to requirements_all.txt

* Removes unuse import

* fixes requirement_all.txt. by regenerating

* Cleans up syantax

* Cleans up syantax

* Cleans up syantax

* Cleans up syantax

* Shortens long line

* adds lutron_caseta component to .coveragerc

* Merges into light, removes component

* Fixes long lines

* Fixes requirement.txt

* Removes 'ERROR' log statements. Adds missing dependency

* savig work to pick up on other machine

* Enables Lutron Caseta component with Light and Switch platforms

* Add missing file to .coveragerc

* Changes based on PR review

* fixes requirements file

* Fixes install of paramiko dependency.

* Moves callback registration to

* comment changes

* Platform have no return value

* Change style for guard

* fix logic

* fix lint
2017-03-23 01:18:14 +01:00
Paulus Schoutsen
25d2df5689 Merge pull request #6740 from home-assistant/release-0-40-2
0.40.2
2017-03-22 09:31:21 -07:00
Paulus Schoutsen
672b83db8a Update constraints 2017-03-22 09:30:36 -07:00
Paulus Schoutsen
b37438ebb7 Constrain core dependencies to core versions (#6738)
* Require at least pip 7.1

* Write and use constraint files for packages

* Update gen_requirements_all.py
2017-03-22 08:51:18 -07:00
Paulus Schoutsen
902b72ba1a Constrain core dependencies to core versions (#6738)
* Require at least pip 7.1

* Write and use constraint files for packages

* Update gen_requirements_all.py
2017-03-22 08:50:54 -07:00
Paulus Schoutsen
f10fede17f Bump PyChromecast to 0.8.1 (#6702) 2017-03-22 08:50:42 -07:00
Paulus Schoutsen
c9548b11b1 Version bump to 0.40.2 2017-03-22 08:50:15 -07:00
Wolfgang Malgadey
f4aec3ac88 Tado climate device (#6572)
* Added tado climate component

named the component v1 because of the unsupported state of the api I
used (mytado.com)

* sensor component
* climate component which uses sensors
* main component initiating sensor and climate devices
* order of imports
* consts for username and password
* removed redundant code
* changed wrong calls and properties

* remove pylint overrides

* merged update() and push_update()

* changed wrong calls
* removed pylint overrides
* moved try..except

* renamed MyTado hass-data object

* added TadoDataStore

* moved update methods from sensor to TadoDataStore

* reorganised climate component

* use new TadoDataStore

* small change to overlay handling

* code refactoring

* removed unnessesary comments
* changed throttle to attribute
* changed suggestions from PR

* Added constant variable for string literal

* remove wrong fget() call

* changed dependencies

* Changed operation mode list

* added human readable list of operations
* removed unnecessary const
* activated update on add_devices

* droped unit

* removed unnused property

* changed temperature conversion

* removed defaults from config
changed naming of tado data const

* switched operation_list key/values

* changed the value returned as state

* added one extra line

* dropped state to use base impl.

* renamed component

* had to inplement temperature_unit

* because it is not implemented in base class

* create a copy of the sensors list

* because it can be changed by other components

* had to check for empty data object

* hass is too fast now
2017-03-22 08:18:13 -04:00
Adam Mills
e7425e9808 ZWave Lock Tests (#6730)
* ZWave Lock Tests

* Linting fixes

* Missed coveragerc
2017-03-21 08:55:21 -07:00
Matt N
978b539111 camera.zoneminder: Show recording state (#6686) 2017-03-21 07:25:10 +01:00
Paulus Schoutsen
9f4cd5fafe Do not log warning on rest_command if no error (#6713) 2017-03-21 07:18:33 +01:00
Anders Melchiorsen
ba3c9f9765 Fix LIFX unregister races (#6723)
* Fix LIFX unregister races

If the initial state request never got a response, we tried to unregister
a device that was not yet registered.

Also, aiolifx 0.4.2 has an "unregister" race fix.

* Update requirements
2017-03-21 07:17:58 +01:00
Paulus Schoutsen
4ee8be52fe Update frontend 2017-03-20 21:31:58 -07:00
Adam Mills
866bf887d3 ZWave switch tests (#6722) 2017-03-20 13:17:42 -04:00
Adam Mills
dddbce82f5 ZWave Sensor tests (#6721)
* ZWave Sensor tests

* Add missed coverage
2017-03-20 13:17:17 -04:00
Jeff Wilson
be15ca3f23 Don't warn if octoprint completion is null (#6719) 2017-03-20 09:00:45 -07:00
Finbarr Brady
9a305c9742 Fix for issue: luci returns 403 invalid token when rebooted #6715 (#6717) 2017-03-20 08:55:59 -07:00
Paulus Schoutsen
de231cf9ab restore_state: do not crash if domain not defined (#6714) 2017-03-20 08:54:51 -07:00
Adam Mills
8325f9db8a Add zwave light tests (#6710)
* Add zwave light tests

* Test turn_off
2017-03-19 22:22:13 -07:00
Paulus Schoutsen
3f38b9e52f Update frontend 2017-03-19 21:59:13 -07:00
printzlau
1cb2a6add0 automatically use bundled certificate it set to auto (#6707)
* automatically use bundled certificate if certificate-parameter is set to 'auto' and seperate this from which port is specified

* Fix travis error and long lines

* Update __init__.py
2017-03-19 14:51:53 -07:00
Greg Dowling
acf75b5253 Revise power and energy units and property names. (#6212)
* Revise power and energy units and property names.

* Add change for new wemo parameter.
2017-03-19 22:02:11 +01:00
Adam Mills
970bde9e99 Fix Kodi when websocket is disabled (#6706) 2017-03-19 21:38:12 +01:00
Adam Mills
796143a6c6 Kodi use websocket loop task created by library (#6703) 2017-03-19 12:46:14 -07:00
Paulus Schoutsen
5569ae38f1 Bump PyChromecast to 0.8.1 (#6702) 2017-03-19 11:53:58 -07:00
John Mihalic
7eaad4fb3a Fix longitude (#6697) 2017-03-19 11:00:13 -07:00
Fabian Affolter
35c679a956 Upgrade distro to 1.0.3 (#6693) 2017-03-19 15:59:07 +01:00
martinfrancois
678f273002 Rflink: added support for lights with toggle type (#6521)
* added support for lights with toggle type

* fixed style errors

* introduced tests for the toggle type

it's not passing yet because of an assertionerror at line 407

* updated to reflect tristate of "state"

* Format code according to pep8

Added line break for import that was too long.

* fixed lint, replaced if statement with 'var = bool(test)'

* changed implementation of state check according to bug on 6bceb04ca1 (r106758784)
2017-03-18 21:34:17 +01:00
Tyler Crumpton
4e91c65d6e Update Torque component to match recent API. (#6671) 2017-03-18 11:25:38 +01:00
Robbie Trencheny
f6106706e5 Merge pull request #6672 from robbiet480/new-plex-fix
Fixed Show All Controls feature
2017-03-17 17:34:26 -07:00
JesseWebDotCom
ecf337b123 Fixed Show All Controls feature 2017-03-17 17:22:38 -07:00
Martin Nöhrer
f5d8327d9a Fix hass script execution on Windows (#4977). (#6601)
* Fix hass script execution on Windows (#4977).

hass.exe returned ERRNO2 on a windows machine and must be started using
package loading. This fix adapts the command line options for
`setup_and_run_hass()` to start
either a script with `python homeassistant/__main__.py` or with
`Scripts/hass.exe`

* Fix code style
2017-03-17 17:07:36 -07:00
John Mihalic
30d4c54187 Update Emby component to async (#6664)
* Update Emby component to async

* Address comments

* Make SSL default

* Bump library

* Port based on SSL, use available property
2017-03-17 15:55:07 +01:00
David Straub
edf20f542a Phone book lookup support for Fritz!Box call monitor (#6474)
* Update fritzconnection dependency in fritz and fritzbox_netmonitor components

* Add phone book name lookup support to FritzBox call monitor

* Updated requirements_all.txt

* Requested changes to FritzBox call monitor

* Listen to HOME_ASSISTANT_STOP and close thread

* Safe CPU time
2017-03-17 14:40:12 +01:00
Jay Love
9778000e9a Add new media_player platform: Volumio Media Player (#6556)
* Add new media_player platform: Volumio Media Player

Volumio media player is a rpi music player, this platfor adds http based control of the player.

* Modify mute command to accept boolean

* Adjust mute call to reset volume after unmute
Remove references to volimi"a"

* Use yield from calls in mute and volume calls

Trying to speed up the indication of mute and volume level changes in UI, but doesn't seem to do much.

* Adjust async_add_devices call
2017-03-16 23:32:52 -07:00
miniconfig
b5149dfba6 Added support for multiple efergy sensors in the same household. (#6630)
* Added support for multiple efergy sensors in the same household.
Also added inital tests for the efergy platform.

* Fixed current_values units.
Changed name to include efergy_ prefix.
2017-03-16 23:22:10 -07:00
John Mihalic
ced3cd2616 Refactor Neurio to add Daily Power Sensor (#6662)
* Refactor Neurio to add Daily Power Sensor
2017-03-16 23:20:14 -07:00
RickyTaterSalad
7050236a61 add latitude and longitude configuration to darksky sensor (#6191)
* Optional latitude and longitude to darksky sensor

allow configuration of latitude and longitude in the darksky sensor.
falls back to home assistant coordinates if latitude/longitude not
specified.

* lat/long as inclusive on schema. removed None check for lat/long in setup_platorm

altered schema to require latitude and longitude configured as a pair.
removed None check on latitude and longitude since values will fall back
to hass config if not present

* adhere to line limit of 79 characters

adhere to line limit of 79 characters
2017-03-16 23:04:01 -07:00
Pascal Vizeli
c8e1ffad89 Pump Android ip webcam to 0.6 (#6665)
* Pump Android ip webcam to 0.5

* update to 0.6
2017-03-16 22:19:48 +01:00
hawk259
e3edff8a72 Add configurable timeout option to camera.synology (#6655) 2017-03-16 21:26:35 +01:00
Sebastian
d6fd0f405e Corrected help text for refresh_node (#6659)
Changed entity_id to node_id.
2017-03-16 21:50:01 +02:00
Fabian Affolter
1ab47b5d2b Check if droplet exists (#6663)
* Check if droplet exists

* Add droplet to message and remove else
2017-03-16 19:59:34 +01:00
Fabian Affolter
a2365eccf6 Upgrade aiohttp to 1.3.5 (#6660) 2017-03-16 18:04:00 +01:00
Fabian Affolter
c46ba3446d Upgrade astral to 1.4 (#6332)
* Upgrade astral to 1.4

* Update test for norway
2017-03-16 17:38:46 +01:00
JesseWebDotCom
5714f156c3 Support for non-clients, NVidia shield, dynamic grouping, extra metad… (#6054)
* Support for non-clients, NVidia shield, dynamic grouping, extra metadata, and more

* Fixed import orderdering, line lengths, and comment violations

* Local player check and season fixes

* Honor entity_namespace when using custom entity ids

* Semi compatibility with channels, force controls option added

* media_position_updated_at fix - only update when media is playing

* Fix: controls now work as expected on 1) casted sessions and 2) client sessions when client and PMS reside on the same sever

* Made PEP8 compliant

* Made PEP8 compliant

* Made PEP8 compliant, hopefully

* Better Tivo compatibility

* Added frozen detection and remediation

* PlayMedia EPISODE now supports season number and episode number (instead of episode index)

* Fix: Dynamic groups now exclude hidden devices

* Fix: Dynamic groups now exclude hidden devices

* Implemented arsaboo suggested formatting

* Implemented pylint command line suggested fixes

* Implemented Travis CI build suggested fixes

* Sorted Imports using Importanize

* Grouped request imports

* Removed dynamic groups, network calls from properties, and other cleanup

* Balloob recommendations and Plex Protocol Capabilities checks

* Remove deprecated disable-msg in favor of disable

* Removed position related items (seek, frozen detection, etc)

* Removed unused datetime
2017-03-16 09:09:46 -07:00
joe248
959dd29c90 round output values (#6657) 2017-03-16 15:36:44 +01:00
John Arild Berentsen
e75a66ed20 Add Zwave sensors test (#6640)
* Test for zwave sensor

* Add test for ZWave sensors

* Hound...

* Hound...

* Review changes
2017-03-16 08:25:37 -04:00
Fabian Affolter
3e72aa8643 Upgrade python-digitalocean to 1.11 (#6653) 2017-03-16 11:14:36 +01:00
John Arild Berentsen
5aaa1f8404 Add test for Z-wave switch (#6619)
* Add test for Z-wave switch

* Changes for new tests
2017-03-16 11:05:51 +01:00
Paulus Schoutsen
7292e564f8 Merge pull request #6652 from home-assistant/release-0-40-1
0.40.1
2017-03-15 23:47:10 -07:00
Wolf-Bastian Pöttner
509cfb6433 Added workday sensor (#6599)
* Added workday sensor

* Added unit tests
2017-03-15 23:46:13 -07:00
Pascal Vizeli
acdab67c1b Bugfix RFLINK remove group (#6580)
* Bugfix RFLINK remove group

* Remove group hack from lutron too

* fix tests

* fix lint

* fix lint
2017-03-15 23:19:33 -07:00
deisi
d7addf59cd Fix #6534 (#6598)
* Fix #6534

Makes sure 0 is not passes to `color_temperature_kelvin_to_mired`.

* Update osramlightify.py

* Update osramlightify.py
2017-03-15 23:19:24 -07:00
Yum
ccf9edf815 since knx_2_float can't handle 0, bypass converting 0 value from knx to float (#6626) 2017-03-15 23:18:55 -07:00
Johann Kellerman
2fd3c186e2 Update SMA solar sensor to work with the new add_devices callback (#6602) 2017-03-15 23:18:11 -07:00
Dale Higgs
719199da45 Update pyecobee version to 0.0.7 (#6593) 2017-03-15 23:17:44 -07:00
Thibault Cohen
a3cd7d653d Fix hydroquebec (#6574) 2017-03-15 23:17:17 -07:00
Andrey
aeeb927e19 Fix for the case of zwave value used in several devices. (#6577) 2017-03-15 23:16:50 -07:00
Jesse Newland
a3a14f9ea4 Don't start the push updater if the Apple TV is 'off' (#6552)
Add an optional extended description…
2017-03-15 23:16:32 -07:00
Tyler Page
f4e7b231bc Fix wake_on_lan ping with None as host (#6532)
* Update configuration validation

With the new update, wake_on_lan requires a host key in the configuration

* cast self._host to str as requested

* Changed host key back to optional
2017-03-15 23:15:56 -07:00
Paulus Schoutsen
5f68735375 Version bump to 0.40.1 2017-03-15 23:10:50 -07:00
Pascal Vizeli
774fd19638 Bugfix RFLINK remove group (#6580)
* Bugfix RFLINK remove group

* Remove group hack from lutron too

* fix tests

* fix lint

* fix lint
2017-03-15 23:08:47 -07:00
Paulus Schoutsen
e265401cd0 self.loop.create_task -> self.add_job (#6632)
* self.loop.create_task -> self.add_job

* Core to use create task
2017-03-16 06:58:54 +01:00
deisi
5b3dc7f2a5 Fix #6534 (#6598)
* Fix #6534

Makes sure 0 is not passes to `color_temperature_kelvin_to_mired`.

* Update osramlightify.py

* Update osramlightify.py
2017-03-15 22:58:06 -07:00
Anders Melchiorsen
9ef084d903 Move LIFX to aiolifx for driving the bulbs (#6584)
* Move LIFX to aiolifx for driving the bulbs

* Fix whitespace

* Fix more whitespace

* Fix lint

* Define _available in init

* Add @callback decorators

* Use hass.async_add_job

* Rename class
2017-03-15 22:50:33 -07:00
Robbie Trencheny
95b1e257bb Merge pull request #6633 from home-assistant/deprecate-event-forwarding
Deprecate event forwarding
2017-03-15 22:27:49 -07:00
Adam Mills
b06cf87c74 Kodi: Fix episode media type classification (#6645) 2017-03-15 22:07:30 -07:00
Adam Mills
326337777a Add ZWave cover tests (#6648) 2017-03-15 22:06:37 -07:00
mvillarejo
2c8a06bfbe media_player.kodi extra attributes for tvshow and music media (#6622)
* media_player.kodi extra attributes for tvshow and music media

* removed extra whitespaces/CR

* Kodi - add extra attributes #6250 (removed music attributes)

* Restored music attributes, this is ready for merge

* linting amended

* Fix Kodi artist support

* Copy-paste error

* Fix for non-music artist lookup

Kodi returns an emtpy list on videos, so we need to be able to
handle that as well.
2017-03-15 19:51:31 -04:00
Pascal Vizeli
198a234468 aioHttp 1.3.4 (#6643) 2017-03-15 22:30:46 +01:00
Paulus Schoutsen
e94aa3afe9 Deprecate event forwarding 2017-03-15 08:38:26 -07:00
Paulus Schoutsen
96e22c7b41 Remove event decorators (#6634) 2017-03-15 14:46:57 +01:00
Nathan Henrie
33450c726d Use sqlite's WAL mode to avoid database is locked errors (#6519)
* Use sqlite's WAL mode to avoid `database is locked` errors

- Relevant issue: https://github.com/home-assistant/home-assistant/issues/4780

Code:

- http://stackoverflow.com/a/23661501/1588795
- http://docs.sqlalchemy.org/en/rel_0_9/dialects/sqlite.html#foreign-key-support
- https://github.com/g2p/bedup/pull/86/files

* Only set WAL if using sqlite

* Reorder imports

* Fix pylint warnings
2017-03-15 00:14:12 -07:00
Yum
97b9d3bd21 since knx_2_float can't handle 0, bypass converting 0 value from knx to float (#6626) 2017-03-14 20:25:52 -07:00
Adam Mills
fff589eeab Correctly flag Kodi media types (#6628) 2017-03-14 20:25:15 -07:00
Adam Mills
5e5d2e8ab8 Tests for ZWave climate (#6629)
But wait, there's more! Also a few fixes discovered writing the tests.
2017-03-14 20:24:35 -07:00
Paulus Schoutsen
1a7ffdca52 Add "Refactor zwave discovery to entity schema" (#6565)
* Revert "Revert "Refactor zwave discovery to entity schema (#6445)" (#6564)"

This reverts commit 58826b264a.

* Update zwave tests for enitity schema

* Fix merge error

* Switch dict_id to id(self)
2017-03-14 19:55:33 -04:00
Erik Eriksson
cada74df22 Merge pull request #6583 from molobrakos/voc-fix
Bump VOC version (fixes heater bug)
2017-03-14 23:37:32 +01:00
Johan Bloemberg
bd3fbe8363 Upgrade to dsmr_parser 0.8, supporting protocol 3 and 5. (#6600)
* Upgrade to dsmr_parser 0.8, supporting protocol 3 and 5.

* Update tests for new import.
2017-03-14 20:16:43 +01:00
Daniyar Yeralin
4d9c7d9684 Update mpd.py (#6553)
* Update mpd.py

Introducing new parameter "Name"

* Update mpd.py

Change `CONF_LOCATION` to `CONF_NAME`
2017-03-14 20:10:35 +01:00
ArrayLabs
0bf66384ed Cover myq fix update pymyq (#6595)
* update pymyq to 0.0.8

update to correct wrong version in setup.py

* update pymyq to 0.0.8

update to correct wrong version in setup.py
2017-03-14 20:06:30 +01:00
tflack
c7798ef43c Define db for SHOW DIAGNOSTICS query since some users will not have a… (#6566)
* Define db for SHOW DIAGNOSTICS query since some users will not have admin perms

* fix white space error from CI
2017-03-14 11:46:46 -07:00
hawk259
f4d8095e54 Add configurable timeout option to notify/smtp (#6609)
* Add configurable timeout option to notify/smtp

* Updated smtp test to include timeout param

* fixed 80 column style issue
2017-03-14 19:00:16 +01:00
Pascal Vizeli
5529d77c62 Prevent entities running multiple updates simultaneously (#6511)
* Protect entity for multible updates on same time.

* Address all comments / make update more robust

* fix unittest

* fix lint

* address comments
2017-03-14 09:26:55 -07:00
Erik Eriksson
c4e151f621 Error handling when connection refused (#6614)
Add an optional extended description…
2017-03-14 10:08:40 +01:00
Fabian Affolter
f58941a0d4 Upgrade googlemaps to 2.4.6 (#6611) 2017-03-14 07:54:19 +01:00
Fabian Affolter
bca673f039 Upgrade py-cpuinfo to 0.2.7 (#6610) 2017-03-14 07:54:10 +01:00
Fabian Affolter
2687f2f623 Fix link (#6612) 2017-03-14 07:53:48 +01:00
Johann Kellerman
134b3d2f3b Update SMA solar sensor to work with the new add_devices callback (#6602) 2017-03-14 06:39:30 +02:00
Dale Higgs
f450c1351c Update pyecobee version to 0.0.7 (#6593) 2017-03-13 21:19:51 +01:00
Fabian Affolter
c6b10f3703 Upgrade Sphinx to 1.5.3 (#6587) 2017-03-13 21:05:27 +01:00
Fabian Affolter
4a08067b9c Upgrade psutil to 5.2.0 (#6585) 2017-03-13 21:05:07 +01:00
Fabian Affolter
9c37437a59 Upgrade sqlalchemy to 1.1.6 (#6591) 2017-03-13 21:02:28 +01:00
Fabian Affolter
9330142987 Upgrade pyasn1 to 0.2.3 (#6588) 2017-03-13 21:01:45 +01:00
Fabian Affolter
2bbaac44d4 Upgrade async_timeout to 1.2.0 (#6590) 2017-03-13 21:01:25 +01:00
Thibault Cohen
253dee8e4d Fix hydroquebec (#6574) 2017-03-13 18:54:23 +01:00
Andrey
5722cf53bf Fix for the case of zwave value used in several devices. (#6577) 2017-03-13 12:19:45 -05:00
Pascal Vizeli
5d301590c3 Remove dispatcher camera (#6579) 2017-03-13 17:54:28 +01:00
Erik
353f5d6b49 bump voc version (fixes heater bug) 2017-03-13 17:28:27 +01:00
Jesse Newland
11da7bed12 Don't start the push updater if the Apple TV is 'off' (#6552)
Add an optional extended description…
2017-03-13 14:23:15 +01:00
Kevin Fronczak
a358c8e10d Upgraded blinkpy version, increased Throttle time for camera (#6561) 2017-03-13 00:12:48 -07:00
Paulus Schoutsen
58826b264a Revert "Refactor zwave discovery to entity schema (#6445)" (#6564)
This reverts commit 56abc7f9b4.
2017-03-12 23:35:10 -07:00
Adam Mills
56abc7f9b4 Refactor zwave discovery to entity schema (#6445)
* Refactor zwave discovery to entity schema

* Address PR concerns

* Split DISCOVERY_SCHEMAS into separate file

* Only check cover reverse workaround once
2017-03-12 23:13:34 -07:00
Adam Mills
55d60a6a13 ZWave binary sensor tests (#6555)
* ZWave binary sensor tests

* Test fixes

* Improve coverage of features
2017-03-12 22:08:53 -07:00
Dennis de Greef
5183cb5903 Be able to select mqtt:tls_version for Python < 3.6 (#6442)
* Be able to select tls_version

* This test should always assert this value, not only in 3.6

* Disable linting on future property (py36)

* Only allow TLS 1.0, 1.1 and 1.2

* Fix line length issue

* Fix check config tests

* Allow auto as a TLS version
2017-03-12 22:02:59 -07:00
Tyler Page
0aa8933df6 Fix wake_on_lan ping with None as host (#6532)
* Update configuration validation

With the new update, wake_on_lan requires a host key in the configuration

* cast self._host to str as requested

* Changed host key back to optional
2017-03-12 20:46:58 +01:00
siebert
fc46a24996 Fix gen_requirements_all.py script for Windows. (#6547) 2017-03-12 21:08:49 +02:00
Paulus Schoutsen
5be58bd056 Simplify Android IP webcam discovery (#6528) 2017-03-12 18:56:48 +01:00
Lewis Juggins
4a423e63f3 Version bump to 0.41.0.dev0 2017-03-12 08:34:35 +00:00
Paulus Schoutsen
a8b32edc8e Merge pull request #6509 from home-assistant/release-0-40
0.40
2017-03-11 11:08:40 -08:00
Greg Dowling
4a5b9db394 Discovery is a dict rather than an array. (#6525) 2017-03-11 11:06:05 -08:00
Greg Dowling
9c23178457 Append vera device id to entity id - but not name. (#6523)
* Append vera device id to entity id - but not name.

* Tidy.

* Tidy.

* Tidy after review.

* Re-order.
2017-03-11 11:06:05 -08:00
Paulus Schoutsen
764d31efcb Remove mint finance sensor (#6522) 2017-03-11 11:06:05 -08:00
Anders Melchiorsen
fc90ccea36 Fix colortemp conversion for osramlightify (#6516)
* Fix colortemp conversion for osramlightify

Copied from the LIFX fix in 75df4be733.

* Fix style

* Updates from review

@armills:

While we're doing cleanup here, can you just change self._brightness,
self._rgb, self._name, self._temperature, and self._state assignments in
__init__ to None? These will get overwritten when self.update() is called, so
it's safer/cleaner to initialize them to None since it shouldn't matter if
everything is working.
2017-03-11 11:06:05 -08:00
Martin Hjelmare
aab63ea22a Fix mysensors gateway windows setup (#6500) 2017-03-11 11:06:05 -08:00
Adam Mills
157ab77232 Update Kodi notifier to async (#6497)
* Update Kodi notifier to async

* Change Kodi CONF_SSL to CONF_PROXY_SSL
2017-03-11 10:41:05 -08:00
Anders Melchiorsen
9a86ccaaea Fix colortemp conversion for osramlightify (#6516)
* Fix colortemp conversion for osramlightify

Copied from the LIFX fix in 75df4be733.

* Fix style

* Updates from review

@armills:

While we're doing cleanup here, can you just change self._brightness,
self._rgb, self._name, self._temperature, and self._state assignments in
__init__ to None? These will get overwritten when self.update() is called, so
it's safer/cleaner to initialize them to None since it shouldn't matter if
everything is working.
2017-03-11 10:40:16 -08:00
Greg Dowling
32dd815852 Discovery is a dict rather than an array. (#6525) 2017-03-11 10:39:26 -08:00
Boris K
9ac3928600 Add type configuration in history_stats (#6430) 2017-03-11 10:38:18 -08:00
William Scanlon
62e57456e1 Wink scene(shortcut) support (#6147)
* Wink scene(shortcut) support

* Scenes to Scene

* Moved wink scenes from switches to scenes

* Updated python-wink version
2017-03-11 10:18:29 -08:00
Róbert Nagy
11f11481b2 Force update support for MQTT sensor (#6492) 2017-03-11 10:07:52 -08:00
Greg Dowling
10f5e9744b Append vera device id to entity id - but not name. (#6523)
* Append vera device id to entity id - but not name.

* Tidy.

* Tidy.

* Tidy after review.

* Re-order.
2017-03-11 10:06:46 -08:00
Paulus Schoutsen
b2a2193ba3 Remove mint finance sensor (#6522) 2017-03-11 01:02:32 -08:00
Paulus Schoutsen
c8b2ba6559 Version bump to 0.40 2017-03-10 23:09:44 -08:00
Paulus Schoutsen
1496f1a570 Update frontend 2017-03-10 22:52:16 -08:00
Paulus Schoutsen
493c0bbb4c Update frontend 2017-03-10 22:51:44 -08:00
Martin Hjelmare
13dd17b2ab Fix mysensors gateway windows setup (#6500) 2017-03-11 03:30:23 +01:00
Pascal Vizeli
0ef1c3af34 Android webcam better error handling / pump library 0.4 (#6518) 2017-03-10 23:11:16 +01:00
Pascal Vizeli
44da43065f Android webcam better error handling / pump library 0.4 (#6518) 2017-03-10 23:10:35 +01:00
Pascal Bach
ffb1613d55 [packaging] Include LICENSE.md in tarball (#6514) 2017-03-10 19:59:38 +00:00
Pascal Vizeli
b0a2909835 Bugfix rpi_rf cleanup (#6513)
Add an optional extended description…
2017-03-10 14:57:03 +01:00
Pascal Vizeli
b5fb558c62 Bugfix rpi_rf cleanup (#6513)
Add an optional extended description…
2017-03-10 14:56:13 +01:00
Robbie Trencheny
846a0513c7 Don't allow sending to invalid iOS targets (#6115)
* Don't allow sending to invalid targets

* Fix valid target check
2017-03-10 12:32:43 +01:00
Craig J. Ward
0f1cad24ce Insteon lib (#6505)
* use lib with caching to reduce collisions

* use 0.43

* change requirements

* update the  lib to 0.44

* update req

* fix typo

* just keep checking

* just keep checking - switch

* use 0.45 with file cache

* requirements

* Update requirements_all.txt

* Update insteon_local.py

* Update requirements_all.txt

* Update insteon_local.py

* update library

* fix lint
2017-03-10 11:17:09 +01:00
Caleb
24630b1ebe Update to Pyunifi2.0 (#6490)
* Updated pyUnifi

* Missing comma

* Security opt-out, not opt-in

* Adjust minimal values

* Update to pyUnifi 2.0
2017-03-10 11:16:51 +01:00
Caleb
b705b3ddb9 Update to Pyunifi2.0 (#6490)
* Updated pyUnifi

* Missing comma

* Security opt-out, not opt-in

* Adjust minimal values

* Update to pyUnifi 2.0
2017-03-10 11:15:21 +01:00
Craig J. Ward
330d352d3a Insteon lib (#6505)
* use lib with caching to reduce collisions

* use 0.43

* change requirements

* update the  lib to 0.44

* update req

* fix typo

* just keep checking

* just keep checking - switch

* use 0.45 with file cache

* requirements

* Update requirements_all.txt

* Update insteon_local.py

* Update requirements_all.txt

* Update insteon_local.py

* update library

* fix lint
2017-03-10 11:14:31 +01:00
Pascal Vizeli
bdaae6f844 Bugfix android camera autodiscovery settings (#6510)
Add an optional extended description…
2017-03-10 10:14:05 +01:00
Pascal Vizeli
49308bec13 Bugfix android camera autodiscovery settings (#6510)
Add an optional extended description…
2017-03-10 10:10:35 +01:00
Paulus Schoutsen
edd96c2b04 Merge remote-tracking branch 'origin/master' into dev 2017-03-09 21:32:04 -08:00
Robbie Trencheny
11c4d3892f Merge pull request #6001 from jumpkick/patch-2
Improvements for WeMo Insight switches
2017-03-09 21:14:33 -08:00
Robbie Trencheny
75817ad46d Merge pull request #6361 from GreenTurtwig/dev
Updated to catch timeout error
2017-03-09 20:39:20 -08:00
Robbie Trencheny
780cdd5f90 Merge pull request #6389 from Deinara/discord_connect_fix
Discord connect fix
2017-03-09 19:57:29 -08:00
Robbie Trencheny
d70eaeb118 Merge pull request #6407 from jvolkman/patch-1
Small typo fix in setup_docker_prereqs
2017-03-09 19:55:57 -08:00
Robbie Trencheny
9e9c6d0184 Make states constants 2017-03-09 19:55:18 -08:00
Robbie Trencheny
2cdc0febf5 Merge pull request #6494 from armills/kodi-command-exceptions
Catch exceptions on kodi commands
2017-03-09 19:50:53 -08:00
Robbie Trencheny
c721ec7ab0 Merge pull request #6507 from amelchio/longer-light-transitions
Increase upper limit on light transitions (#2843)
2017-03-09 19:50:22 -08:00
Robbie Trencheny
4e63e8328e Merge pull request #6506 from happyleavesaoc/enhance-moon
more moon states
2017-03-09 19:45:40 -08:00
Robbie Trencheny
8f2009a187 Merge pull request #6488 from pvizeli/pydroid-ipcam
Android IP Cam support
2017-03-09 19:41:23 -08:00
Adam Mills
529eee994b Include functools.wraps 2017-03-09 19:52:45 -05:00
Christiaan Blom
3f7dd7ed9a Added an extra space 2017-03-10 00:33:29 +01:00
Christiaan Blom
b952cfe705 Added pylint ignore message and re-added .close() 2017-03-10 00:21:26 +01:00
Anders Melchiorsen
b9cf4df557 Increase upper limit on light transitions (#2843)
This increases the global limit to 6535 seconds (1h48m55s) because that is
supported by all light platforms.

Some platforms support even longer transition times so the limit should
actually be platform specific.
2017-03-09 23:59:35 +01:00
happyleaves
7892297240 more moon states 2017-03-09 16:54:04 -05:00
Pascal Vizeli
20fcd1f0e2 Bugfix mqtt socket memory error (#6501)
* Bugfix mqtt socket memory error

* Fix tests

* Fix lint
2017-03-09 06:31:43 -08:00
pvizeli
8bbf13ef9f Fix lint 2017-03-09 15:10:39 +01:00
Paulus Schoutsen
855756cb2a Add first pass at Z-Wave light tests (#6483)
* Add first pass at Z-Wave light tests

* Remove unused SIGNAL_VALUE

* Lint

* Update test_init.py
2017-03-09 14:35:04 +01:00
pvizeli
bbcfb9158a fix lint 2017-03-09 12:03:08 +01:00
pvizeli
bcd4def0ae pump version 0.3 / make a lot of improvments 2017-03-09 12:00:50 +01:00
Robbie Trencheny
ddc260b628 Add required changes to support MQTT discovery that I previously missed. See #6481. 2017-03-08 23:57:54 -08:00
Robbie Trencheny
06bc062221 MQTT should load the provided platform not DOMAIN
See #6481
2017-03-08 23:53:17 -08:00
Pascal Vizeli
21f3b62d09 fix coverage 2017-03-09 01:03:15 +01:00
Pascal Vizeli
185ccc4fc4 Fix some things 2017-03-09 01:00:57 +01:00
Adam Mills
2d337fd29a Catch exceptions on kodi commands 2017-03-08 15:54:51 -05:00
Paulus Schoutsen
c625e219c7 Not always asume manufacturername is present (#6484) 2017-03-08 08:57:38 -08:00
pvizeli
b1736994b7 fix lint 2017-03-08 17:57:22 +01:00
pvizeli
21feff5fd8 add available 2017-03-08 17:52:49 +01:00
pvizeli
93118fcade Android IP Cam support 2017-03-08 17:48:55 +01:00
ArrayLabs
5e1c74b430 Update pymyq requirement (#6486)
* update pymyq to 0.0.7

* update pymyq requirement

Adds additional state for stopped door.
2017-03-08 16:47:43 +01:00
Reed Riley
3d3a0a7a4f Improved iCloud 2FA support. (#5984) 2017-03-08 12:07:34 +01:00
Colin O'Dell
fdd1957750 Allow configurable conditions for Pi-Hole sensor (#6465)
* Allow configurable conditions for Pi-Hole sensor

* Include all three conditions by default

* Share Pi-Hole API data across all sensors; eliminate redundant API calls
2017-03-07 23:20:30 -08:00
Robbie Trencheny
2b97449d98 Expand MQTT lights (#6481)
* Add effect support to MQTT Light

* Use effect state topic for supported_features

* Dont use rainbow as default color

* Add color_temp support to MQTT JSON Light

* Add effect to MQTT JSON light

* Support lights in MQTT discovery

* Allow discovered devices to set their platform

* Add white value support to MQTT Light

* Add white value support to MQTT JSON Light

* Remove blank line

* Add color_temp support to MQTT Template light

* Add white value support to MQTT Template Light

* Remove unused SUPPORT_MQTT_TEMPLATE and stale unused flash and transition code from MQTT Template

* Add XY Color to MQTT Light Platform

* Fix syntax

* Fix more syntax errors

* Revert "Remove unused SUPPORT_MQTT_TEMPLATE and stale unused flash and transition code from MQTT Template"

This reverts commit c03798cb63.

* MQTT Template supports flash and transition but doesnt allow templating of the values

* Add XY color support to MQTT JSON

* Proper variable names

* Only allow whitelisted MQTT platforms to be loaded via MQTT Discovery

* Minor tweaks.
2017-03-07 23:01:36 -08:00
Pascal Vizeli
c937a7bcb0 Add support for remove services / Reload script support (#6441)
* Add support for remove services / Reload script support

* Reload support for scripts

* Add more unittest for services

* Add unittest for script reload

* Address paulus comments
2017-03-07 22:51:34 -08:00
Pascal Vizeli
e7f442d66b Add dispatcher camera for internal image. (#6471)
* Add dispatcher camera for internal image.

* fix lint

* Add unittest

* Update dispatcher.py
2017-03-07 22:37:23 -08:00
siebert
2c5d3387f2 Fix wake_on_lan ping for Linux. (#6480) 2017-03-07 20:57:35 -08:00
Paulus Schoutsen
bb4f23f8e7 Add warning for slow platforms/components (#6467)
* Add warning for slow platforms/components

* Add test for slow component setup.

* Add test for slow platform setup

* Fix tests on Py34
2017-03-07 20:31:57 -08:00
Kevin Fronczak
629b2e81ba Support for Blink Camera System (#6444)
* Passing pep8, no tests yet

* Fixed some issues with the request throttling

* Removed ability to set throttle time because it was causing more issues than it was worth

* Added blink to .coveragerc

* Changed blinkpy version

* Removed global var, fixed per PR requests

* Added services for camera, migrated switch to binary_sensor

* Added schema for service, fixed naming, removed unused function
2017-03-07 23:26:53 +01:00
Johan Bloemberg
3508f74fb2 Remove connection status state. (#6475)
Current implementation of connection status doesn't follow convention and is not properly configurable. Might be added again in the future as a full fledged entity or some other way.

For now users can rely on error logging to determine connection status.
2017-03-07 23:20:27 +01:00
Kevin Siml
d16bc632da fix issue (#6470)
* fix issue

fix issue: https://community.home-assistant.io/t/error-in-new-notification-pushsafer/13308

* Update pushsafer.py

* Update requirements_all.txt

* Update pushsafer.py

* Update pushsafer.py

* Update pushsafer.py
2017-03-07 21:18:28 +01:00
Paulus Schoutsen
44d4987536 Allow testing against uvloop (#6468) 2017-03-07 10:11:41 +01:00
Paulus Schoutsen
470702261a Upgrade netdisco to 0.9.2 (#6466) 2017-03-06 22:35:49 -08:00
Josh Anderson
5fb7aa212b Send a logo with webostv notifications (#6380)
* Update to pylgtv 0.1.4

* Send icon with webostv notifications

Default to the homeassistant logo, but allow customizing it on the
component and for individual notifications
2017-03-06 20:56:31 -08:00
Barry Williams
9522fe3a92 Bumped version number for supporting lib (#6462) 2017-03-06 20:38:33 -08:00
Markus Peter
ff3c90fb80 KWB Easyfire support (#6018)
* KWB Easyfire Support

* requirements, coverage

* Initialization fun

* lint

* requirements bump

* lint

* Second best validation ...

* changes

* reworked validation
2017-03-06 17:37:29 +01:00
Paulus Schoutsen
90ad54da7d Shorten recorder connection init (#6432)
* Wait up to 9 seconds

* Set number of recorder retries to 8

* Do not sleep when reporting last connection error if no retries left

* Make sure we clean up old engine if connection is retrying

* Update __init__.py
2017-03-06 12:20:12 +01:00
Marcelo Moreira de Mello
2baa838ba7 Added unittest for Ring sensor (#6447)
Add an optional extended description…
2017-03-06 12:15:08 +01:00
Pascal Vizeli
a8add06a40 Bugfix samsungtv discovery (#6438) 2017-03-05 23:52:15 +01:00
Dennis de Greef
1b23b32817 Use bundled certificates if port matches mqtts (#6429)
* Use bundled certificates if port matches mqtts

* Move import requests.certs to top, since it's used in more places

* Add happy and non-happy path tests for default certificate bundle on mqtts port
2017-03-05 14:08:29 -08:00
Andrey
eaaa0442e2 Add a Z-wave workaround to do full refresh on update (#6403)
* Add Zwave refresh services

* services file

* Use dispatcher

* Add zwave prefix to signal

* Add a Z-wave workaround to do full refresh on update
2017-03-05 12:55:52 -08:00
Job Vermeulen
bc9f2d21c4 Tado device_tracker exception when mobile device has geofencing enabled but location is currently unknown. (#6401) 2017-03-05 21:38:14 +01:00
Adam Mills
1a139234af Revert "Use dynamic port allocation for tests" (#6436) 2017-03-05 21:14:21 +01:00
Andrey
d5435cf066 Rename _scheduled_update to _update_scheduled (#6434) 2017-03-05 10:53:47 -08:00
Andrey
46ec6d6dce Delay zwave updates for 100ms to group them. (#6420)
* Add Zwave refresh services

* services file

* Use dispatcher

* Add zwave prefix to signal

* Delay zwave updates for 100ms to group them.

* Fixes

* lint

* Access _scheduled_update from loop thread only.

* More async

* Some optimizations

* Fix
2017-03-05 09:29:59 -08:00
Anders Melchiorsen
660e777f01 Ignore deleted mails in IMAP unread count (#6394) (#6395)
Message deletion in IMAP is a two step process: first delete, then expunge.
Deleting a message just sets a flag that usually makes the mail client hide
the message. It is the expunge that actually removes the message.

Thus, exclude the deleted messages so that the unread count matches up with
that of most mail clients.
2017-03-05 08:15:25 -08:00
Igor Shults
de038bae65 Don't log username and password in camera url (#6390)
* Don't log username and password in camera url

* Attempt fix of tox issues

* Attempt to fix indentation issue
2017-03-05 08:07:09 -08:00
Anders Melchiorsen
7774f0ae53 Set new color before turning LIFX bulbs on (#6402)
A LIFX bulb maintains its previous color even when the light is off.
For example, if the previous color is blue and the bulb is turned on
and then set to a red color, it will transition through purple colors.

After this commit, the target color is set while the bulb is still
turned off. This overrides the previous color and brightness that the
bulb remembered. The light is then turned on with the requested
transition duration.

For the example, this gives the expected result of only going through
red colors.
2017-03-05 11:11:33 +01:00
Paulus Schoutsen
7655b6271d Better restore_state warnings (#6418) 2017-03-05 10:54:49 +01:00
Paulus Schoutsen
10bf659773 Fix unnecessary warning for ip bans.yaml (#6417) 2017-03-05 10:53:21 +01:00
Paulus Schoutsen
e8a22cb4a8 Tweak recorder/restore_state (#6412)
* Tweak recorder/restore_state

* Lint
2017-03-05 10:52:08 +01:00
Paulus Schoutsen
2650c73a89 Split bootstrap into bs + setup (#6416)
* Split bootstrap into bs + setup

* Lint
2017-03-05 10:41:54 +01:00
Thibault Cohen
bdf948d866 Add Mint finance sensor (#6132)
* Add Mint finance sensor

* Add retry

* Fix PR comments

* Upgrade mintapi version

* Update mint_finance.py

* Doc tweak

* Update mint_finance.py
2017-03-05 00:08:58 -08:00
Marcelo Moreira de Mello
928e025910 Added sensors to support Ring.com devices (#6419) 2017-03-05 00:03:00 -08:00
Teemu R
96aae1292b switch.tplink: catch exceptions coming from pyHS100 to avoid flooding the logs when the plug is not available (#6400) 2017-03-05 08:44:34 +01:00
Pascal Vizeli
c0bf3d7f32 Restore flow on device_tracker platform (#6374)
* Restore flow on device_tracker platform

* fix flow

* fix lint
2017-03-05 08:06:53 +01:00
Paulus Schoutsen
307514e3a7 Prevent more I/O in apns (#6413) 2017-03-04 19:57:04 -08:00
Paulus Schoutsen
b939626497 Fix tests no internet (#6411)
* Fix honeywell tests without internet

* Fix device tracker without internet

* Fix MFI using internet during tests

* Remove I/O from apns tests
2017-03-04 17:15:20 -08:00
Johann Kellerman
1522e67351 Restore for automation entities (#6254)
* Restore for automation entities

* coroutine

* no clue what i'm doing now

* Still passes nicely in py 3.4
2017-03-04 15:19:01 -08:00
Pascal Vizeli
8232f1ef65 Cleanup async handling (#6388)
* Cleanups unneeded blocks

* Cleanup bootstrap

* dedicated update_ha_state

* Fix imap_email_content

* fx tests

* Fix lint & spell
2017-03-04 15:10:36 -08:00
Jeremy Volkman
78f5a8a6f8 Small typo fix in setup_docker_prereqs 2017-03-04 12:39:25 -08:00
Daniel Høyer Iversen
3044aecbe9 flux led lib (#6404) 2017-03-04 21:33:24 +01:00
siebert
a5081ac307 Fix wake_on_lan for german version of Windows 10 (#6397) (#6398) 2017-03-04 09:58:01 -08:00
Lev Aronsky
f396a4593e Add keep-alive feature to the generic thermostat (#6040)
* Add keep-alive feature to the generic thermostat

* Comply with maximum line lengths

* Added tests for the keep-alive functionality
2017-03-04 09:42:43 -08:00
Andrey
aac9f972cf Add Zwave refresh services (#6377)
* Add Zwave refresh services

* services file

* Use dispatcher

* Add zwave prefix to signal
2017-03-04 09:13:24 -08:00
Thibault Cohen
aaa0944595 Add multi contracts support for Hydroquebec (#6392) 2017-03-04 09:11:58 +01:00
Paulus Schoutsen
6cca127bbf Merge pull request #6384 from home-assistant/release-0-39-3
0.39.3
2017-03-03 19:24:29 -08:00
Christiaan Blom
c1f3ce78e1 Edit docstring 2017-03-04 01:48:47 +01:00
Christiaan Blom
887b53b794 Changes for Travis bot. Unused variable 'on_ready' will likely remain reported 2017-03-04 01:31:19 +01:00
Christiaan Blom
a444df3fde tweaks 2017-03-04 01:03:10 +01:00
Christiaan Blom
b038a1650e Resolved issue #5688 2017-03-03 23:15:03 +01:00
joe248
483556ac5b Comed Hourly Pricing sensor (#6378)
* Add ComEd RRTP price sensor

* Update wording to reflect ComEd's naming change from 'RRTP' to 'Hourly Pricing'

* Changed name of sensor source file

* Cleanup based on requested changes

* More cleanup

* small cleanups
2017-03-03 23:14:22 +01:00
Paulus Schoutsen
3e9e388745 Version bump to 0.39.3 2017-03-03 13:15:07 -08:00
Colin O'Dell
ab42acf4d7 Don't initialize components which have already been discovered (#6381)
* Don't initialize components which have already been discovered (fixes #5588)

* Don't log that we've found a service unless we know it's not a duplicate

* Encode discovery data hash with JSON

This also solves the issue of trying to hash non-hashable objects like dicts

* Add test for duplicate device discovery
2017-03-03 13:13:04 -08:00
Colin O'Dell
0489ae53c4 Don't initialize components which have already been discovered (#6381)
* Don't initialize components which have already been discovered (fixes #5588)

* Don't log that we've found a service unless we know it's not a duplicate

* Encode discovery data hash with JSON

This also solves the issue of trying to hash non-hashable objects like dicts

* Add test for duplicate device discovery
2017-03-03 13:11:40 -08:00
John Mihalic
35fcc299c0 Update Hikvision Binary Sensors to latest library, remove pyDispatcher (#6231)
* Update pyHik version, remove pyDispatcher in favor of callbacks

* Fix naming

* Fix lint blank line

* Move stream thread start to HOMEASSISTANT_START event

* Bump library version to cleanup shutdown

* Fix requirements
2017-03-03 15:11:30 +01:00
Valentin Alexeev
568c549353 Update pwaqi to 3.0 to use public API (#6376)
The underlying PWAQI library version 3.0 is now using public API to
access AQICN data.
2017-03-03 14:50:54 +01:00
Paulus Schoutsen
edf130b341 Z-Wave prevent I/O event loop (#6369)
* Prevent Z-Wave I/O in event loop

* Move value_handler to util class.

* Add docstring
2017-03-03 14:47:59 +02:00
Pascal Vizeli
ed9e93c29f Migrate mqtt tracker and arwn sensor to async / cleanup owntrack (#6373)
* Migrate mqtt tracker and arwn sensor to async / cleanup owntrack

* Fix tests / lint
2017-03-03 12:09:10 +01:00
Pascal Vizeli
55f8ec8866 Fix possibility that have multible topic subscribe mqtt (#6372) 2017-03-03 10:05:52 +01:00
Pascal Vizeli
3e70154695 OwnTrack Async (#6363)
* Migrate owntrack to async

* fix tests
2017-03-03 09:23:58 +01:00
Andrey
aa17481c94 Add Z-Wave battery level as a sensor. (#6341) 2017-03-03 09:19:06 +02:00
happyleavesaoc
b53bc24a63 twilio component (#6348)
* twilio component

* add http dependency to twilio

* fire->async_fire
2017-03-03 08:14:51 +01:00
Johann Kellerman
fbd0bf77c7 [recorder] Catch more startup errors #6179 (#6192)
* [recorder] Catch more startup errors #6179

* Rebase on new recorder
2017-03-02 22:44:52 -08:00
Jose Juan Montes
4da2156ebf Return None instead of raising ValueException from as_timestamp template function. (#6155) 2017-03-02 22:18:01 -08:00
Open Home Automation
8a67fcfee3 Added IPv4 data collector (#6304)
* Added IPv4 data collector

* Formatting

* Bugfix: data is in kBit/s not kByte/s
2017-03-02 22:16:50 -08:00
Pascal Vizeli
08f9793175 Restore for input_slider (#6360) 2017-03-02 08:36:26 -08:00
Pascal Vizeli
a5b2fc9759 Bugfix new async_add_devices function (#6362) 2017-03-02 17:27:45 +01:00
Micha LaQua
3fa8aff78e snmp: upgrade pysnmp to 4.3.4 (#6359)
* snmp: upgrade pysnmp to 4.3.4

fixes https://github.com/home-assistant/home-assistant/issues/6238

* snmp: v4.3.4: add missing definition changes
2017-03-02 16:12:44 +01:00
Rowan
09ff9cb08e Updated to catch timeout error 2017-03-02 14:58:35 +00:00
Jan Losinski
c32300a386 Bump limitlessled dependency to 1.0.5. (#6334)
This fixes issue #6295.
2017-03-02 14:18:44 +01:00
Pascal Vizeli
55dc483c91 Template switch change flow / add restore (#6356)
* Template switch change flow / add restore

* fix tests

* fix binary_sensor template
2017-03-02 14:09:53 +01:00
Andrey
597ae2e716 Zwave: Add remove/replace failed node services. (#6248)
* Zwave: Add remove/replace failed node services.

* Fix text
2017-03-02 12:36:40 +01:00
Pascal Vizeli
50887e7e2c Move dispatcher out of init. (#6355) 2017-03-02 10:20:57 +01:00
Alan Fischer
8743f23f13 Fix calendar authentication text, and handle calendar events without summaries. (#6337)
* Fixed google authorization text

* Let calendar handle events without a summary
2017-03-02 00:22:38 -08:00
Jeff Wilson
72fe50bef6 Fix command sudo not found error in dev Dockerfile (#6346) 2017-03-02 00:14:20 -08:00
Pierre Ståhl
bae6333c26 Use push updates in Apple TV (#6323)
* Use push updates in Apple TV

* Fix review comments
2017-03-02 00:12:55 -08:00
martinfrancois
a08539d88d Added support for multiple codes executed in a row (#5908)
* Added support for multiple codes executed in a row

now codes can be specified either by simply providing a single code, which will then be sent like usual, or multiple codes can be executed in a row, specified in a comma delimited format in the configuration.yaml. For example: 111111,222222,333333,444444 would mean 111111 would be sent first, followed by 222222 and 333333 and 444444.

* rpi_rf: added line breaks to not exceed 79 characters per line

* include validation for correct formatting of codes

added regex which only allows either a single number (like 1252456245) or a sequence of commas followed by another number.

* added line breaks to not exceed 79 characters per line

* fix for 'continuation line under-indented for visual indent'

* another try at 'continuation line under-indented for visual indent'

* changed from regex to list for easier maintainability

* removed unnecessary splitting of strings
2017-03-02 00:10:49 -08:00
Adam Mills
bf7aecce90 Use dynamic ports for test instances (#6232) 2017-03-02 00:07:50 -08:00
Paulus Schoutsen
e2aa024a05 Update coveragerc 2017-03-02 00:06:26 -08:00
dramamoose
edd5db296d Update Formulas in Convert XY to RGB (#6322)
* Update to Current RGB D65 Conversion

As per Philips Hue https://developers.meethue.com/documentation/color-conversions-rgb-xy

* Update the source of the XYZ to RGB formulas

* Fix Whitespace

* Update Whitespace

* Update Tests for new Formulas

* Update Tests

* Update XY_Brightness_to_hsv tests

* Update test_color.py
2017-03-02 08:54:45 +01:00
Duoxilian
e14d6f11c6 Additional support for ecobee hold mode (#6258)
* Integrate suggestion in #5590 by nordlead2005. This change has been
sitting in limbo for over a month, but it is a good idea. I don't
mean to step on nordlead2005's toes, but we need to make progress.

* Use defined constant for TEMPERATURE_HOLD

* Integrate handling of vacation into hold mode. Canceling vacation
hold requires an update to the external pyecobee library. Creation
of vacation is not supported (it would be straightforward in the code,
but a complex user interface would be required, similar to what is
now done in the ecobee thermostat).

* Add capability to retrieve list of defined climates from ecobee.

* The mode() method used to return the system mode in internal
representation. However, the user sees a different notation in
the ecobee thermostat. Seeing some internal name is particularly
weired with user-defined climates, where these are named "smart1",
"smart2", etc., instead of the name the user has defined. Return
the user-defined name instead. This change might break some user
interfaces but is easily remedied (e.g., use "Away" instead of
"away").

* Simplify is_away_mode_on().

* Correction of erroneously indented else statement.

* Change comment as flake8 gets confused.
2017-03-01 23:52:31 -08:00
Pascal Vizeli
f3870a8a48 Template binary_sensor change flow / add restore (#6343)
* Template binary_sensor change flow / add restore

* fix lint
2017-03-02 08:50:41 +01:00
Thibault Cohen
31bf5b8ff0 Improve Honeywell US climate component (#5313)
* Improve Honeywell US climate component

* Fix tests

* Fix tests

* Add cool_away_temp and heat_away_temp for honeywell US

* Fix honeywell tests

* Fix PR comments
2017-03-01 23:49:49 -08:00
Paulus Schoutsen
a7ae456a06 Merge remote-tracking branch 'origin/master' into dev 2017-03-01 23:44:11 -08:00
Reed Riley
c03022efa3 Add fallback for name if userdevicename isn't set using old serialnumber logic (#6265)
Add an optional extended description…
2017-03-02 08:41:31 +01:00
Mitko Masarliev
46f5a65e68 Update Adafruit_Python_DHT to support new raspberry kernel (#6325)
* Update Adafruit_Python_DHT to support new raspberry kernel

* update Adafruit Python DHT
2017-03-02 08:39:33 +01:00
Alexander Fortin
44ec6b056e Update Vagrant provision.sh (#6236)
- Bugfix: with f63a79ee we removed `script/home-assistant@.service`
  systemd unit file, which is used by Vagrant box to start/stop hass
- simplify interaction with Vagrant, provision.sh now is the only
  entry point and doesn't need the user to touch/remove files in
  order to change provisioner behavior
2017-03-01 23:15:30 -08:00
Andrey
354007f265 Zwave optimize value_added (#6210)
* Make zwave devices listen on less network changes.

* Convert more platforms

* Remove printouts.

* Fix copy-paste

* Change default dependent list to empty list
2017-03-01 22:41:19 -08:00
Pascal Vizeli
6cb8a36cf1 Template sensor change flow / add restore (#6336) 2017-03-02 07:38:19 +02:00
Fabian Affolter
435f253be8 Upgrade py-cpuinfo to 0.2.6 (#6335) 2017-03-02 05:58:03 +01:00
Fabian Affolter
0fe41ffb00 Upgrade TwitterAPI to 2.4.5 (#6351) 2017-03-02 05:57:51 +01:00
Martin Hjelmare
bafa0cc3b8 Fix mysensors callback race (#6311)
* Fix possible race at startup in mysensors callback

* Update devices via persistence before starting gateway to avoid
  two threads calling the same callback at the same time.

* Call add_devices max once per callback
2017-03-01 23:09:27 +01:00
Erik Eriksson
e23aa1ccf8 sensor.dovado: compute state in update (#6340) 2017-03-01 22:57:37 +01:00
Paulus Schoutsen
a9db6b16eb Merge pull request #6339 from home-assistant/release-0-39-2
0.39.2
2017-03-01 12:43:00 -08:00
Paulus Schoutsen
bf30f2e9e8 Version bump to 0.39.2 2017-03-01 09:29:34 -08:00
Paulus Schoutsen
ee9d50c0a5 Bump netdisco to 0.9.1 (#6338) 2017-03-01 09:29:14 -08:00
Paulus Schoutsen
6736534a52 Discovery fix (#6321)
* Fix incorrect import

* Create own discovery service

* Fix tests

* Fix hdmi_cec bad import
2017-03-01 09:28:49 -08:00
Paulus Schoutsen
4ccd819ec5 Bump netdisco to 0.9.1 (#6338) 2017-03-01 09:05:05 -08:00
Pascal Vizeli
52b1e13aca Bugfix ZigBee / Move from eventbus to dispatcher (#6333)
* Bugfix ZigBee / Move from eventbus to dispatcher

* fix lint
2017-03-01 08:57:53 -08:00
Pascal Vizeli
67f3910f03 Bugfix ZigBee / Move from eventbus to dispatcher (#6333)
* Bugfix ZigBee / Move from eventbus to dispatcher

* fix lint
2017-03-01 08:57:23 -08:00
Paulus Schoutsen
64cb3390ea Test against 3.6-dev (#6324) 2017-03-01 08:53:40 -08:00
Paulus Schoutsen
0ac4a152be Discovery fix (#6321)
* Fix incorrect import

* Create own discovery service

* Fix tests

* Fix hdmi_cec bad import
2017-03-01 07:38:49 -08:00
Pascal Vizeli
4e96e461f7 Cleanup component track_point_in_utc_time usage (#6330) 2017-03-01 07:37:48 -08:00
Stefano Scipioni
30bed8341a Telegram webhooks new text event (#6301)
* new TELEGRAM_TEXT

* telegram command event renamed in 'telegram_command'

* fire telegram_text event anyway
2017-03-01 12:15:16 +01:00
Erik Eriksson
d17733427b Merge pull request #6329 from molobrakos/dovado
sensor.dovado: Upgraded library version
2017-03-01 12:02:58 +01:00
Erik Eriksson
782d2a30cd Merge pull request #6328 from molobrakos/eliqonline
sensor.eliqonline: Change to more appropriate icon
2017-03-01 11:52:44 +01:00
Paulus Schoutsen
84f30d9ef8 Bootstrap tweaks tests (#6326)
* Update strings/fix component not found message.

* Fix tests

* More tweak text
2017-02-28 23:42:31 -08:00
Johann Kellerman
ac49298c8d Log errors when loading yaml (#6257) 2017-03-01 06:56:23 +02:00
jumpkick
a0256e1947 Rollback netdisco to 0.8.2 to resolve #6165 (#6314)
* Rollback netdisco to 0.8.2 to resolve #6165

* Rollback netdisco to 0.8.2 to resolve #6165
2017-02-28 20:37:56 -08:00
ericgingras
7bc2e1238d Convert kpH and mpH to kph and mph (#6316)
There is no reason for the H to be capitalized. Changing it to lowercase increases consistency with other components and allows for use of the min/max sensor, which throws an error if the units of measurement are not the same.
2017-03-01 05:34:40 +01:00
Pascal Vizeli
41f558b181 Bootstrap / Component setup async (#6264)
* Bootstrap / Entiy setup async

* Cleanup add_job stuff / return task/future object

* Address paulus comments / part 1

* fix install pip

* Cleanup bootstrap / move config stuff to config.py

* Make demo async

* Further bootstrap improvement

* Address Martin's comments

* Fix initial tests

* Fix final tests

* Fix bug with prepare loader

* Remove no longer needed things

* Log error when invalid config

* More cleanup

* Cleanups platform events & fix lint

* Use a non blocking add_entities callback for platform

* Fix Autoamtion is setup befor entity is ready

* Better automation fix

* Address paulus comments

* Typo

* fix lint

* rename functions

* fix tests

* fix test

* change exceptions

* fix spell
2017-02-28 20:33:19 -08:00
Adam Mills
383b0914b3 Version bump to 0.40.0.dev0 2017-02-28 11:01:19 -05:00
Krasimir Zhelev
be297c4c7e Frontier silicon (#6131)
* added frontier_silicon constant

* added the frontier_silicon component

* cleaning up according to travis

* trying to satisfy pylint

* trying to satisfy pylint

* fsapi version 0.0.6

* with fsapi version 0.0.7

* added fsapi dependency

* yielding the FSAPI

* Removing white space from docstring

* Removing white space from an empty line

* Switching to sync

* clean up white spaces and rename device to FSAPIDevice

* added frontier_silicon constant

* added the frontier_silicon component

* cleaning up according to travis

* trying to satisfy pylint

* trying to satisfy pylint

* fsapi version 0.0.6

* with fsapi version 0.0.7

* added fsapi dependency

* yielding the FSAPI

* Removing white space from docstring

* Removing white space from an empty line

* Switching to sync

* clean up white spaces and rename device to FSAPIDevice

* changed info to debug

* added frontier_silicon constant

* added the frontier_silicon component

* cleaning up according to travis

* trying to satisfy pylint

* trying to satisfy pylint

* fsapi version 0.0.6

* with fsapi version 0.0.7

* added fsapi dependency

* yielding the FSAPI

* Removing white space from docstring

* Removing white space from an empty line

* Switching to sync

* clean up white spaces and rename device to FSAPIDevice

* added the frontier_silicon component

* trying to satisfy pylint

* fsapi version 0.0.6

* remove white space

* generated requirements

* added the frontier_silicon component

* cleaning up according to travis

* trying to satisfy pylint

* trying to satisfy pylint

* fsapi version 0.0.6

* with fsapi version 0.0.7

* added fsapi dependency

* yielding the FSAPI

* Removing white space from docstring

* Removing white space from an empty line

* Switching to sync

* clean up white spaces and rename device to FSAPIDevice

* trying to satisfy pylint

* changed info to debug

* added the frontier_silicon component

* fsapi version 0.0.6

* generated requirements

* pylint

* moved import requests to the method where it is being used

* add a basic unit test

* cleaned up source code

* added frontier_silicon constant

* added the frontier_silicon component

* added basic test

* added fsapi to requirements_all.txt

* added coverage omit, though a basic test was included

* added MEDIA_TYPE_MUSIC for artist and album

* removed duplicate cons

* switched fsapi call to a property, removed unecessary comment

* detailed docstring for fs_device

* added a space for the info_name - info_text separator

* reduced proeprty (fsapi) access for volume down/up
2017-02-28 09:23:07 -05:00
Pascal Vizeli
aa1f64bed6 Migrate calendar setup to async. (#6305) 2017-02-28 10:49:06 +01:00
Adam Mills
faf8bbcf13 Fix toggle and media_play_pause post async (#6291) 2017-02-27 19:55:34 -08:00
Open Home Automation
0fa259089d Influx fix (#6289)
* Fix: replace influxdb query by another query that is more lightweight and won't timeout

* Fix: replace influxdb query by another query that is more lightweight and won't timeout
2017-02-27 19:54:43 -08:00
Alan Fischer
f7c7073cd7 Updated pyitachip2ir (#6296)
* Updated pyitachip2ir

* Updated requirements_all.txt
2017-02-27 19:52:32 -08:00
Boris K
d7bf3920a5 improve history_stats accuracy (#6294) 2017-02-27 19:52:10 -08:00
Andrey
7ee75d67c5 Add temperature support for MH-Z19 CO2 sensor. (#6169)
* Add temperature support for MH-Z19 CO2 sensor.

* Remove debug printout

* More tests

* Minor fixes
2017-02-27 21:19:11 +02:00
arjenfvellinga
d7db3aba36 Prevent duplicate names on Vera devices by appending the device id (#6100)
* Prevent duplicate names by prepending device id to it.

* Always append device id, not conditionally.

* Moved naming of devices

* flake8
2017-02-27 18:57:39 +00:00
Lindsay Ward
7f99e99dad Update library version for Yeelight Sunflower lights platform (fix for packaging problem with 0.0.7) (#6233) 2017-02-27 13:47:51 -05:00
Greg Dowling
fbea5b4cac Merge pull request #6278 from home-assistant/fix_vera_thormostat_bug
Fix vera thermostat bug
2017-02-27 10:55:33 +00:00
Daniel Perna
e6c88c05ad [sensor.dnsip] New Sensor: DNS IP (#6214)
* Added DNS IP sensor

* Removed unused import

* Added coverage

* fixed flake

* Applied suggested changes

* Removed debug code

* Switched to aiodns

* Raised scan interval

* Updating state with entity creation

* Lint

* Updated requirements_all
2017-02-27 10:45:32 +00:00
pavoni
757813411d Fix vera thermostat mode set bug 2017-02-27 10:18:22 +00:00
TimV
7dc05785cc Analog modem callerid support (#5840)
* analog-modem-callerid

* analog-modem-callerid

* analog-mod

* Updates from latest review

* Updates from latest review
2017-02-26 21:38:47 -08:00
Wolf-Bastian Pöttner
d7af43b87d Add support for MAX!Cube thermostats and window shutter sensors (#6105) 2017-02-26 21:35:33 -08:00
groth-its
6ea74ce740 Fix for OSRAM lights connected to hue bridge (#6122)
* Fix for OSRAM lights connected to hue bridge

Do not send command "effect = none" to OSRAM lights
Osram lights connected to a hue bridge do not seem to handle "effect =
none" very well. Most of the times they jump to the selected color and
then change to red within a second.

Osram lights connected to a hue bridge do not handle xy values outside
of their gamut. Since they just stay at their old color value, handling
the UI is very unpredictable. Sending HSV values to the lights fixes this.

* Add tests for new util methods
2017-02-26 21:28:31 -08:00
Nate
d5bdf7783e light.transition now supports float instead of int in order to be able to perform faster transitions (#6163) 2017-02-26 21:21:12 -08:00
Jose Juan Montes
65d255a626 Local file camera now supports yet inexisting files. (#6157) 2017-02-26 21:16:11 -08:00
Jeff Wilson
53a735a329 Properly report features for each hue bulb type (#6271) 2017-02-26 20:59:23 -08:00
Paulus Schoutsen
68713822fd Merge pull request #6272 from home-assistant/release-0-39-1
0.39.1
2017-02-26 20:48:57 -08:00
Pascal Vizeli
3d26ac3323 Bugfix mqtt paho client to speend time (#6266) 2017-02-26 20:31:11 -08:00
Paulus Schoutsen
d487960ad8 Config fix (#6261) 2017-02-26 20:31:11 -08:00
Pascal Vizeli
9403cdd2a5 Bugfix mqtt socket error (#6256) 2017-02-26 20:31:11 -08:00
Paulus Schoutsen
1fb1a32c9a Version bump to 0.39.1 2017-02-26 20:30:21 -08:00
Pascal Vizeli
31ddcc6278 Bugfix mqtt paho client to speend time (#6266) 2017-02-26 15:28:54 -08:00
Paulus Schoutsen
d789de9ea2 Config fix (#6261) 2017-02-26 15:28:12 -08:00
Scott Henning
e2014eb153 Notify ciscospark (#6130)
* Adding ciscospark notifier

* Adding ciscospark notifier

* CI cleanup.

* houndci-bot changes

* ok --- a bunch of code verify changes
2017-02-26 15:04:30 -08:00
Pascal Vizeli
5932446508 Bugfix mqtt socket error (#6256) 2017-02-26 14:43:02 -08:00
Paulus Schoutsen
61909e873f Feature/reorg recorder (#6237)
* Re-organize recorder

* Fix history

* Fix history stats

* Fix restore state

* Lint

* Fix session reconfigure

* Move imports around

* Do not start recording till HASS started

* Lint

* Fix logbook

* Fix race condition recorder init

* Better reporting on errors
2017-02-26 14:38:06 -08:00
Pascal Vizeli
48cf7a4af9 Move ffmpeg to dispatcher from hass.data entity store. (#6211)
* Move ffmpeg to dispatcher from hass.data entity store.

* fix lint

* address paulus comments

* add more unittest for better coverage
2017-02-26 14:31:46 -08:00
Pierre Ståhl
9490cb4c8f Add service to change log levels (#6221)
* Add service to change log levels

* Fix review comments
2017-02-26 14:15:44 -08:00
Paulus Schoutsen
86d4d10176 Ensure we properly close HASS instances. (#6234) 2017-02-26 14:05:18 -08:00
Philipp Schmitt
7b3b755aaf Fix livebox-play interactions for Python < 3.6 (#6243) 2017-02-26 14:04:22 -08:00
Paulus Schoutsen
9afcbaed1d Fix recorder async (#6228) 2017-02-25 15:21:40 -08:00
Paulus Schoutsen
3a7cc9bb45 Update frontend 2017-02-25 15:03:24 -08:00
Paulus Schoutsen
ecd1da6525 Merge pull request #6167 from kellerza/recorder_timer
Recorder async & remove start_recording event
2017-02-25 15:01:29 -08:00
Fabian Affolter
9f2719bb1f Update regex (#6216) 2017-02-25 12:55:01 -08:00
Andrey
85d0f2e861 Make glob preserve order (#6224) 2017-02-25 12:54:04 -08:00
Johann Kellerman
5d007e636b No wait for start and more async 2017-02-25 17:51:37 +02:00
Johann Kellerman
7cd6f9038c Allow 4.5min startup time for recorder 2017-02-25 17:28:53 +02:00
Fabian Affolter
a80fd2f243 Fix link (#6219) 2017-02-25 13:24:43 +01:00
Erik
2487d27c45 sensor.eliqonline: Change icon 2017-02-25 12:51:48 +01:00
Erik
be7162a0df sensor.dovado: Upgraded library version 2017-02-25 12:50:10 +01:00
Andrey
c5a8372f13 Update flake8 and pylint to latest (#6217) 2017-02-25 09:44:22 +01:00
Pascal Vizeli
81ca978413 Move mqtt from eventbus to dispatcher / add unsub for dispatcher (#6206)
* Move mqtt from eventbus to dispatcher / add unsub for dispatcher

* Fix lint

* Fix test

* Fix lint v2

* fix dispatcher_send
2017-02-24 17:11:50 -08:00
Paulus Schoutsen
d6818c7015 Fix reporting on bad login (#6201) 2017-02-24 16:33:58 -08:00
Greg Dowling
df5866dd95 Merge pull request #6213 from home-assistant/bump_pyloopenergy
Bump pyloopenergy - catch socketIO exceptions.
2017-02-24 22:16:03 +00:00
pavoni
34ee2b1ae9 Bump pyloopenergy - catch socketIO exceptions. 2017-02-24 22:04:41 +00:00
Zac Hatfield Dodds
8ca897da57 Zamg weather (#5894)
* Fast & efficient updates for ZAMG weather data

ZAMG updates on the hour, so instead of checking every half-hour we can
check each minute - only after the observations are taken until
receiving them.

* sensor.zamg: test instead of whitelist for station_id

* Autodetect closest ZAMG station if not given

* ZAMG weather component, based on the sensor

* Review improvements

* Update to new ZAMG schema, add logging

Turns out it wasn't a typo, but rather an upstream schema change.  Added
better error handling to ease diagnosis in case it happens again.

* No hardcoded name
2017-02-24 22:45:46 +01:00
Lev Aronsky
c7fcd98cad Test the temperature returned by RM2 (#6205)
* Test the temperature returned by RM2
* Validate fields via voluptuous
* Fixed range for humidity
2017-02-24 20:54:31 +01:00
Erik Eriksson
8aa3124aa6 sensor.speedtest: provide a default icon (#6207) 2017-02-24 18:40:52 +01:00
Andrey
b27ba9660b Some zwave cleanup (#6203) 2017-02-24 16:17:27 +02:00
Lindsay Ward
9f04b55572 Update Yeelight Sunflower light platform to 0.0.6 (#6208)
Add an optional extended description…
2017-02-24 14:13:55 +01:00
Daniel Høyer Iversen
c4f4a9a158 minor broadlink fix (#6202) 2017-02-24 09:49:42 +01:00
Paulus Schoutsen
e2e8b43902 Default config to setup group editor (#6198) 2017-02-23 22:53:16 -08:00
Paulus Schoutsen
3a35642dc1 Remove automatically reloading group config (#6197) 2017-02-23 22:40:21 -08:00
Paulus Schoutsen
34a7aa2376 Extend test for group config 2017-02-23 21:57:48 -08:00
Paulus Schoutsen
58eb32bce4 Random test fixes (#6195)
* Store persistent errors in hass (speeds up tests)

* Fix sleepiq test dependency on test order

* Fix sleepiq validation
2017-02-23 21:44:47 -08:00
Johann Kellerman
c940d26f07 Bugfix restore startup state (#6189) 2017-02-23 20:06:21 -08:00
jumpkick
fc5e25a07b Incorporate comment suggestions
- Separate attribs from coffeemaker condition
- Set power units for threshold to mW to be consistent with others
- Adjust on-time labels to be more clear
2017-02-23 18:03:49 -05:00
Andrey
1d32bced1c Create zwave devices on OZW thread and only add them during discovery (#6096)
* Create zwave devices on OZW thread and only add them during discovery.

* Read and write devices dict from loop thread.

* More async

* replace callback with coroutine

* import common function instead of callin git
2017-02-23 13:06:28 -08:00
Pascal Vizeli
f2a2d6bfa1 Refactory of envisalink (#6160)
* Refactory of envisalink

* remove event buss

* init dispatcher from hass.

* Move platform to new dispatcher

* fix lint

* add unittest & threadded functions

* fix copy & past error
2017-02-23 13:02:56 -08:00
Colin O'Dell
4f990ce488 Use H2 headers to split up the different sections (#6183)
Using headers makes it easier to visually differentiate between the different sections
2017-02-23 12:58:18 -08:00
Pascal Vizeli
106b7a9d8f Cleanup run_callback_threadsafe (#6187)
* Cleanup run_callback_threadsafe

* fix spell

* Revert image_processing, they need to wait for update
2017-02-23 12:57:25 -08:00
jumpkick
ef87d4dad4 Update device_state_attributes only
This gets rid of the other stuff and just updates device_state_attributes, leaving the default properties alone.
2017-02-23 04:54:09 -05:00
jumpkick
f6e46aecf5 Update wemo.py 2017-02-15 17:32:45 -05:00
jumpkick
e9cf5f6f42 Update wemo.py 2017-02-15 16:58:11 -05:00
jumpkick
e221c8a37d Update wemo.py 2017-02-15 16:57:16 -05:00
jumpkick
b163544e3c Back to you travis.... 2017-02-15 16:47:02 -05:00
jumpkick
a718e92708 Update wemo.py
trailing whitespace... (argh... the bot should just trim it)
2017-02-15 15:40:02 -05:00
jumpkick
44d274e428 Update wemo.py
* continuation line under-indented for visual indent
2017-02-15 15:38:41 -05:00
jumpkick
c404fb7142 Update wemo.py
* Reordered datetime import
* Spaces by 4
2017-02-15 15:34:42 -05:00
jumpkick
29c7987453 Improvements for WeMo Insight switches
* Changes current power units to watts
* Adds power on times and additional totals
2017-02-14 18:29:23 -05:00
619 changed files with 21418 additions and 8666 deletions

View File

@@ -14,9 +14,15 @@ omit =
homeassistant/components/arduino.py
homeassistant/components/*/arduino.py
homeassistant/components/android_ip_webcam.py
homeassistant/components/*/android_ip_webcam.py
homeassistant/components/bbb_gpio.py
homeassistant/components/*/bbb_gpio.py
homeassistant/components/blink.py
homeassistant/components/*/blink.py
homeassistant/components/bloomsky.py
homeassistant/components/*/bloomsky.py
@@ -53,6 +59,9 @@ omit =
homeassistant/components/lutron.py
homeassistant/components/*/lutron.py
homeassistant/components/lutron_caseta.py
homeassistant/components/*/lutron_caseta.py
homeassistant/components/modbus.py
homeassistant/components/*/modbus.py
@@ -85,6 +94,10 @@ omit =
homeassistant/components/*/thinkingcleaner.py
homeassistant/components/twilio.py
homeassistant/components/notify/twilio_sms.py
homeassistant/components/notify/twilio_call.py
homeassistant/components/vera.py
homeassistant/components/*/vera.py
@@ -105,9 +118,6 @@ omit =
homeassistant/components/zigbee.py
homeassistant/components/*/zigbee.py
homeassistant/components/zwave/*
homeassistant/components/*/zwave.py
homeassistant/components/enocean.py
homeassistant/components/*/enocean.py
@@ -132,10 +142,17 @@ omit =
homeassistant/components/zabbix.py
homeassistant/components/*/zabbix.py
homeassistant/components/maxcube.py
homeassistant/components/*/maxcube.py
homeassistant/components/tado.py
homeassistant/components/*/tado.py
homeassistant/components/alarm_control_panel/alarmdotcom.py
homeassistant/components/alarm_control_panel/concord232.py
homeassistant/components/alarm_control_panel/nx584.py
homeassistant/components/alarm_control_panel/simplisafe.py
homeassistant/components/alarm_control_panel/totalconnect.py
homeassistant/components/apiai.py
homeassistant/components/binary_sensor/arest.py
homeassistant/components/binary_sensor/concord232.py
@@ -158,6 +175,7 @@ omit =
homeassistant/components/climate/oem.py
homeassistant/components/climate/proliphix.py
homeassistant/components/climate/radiotherm.py
homeassistant/components/config/zwave.py
homeassistant/components/cover/garadget.py
homeassistant/components/cover/homematic.py
homeassistant/components/cover/myq.py
@@ -208,6 +226,7 @@ omit =
homeassistant/components/light/hue.py
homeassistant/components/light/hyperion.py
homeassistant/components/light/lifx.py
homeassistant/components/light/lifx_legacy.py
homeassistant/components/light/limitlessled.py
homeassistant/components/light/osramlightify.py
homeassistant/components/light/tikteck.py
@@ -218,6 +237,7 @@ omit =
homeassistant/components/light/zengge.py
homeassistant/components/lirc.py
homeassistant/components/lock/nuki.py
homeassistant/components/lock/lockitron.py
homeassistant/components/media_player/anthemav.py
homeassistant/components/media_player/apple_tv.py
homeassistant/components/media_player/aquostv.py
@@ -231,6 +251,7 @@ omit =
homeassistant/components/media_player/dunehd.py
homeassistant/components/media_player/emby.py
homeassistant/components/media_player/firetv.py
homeassistant/components/media_player/frontier_silicon.py
homeassistant/components/media_player/gpmdp.py
homeassistant/components/media_player/gstreamer.py
homeassistant/components/media_player/hdmi_cec.py
@@ -255,10 +276,12 @@ omit =
homeassistant/components/media_player/sonos.py
homeassistant/components/media_player/squeezebox.py
homeassistant/components/media_player/vlc.py
homeassistant/components/media_player/volumio.py
homeassistant/components/media_player/yamaha.py
homeassistant/components/notify/aws_lambda.py
homeassistant/components/notify/aws_sns.py
homeassistant/components/notify/aws_sqs.py
homeassistant/components/notify/ciscospark.py
homeassistant/components/notify/discord.py
homeassistant/components/notify/facebook.py
homeassistant/components/notify/free_mobile.py
@@ -286,8 +309,6 @@ omit =
homeassistant/components/notify/syslog.py
homeassistant/components/notify/telegram.py
homeassistant/components/notify/telstra.py
homeassistant/components/notify/twilio_sms.py
homeassistant/components/notify/twilio_call.py
homeassistant/components/notify/twitter.py
homeassistant/components/notify/xmpp.py
homeassistant/components/nuimo_controller.py
@@ -303,16 +324,19 @@ omit =
homeassistant/components/sensor/broadlink.py
homeassistant/components/sensor/dublin_bus_transport.py
homeassistant/components/sensor/coinmarketcap.py
homeassistant/components/sensor/comed_hourly_pricing.py
homeassistant/components/sensor/cpuspeed.py
homeassistant/components/sensor/crimereports.py
homeassistant/components/sensor/cups.py
homeassistant/components/sensor/currencylayer.py
homeassistant/components/sensor/darksky.py
homeassistant/components/sensor/deutsche_bahn.py
homeassistant/components/sensor/dht.py
homeassistant/components/sensor/dnsip.py
homeassistant/components/sensor/dovado.py
homeassistant/components/sensor/dte_energy_bridge.py
homeassistant/components/sensor/ebox.py
homeassistant/components/sensor/efergy.py
homeassistant/components/sensor/eddystone_temperature.py
homeassistant/components/sensor/eliqonline.py
homeassistant/components/sensor/emoncms.py
homeassistant/components/sensor/fastdotcom.py
@@ -333,12 +357,16 @@ omit =
homeassistant/components/sensor/imap.py
homeassistant/components/sensor/imap_email_content.py
homeassistant/components/sensor/influxdb.py
homeassistant/components/sensor/kwb.py
homeassistant/components/sensor/lastfm.py
homeassistant/components/sensor/linux_battery.py
homeassistant/components/sensor/loopenergy.py
homeassistant/components/sensor/mhz19.py
homeassistant/components/sensor/lyft.py
homeassistant/components/sensor/metoffice.py
homeassistant/components/sensor/miflora.py
homeassistant/components/sensor/modem_callerid.py
homeassistant/components/sensor/mqtt_room.py
homeassistant/components/sensor/mvglive.py
homeassistant/components/sensor/netdata.py
homeassistant/components/sensor/neurio_energy.py
homeassistant/components/sensor/nut.py
@@ -410,8 +438,12 @@ omit =
homeassistant/components/tts/picotts.py
homeassistant/components/upnp.py
homeassistant/components/weather/bom.py
homeassistant/components/weather/metoffice.py
homeassistant/components/weather/openweathermap.py
homeassistant/components/weather/zamg.py
homeassistant/components/zeroconf.py
homeassistant/components/zwave/__init__.py
homeassistant/components/zwave/util.py
[report]

View File

@@ -1,16 +1,16 @@
**Description:**
## Description:
**Related issue (if applicable):** fixes #<home-assistant issue number goes here>
**Pull request in [home-assistant.github.io](https://github.com/home-assistant/home-assistant.github.io) with documentation (if applicable):** home-assistant/home-assistant.github.io#<home-assistant.github.io PR number goes here>
**Example entry for `configuration.yaml` (if applicable):**
## Example entry for `configuration.yaml` (if applicable):
```yaml
```
**Checklist:**
## Checklist:
If user exposed functionality or configuration variables are added/changed:
- [ ] Documentation added/updated in [home-assistant.github.io](https://github.com/home-assistant/home-assistant.github.io)

11
.gitignore vendored
View File

@@ -1,15 +1,4 @@
config/*
!config/home-assistant.conf.default
# There is not a better solution afaik..
!config/custom_components
config/custom_components/*
!config/custom_components/example.py
!config/custom_components/hello_world.py
!config/custom_components/mqtt_example.py
!config/panels
config/panels/*
!config/panels/react.html
tests/testing_config/deps
tests/testing_config/home-assistant.log

View File

@@ -14,6 +14,8 @@ matrix:
env: TOXENV=py35
- python: "3.6"
env: TOXENV=py36
- python: "3.6-dev"
env: TOXENV=py36
# allow_failures:
# - python: "3.5"
# env: TOXENV=typing

View File

@@ -1,5 +1,5 @@
include README.rst
include LICENSE
include LICENSE.md
graft homeassistant
prune homeassistant/components/frontend/www_static/home-assistant-polymer
recursive-exclude * *.py[co]

View File

@@ -1,9 +1,7 @@
Home Assistant |Build Status| |Coverage Status| |Join the chat at https://gitter.im/home-assistant/home-assistant| |Join the dev chat at https://gitter.im/home-assistant/home-assistant/devs|
==============================================================================================================================================================================================
Home Assistant is a home automation platform running on Python 3. The
goal of Home Assistant is to be able to track and control all devices at
home and offer a platform for automating control.
Home Assistant is a home automation platform running on Python 3. It is able to track and control all devices at home and offer a platform for automating control.
To get started:
@@ -12,83 +10,22 @@ To get started:
python3 -m pip install homeassistant
hass --open-ui
Check out `the website <https://home-assistant.io>`__ for `a
demo <https://home-assistant.io/demo/>`__, installation instructions,
tutorials and documentation.
Check out `home-assistant.io <https://home-assistant.io>`__ for `a
demo <https://home-assistant.io/demo/>`__, `installation instructions <https://home-assistant.io/getting-started/>`__,
`tutorials <https://home-assistant.io/getting-started/automation-2/>`__ and `documentation <https://home-assistant.io/docs/>`__.
|screenshot-states|
Examples of devices Home Assistant can interface with:
Featured integrations
---------------------
- Monitoring connected devices to a wireless router:
`OpenWrt <https://openwrt.org/>`__,
`Tomato <http://www.polarcloud.com/tomato>`__,
`Netgear <http://netgear.com>`__,
`DD-WRT <http://www.dd-wrt.com/site/index>`__,
`TPLink <http://www.tp-link.us/>`__,
`ASUSWRT <http://event.asus.com/2013/nw/ASUSWRT/>`__,
`Xiaomi <http://miwifi.com/>`__ and any SNMP
capable Linksys WAP/WRT
- `Philips Hue <http://meethue.com>`__ lights,
`WeMo <http://www.belkin.com/us/Products/home-automation/c/wemo-home-automation/>`__
switches, `Edimax <http://www.edimax.com/>`__ switches,
`Efergy <https://efergy.com>`__ energy monitoring, and
`Tellstick <http://www.telldus.se/products/tellstick>`__ devices and
sensors
- `Google
Chromecasts <http://www.google.com/intl/en/chrome/devices/chromecast>`__,
`Music Player Daemon <http://www.musicpd.org/>`__, `Logitech
Squeezebox <https://en.wikipedia.org/wiki/Squeezebox_%28network_music_player%29>`__,
`Plex <https://plex.tv/>`__, `Kodi (XBMC) <http://kodi.tv/>`__,
iTunes (by way of
`itunes-api <https://github.com/maddox/itunes-api>`__), and Amazon
Fire TV (by way of
`python-firetv <https://github.com/happyleavesaoc/python-firetv>`__)
- Support for
`ISY994 <https://www.universal-devices.com/residential/isy994i-series/>`__
(Insteon and X10 devices), `Z-Wave <http://www.z-wave.com/>`__, `Nest
Thermostats <https://nest.com/>`__,
`RFXtrx <http://www.rfxcom.com/>`__,
`Arduino <https://www.arduino.cc/>`__, `Raspberry
Pi <https://www.raspberrypi.org/>`__, and
`Modbus <http://www.modbus.org/>`__
- Interaction with `IFTTT <https://ifttt.com/>`__
- Integrate data from the `Bitcoin <https://bitcoin.org>`__ network,
meteorological data from
`OpenWeatherMap <http://openweathermap.org/>`__ and
`Forecast.io <https://forecast.io/>`__,
`Transmission <http://www.transmissionbt.com/>`__, or
`SABnzbd <http://sabnzbd.org>`__.
- `See full list of supported
devices <https://home-assistant.io/components/>`__
|screenshot-components|
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 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/rest_api/>`__
and can interface with MQTT for easy integration with other projects
like `OwnTracks <http://owntracks.org/>`__
- Allow sending notifications using
`Instapush <https://instapush.im>`__, `Notify My Android
(NMA) <http://www.notifymyandroid.com/>`__,
`PushBullet <https://www.pushbullet.com/>`__,
`PushOver <https://pushover.net/>`__,
`Slack <https://slack.com/>`__,
`Telegram <https://telegram.org/>`__, `Join <http://joaoapps.com/join/>`__, and `Jabber
(XMPP) <http://xmpp.org>`__
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
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
components <https://home-assistant.io/developers/creating_components/>`__.
If you run into issues while using Home Assistant or during development
of a component, check the `Home Assistant help
section <https://home-assistant.io/help/>`__ of our website for further help and information.
of a component, check the `Home Assistant help section <https://home-assistant.io/help/>`__ of our website for further help and information.
.. |Build Status| image:: https://travis-ci.org/home-assistant/home-assistant.svg?branch=master
:target: https://travis-ci.org/home-assistant/home-assistant
@@ -100,3 +37,5 @@ section <https://home-assistant.io/help/>`__ of our website for further help and
:target: https://gitter.im/home-assistant/home-assistant/devs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
.. |screenshot-states| image:: https://raw.github.com/home-assistant/home-assistant/master/docs/screenshots.png
:target: https://home-assistant.io/demo/
.. |screenshot-components| image:: https://raw.github.com/home-assistant/home-assistant/dev/docs/screenshot-components.png
:target: https://home-assistant.io/components/

View File

@@ -1,158 +0,0 @@
homeassistant:
# Omitted values in this section will be auto detected using freegeoip.io
# Location required to calculate the time the sun rises and sets.
# Coordinates are also used for location for weather related components.
# Google Maps can be used to determine more precise GPS coordinates.
latitude: 32.87336
longitude: 117.22743
# Impacts weather/sunrise data
elevation: 665
# 'metric' for Metric System, 'imperial' for imperial system
unit_system: metric
# Pick yours from here:
# http://en.wikipedia.org/wiki/List_of_tz_database_time_zones
time_zone: America/Los_Angeles
# Name of the location where Home Assistant is running
name: Home
http:
api_password: mypass
# Set to 1 to enable development mode
# development: 1
# Enable the frontend
frontend:
light:
# platform: hue
wink:
# Get your token at https://winkbearertoken.appspot.com
access_token: 'YOUR_TOKEN'
device_tracker:
# The following tracker are available:
# https://home-assistant.io/components/#presence-detection
platform: netgear
host: 192.168.1.1
username: admin
password: PASSWORD
switch:
platform: wemo
climate:
platform: nest
# Required: username and password that are used to login to the Nest thermostat.
username: myemail@mydomain.com
password: mypassword
downloader:
download_dir: downloads
notify:
platform: pushbullet
api_key: ABCDEFGHJKLMNOPQRSTUVXYZ
device_sun_light_trigger:
# Optional: specify a specific light/group of lights that has to be turned on
light_group: group.living_room
# Optional: specify which light profile to use when turning lights on
light_profile: relax
# Optional: disable lights being turned off when everybody leaves the house
# disable_turn_off: 1
# 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:
default_view:
view: yes
entities:
- group.awesome_people
- group.climate
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
browser:
keyboard:
# https://home-assistant.io/getting-started/automation/
automation:
- alias: Turn on light when sun sets
trigger:
platform: sun
event: sunset
offset: "-01:00:00"
condition:
condition: state
entity_id: group.all_devices
state: 'home'
action:
service: light.turn_on
# 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
resources:
- type: 'disk_use_percent'
arg: '/'
- type: 'disk_use_percent'
arg: '/home'
sensor 2:
platform: cpuspeed
script:
wakeup:
alias: Wake Up
sequence:
- event: LOGBOOK_ENTRY
event_data:
name: Paulus
message: is waking up
entity_id: device_tracker.paulus
domain: light
- alias: Bedroom lights on
service: light.turn_on
data:
entity_id: group.bedroom
brightness: 100
- delay:
minutes: 1
- alias: Living room lights on
service: light.turn_on
data:
entity_id: group.living_room
scene:
- name: Romantic
entities:
light.tv_back_light: on
light.ceiling:
state: on
xy_color: [0.33, 0.66]
brightness: 200

View File

@@ -1,149 +0,0 @@
"""
Example of a custom component.
Example component to target an entity_id to:
- turn it on at 7AM in the morning
- turn it on if anyone comes home and it is off
- turn it off if all lights are turned off
- turn it off if all people leave the house
- offer a service to turn it on for 10 seconds
Configuration:
To use the Example custom component you will need to add the following to
your configuration.yaml file.
example:
target: TARGET_ENTITY
Variable:
target
*Required
TARGET_ENTITY should be one of your devices that can be turned on and off,
ie a light or a switch. Example value could be light.Ceiling or switch.AC
(if you have these devices with those names).
"""
import time
import logging
from homeassistant.const import STATE_HOME, STATE_NOT_HOME, STATE_ON, STATE_OFF
from homeassistant.helpers import validate_config
from homeassistant.helpers.event_decorators import \
track_state_change, track_time_change
from homeassistant.helpers.service import service
import homeassistant.components as core
from homeassistant.components import device_tracker
from homeassistant.components import light
# The domain of your component. Should be equal to the name of your component.
DOMAIN = "example"
# List of component names (string) your component depends upon.
# We depend on group because group will be loaded after all the components that
# initialize devices have been setup.
DEPENDENCIES = ['group', 'device_tracker', 'light']
# Configuration key for the entity id we are targeting.
CONF_TARGET = 'target'
# Variable for storing configuration parameters.
TARGET_ID = None
# Name of the service that we expose.
SERVICE_FLASH = 'flash'
# Shortcut for the logger
_LOGGER = logging.getLogger(__name__)
def setup(hass, config):
"""Setup example component."""
global TARGET_ID
# Validate that all required config options are given.
if not validate_config(config, {DOMAIN: [CONF_TARGET]}, _LOGGER):
return False
TARGET_ID = config[DOMAIN][CONF_TARGET]
# Validate that the target entity id exists.
if hass.states.get(TARGET_ID) is None:
_LOGGER.error("Target entity id %s does not exist",
TARGET_ID)
# Tell the bootstrapper that we failed to initialize and clear the
# stored target id so our functions don't run.
TARGET_ID = None
return False
# Tell the bootstrapper that we initialized successfully.
return True
@track_state_change(device_tracker.ENTITY_ID_ALL_DEVICES)
def track_devices(hass, entity_id, old_state, new_state):
"""Called when the group.all devices change state."""
# If the target id is not set, return
if not TARGET_ID:
return
# If anyone comes home and the entity is not on, turn it on.
if new_state.state == STATE_HOME and not core.is_on(hass, TARGET_ID):
core.turn_on(hass, TARGET_ID)
# If all people leave the house and the entity is on, turn it off.
elif new_state.state == STATE_NOT_HOME and core.is_on(hass, TARGET_ID):
core.turn_off(hass, TARGET_ID)
@track_time_change(hour=7, minute=0, second=0)
def wake_up(hass, now):
"""Turn light on in the morning.
Turn the light on at 7 AM if there are people home and it is not already
on.
"""
if not TARGET_ID:
return
if device_tracker.is_on(hass) and not core.is_on(hass, TARGET_ID):
_LOGGER.info('People home at 7AM, turning it on')
core.turn_on(hass, TARGET_ID)
@track_state_change(light.ENTITY_ID_ALL_LIGHTS, STATE_ON, STATE_OFF)
def all_lights_off(hass, entity_id, old_state, new_state):
"""If all lights turn off, turn off."""
if not TARGET_ID:
return
if core.is_on(hass, TARGET_ID):
_LOGGER.info('All lights have been turned off, turning it off')
core.turn_off(hass, TARGET_ID)
@service(DOMAIN, SERVICE_FLASH)
def flash_service(hass, call):
"""Service that will toggle the target.
Set the light to off for 10 seconds if on and vice versa.
"""
if not TARGET_ID:
return
if core.is_on(hass, TARGET_ID):
core.turn_off(hass, TARGET_ID)
time.sleep(10)
core.turn_on(hass, TARGET_ID)
else:
core.turn_on(hass, TARGET_ID)
time.sleep(10)
core.turn_off(hass, TARGET_ID)

View File

@@ -1,27 +0,0 @@
"""
The "hello world" custom component.
This component implements the bare minimum that a component should implement.
Configuration:
To use the hello_word component you will need to add the following to your
configuration.yaml file.
hello_world:
"""
# The domain of your component. Should be equal to the name of your component.
DOMAIN = "hello_world"
# List of component names (string) your component depends upon.
DEPENDENCIES = []
def setup(hass, config):
"""Setup our skeleton component."""
# States are in the format DOMAIN.OBJECT_ID.
hass.states.set('hello_world.Hello_World', 'Works!')
# Return boolean to indicate that initialization was successfully.
return True

View File

@@ -1,55 +0,0 @@
"""
Example of a custom MQTT component.
Shows how to communicate with MQTT. Follows a topic on MQTT and updates the
state of an entity to the last message received on that topic.
Also offers a service 'set_state' that will publish a message on the topic that
will be passed via MQTT to our message received listener. Call the service with
example payload {"new_state": "some new state"}.
Configuration:
To use the mqtt_example component you will need to add the following to your
configuration.yaml file.
mqtt_example:
topic: "home-assistant/mqtt_example"
"""
import homeassistant.loader as loader
# The domain of your component. Should be equal to the name of your component.
DOMAIN = "mqtt_example"
# List of component names (string) your component depends upon.
DEPENDENCIES = ['mqtt']
CONF_TOPIC = 'topic'
DEFAULT_TOPIC = 'home-assistant/mqtt_example'
def setup(hass, config):
"""Setup the MQTT example component."""
mqtt = loader.get_component('mqtt')
topic = config[DOMAIN].get('topic', DEFAULT_TOPIC)
entity_id = 'mqtt_example.last_message'
# Listen to a message on MQTT.
def message_received(topic, payload, qos):
"""A new MQTT message has been received."""
hass.states.set(entity_id, payload)
mqtt.subscribe(hass, topic, message_received)
hass.states.set(entity_id, 'No messages')
# Service to publish a message on MQTT.
def set_state_service(call):
"""Service to send a message."""
mqtt.publish(hass, topic, call.data.get('new_state'))
# Register our service with Home Assistant.
hass.services.register(DOMAIN, 'set_state', set_state_service)
# Return boolean to indicate that initialization was successfully.
return True

View File

@@ -1,432 +0,0 @@
<!--
Custom Home Assistant panel example.
Currently only works in Firefox and Chrome because it uses ES6.
Make sure this file is in <config>/panels/react.html
Add to your configuration.yaml:
panel_custom:
- name: react
sidebar_title: TodoMVC
sidebar_icon: mdi:checkbox-marked-outline
config:
title: Wow hello!
-->
<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>

BIN
docs/screenshot-components.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View File

@@ -20,6 +20,17 @@ from homeassistant.const import (
from homeassistant.util.async import run_callback_threadsafe
def attempt_use_uvloop():
"""Attempt to use uvloop."""
import asyncio
try:
import uvloop
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
except ImportError:
pass
def monkey_patch_asyncio():
"""Replace weakref.WeakSet to address Python 3 bug.
@@ -255,10 +266,13 @@ def closefds_osx(min_fd: int, max_fd: int) -> None:
def cmdline() -> List[str]:
"""Collect path and arguments to re-execute the current hass instance."""
if sys.argv[0].endswith('/__main__.py'):
if sys.argv[0].endswith(os.path.sep + '__main__.py'):
modulepath = os.path.dirname(sys.argv[0])
os.environ['PYTHONPATH'] = os.path.dirname(modulepath)
return [sys.executable] + [arg for arg in sys.argv if arg != '--daemon']
return [sys.executable] + [arg for arg in sys.argv if
arg != '--daemon']
else:
return [arg for arg in sys.argv if arg != '--daemon']
def setup_and_run_hass(config_dir: str,
@@ -308,8 +322,7 @@ def setup_and_run_hass(config_dir: str,
EVENT_HOMEASSISTANT_START, open_browser
)
hass.start()
return hass.exit_code
return hass.start()
def try_to_restart() -> None:
@@ -356,11 +369,13 @@ def try_to_restart() -> None:
def main() -> int:
"""Start Home Assistant."""
validate_python()
attempt_use_uvloop()
if sys.version_info[:3] < (3, 5, 3):
monkey_patch_asyncio()
validate_python()
args = get_arguments()
if args.script is not None:

View File

@@ -4,320 +4,30 @@ import logging
import logging.handlers
import os
import sys
from time import time
from collections import OrderedDict
from types import ModuleType
from typing import Any, Optional, Dict
import voluptuous as vol
from voluptuous.humanize import humanize_error
import homeassistant.components as core_components
from homeassistant.components import persistent_notification
import homeassistant.config as conf_util
import homeassistant.core as core
from homeassistant.const import EVENT_HOMEASSISTANT_CLOSE
from homeassistant.setup import async_setup_component
import homeassistant.loader as loader
import homeassistant.util.package as pkg_util
from homeassistant.util.async import (
run_coroutine_threadsafe, run_callback_threadsafe)
from homeassistant.util.logging import AsyncHandler
from homeassistant.util.yaml import clear_secret_cache
from homeassistant.const import EVENT_COMPONENT_LOADED, PLATFORM_FORMAT
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import (
event_decorators, service, config_per_platform, extract_domain_configs)
from homeassistant.helpers.signal import async_register_signal_handling
_LOGGER = logging.getLogger(__name__)
ATTR_COMPONENT = 'component'
ERROR_LOG_FILENAME = 'home-assistant.log'
_PERSISTENT_ERRORS = {}
HA_COMPONENT_URL = '[{}](https://home-assistant.io/components/{}/)'
def setup_component(hass: core.HomeAssistant, domain: str,
config: Optional[Dict]=None) -> bool:
"""Setup a component and all its dependencies."""
return run_coroutine_threadsafe(
async_setup_component(hass, domain, config), loop=hass.loop).result()
@asyncio.coroutine
def async_setup_component(hass: core.HomeAssistant, domain: str,
config: Optional[Dict]=None) -> bool:
"""Setup a component and all its dependencies.
This method is a coroutine.
"""
if domain in hass.config.components:
_LOGGER.debug('Component %s already set up.', domain)
return True
if not loader.PREPARED:
yield from hass.loop.run_in_executor(None, loader.prepare, hass)
if config is None:
config = {}
components = loader.load_order_component(domain)
# OrderedSet is empty if component or dependencies could not be resolved
if not components:
_async_persistent_notification(hass, domain, True)
return False
for component in components:
res = yield from _async_setup_component(hass, component, config)
if not res:
_LOGGER.error('Component %s failed to setup', component)
_async_persistent_notification(hass, component, True)
return False
return True
def _handle_requirements(hass: core.HomeAssistant, component,
name: str) -> bool:
"""Install the requirements for a component.
This method needs to run in an executor.
"""
if hass.config.skip_pip or not hasattr(component, 'REQUIREMENTS'):
return True
for req in component.REQUIREMENTS:
if not pkg_util.install_package(req, target=hass.config.path('deps')):
_LOGGER.error('Not initializing %s because could not install '
'dependency %s', name, req)
_async_persistent_notification(hass, name)
return False
return True
@asyncio.coroutine
def _async_setup_component(hass: core.HomeAssistant,
domain: str, config) -> bool:
"""Setup a component for Home Assistant.
This method is a coroutine.
"""
# pylint: disable=too-many-return-statements
if domain in hass.config.components:
return True
setup_lock = hass.data.get('setup_lock')
if setup_lock is None:
setup_lock = hass.data['setup_lock'] = asyncio.Lock(loop=hass.loop)
setup_progress = hass.data.get('setup_progress')
if setup_progress is None:
setup_progress = hass.data['setup_progress'] = []
if domain in setup_progress:
_LOGGER.error('Attempt made to setup %s during setup of %s',
domain, domain)
_async_persistent_notification(hass, domain, True)
return False
try:
# Used to indicate to discovery that a setup is ongoing and allow it
# to wait till it is done.
did_lock = False
if not setup_lock.locked():
yield from setup_lock.acquire()
did_lock = True
setup_progress.append(domain)
config = yield from async_prepare_setup_component(hass, config, domain)
if config is None:
return False
component = loader.get_component(domain)
if component is None:
_async_persistent_notification(hass, domain)
return False
async_comp = hasattr(component, 'async_setup')
try:
_LOGGER.info("Setting up %s", domain)
if async_comp:
result = yield from component.async_setup(hass, config)
else:
result = yield from hass.loop.run_in_executor(
None, component.setup, hass, config)
except Exception: # pylint: disable=broad-except
_LOGGER.exception('Error during setup of component %s', domain)
_async_persistent_notification(hass, domain, True)
return False
if result is False:
_LOGGER.error('component %s failed to initialize', domain)
_async_persistent_notification(hass, domain, True)
return False
elif result is not True:
_LOGGER.error('component %s did not return boolean if setup '
'was successful. Disabling component.', domain)
_async_persistent_notification(hass, domain, True)
loader.set_component(domain, None)
return False
hass.config.components.add(component.DOMAIN)
hass.bus.async_fire(
EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: component.DOMAIN}
)
return True
finally:
setup_progress.remove(domain)
if did_lock:
setup_lock.release()
def prepare_setup_component(hass: core.HomeAssistant, config: dict,
domain: str):
"""Prepare setup of a component and return processed config."""
return run_coroutine_threadsafe(
async_prepare_setup_component(hass, config, domain), loop=hass.loop
).result()
@asyncio.coroutine
def async_prepare_setup_component(hass: core.HomeAssistant, config: dict,
domain: str):
"""Prepare setup of a component and return processed config.
This method is a coroutine.
"""
# pylint: disable=too-many-return-statements
component = loader.get_component(domain)
missing_deps = [dep for dep in getattr(component, 'DEPENDENCIES', [])
if dep not in hass.config.components]
if missing_deps:
_LOGGER.error(
'Not initializing %s because not all dependencies loaded: %s',
domain, ", ".join(missing_deps))
return None
if hasattr(component, 'CONFIG_SCHEMA'):
try:
config = component.CONFIG_SCHEMA(config)
except vol.Invalid as ex:
async_log_exception(ex, domain, config, hass)
return None
elif hasattr(component, 'PLATFORM_SCHEMA'):
platforms = []
for p_name, p_config in config_per_platform(config, domain):
# Validate component specific platform schema
try:
p_validated = component.PLATFORM_SCHEMA(p_config)
except vol.Invalid as ex:
async_log_exception(ex, domain, config, hass)
continue
# Not all platform components follow same pattern for platforms
# So if p_name is None we are not going to validate platform
# (the automation component is one of them)
if p_name is None:
platforms.append(p_validated)
continue
platform = yield from async_prepare_setup_platform(
hass, config, domain, p_name)
if platform is None:
continue
# Validate platform specific schema
if hasattr(platform, 'PLATFORM_SCHEMA'):
try:
# pylint: disable=no-member
p_validated = platform.PLATFORM_SCHEMA(p_validated)
except vol.Invalid as ex:
async_log_exception(ex, '{}.{}'.format(domain, p_name),
p_validated, hass)
continue
platforms.append(p_validated)
# Create a copy of the configuration with all config for current
# component removed and add validated config back in.
filter_keys = extract_domain_configs(config, domain)
config = {key: value for key, value in config.items()
if key not in filter_keys}
config[domain] = platforms
res = yield from hass.loop.run_in_executor(
None, _handle_requirements, hass, component, domain)
if not res:
return None
return config
def prepare_setup_platform(hass: core.HomeAssistant, config, domain: str,
platform_name: str) -> Optional[ModuleType]:
"""Load a platform and makes sure dependencies are setup."""
return run_coroutine_threadsafe(
async_prepare_setup_platform(hass, config, domain, platform_name),
loop=hass.loop
).result()
@asyncio.coroutine
def async_prepare_setup_platform(hass: core.HomeAssistant, config, domain: str,
platform_name: str) \
-> Optional[ModuleType]:
"""Load a platform and makes sure dependencies are setup.
This method is a coroutine.
"""
if not loader.PREPARED:
yield from hass.loop.run_in_executor(None, loader.prepare, hass)
platform_path = PLATFORM_FORMAT.format(domain, platform_name)
platform = loader.get_platform(domain, platform_name)
# Not found
if platform is None:
_LOGGER.error('Unable to find platform %s', platform_path)
_async_persistent_notification(hass, platform_path)
return None
# Already loaded
elif platform_path in hass.config.components:
return platform
# Load dependencies
for component in getattr(platform, 'DEPENDENCIES', []):
if component in loader.DEPENDENCY_BLACKLIST:
raise HomeAssistantError(
'{} is not allowed to be a dependency.'.format(component))
res = yield from async_setup_component(hass, component, config)
if not res:
_LOGGER.error(
'Unable to prepare setup for platform %s because '
'dependency %s could not be initialized', platform_path,
component)
_async_persistent_notification(hass, platform_path, True)
return None
res = yield from hass.loop.run_in_executor(
None, _handle_requirements, hass, platform, platform_path)
if not res:
return None
return platform
FIRST_INIT_COMPONENT = set((
'recorder', 'mqtt', 'mqtt_eventstream', 'logger', 'introduction'))
def from_config_dict(config: Dict[str, Any],
@@ -339,23 +49,14 @@ def from_config_dict(config: Dict[str, Any],
hass.config.config_dir = config_dir
mount_local_lib_path(config_dir)
@asyncio.coroutine
def _async_init_from_config_dict(future):
try:
re_hass = yield from async_from_config_dict(
config, hass, config_dir, enable_log, verbose, skip_pip,
log_rotate_days)
future.set_result(re_hass)
# pylint: disable=broad-except
except Exception as exc:
future.set_exception(exc)
# run task
future = asyncio.Future(loop=hass.loop)
hass.async_add_job(_async_init_from_config_dict(future))
hass.loop.run_until_complete(future)
hass = hass.loop.run_until_complete(
async_from_config_dict(
config, hass, config_dir, enable_log, verbose, skip_pip,
log_rotate_days)
)
return future.result()
return hass
@asyncio.coroutine
@@ -372,19 +73,13 @@ def async_from_config_dict(config: Dict[str, Any],
Dynamically loads required components and its dependencies.
This method is a coroutine.
"""
hass.async_track_tasks()
setup_lock = hass.data.get('setup_lock')
if setup_lock is None:
setup_lock = hass.data['setup_lock'] = asyncio.Lock(loop=hass.loop)
yield from setup_lock.acquire()
start = time()
core_config = config.get(core.DOMAIN, {})
try:
yield from conf_util.async_process_ha_core_config(hass, core_config)
except vol.Invalid as ex:
async_log_exception(ex, 'homeassistant', core_config, hass)
conf_util.async_log_exception(ex, 'homeassistant', core_config, hass)
return None
yield from hass.loop.run_in_executor(
@@ -429,23 +124,24 @@ def async_from_config_dict(config: Dict[str, Any],
_LOGGER.info('Home Assistant core initialized')
# Give event decorators access to HASS
event_decorators.HASS = hass
service.HASS = hass
# stage 1
for component in components:
if component not in FIRST_INIT_COMPONENT:
continue
hass.async_add_job(async_setup_component(hass, component, config))
# Setup the components
dependency_blacklist = loader.DEPENDENCY_BLACKLIST - set(components)
yield from hass.async_block_till_done()
for domain in loader.load_order_components(components):
if domain in dependency_blacklist:
raise HomeAssistantError(
'{} is not allowed to be a dependency'.format(domain))
# stage 2
for component in components:
if component in FIRST_INIT_COMPONENT:
continue
hass.async_add_job(async_setup_component(hass, component, config))
yield from _async_setup_component(hass, domain, config)
yield from hass.async_block_till_done()
setup_lock.release()
yield from hass.async_stop_track_tasks()
stop = time()
_LOGGER.info('Home Assistant initialized in %.2fs', stop-start)
async_register_signal_handling(hass)
return hass
@@ -464,22 +160,13 @@ def from_config_file(config_path: str,
if hass is None:
hass = core.HomeAssistant()
@asyncio.coroutine
def _async_init_from_config_file(future):
try:
re_hass = yield from async_from_config_file(
config_path, hass, verbose, skip_pip, log_rotate_days)
future.set_result(re_hass)
# pylint: disable=broad-except
except Exception as exc:
future.set_exception(exc)
# run task
future = asyncio.Future(loop=hass.loop)
hass.loop.create_task(_async_init_from_config_file(future))
hass.loop.run_until_complete(future)
hass = hass.loop.run_until_complete(
async_from_config_file(
config_path, hass, verbose, skip_pip, log_rotate_days)
)
return future.result()
return hass
@asyncio.coroutine
@@ -504,7 +191,8 @@ def async_from_config_file(config_path: str,
try:
config_dict = yield from hass.loop.run_in_executor(
None, conf_util.load_yaml_config_file, config_path)
except HomeAssistantError:
except HomeAssistantError as err:
_LOGGER.error('Error loading %s: %s', config_path, err)
return None
finally:
clear_secret_cache()
@@ -588,57 +276,6 @@ def async_enable_logging(hass: core.HomeAssistant, verbose: bool=False,
'Unable to setup error log %s (access denied)', err_log_path)
def log_exception(ex, domain, config, hass):
"""Generate log exception for config validation."""
run_callback_threadsafe(
hass.loop, async_log_exception, ex, domain, config, hass).result()
@core.callback
def _async_persistent_notification(hass: core.HomeAssistant, component: str,
link: Optional[bool]=False):
"""Print a persistent notification.
This method must be run in the event loop.
"""
_PERSISTENT_ERRORS[component] = _PERSISTENT_ERRORS.get(component) or link
_lst = [HA_COMPONENT_URL.format(name.replace('_', '-'), name)
if link else name for name, link in _PERSISTENT_ERRORS.items()]
message = ('The following components and platforms could not be set up:\n'
'* ' + '\n* '.join(list(_lst)) + '\nPlease check your config')
persistent_notification.async_create(
hass, message, 'Invalid config', 'invalid_config')
@core.callback
def async_log_exception(ex, domain, config, hass):
"""Generate log exception for config validation.
This method must be run in the event loop.
"""
message = 'Invalid config for [{}]: '.format(domain)
if hass is not None:
_async_persistent_notification(hass, domain, True)
if 'extra keys not allowed' in ex.error_message:
message += '[{}] is an invalid option for [{}]. Check: {}->{}.'\
.format(ex.path[-1], domain, domain,
'->'.join(str(m) for m in ex.path))
else:
message += '{}.'.format(humanize_error(config, ex))
domain_config = config.get(domain, config)
message += " (See {}, line {}). ".format(
getattr(domain_config, '__config_file__', '?'),
getattr(domain_config, '__line__', '?'))
if domain != 'homeassistant':
message += ('Please check the docs at '
'https://home-assistant.io/components/{}/'.format(domain))
_LOGGER.error(message)
def mount_local_lib_path(config_dir: str) -> str:
"""Add local library to Python Path.

View File

@@ -113,7 +113,7 @@ def async_setup(hass, config):
if not alarm.should_poll:
continue
update_coro = hass.loop.create_task(
update_coro = hass.async_add_job(
alarm.async_update_ha_state(True))
if hasattr(alarm, 'async_update'):
update_tasks.append(update_coro)

View File

@@ -1,13 +1,13 @@
"""
Interfaces with Alarm.com alarm control panels.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.alarmdotcom/
"""
import logging
import asyncio
import voluptuous as vol
import homeassistant.components.alarm_control_panel as alarm
from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA
from homeassistant.const import (
@@ -15,10 +15,9 @@ from homeassistant.const import (
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_UNKNOWN, CONF_CODE,
CONF_NAME)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
REQUIREMENTS = ['https://github.com/Xorso/pyalarmdotcom'
'/archive/0.1.1.zip'
'#pyalarmdotcom==0.1.1']
REQUIREMENTS = ['pyalarmdotcom==0.2.9']
_LOGGER = logging.getLogger(__name__)
@@ -32,14 +31,17 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup an Alarm.com control panel."""
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Setup a Alarm.com control panel."""
name = config.get(CONF_NAME)
code = config.get(CONF_CODE)
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
add_devices([AlarmDotCom(hass, name, code, username, password)], True)
alarmdotcom = AlarmDotCom(hass, name, code, username, password)
yield from alarmdotcom.async_login()
async_add_devices([alarmdotcom])
class AlarmDotCom(alarm.AlarmControlPanel):
@@ -47,18 +49,30 @@ class AlarmDotCom(alarm.AlarmControlPanel):
def __init__(self, hass, name, code, username, password):
"""Initialize the Alarm.com status."""
from pyalarmdotcom.pyalarmdotcom import Alarmdotcom
self._alarm = Alarmdotcom(username, password, timeout=10)
from pyalarmdotcom import Alarmdotcom
_LOGGER.debug('Setting up Alarm.com...')
self._hass = hass
self._name = name
self._code = str(code) if code else None
self._username = username
self._password = password
self._websession = async_get_clientsession(self._hass)
self._state = STATE_UNKNOWN
self._alarm = Alarmdotcom(username,
password,
self._websession,
hass.loop)
def update(self):
@asyncio.coroutine
def async_login(self):
"""Login to Alarm.com."""
yield from self._alarm.async_login()
@asyncio.coroutine
def async_update(self):
"""Fetch the latest state."""
self._state = self._alarm.state
yield from self._alarm.async_update()
return self._alarm.state
@property
def name(self):
@@ -73,45 +87,36 @@ class AlarmDotCom(alarm.AlarmControlPanel):
@property
def state(self):
"""Return the state of the device."""
if self._state == 'Disarmed':
if self._alarm.state.lower() == 'disarmed':
return STATE_ALARM_DISARMED
elif self._state == 'Armed Stay':
elif self._alarm.state.lower() == 'armed stay':
return STATE_ALARM_ARMED_HOME
elif self._state == 'Armed Away':
elif self._alarm.state.lower() == 'armed away':
return STATE_ALARM_ARMED_AWAY
else:
return STATE_UNKNOWN
def alarm_disarm(self, code=None):
@asyncio.coroutine
def async_alarm_disarm(self, code=None):
"""Send disarm command."""
if not self._validate_code(code, 'disarming home'):
return
from pyalarmdotcom.pyalarmdotcom import Alarmdotcom
# Open another session to alarm.com to fire off the command
_alarm = Alarmdotcom(self._username, self._password, timeout=10)
_alarm.disarm()
if self._validate_code(code):
yield from self._alarm.async_alarm_disarm()
def alarm_arm_home(self, code=None):
"""Send arm home command."""
if not self._validate_code(code, 'arming home'):
return
from pyalarmdotcom.pyalarmdotcom import Alarmdotcom
# Open another session to alarm.com to fire off the command
_alarm = Alarmdotcom(self._username, self._password, timeout=10)
_alarm.arm_stay()
@asyncio.coroutine
def async_alarm_arm_home(self, code=None):
"""Send arm hom command."""
if self._validate_code(code):
yield from self._alarm.async_alarm_arm_home()
def alarm_arm_away(self, code=None):
@asyncio.coroutine
def async_alarm_arm_away(self, code=None):
"""Send arm away command."""
if not self._validate_code(code, 'arming home'):
return
from pyalarmdotcom.pyalarmdotcom import Alarmdotcom
# Open another session to alarm.com to fire off the command
_alarm = Alarmdotcom(self._username, self._password, timeout=10)
_alarm.arm_away()
if self._validate_code(code):
yield from self._alarm.async_alarm_arm_away()
def _validate_code(self, code, state):
def _validate_code(self, code):
"""Validate given code."""
check = self._code is None or code == self._code
if not check:
_LOGGER.warning('Wrong code entered for %s', state)
_LOGGER.warning('Wrong code entered.')
return check

View File

@@ -55,7 +55,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
)
devices.append(device)
yield from async_add_devices(devices)
async_add_devices(devices)
@callback
def alarm_keypress_handler(service):
@@ -94,10 +94,13 @@ class EnvisalinkAlarm(EnvisalinkDevice, alarm.AlarmControlPanel):
_LOGGER.debug("Setting up alarm: %s", alarm_name)
super().__init__(alarm_name, info, controller)
@asyncio.coroutine
def async_added_to_hass(self):
"""Register callbacks."""
async_dispatcher_connect(
hass, SIGNAL_KEYPAD_UPDATE, self._update_callback)
self.hass, SIGNAL_KEYPAD_UPDATE, self._update_callback)
async_dispatcher_connect(
hass, SIGNAL_PARTITION_UPDATE, self._update_callback)
self.hass, SIGNAL_PARTITION_UPDATE, self._update_callback)
@callback
def _update_callback(self, partition):

View File

@@ -46,7 +46,7 @@ PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Setup the MQTT platform."""
yield from async_add_devices([MqttAlarm(
async_add_devices([MqttAlarm(
config.get(CONF_NAME),
config.get(CONF_STATE_TOPIC),
config.get(CONF_COMMAND_TOPIC),

View File

@@ -0,0 +1,87 @@
"""Interfaces with TotalConnect alarm control panels."""
import logging
import voluptuous as vol
import homeassistant.components.alarm_control_panel as alarm
from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA
from homeassistant.const import (
CONF_PASSWORD, CONF_USERNAME, STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_UNKNOWN,
CONF_NAME)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['total_connect_client==0.7']
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'Total Connect'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_USERNAME): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup a TotalConnect control panel."""
name = config.get(CONF_NAME)
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
total_connect = TotalConnect(name, username, password)
add_devices([total_connect], True)
class TotalConnect(alarm.AlarmControlPanel):
"""Represent an TotalConnect status."""
def __init__(self, name, username, password):
"""Initialize the TotalConnect status."""
from total_connect_client import TotalConnectClient
_LOGGER.debug('Setting up TotalConnect...')
self._name = name
self._username = username
self._password = password
self._state = STATE_UNKNOWN
self._client = TotalConnectClient.TotalConnectClient(username,
password)
@property
def name(self):
"""Return the name of the device."""
return self._name
@property
def state(self):
"""Return the state of the device."""
return self._state
def update(self):
"""Return the state of the device."""
status = self._client.get_armed_status()
if status == self._client.DISARMED:
state = STATE_ALARM_DISARMED
elif status == self._client.ARMED_STAY:
state = STATE_ALARM_ARMED_HOME
elif status == self._client.ARMED_AWAY:
state = STATE_ALARM_ARMED_AWAY
else:
state = STATE_UNKNOWN
self._state = state
def alarm_disarm(self, code=None):
"""Send disarm command."""
self._client.disarm()
def alarm_arm_home(self, code=None):
"""Send arm home command."""
self._client.arm_stay()
def alarm_arm_away(self, code=None):
"""Send arm away command."""
self._client.arm_away()

View File

@@ -18,7 +18,6 @@ from homeassistant.const import (
SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE, ATTR_ENTITY_ID)
from homeassistant.helpers.entity import ToggleEntity
from homeassistant.helpers import service, event
from homeassistant.util.async import run_callback_threadsafe
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
@@ -62,8 +61,7 @@ def is_on(hass, entity_id):
def turn_on(hass, entity_id):
"""Reset the alert."""
run_callback_threadsafe(
hass.loop, async_turn_on, hass, entity_id).result()
hass.add_job(async_turn_on, hass, entity_id)
@callback
@@ -76,8 +74,7 @@ def async_turn_on(hass, entity_id):
def turn_off(hass, entity_id):
"""Acknowledge alert."""
run_callback_threadsafe(
hass.loop, async_turn_off, hass, entity_id).result()
hass.add_job(async_turn_off, hass, entity_id)
@callback
@@ -90,7 +87,7 @@ def async_turn_off(hass, entity_id):
def toggle(hass, entity_id):
"""Toggle acknowledgement of alert."""
run_callback_threadsafe(hass.loop, async_toggle, hass, entity_id)
hass.add_job(async_toggle, hass, entity_id)
@callback

View File

@@ -0,0 +1,293 @@
"""
Support for IP Webcam, an Android app that acts as a full-featured webcam.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/android_ip_webcam/
"""
import asyncio
import logging
from datetime import timedelta
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.const import (
CONF_NAME, CONF_HOST, CONF_PORT, CONF_USERNAME, CONF_PASSWORD,
CONF_SENSORS, CONF_SWITCHES, CONF_TIMEOUT, CONF_SCAN_INTERVAL,
CONF_PLATFORM)
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import (
async_dispatcher_send, async_dispatcher_connect)
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import async_track_point_in_utc_time
from homeassistant.util.dt import utcnow
from homeassistant.components.camera.mjpeg import (
CONF_MJPEG_URL, CONF_STILL_IMAGE_URL)
REQUIREMENTS = ['pydroid-ipcam==0.8']
_LOGGER = logging.getLogger(__name__)
ATTR_AUD_CONNS = 'Audio Connections'
ATTR_HOST = 'host'
ATTR_VID_CONNS = 'Video Connections'
CONF_MOTION_SENSOR = 'motion_sensor'
DATA_IP_WEBCAM = 'android_ip_webcam'
DEFAULT_NAME = 'IP Webcam'
DEFAULT_PORT = 8080
DEFAULT_TIMEOUT = 10
DOMAIN = 'android_ip_webcam'
SCAN_INTERVAL = timedelta(seconds=10)
SIGNAL_UPDATE_DATA = 'android_ip_webcam_update'
KEY_MAP = {
'audio_connections': 'Audio Connections',
'adet_limit': 'Audio Trigger Limit',
'antibanding': 'Anti-banding',
'audio_only': 'Audio Only',
'battery_level': 'Battery Level',
'battery_temp': 'Battery Temperature',
'battery_voltage': 'Battery Voltage',
'coloreffect': 'Color Effect',
'exposure': 'Exposure Level',
'exposure_lock': 'Exposure Lock',
'ffc': 'Front-facing Camera',
'flashmode': 'Flash Mode',
'focus': 'Focus',
'focus_homing': 'Focus Homing',
'focus_region': 'Focus Region',
'focusmode': 'Focus Mode',
'gps_active': 'GPS Active',
'idle': 'Idle',
'ip_address': 'IPv4 Address',
'ipv6_address': 'IPv6 Address',
'ivideon_streaming': 'Ivideon Streaming',
'light': 'Light Level',
'mirror_flip': 'Mirror Flip',
'motion': 'Motion',
'motion_active': 'Motion Active',
'motion_detect': 'Motion Detection',
'motion_event': 'Motion Event',
'motion_limit': 'Motion Limit',
'night_vision': 'Night Vision',
'night_vision_average': 'Night Vision Average',
'night_vision_gain': 'Night Vision Gain',
'orientation': 'Orientation',
'overlay': 'Overlay',
'photo_size': 'Photo Size',
'pressure': 'Pressure',
'proximity': 'Proximity',
'quality': 'Quality',
'scenemode': 'Scene Mode',
'sound': 'Sound',
'sound_event': 'Sound Event',
'sound_timeout': 'Sound Timeout',
'torch': 'Torch',
'video_connections': 'Video Connections',
'video_chunk_len': 'Video Chunk Length',
'video_recording': 'Video Recording',
'video_size': 'Video Size',
'whitebalance': 'White Balance',
'whitebalance_lock': 'White Balance Lock',
'zoom': 'Zoom'
}
ICON_MAP = {
'audio_connections': 'mdi:speaker',
'battery_level': 'mdi:battery',
'battery_temp': 'mdi:thermometer',
'battery_voltage': 'mdi:battery-charging-100',
'exposure_lock': 'mdi:camera',
'ffc': 'mdi:camera-front-variant',
'focus': 'mdi:image-filter-center-focus',
'gps_active': 'mdi:crosshairs-gps',
'light': 'mdi:flashlight',
'motion': 'mdi:run',
'night_vision': 'mdi:weather-night',
'overlay': 'mdi:monitor',
'pressure': 'mdi:gauge',
'proximity': 'mdi:map-marker-radius',
'quality': 'mdi:quality-high',
'sound': 'mdi:speaker',
'sound_event': 'mdi:speaker',
'sound_timeout': 'mdi:speaker',
'torch': 'mdi:white-balance-sunny',
'video_chunk_len': 'mdi:video',
'video_connections': 'mdi:eye',
'video_recording': 'mdi:record-rec',
'whitebalance_lock': 'mdi:white-balance-auto'
}
SWITCHES = ['exposure_lock', 'ffc', 'focus', 'gps_active', 'night_vision',
'overlay', 'torch', 'whitebalance_lock', 'video_recording']
SENSORS = ['audio_connections', 'battery_level', 'battery_temp',
'battery_voltage', 'light', 'motion', 'pressure', 'proximity',
'sound', 'video_connections']
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.All(cv.ensure_list, [vol.Schema({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
vol.Optional(CONF_SCAN_INTERVAL, default=SCAN_INTERVAL):
cv.time_period,
vol.Inclusive(CONF_USERNAME, 'authentication'): cv.string,
vol.Inclusive(CONF_PASSWORD, 'authentication'): cv.string,
vol.Optional(CONF_SWITCHES, default=None):
vol.All(cv.ensure_list, [vol.In(SWITCHES)]),
vol.Optional(CONF_SENSORS, default=None):
vol.All(cv.ensure_list, [vol.In(SENSORS)]),
vol.Optional(CONF_MOTION_SENSOR, default=None): cv.boolean,
})])
}, extra=vol.ALLOW_EXTRA)
@asyncio.coroutine
def async_setup(hass, config):
"""Set up the IP Webcam component."""
from pydroid_ipcam import PyDroidIPCam
webcams = hass.data[DATA_IP_WEBCAM] = {}
websession = async_get_clientsession(hass)
@asyncio.coroutine
def async_setup_ipcamera(cam_config):
"""Set up an IP camera."""
host = cam_config[CONF_HOST]
username = cam_config.get(CONF_USERNAME)
password = cam_config.get(CONF_PASSWORD)
name = cam_config[CONF_NAME]
interval = cam_config[CONF_SCAN_INTERVAL]
switches = cam_config[CONF_SWITCHES]
sensors = cam_config[CONF_SENSORS]
motion = cam_config[CONF_MOTION_SENSOR]
# Init ip webcam
cam = PyDroidIPCam(
hass.loop, websession, host, cam_config[CONF_PORT],
username=username, password=password,
timeout=cam_config[CONF_TIMEOUT]
)
if switches is None:
switches = [setting for setting in cam.enabled_settings
if setting in SWITCHES]
if sensors is None:
sensors = [sensor for sensor in cam.enabled_sensors
if sensor in SENSORS]
sensors.extend(['audio_connections', 'video_connections'])
if motion is None:
motion = 'motion_active' in cam.enabled_sensors
@asyncio.coroutine
def async_update_data(now):
"""Update data from IP camera in SCAN_INTERVAL."""
yield from cam.update()
async_dispatcher_send(hass, SIGNAL_UPDATE_DATA, host)
async_track_point_in_utc_time(
hass, async_update_data, utcnow() + interval)
yield from async_update_data(None)
# Load platforms
webcams[host] = cam
mjpeg_camera = {
CONF_PLATFORM: 'mjpeg',
CONF_MJPEG_URL: cam.mjpeg_url,
CONF_STILL_IMAGE_URL: cam.image_url,
CONF_NAME: name,
}
if username and password:
mjpeg_camera.update({
CONF_USERNAME: username,
CONF_PASSWORD: password
})
hass.async_add_job(discovery.async_load_platform(
hass, 'camera', 'mjpeg', mjpeg_camera, config))
if sensors:
hass.async_add_job(discovery.async_load_platform(
hass, 'sensor', DOMAIN, {
CONF_NAME: name,
CONF_HOST: host,
CONF_SENSORS: sensors,
}, config))
if switches:
hass.async_add_job(discovery.async_load_platform(
hass, 'switch', DOMAIN, {
CONF_NAME: name,
CONF_HOST: host,
CONF_SWITCHES: switches,
}, config))
if motion:
hass.async_add_job(discovery.async_load_platform(
hass, 'binary_sensor', DOMAIN, {
CONF_HOST: host,
CONF_NAME: name,
}, config))
tasks = [async_setup_ipcamera(conf) for conf in config[DOMAIN]]
if tasks:
yield from asyncio.wait(tasks, loop=hass.loop)
return True
class AndroidIPCamEntity(Entity):
"""The Android device running IP Webcam."""
def __init__(self, host, ipcam):
"""Initialize the data oject."""
self._host = host
self._ipcam = ipcam
@asyncio.coroutine
def async_added_to_hass(self):
"""Register update dispatcher."""
@callback
def async_ipcam_update(host):
"""Update callback."""
if self._host != host:
return
self.hass.async_add_job(self.async_update_ha_state(True))
async_dispatcher_connect(
self.hass, SIGNAL_UPDATE_DATA, async_ipcam_update)
@property
def should_poll(self):
"""Return True if entity has to be polled for state."""
return False
@property
def available(self):
"""Return True if entity is available."""
return self._ipcam.available
@property
def device_state_attributes(self):
"""Return the state attributes."""
state_attr = {ATTR_HOST: self._host}
if self._ipcam.status_data is None:
return state_attr
state_attr[ATTR_VID_CONNS] = \
self._ipcam.status_data.get('video_connections')
state_attr[ATTR_AUD_CONNS] = \
self._ipcam.status_data.get('audio_connections')
return state_attr

View File

@@ -50,9 +50,11 @@ def setup(hass, config):
hass.http.register_view(APIDomainServicesView)
hass.http.register_view(APIEventForwardingView)
hass.http.register_view(APIComponentsView)
hass.http.register_view(APIErrorLogView)
hass.http.register_view(APITemplateView)
hass.http.register_static_path(
URL_API_ERROR_LOG, hass.config.path(ERROR_LOG_FILENAME), False)
return True
@@ -327,6 +329,8 @@ class APIEventForwardingView(HomeAssistantView):
@asyncio.coroutine
def post(self, request):
"""Setup an event forwarder."""
_LOGGER.warning('Event forwarding is deprecated. '
'Will be removed by 0.43')
hass = request.app['hass']
try:
data = yield from request.json()
@@ -400,20 +404,6 @@ class APIComponentsView(HomeAssistantView):
return self.json(request.app['hass'].config.components)
class APIErrorLogView(HomeAssistantView):
"""View to handle ErrorLog requests."""
url = URL_API_ERROR_LOG
name = "api:error-log"
@asyncio.coroutine
def get(self, request):
"""Serve error log."""
resp = yield from self.file(
request, request.app['hass'].config.path(ERROR_LOG_FILENAME))
return resp
class APITemplateView(HomeAssistantView):
"""View to handle requests."""

View File

@@ -11,16 +11,18 @@ import os
import voluptuous as vol
from homeassistant.bootstrap import async_prepare_setup_platform
from homeassistant.setup import async_prepare_setup_platform
from homeassistant.core import CoreState
from homeassistant import config as conf_util
from homeassistant.const import (
ATTR_ENTITY_ID, CONF_PLATFORM, STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF,
SERVICE_TOGGLE)
SERVICE_TOGGLE, SERVICE_RELOAD, EVENT_HOMEASSISTANT_START)
from homeassistant.components import logbook
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import extract_domain_configs, script, condition
from homeassistant.helpers.entity import ToggleEntity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.restore_state import async_get_last_state
from homeassistant.loader import get_platform
from homeassistant.util.dt import utcnow
import homeassistant.helpers.config_validation as cv
@@ -28,8 +30,6 @@ import homeassistant.helpers.config_validation as cv
DOMAIN = 'automation'
ENTITY_ID_FORMAT = DOMAIN + '.{}'
DEPENDENCIES = ['group']
GROUP_NAME_ALL_AUTOMATIONS = 'all automations'
CONF_ALIAS = 'alias'
@@ -52,7 +52,6 @@ DEFAULT_INITIAL_STATE = True
ATTR_LAST_TRIGGERED = 'last_triggered'
ATTR_VARIABLES = 'variables'
SERVICE_TRIGGER = 'trigger'
SERVICE_RELOAD = 'reload'
_LOGGER = logging.getLogger(__name__)
@@ -83,8 +82,7 @@ _CONDITION_SCHEMA = vol.All(cv.ensure_list, [cv.CONDITION_SCHEMA])
PLATFORM_SCHEMA = vol.Schema({
CONF_ALIAS: cv.string,
vol.Optional(CONF_INITIAL_STATE,
default=DEFAULT_INITIAL_STATE): cv.boolean,
vol.Optional(CONF_INITIAL_STATE): cv.boolean,
vol.Optional(CONF_HIDE_ENTITY, default=DEFAULT_HIDE_ENTITY): cv.boolean,
vol.Required(CONF_TRIGGER): _TRIGGER_SCHEMA,
vol.Optional(CONF_CONDITION): _CONDITION_SCHEMA,
@@ -103,15 +101,13 @@ TRIGGER_SERVICE_SCHEMA = vol.Schema({
RELOAD_SERVICE_SCHEMA = vol.Schema({})
def is_on(hass, entity_id=None):
def is_on(hass, entity_id):
"""
Return true if specified automation entity_id is on.
Check all automation if no entity_id specified.
Async friendly.
"""
entity_ids = [entity_id] if entity_id else hass.states.entity_ids(DOMAIN)
return any(hass.states.is_state(entity_id, STATE_ON)
for entity_id in entity_ids)
return hass.states.is_state(entity_id, STATE_ON)
def turn_on(hass, entity_id=None):
@@ -226,16 +222,16 @@ class AutomationEntity(ToggleEntity):
"""Entity to show status of entity."""
def __init__(self, name, async_attach_triggers, cond_func, async_action,
hidden):
hidden, initial_state):
"""Initialize an automation entity."""
self._name = name
self._async_attach_triggers = async_attach_triggers
self._async_detach_triggers = None
self._cond_func = cond_func
self._async_action = async_action
self._enabled = False
self._last_triggered = None
self._hidden = hidden
self._initial_state = initial_state
@property
def name(self):
@@ -262,26 +258,54 @@ class AutomationEntity(ToggleEntity):
@property
def is_on(self) -> bool:
"""Return True if entity is on."""
return self._enabled
return self._async_detach_triggers is not None
@asyncio.coroutine
def async_added_to_hass(self) -> None:
"""Startup with initial state or previous state."""
enable_automation = DEFAULT_INITIAL_STATE
if self._initial_state is not None:
enable_automation = self._initial_state
else:
state = yield from async_get_last_state(self.hass, self.entity_id)
if state:
enable_automation = state.state == STATE_ON
self._last_triggered = state.attributes.get('last_triggered')
if not enable_automation:
return
# HomeAssistant is starting up
elif self.hass.state == CoreState.not_running:
@asyncio.coroutine
def async_enable_automation(event):
"""Start automation on startup."""
yield from self.async_enable()
self.hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_START, async_enable_automation)
# HomeAssistant is running
else:
yield from self.async_enable()
@asyncio.coroutine
def async_turn_on(self, **kwargs) -> None:
"""Turn the entity on and update the state."""
if self._enabled:
if self.is_on:
return
yield from self.async_enable()
yield from self.async_update_ha_state()
@asyncio.coroutine
def async_turn_off(self, **kwargs) -> None:
"""Turn the entity off."""
if not self._enabled:
if not self.is_on:
return
self._async_detach_triggers()
self._async_detach_triggers = None
self._enabled = False
yield from self.async_update_ha_state()
@asyncio.coroutine
@@ -307,12 +331,12 @@ class AutomationEntity(ToggleEntity):
This method is a coroutine.
"""
if self._enabled:
if self.is_on:
return
self._async_detach_triggers = yield from self._async_attach_triggers(
self.async_trigger)
self._enabled = True
yield from self.async_update_ha_state()
@asyncio.coroutine
@@ -322,7 +346,6 @@ def _async_process_config(hass, config, component):
This method is a coroutine.
"""
entities = []
tasks = []
for config_key in extract_domain_configs(config, DOMAIN):
conf = config[config_key]
@@ -332,6 +355,7 @@ def _async_process_config(hass, config, component):
list_no)
hidden = config_block[CONF_HIDE_ENTITY]
initial_state = config_block.get(CONF_INITIAL_STATE)
action = _async_get_action(hass, config_block.get(CONF_ACTION, {}),
name)
@@ -348,15 +372,14 @@ def _async_process_config(hass, config, component):
async_attach_triggers = partial(
_async_process_trigger, hass, config,
config_block.get(CONF_TRIGGER, []), name)
entity = AutomationEntity(name, async_attach_triggers, cond_func,
action, hidden)
if config_block[CONF_INITIAL_STATE]:
tasks.append(entity.async_enable())
config_block.get(CONF_TRIGGER, []), name
)
entity = AutomationEntity(
name, async_attach_triggers, cond_func, action, hidden,
initial_state)
entities.append(entity)
if tasks:
yield from asyncio.wait(tasks, loop=hass.loop)
if entities:
yield from component.async_add_entities(entities)

View File

@@ -2,15 +2,15 @@
Offer event listening automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#event-trigger
at https://home-assistant.io/docs/automation/trigger/#event-trigger
"""
import asyncio
import logging
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.const import CONF_PLATFORM
from homeassistant.core import callback, CoreState
from homeassistant.const import CONF_PLATFORM, EVENT_HOMEASSISTANT_START
from homeassistant.helpers import config_validation as cv
CONF_EVENT_TYPE = "event_type"
@@ -31,6 +31,19 @@ def async_trigger(hass, config, action):
event_type = config.get(CONF_EVENT_TYPE)
event_data = config.get(CONF_EVENT_DATA)
if (event_type == EVENT_HOMEASSISTANT_START and
hass.state == CoreState.starting):
_LOGGER.warning('Deprecation: Automations should not listen to event '
"'homeassistant_start'. Use platform 'homeassistant' "
'instead. Feature will be removed in 0.45')
hass.async_run_job(action, {
'trigger': {
'platform': 'event',
'event': None,
},
})
return lambda: None
@callback
def handle_event(event):
"""Listen for events and calls the action when data matches."""

View File

@@ -0,0 +1,55 @@
"""
Offer Home Assistant core automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#homeassistant-trigger
"""
import asyncio
import logging
import voluptuous as vol
from homeassistant.core import callback, CoreState
from homeassistant.const import (
CONF_PLATFORM, CONF_EVENT, EVENT_HOMEASSISTANT_STOP)
EVENT_START = 'start'
EVENT_SHUTDOWN = 'shutdown'
_LOGGER = logging.getLogger(__name__)
TRIGGER_SCHEMA = vol.Schema({
vol.Required(CONF_PLATFORM): 'homeassistant',
vol.Required(CONF_EVENT): vol.Any(EVENT_START, EVENT_SHUTDOWN),
})
@asyncio.coroutine
def async_trigger(hass, config, action):
"""Listen for events based on configuration."""
event = config.get(CONF_EVENT)
if event == EVENT_SHUTDOWN:
@callback
def hass_shutdown(event):
"""Called when Home Assistant is shutting down."""
hass.async_run_job(action, {
'trigger': {
'platform': 'homeassistant',
'event': event,
},
})
return hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP,
hass_shutdown)
# Automation are enabled while hass is starting up, fire right away
# Check state because a config reload shouldn't trigger it.
elif hass.state == CoreState.starting:
hass.async_run_job(action, {
'trigger': {
'platform': 'homeassistant',
'event': event,
},
})
return lambda: None

View File

@@ -70,7 +70,7 @@ def async_trigger(hass, config, action):
nonlocal held_less_than, held_more_than
pressed_time = dt_util.utcnow()
if held_more_than is None and held_less_than is None:
call_action()
hass.add_job(call_action)
if held_more_than is not None and held_less_than is None:
cancel_pressed_more_than = track_point_in_utc_time(
hass,
@@ -88,7 +88,7 @@ def async_trigger(hass, config, action):
held_time = dt_util.utcnow() - pressed_time
if held_less_than is not None and held_time < held_less_than:
if held_more_than is None or held_time > held_more_than:
call_action()
hass.add_job(call_action)
hass.data['litejet_system'].on_switch_pressed(number, pressed)
hass.data['litejet_system'].on_switch_released(number, released)

View File

@@ -2,7 +2,7 @@
Offer MQTT listening automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#mqtt-trigger
at https://home-assistant.io/docs/automation/trigger/#mqtt-trigger
"""
import asyncio
import json

View File

@@ -2,7 +2,7 @@
Offer numeric state listening automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#numeric-state-trigger
at https://home-assistant.io/docs/automation/trigger/#numeric-state-trigger
"""
import asyncio
import logging

View File

@@ -2,7 +2,7 @@
Offer state listening automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#state-trigger
at https://home-assistant.io/docs/automation/trigger/#state-trigger
"""
import asyncio
import voluptuous as vol

View File

@@ -2,7 +2,7 @@
Offer sun based automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#sun-trigger
at https://home-assistant.io/docs/automation/trigger/#sun-trigger
"""
import asyncio
from datetime import timedelta

View File

@@ -2,7 +2,7 @@
Offer template automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#template-trigger
at https://home-assistant.io/docs/automation/trigger/#template-trigger
"""
import asyncio
import logging

View File

@@ -2,7 +2,7 @@
Offer time listening automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#time-trigger
at https://home-assistant.io/docs/automation/trigger/#time-trigger
"""
import asyncio
import logging

View File

@@ -2,7 +2,7 @@
Offer zone automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#zone-trigger
at https://home-assistant.io/docs/automation/trigger/#zone-trigger
"""
import asyncio
import voluptuous as vol

View File

@@ -0,0 +1,62 @@
"""
Support for IP Webcam binary sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.android_ip_webcam/
"""
import asyncio
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.android_ip_webcam import (
KEY_MAP, DATA_IP_WEBCAM, AndroidIPCamEntity, CONF_HOST, CONF_NAME)
DEPENDENCIES = ['android_ip_webcam']
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Setup IP Webcam binary sensors."""
if discovery_info is None:
return
host = discovery_info[CONF_HOST]
name = discovery_info[CONF_NAME]
ipcam = hass.data[DATA_IP_WEBCAM][host]
async_add_devices(
[IPWebcamBinarySensor(name, host, ipcam, 'motion_active')], True)
class IPWebcamBinarySensor(AndroidIPCamEntity, BinarySensorDevice):
"""Represents an IP Webcam binary sensor."""
def __init__(self, name, host, ipcam, sensor):
"""Initialize the binary sensor."""
super().__init__(host, ipcam)
self._sensor = sensor
self._mapped_name = KEY_MAP.get(self._sensor, self._sensor)
self._name = '{} {}'.format(name, self._mapped_name)
self._state = None
self._unit = None
@property
def name(self):
"""Return the name of the binary sensor, if any."""
return self._name
@property
def is_on(self):
"""True if the binary sensor is on."""
return self._state
@asyncio.coroutine
def async_update(self):
"""Retrieve latest state."""
state, _ = self._ipcam.export_sensor(self._sensor)
self._state = state == 1.0
@property
def device_class(self):
"""Return the class of this device, from component DEVICE_CLASSES."""
return 'motion'

View File

@@ -0,0 +1,74 @@
"""
Support for Blink system camera control.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.blink/
"""
from homeassistant.components.blink import DOMAIN
from homeassistant.components.binary_sensor import BinarySensorDevice
DEPENDENCIES = ['blink']
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the blink binary sensors."""
if discovery_info is None:
return
data = hass.data[DOMAIN].blink
devs = list()
for name in data.cameras:
devs.append(BlinkCameraMotionSensor(name, data))
devs.append(BlinkSystemSensor(data))
add_devices(devs, True)
class BlinkCameraMotionSensor(BinarySensorDevice):
"""A representation of a Blink binary sensor."""
def __init__(self, name, data):
"""Initialize the sensor."""
self._name = 'blink_' + name + '_motion_enabled'
self._camera_name = name
self.data = data
self._state = self.data.cameras[self._camera_name].armed
@property
def name(self):
"""Return the name of the blink sensor."""
return self._name
@property
def is_on(self):
"""Return the status of the sensor."""
return self._state
def update(self):
"""Update sensor state."""
self.data.refresh()
self._state = self.data.cameras[self._camera_name].armed
class BlinkSystemSensor(BinarySensorDevice):
"""A representation of a Blink system sensor."""
def __init__(self, data):
"""Initialize the sensor."""
self._name = 'blink armed status'
self.data = data
self._state = self.data.arm
@property
def name(self):
"""Return the name of the blink sensor."""
return self._name.replace(" ", "_")
@property
def is_on(self):
"""Return the status of the sensor."""
return self._state
def update(self):
"""Update sensor state."""
self.data.refresh()
self._state = self.data.arm

View File

@@ -36,6 +36,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
dev = []
for droplet in droplets:
droplet_id = digital_ocean.DIGITAL_OCEAN.get_droplet_id(droplet)
if droplet_id is None:
_LOGGER.error("Droplet %s is not available", droplet)
return False
dev.append(DigitalOceanBinarySensor(
digital_ocean.DIGITAL_OCEAN, droplet_id))

View File

@@ -67,7 +67,7 @@ class EnOceanBinarySensor(enocean.EnOceanDevice, BinarySensorDevice):
This method is called when there is an incoming packet associated
with this platform.
"""
self.update_ha_state()
self.schedule_update_ha_state()
if value2 == 0x70:
self.which = 0
self.onoff = 0

View File

@@ -37,7 +37,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
)
devices.append(device)
yield from async_add_devices(devices)
async_add_devices(devices)
class EnvisalinkBinarySensor(EnvisalinkDevice, BinarySensorDevice):
@@ -52,8 +52,11 @@ class EnvisalinkBinarySensor(EnvisalinkDevice, BinarySensorDevice):
_LOGGER.debug('Setting up zone: ' + zone_name)
super().__init__(zone_name, info, controller)
@asyncio.coroutine
def async_added_to_hass(self):
"""Register callbacks."""
async_dispatcher_connect(
hass, SIGNAL_ZONE_UPDATE, self._update_callback)
self.hass, SIGNAL_ZONE_UPDATE, self._update_callback)
@property
def device_state_attributes(self):

View File

@@ -57,16 +57,13 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
# generate sensor object
entity = FFmpegMotion(hass, manager, config)
# add to system
manager.async_register_device(entity)
yield from async_add_devices([entity])
async_add_devices([entity])
class FFmpegBinarySensor(FFmpegBase, BinarySensorDevice):
"""A binary sensor which use ffmpeg for noise detection."""
def __init__(self, hass, config):
def __init__(self, config):
"""Constructor for binary sensor noise detection."""
super().__init__(config.get(CONF_INITIAL_STATE))
@@ -98,15 +95,19 @@ class FFmpegMotion(FFmpegBinarySensor):
"""Initialize ffmpeg motion binary sensor."""
from haffmpeg import SensorMotion
super().__init__(hass, config)
super().__init__(config)
self.ffmpeg = SensorMotion(
manager.binary, hass.loop, self._async_callback)
def async_start_ffmpeg(self):
@asyncio.coroutine
def _async_start_ffmpeg(self, entity_ids):
"""Start a FFmpeg instance.
This method must be run in the event loop and returns a coroutine.
This method is a coroutine.
"""
if entity_ids is not None and self.entity_id not in entity_ids:
return
# init config
self.ffmpeg.set_options(
time_reset=self._config.get(CONF_RESET),
@@ -116,7 +117,7 @@ class FFmpegMotion(FFmpegBinarySensor):
)
# run
return self.ffmpeg.open_sensor(
yield from self.ffmpeg.open_sensor(
input_source=self._config.get(CONF_INPUT),
extra_cmd=self._config.get(CONF_EXTRA_ARGUMENTS),
)

View File

@@ -54,10 +54,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
# generate sensor object
entity = FFmpegNoise(hass, manager, config)
# add to system
manager.async_register_device(entity)
yield from async_add_devices([entity])
async_add_devices([entity])
class FFmpegNoise(FFmpegBinarySensor):
@@ -67,15 +64,19 @@ class FFmpegNoise(FFmpegBinarySensor):
"""Initialize ffmpeg noise binary sensor."""
from haffmpeg import SensorNoise
super().__init__(hass, config)
super().__init__(config)
self.ffmpeg = SensorNoise(
manager.binary, hass.loop, self._async_callback)
def async_start_ffmpeg(self):
@asyncio.coroutine
def _async_start_ffmpeg(self, entity_ids):
"""Start a FFmpeg instance.
This method must be run in the event loop and returns a coroutine.
This method is a coroutine.
"""
if entity_ids is not None and self.entity_id not in entity_ids:
return
# init config
self.ffmpeg.set_options(
time_duration=self._config.get(CONF_DURATION),
@@ -84,7 +85,7 @@ class FFmpegNoise(FFmpegBinarySensor):
)
# run
return self.ffmpeg.open_sensor(
yield from self.ffmpeg.open_sensor(
input_source=self._config.get(CONF_INPUT),
output_dest=self._config.get(CONF_OUTPUT),
extra_cmd=self._config.get(CONF_EXTRA_ARGUMENTS),

View File

@@ -15,9 +15,10 @@ from homeassistant.components.binary_sensor import (
import homeassistant.helpers.config_validation as cv
from homeassistant.const import (
CONF_HOST, CONF_PORT, CONF_NAME, CONF_USERNAME, CONF_PASSWORD,
CONF_SSL, EVENT_HOMEASSISTANT_STOP, ATTR_LAST_TRIP_TIME, CONF_CUSTOMIZE)
CONF_SSL, EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_START,
ATTR_LAST_TRIP_TIME, CONF_CUSTOMIZE)
REQUIREMENTS = ['pyhik==0.0.7', 'pydispatcher==2.0.5']
REQUIREMENTS = ['pyhik==0.1.2']
_LOGGER = logging.getLogger(__name__)
CONF_IGNORED = 'ignored'
@@ -32,7 +33,6 @@ ATTR_DELAY = 'delay'
DEVICE_CLASS_MAP = {
'Motion': 'motion',
'Line Crossing': 'motion',
'IO Trigger': None,
'Field Detection': 'motion',
'Video Loss': None,
'Tamper Detection': 'motion',
@@ -46,6 +46,7 @@ DEVICE_CLASS_MAP = {
'Bad Video': None,
'PIR Alarm': 'motion',
'Face Detection': 'motion',
'Scene Change Detection': 'motion',
}
CUSTOMIZE_SCHEMA = vol.Schema({
@@ -90,24 +91,30 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
entities = []
for sensor in data.sensors:
# Build sensor name, then parse customize config.
sensor_name = sensor.replace(' ', '_')
for sensor, channel_list in data.sensors.items():
for channel in channel_list:
# Build sensor name, then parse customize config.
if data.type == 'NVR':
sensor_name = '{}_{}'.format(
sensor.replace(' ', '_'), channel[1])
else:
sensor_name = sensor.replace(' ', '_')
custom = customize.get(sensor_name.lower(), {})
ignore = custom.get(CONF_IGNORED)
delay = custom.get(CONF_DELAY)
custom = customize.get(sensor_name.lower(), {})
ignore = custom.get(CONF_IGNORED)
delay = custom.get(CONF_DELAY)
_LOGGER.debug('Entity: %s - %s, Options - Ignore: %s, Delay: %s',
data.name, sensor_name, ignore, delay)
if not ignore:
entities.append(HikvisionBinarySensor(hass, sensor, data, delay))
_LOGGER.debug('Entity: %s - %s, Options - Ignore: %s, Delay: %s',
data.name, sensor_name, ignore, delay)
if not ignore:
entities.append(HikvisionBinarySensor(
hass, sensor, channel[1], data, delay))
add_entities(entities)
class HikvisionData(object):
"""Hikvision camera event stream object."""
"""Hikvision device event stream object."""
def __init__(self, hass, url, port, name, username, password):
"""Initialize the data oject."""
@@ -119,49 +126,64 @@ class HikvisionData(object):
self._password = password
# Establish camera
self._cam = HikCamera(self._url, self._port,
self._username, self._password)
self.camdata = HikCamera(self._url, self._port,
self._username, self._password)
if self._name is None:
self._name = self._cam.get_name
# Start event stream
self._cam.start_stream()
self._name = self.camdata.get_name
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, self.stop_hik)
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, self.start_hik)
def stop_hik(self, event):
"""Shutdown Hikvision subscriptions and subscription thread on exit."""
self._cam.disconnect()
self.camdata.disconnect()
def start_hik(self, event):
"""Start Hikvision event stream thread."""
self.camdata.start_stream()
@property
def sensors(self):
"""Return list of available sensors and their states."""
return self._cam.current_event_states
return self.camdata.current_event_states
@property
def cam_id(self):
"""Return camera id."""
return self._cam.get_id
"""Return device id."""
return self.camdata.get_id
@property
def name(self):
"""Return camera name."""
"""Return device name."""
return self._name
@property
def type(self):
"""Return device type."""
return self.camdata.get_type
def get_attributes(self, sensor, channel):
"""Return attribute list for sensor/channel."""
return self.camdata.fetch_attributes(sensor, channel)
class HikvisionBinarySensor(BinarySensorDevice):
"""Representation of a Hikvision binary sensor."""
def __init__(self, hass, sensor, cam, delay):
def __init__(self, hass, sensor, channel, cam, delay):
"""Initialize the binary_sensor."""
from pydispatch import dispatcher
self._hass = hass
self._cam = cam
self._name = self._cam.name + ' ' + sensor
self._id = self._cam.cam_id + '.' + sensor
self._sensor = sensor
self._channel = channel
if self._cam.type == 'NVR':
self._name = '{} {} {}'.format(self._cam.name, sensor, channel)
else:
self._name = '{} {}'.format(self._cam.name, sensor)
self._id = '{}.{}.{}'.format(self._cam.cam_id, sensor, channel)
if delay is None:
self._delay = 0
@@ -170,20 +192,16 @@ class HikvisionBinarySensor(BinarySensorDevice):
self._timer = None
# Form signal for dispatcher
signal = 'ValueChanged.{}'.format(self._cam.cam_id)
dispatcher.connect(self._update_callback,
signal=signal,
sender=self._sensor)
# Register callback function with pyHik
self._cam.camdata.add_update_callback(self._update_callback, self._id)
def _sensor_state(self):
"""Extract sensor state."""
return self._cam.sensors[self._sensor][0]
return self._cam.get_attributes(self._sensor, self._channel)[0]
def _sensor_last_update(self):
"""Extract sensor last update time."""
return self._cam.sensors[self._sensor][3]
return self._cam.get_attributes(self._sensor, self._channel)[3]
@property
def name(self):
@@ -225,13 +243,9 @@ class HikvisionBinarySensor(BinarySensorDevice):
return attr
def _update_callback(self, signal, sender):
def _update_callback(self, msg):
"""Update the sensor's state, if needed."""
_LOGGER.debug('Dispatcher callback, signal: %s, sender: %s',
signal, sender)
if sender is not self._sensor:
return
_LOGGER.debug('Callback signal from: %s', msg)
if self._delay > 0 and not self.is_on:
# Set timer to wait until updating the state

View File

@@ -32,7 +32,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
InsteonPLMBinarySensorDevice(hass, plm, address, name)
)
hass.async_add_job(async_add_devices(device_list))
async_add_devices(device_list)
class InsteonPLMBinarySensorDevice(BinarySensorDevice):

View File

@@ -0,0 +1,76 @@
"""
Support for MAX! Window Shutter via MAX! Cube.
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/maxcube/
"""
import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.maxcube import MAXCUBE_HANDLE
from homeassistant.const import STATE_UNKNOWN
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Iterate through all MAX! Devices and add window shutters to HASS."""
cube = hass.data[MAXCUBE_HANDLE].cube
# List of devices
devices = []
for device in cube.devices:
# Create device name by concatenating room name + device name
name = "%s %s" % (cube.room_by_id(device.room_id).name, device.name)
# Only add Window Shutters
if cube.is_windowshutter(device):
# add device to HASS
devices.append(MaxCubeShutter(hass, name, device.rf_address))
if len(devices) > 0:
add_devices(devices)
class MaxCubeShutter(BinarySensorDevice):
"""MAX! Cube BinarySensor device."""
def __init__(self, hass, name, rf_address):
"""Initialize MAX! Cube BinarySensorDevice."""
self._name = name
self._sensor_type = 'opening'
self._rf_address = rf_address
self._cubehandle = hass.data[MAXCUBE_HANDLE]
self._state = STATE_UNKNOWN
@property
def should_poll(self):
"""Polling is required."""
return True
@property
def name(self):
"""Return the name of the BinarySensorDevice."""
return self._name
@property
def device_class(self):
"""Return the class of this sensor."""
return self._sensor_type
@property
def is_on(self):
"""Return true if the binary sensor is on/open."""
return self._state
def update(self):
"""Get latest data from MAX! Cube."""
self._cubehandle.update()
# Get the device we want to update
device = self._cubehandle.cube.device_by_rf(self._rf_address)
# Update our internal state
self._state = device.is_open

View File

@@ -46,7 +46,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
if value_template is not None:
value_template.hass = hass
yield from async_add_devices([MqttBinarySensor(
async_add_devices([MqttBinarySensor(
config.get(CONF_NAME),
config.get(CONF_STATE_TOPIC),
get_deprecated(config, CONF_DEVICE_CLASS, CONF_SENSOR_CLASS),

View File

@@ -0,0 +1,109 @@
"""
This component provides HA sensor support for Ring Door Bell/Chimes.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.ring/
"""
import logging
from datetime import timedelta
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.ring import (
CONF_ATTRIBUTION, DEFAULT_ENTITY_NAMESPACE)
from homeassistant.const import (
ATTR_ATTRIBUTION, CONF_ENTITY_NAMESPACE, CONF_MONITORED_CONDITIONS)
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA)
DEPENDENCIES = ['ring']
_LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(seconds=5)
# Sensor types: Name, category, device_class
SENSOR_TYPES = {
'ding': ['Ding', ['doorbell'], 'occupancy'],
'motion': ['Motion', ['doorbell'], 'motion'],
}
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_ENTITY_NAMESPACE, default=DEFAULT_ENTITY_NAMESPACE):
cv.string,
vol.Required(CONF_MONITORED_CONDITIONS, default=[]):
vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]),
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up a sensor for a Ring device."""
ring = hass.data.get('ring')
sensors = []
for sensor_type in config.get(CONF_MONITORED_CONDITIONS):
for device in ring.doorbells:
if 'doorbell' in SENSOR_TYPES[sensor_type][1]:
sensors.append(RingBinarySensor(hass,
device,
sensor_type))
add_devices(sensors, True)
return True
class RingBinarySensor(BinarySensorDevice):
"""A binary sensor implementation for Ring device."""
def __init__(self, hass, data, sensor_type):
"""Initialize a sensor for Ring device."""
super(RingBinarySensor, self).__init__()
self._sensor_type = sensor_type
self._data = data
self._name = "{0} {1}".format(self._data.name,
SENSOR_TYPES.get(self._sensor_type)[0])
self._device_class = SENSOR_TYPES.get(self._sensor_type)[2]
self._state = None
@property
def name(self):
"""Return the name of the sensor."""
return self._name
@property
def is_on(self):
"""Return True if the binary sensor is on."""
return self._state
@property
def device_class(self):
"""Return the class of the binary sensor."""
return self._device_class
@property
def device_state_attributes(self):
"""Return the state attributes."""
attrs = {}
attrs[ATTR_ATTRIBUTION] = CONF_ATTRIBUTION
attrs['device_id'] = self._data.id
attrs['firmware'] = self._data.firmware
attrs['timezone'] = self._data.timezone
if self._data.alert and self._data.alert_expires_at:
attrs['expires_at'] = self._data.alert_expires_at
attrs['state'] = self._data.alert.get('state')
return attrs
def update(self):
"""Get the latest data and updates the state."""
self._data.check_alerts()
if self._data.alert:
self._state = (self._sensor_type ==
self._data.alert.get('kind'))
else:
self._state = False

View File

@@ -15,12 +15,14 @@ from homeassistant.components.binary_sensor import (
DEVICE_CLASSES_SCHEMA)
from homeassistant.const import (
ATTR_FRIENDLY_NAME, ATTR_ENTITY_ID, CONF_VALUE_TEMPLATE,
CONF_SENSOR_CLASS, CONF_SENSORS, CONF_DEVICE_CLASS)
CONF_SENSOR_CLASS, CONF_SENSORS, CONF_DEVICE_CLASS,
EVENT_HOMEASSISTANT_START, STATE_ON)
from homeassistant.exceptions import TemplateError
from homeassistant.helpers.entity import async_generate_entity_id
from homeassistant.helpers.event import async_track_state_change
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.deprecation import get_deprecated
from homeassistant.helpers.entity import async_generate_entity_id
from homeassistant.helpers.event import async_track_state_change
from homeassistant.helpers.restore_state import async_get_last_state
_LOGGER = logging.getLogger(__name__)
@@ -66,7 +68,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
_LOGGER.error('No sensors added')
return False
yield from async_add_devices(sensors, True)
async_add_devices(sensors, True)
return True
@@ -83,14 +85,30 @@ class BinarySensorTemplate(BinarySensorDevice):
self._device_class = device_class
self._template = value_template
self._state = None
self._entities = entity_ids
@asyncio.coroutine
def async_added_to_hass(self):
"""Register callbacks."""
state = yield from async_get_last_state(self.hass, self.entity_id)
if state:
self._state = state.state == STATE_ON
@callback
def template_bsensor_state_listener(entity, old_state, new_state):
"""Called when the target device changes state."""
hass.async_add_job(self.async_update_ha_state, True)
self.hass.async_add_job(self.async_update_ha_state(True))
async_track_state_change(
hass, entity_ids, template_bsensor_state_listener)
@callback
def template_bsensor_startup(event):
"""Update template on startup."""
async_track_state_change(
self.hass, self._entities, template_bsensor_state_listener)
self.hass.async_add_job(self.async_update_ha_state(True))
self.hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_START, template_bsensor_startup)
@property
def name(self):

View File

@@ -52,7 +52,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
limit_type = config.get(CONF_TYPE)
device_class = get_deprecated(config, CONF_DEVICE_CLASS, CONF_SENSOR_CLASS)
yield from async_add_devices(
async_add_devices(
[ThresholdSensor(hass, entity_id, name, threshold, limit_type,
device_class)], True)
return True

View File

@@ -7,9 +7,9 @@ https://home-assistant.io/components/binary_sensor.vera/
import logging
from homeassistant.components.binary_sensor import (
BinarySensorDevice)
BinarySensorDevice, ENTITY_ID_FORMAT)
from homeassistant.components.vera import (
VeraDevice, VERA_DEVICES, VERA_CONTROLLER)
VERA_CONTROLLER, VERA_DEVICES, VeraDevice)
DEPENDENCIES = ['vera']
@@ -30,6 +30,7 @@ class VeraBinarySensor(VeraDevice, BinarySensorDevice):
"""Initialize the binary_sensor."""
self._state = False
VeraDevice.__init__(self, vera_device, controller)
self.entity_id = ENTITY_ID_FORMAT.format(self.vera_id)
@property
def is_on(self):

View File

@@ -0,0 +1,147 @@
"""
Sensor to indicate whether the current day is a workday.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.workday/
"""
import asyncio
import logging
import datetime
import voluptuous as vol
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (
STATE_ON, STATE_OFF, STATE_UNKNOWN, CONF_NAME, WEEKDAYS)
import homeassistant.util.dt as dt_util
from homeassistant.helpers.entity import Entity
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['holidays==0.8.1']
# List of all countries currently supported by holidays
# There seems to be no way to get the list out at runtime
ALL_COUNTRIES = ['Australia', 'AU', 'Austria', 'AT', 'Canada', 'CA',
'Colombia', 'CO', 'Czech', 'CZ', 'Denmark', 'DK', 'England',
'EuropeanCentralBank', 'ECB', 'TAR', 'Germany', 'DE',
'Ireland', 'Isle of Man', 'Mexico', 'MX', 'Netherlands', 'NL',
'NewZealand', 'NZ', 'Northern Ireland', 'Norway', 'NO',
'Portugal', 'PT', 'PortugalExt', 'PTE', 'Scotland', 'Spain',
'ES', 'UnitedKingdom', 'UK', 'UnitedStates', 'US', 'Wales']
CONF_COUNTRY = 'country'
CONF_PROVINCE = 'province'
CONF_WORKDAYS = 'workdays'
# By default, Monday - Friday are workdays
DEFAULT_WORKDAYS = ['mon', 'tue', 'wed', 'thu', 'fri']
CONF_EXCLUDES = 'excludes'
# By default, public holidays, Saturdays and Sundays are excluded from workdays
DEFAULT_EXCLUDES = ['sat', 'sun', 'holiday']
DEFAULT_NAME = 'Workday Sensor'
ALLOWED_DAYS = WEEKDAYS + ['holiday']
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_COUNTRY): vol.In(ALL_COUNTRIES),
vol.Optional(CONF_PROVINCE, default=None): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_WORKDAYS, default=DEFAULT_WORKDAYS):
vol.All(cv.ensure_list, [vol.In(ALLOWED_DAYS)]),
vol.Optional(CONF_EXCLUDES, default=DEFAULT_EXCLUDES):
vol.All(cv.ensure_list, [vol.In(ALLOWED_DAYS)]),
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Workday sensor."""
import holidays
sensor_name = config.get(CONF_NAME)
country = config.get(CONF_COUNTRY)
province = config.get(CONF_PROVINCE)
workdays = config.get(CONF_WORKDAYS)
excludes = config.get(CONF_EXCLUDES)
year = datetime.datetime.now().year
obj_holidays = getattr(holidays, country)(years=year)
if province:
if province not in obj_holidays.PROVINCES:
_LOGGER.error('There is no province/state %s in country %s',
province, country)
return False
else:
year = datetime.datetime.now().year
obj_holidays = getattr(holidays, country)(prov=province,
years=year)
_LOGGER.debug("Found the following holidays for your configuration:")
for date, name in sorted(obj_holidays.items()):
_LOGGER.debug("%s %s", date, name)
add_devices([IsWorkdaySensor(
obj_holidays, workdays, excludes, sensor_name)], True)
def day_to_string(day):
"""Convert day index 0 - 7 to string."""
try:
return ALLOWED_DAYS[day]
except IndexError:
return None
class IsWorkdaySensor(Entity):
"""Implementation of a Workday sensor."""
def __init__(self, obj_holidays, workdays, excludes, name):
"""Initialize the Workday sensor."""
self._name = name
self._obj_holidays = obj_holidays
self._workdays = workdays
self._excludes = excludes
self._state = STATE_UNKNOWN
@property
def name(self):
"""Return the name of the sensor."""
return self._name
@property
def state(self):
"""Return the state of the device."""
return self._state
def is_include(self, day, now):
"""Check if given day is in the includes list."""
if day in self._workdays:
return True
elif 'holiday' in self._workdays and now in self._obj_holidays:
return True
return False
def is_exclude(self, day, now):
"""Check if given day is in the excludes list."""
if day in self._excludes:
return True
elif 'holiday' in self._excludes and now in self._obj_holidays:
return True
return False
@asyncio.coroutine
def async_update(self):
"""Get date and look whether it is a holiday."""
# Default is no workday
self._state = STATE_OFF
# Get iso day of the week (1 = Monday, 7 = Sunday)
day = datetime.datetime.today().isoweekday() - 1
day_of_week = day_to_string(day)
if self.is_include(day_of_week, dt_util.now()):
self._state = STATE_ON
if self.is_exclude(day_of_week, dt_util.now()):
self._state = STATE_OFF

View File

@@ -24,7 +24,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the ZigBee binary sensor platform."""
add_devices([ZigBeeBinarySensor(hass, ZigBeeDigitalInConfig(config))])
add_devices(
[ZigBeeBinarySensor(hass, ZigBeeDigitalInConfig(config))], True)
class ZigBeeBinarySensor(ZigBeeDigitalIn, BinarySensorDevice):

View File

@@ -10,6 +10,7 @@ import homeassistant.util.dt as dt_util
from homeassistant.helpers.event import track_point_in_time
from homeassistant.components import zwave
from homeassistant.components.zwave import workaround
from homeassistant.components.zwave import async_setup_platform # noqa # pylint: disable=unused-import
from homeassistant.components.binary_sensor import (
DOMAIN,
BinarySensorDevice)
@@ -18,45 +19,34 @@ _LOGGER = logging.getLogger(__name__)
DEPENDENCIES = []
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Z-Wave platform for binary sensors."""
if discovery_info is None or zwave.NETWORK is None:
return
node = zwave.NETWORK.nodes[discovery_info[zwave.const.ATTR_NODE_ID]]
value = node.values[discovery_info[zwave.const.ATTR_VALUE_ID]]
value.set_change_verified(False)
device_mapping = workaround.get_device_mapping(value)
def get_device(values, **kwargs):
"""Create zwave entity device."""
device_mapping = workaround.get_device_mapping(values.primary)
if device_mapping == workaround.WORKAROUND_NO_OFF_EVENT:
# Default the multiplier to 4
re_arm_multiplier = (zwave.get_config_value(value.node, 9) or 4)
add_devices([
ZWaveTriggerSensor(value, "motion",
hass, re_arm_multiplier * 8)
])
return
re_arm_multiplier = zwave.get_config_value(values.primary.node, 9) or 4
return ZWaveTriggerSensor(values, "motion", re_arm_multiplier * 8)
if workaround.get_device_component_mapping(value) == DOMAIN:
add_devices([ZWaveBinarySensor(value, None)])
return
if workaround.get_device_component_mapping(values.primary) == DOMAIN:
return ZWaveBinarySensor(values, None)
if value.command_class == zwave.const.COMMAND_CLASS_SENSOR_BINARY:
add_devices([ZWaveBinarySensor(value, None)])
if values.primary.command_class == zwave.const.COMMAND_CLASS_SENSOR_BINARY:
return ZWaveBinarySensor(values, None)
return None
class ZWaveBinarySensor(BinarySensorDevice, zwave.ZWaveDeviceEntity):
"""Representation of a binary sensor within Z-Wave."""
def __init__(self, value, device_class):
def __init__(self, values, device_class):
"""Initialize the sensor."""
zwave.ZWaveDeviceEntity.__init__(self, value, DOMAIN)
zwave.ZWaveDeviceEntity.__init__(self, values, DOMAIN)
self._sensor_type = device_class
self._state = self._value.data
self._state = self.values.primary.data
def update_properties(self):
"""Callback on data changes for node values."""
self._state = self._value.data
self._state = self.values.primary.data
@property
def is_on(self):
@@ -68,35 +58,27 @@ class ZWaveBinarySensor(BinarySensorDevice, zwave.ZWaveDeviceEntity):
"""Return the class of this sensor, from DEVICE_CLASSES."""
return self._sensor_type
@property
def should_poll(self):
"""No polling needed."""
return False
class ZWaveTriggerSensor(ZWaveBinarySensor):
"""Representation of a stateless sensor within Z-Wave."""
def __init__(self, value, device_class, hass, re_arm_sec=60):
def __init__(self, values, device_class, re_arm_sec=60):
"""Initialize the sensor."""
super(ZWaveTriggerSensor, self).__init__(value, device_class)
self._hass = hass
super(ZWaveTriggerSensor, self).__init__(values, device_class)
self.re_arm_sec = re_arm_sec
self.invalidate_after = dt_util.utcnow() + datetime.timedelta(
seconds=self.re_arm_sec)
# If it's active make sure that we set the timeout tracker
track_point_in_time(
self._hass, self.async_update_ha_state,
self.invalidate_after)
self.invalidate_after = None
def update_properties(self):
"""Called when a value for this entity's node has changed."""
self._state = self._value.data
self._state = self.values.primary.data
# only allow this value to be true for re_arm secs
if not self.hass:
return
self.invalidate_after = dt_util.utcnow() + datetime.timedelta(
seconds=self.re_arm_sec)
track_point_in_time(
self._hass, self.async_update_ha_state,
self.hass, self.async_update_ha_state,
self.invalidate_after)
@property

View File

@@ -0,0 +1,89 @@
"""
Support for Blink Home Camera System.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/blink/
"""
import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.const import (
CONF_USERNAME, CONF_PASSWORD, ATTR_FRIENDLY_NAME, ATTR_ARMED)
from homeassistant.helpers import discovery
REQUIREMENTS = ['blinkpy==0.5.2']
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'blink'
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string
})
}, extra=vol.ALLOW_EXTRA)
ARM_SYSTEM_SCHEMA = vol.Schema({
vol.Optional(ATTR_ARMED): cv.boolean
})
ARM_CAMERA_SCHEMA = vol.Schema({
vol.Required(ATTR_FRIENDLY_NAME): cv.string,
vol.Optional(ATTR_ARMED): cv.boolean
})
SNAP_PICTURE_SCHEMA = vol.Schema({
vol.Required(ATTR_FRIENDLY_NAME): cv.string
})
class BlinkSystem(object):
"""Blink System class."""
def __init__(self, config_info):
"""Initialize the system."""
import blinkpy
self.blink = blinkpy.Blink(username=config_info[DOMAIN][CONF_USERNAME],
password=config_info[DOMAIN][CONF_PASSWORD])
self.blink.setup_system()
def setup(hass, config):
"""Set up Blink System."""
hass.data[DOMAIN] = BlinkSystem(config)
discovery.load_platform(hass, 'camera', DOMAIN, {}, config)
discovery.load_platform(hass, 'sensor', DOMAIN, {}, config)
discovery.load_platform(hass, 'binary_sensor', DOMAIN, {}, config)
def snap_picture(call):
"""Take a picture."""
cameras = hass.data[DOMAIN].blink.cameras
name = call.data.get(ATTR_FRIENDLY_NAME, '')
if name in cameras:
cameras[name].snap_picture()
def arm_camera(call):
"""Arm a camera."""
cameras = hass.data[DOMAIN].blink.cameras
name = call.data.get(ATTR_FRIENDLY_NAME, '')
value = call.data.get(ATTR_ARMED, True)
if name in cameras:
cameras[name].set_motion_detect(value)
def arm_system(call):
"""Arm the system."""
value = call.data.get(ATTR_ARMED, True)
hass.data[DOMAIN].blink.arm = value
hass.data[DOMAIN].blink.refresh()
hass.services.register(
DOMAIN, 'snap_picture', snap_picture, schema=SNAP_PICTURE_SCHEMA)
hass.services.register(
DOMAIN, 'arm_camera', arm_camera, schema=ARM_CAMERA_SCHEMA)
hass.services.register(
DOMAIN, 'arm_system', arm_system, schema=ARM_SYSTEM_SCHEMA)
return True

View File

@@ -3,8 +3,8 @@ Support for Google Calendar event device sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/calendar/
"""
import asyncio
import logging
from datetime import timedelta
@@ -27,13 +27,13 @@ DOMAIN = 'calendar'
ENTITY_ID_FORMAT = DOMAIN + '.{}'
def setup(hass, config):
@asyncio.coroutine
def async_setup(hass, config):
"""Track states and offer events for calendars."""
component = EntityComponent(
logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL, DOMAIN)
component.setup(config)
yield from component.async_setup(config)
return True
@@ -155,7 +155,7 @@ class CalendarEventDevice(Entity):
start = _get_date(self.data.event['start'])
end = _get_date(self.data.event['end'])
summary = self.data.event['summary']
summary = self.data.event.get('summary', '')
# check if we have an offset tag in the message
# time is HH:MM or MM

View File

@@ -7,6 +7,7 @@ https://home-assistant.io/components/camera/
"""
import asyncio
import collections
from contextlib import suppress
from datetime import timedelta
import logging
import hashlib
@@ -58,7 +59,6 @@ def async_get_image(hass, entity_id, timeout=10):
state.attributes.get(ATTR_ENTITY_PICTURE)
)
response = None
try:
with async_timeout.timeout(timeout, loop=hass.loop):
response = yield from websession.get(url)
@@ -70,13 +70,9 @@ def async_get_image(hass, entity_id, timeout=10):
image = yield from response.read()
return image
except (asyncio.TimeoutError, aiohttp.errors.ClientError):
except (asyncio.TimeoutError, aiohttp.ClientError):
raise HomeAssistantError("Can't connect to {0}".format(url))
finally:
if response is not None:
yield from response.release()
@asyncio.coroutine
def async_setup(hass, config):
@@ -172,7 +168,7 @@ class Camera(Entity):
if not img_bytes:
break
if img_bytes is not None and img_bytes != last_image:
if img_bytes and img_bytes != last_image:
write(img_bytes)
# Chrome seems to always ignore first picture,
@@ -185,8 +181,8 @@ class Camera(Entity):
yield from asyncio.sleep(.5)
except (asyncio.CancelledError, ConnectionResetError):
_LOGGER.debug("Close stream by frontend.")
except asyncio.CancelledError:
_LOGGER.debug("Stream closed by frontend.")
response = None
finally:
@@ -268,16 +264,14 @@ class CameraImageView(CameraView):
@asyncio.coroutine
def handle(self, request, camera):
"""Serve camera image."""
try:
image = yield from camera.async_camera_image()
with suppress(asyncio.CancelledError, asyncio.TimeoutError):
with async_timeout.timeout(10, loop=request.app['hass'].loop):
image = yield from camera.async_camera_image()
if image is None:
return web.Response(status=500)
if image:
return web.Response(body=image)
return web.Response(body=image)
except asyncio.CancelledError:
_LOGGER.debug("Close stream by frontend.")
return web.Response(status=500)
class CameraMjpegStream(CameraView):

View File

@@ -16,9 +16,9 @@ from homeassistant.const import (
CONF_HOST, CONF_NAME, CONF_USERNAME, CONF_PASSWORD, CONF_PORT)
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import (
async_get_clientsession, async_aiohttp_proxy_stream)
async_get_clientsession, async_aiohttp_proxy_web)
REQUIREMENTS = ['amcrest==1.1.4']
REQUIREMENTS = ['amcrest==1.1.8']
_LOGGER = logging.getLogger(__name__)
@@ -125,7 +125,7 @@ class AmcrestCam(Camera):
stream_coro = websession.get(
streaming_url, auth=self._token, timeout=TIMEOUT)
yield from async_aiohttp_proxy_stream(self.hass, request, stream_coro)
yield from async_aiohttp_proxy_web(self.hass, request, stream_coro)
@property
def name(self):

View File

@@ -0,0 +1,81 @@
"""
Support for Blink system camera.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.blink/
"""
import logging
from datetime import timedelta
import requests
from homeassistant.components.blink import DOMAIN
from homeassistant.components.camera import Camera
from homeassistant.util import Throttle
DEPENDENCIES = ['blink']
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=90)
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup a Blink Camera."""
if discovery_info is None:
return
data = hass.data[DOMAIN].blink
devs = list()
for name in data.cameras:
devs.append(BlinkCamera(hass, config, data, name))
add_devices(devs)
class BlinkCamera(Camera):
"""An implementation of a Blink Camera."""
def __init__(self, hass, config, data, name):
"""Initialize a camera."""
super().__init__()
self.data = data
self.hass = hass
self._name = name
self.notifications = self.data.cameras[self._name].notifications
self.response = None
_LOGGER.info("Initialized blink camera %s", self._name)
@property
def name(self):
"""A camera name."""
return self._name
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def request_image(self):
"""An image request from Blink servers."""
_LOGGER.info("Requesting new image from blink servers")
image_url = self.check_for_motion()
header = self.data.cameras[self._name].header
self.response = requests.get(image_url, headers=header, stream=True)
def check_for_motion(self):
"""A method to check if motion has been detected since last update."""
self.data.refresh()
notifs = self.data.cameras[self._name].notifications
if notifs > self.notifications:
# We detected motion at some point
self.data.last_motion()
self.notifications = notifs
# returning motion image currently not working
# return self.data.cameras[self._name].motion['image']
elif notifs < self.notifications:
self.notifications = notifs
return self.data.camera_thumbs[self._name]
def camera_image(self):
"""Return a still image reponse from the camera."""
self.request_image()
return self.response.content

View File

@@ -8,14 +8,14 @@ import asyncio
import logging
import voluptuous as vol
from aiohttp import web
from homeassistant.const import CONF_NAME
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
from homeassistant.components.ffmpeg import (
DATA_FFMPEG, CONF_INPUT, CONF_EXTRA_ARGUMENTS)
import homeassistant.helpers.config_validation as cv
from homeassistant.const import CONF_NAME
from homeassistant.helpers.aiohttp_client import (
async_aiohttp_proxy_stream)
DEPENDENCIES = ['ffmpeg']
_LOGGER = logging.getLogger(__name__)
@@ -34,7 +34,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Setup a FFmpeg Camera."""
if not hass.data[DATA_FFMPEG].async_run_test(config.get(CONF_INPUT)):
return
yield from async_add_devices([FFmpegCamera(hass, config)])
async_add_devices([FFmpegCamera(hass, config)])
class FFmpegCamera(Camera):
@@ -69,26 +69,10 @@ class FFmpegCamera(Camera):
yield from stream.open_camera(
self._input, extra_cmd=self._extra_arguments)
response = web.StreamResponse()
response.content_type = 'multipart/x-mixed-replace;boundary=ffserver'
yield from response.prepare(request)
try:
while True:
data = yield from stream.read(102400)
if not data:
break
response.write(data)
except asyncio.CancelledError:
_LOGGER.debug("Close stream by frontend.")
response = None
finally:
yield from stream.close()
if response is not None:
yield from response.write_eof()
yield from async_aiohttp_proxy_stream(
self.hass, request, stream,
'multipart/x-mixed-replace;boundary=ffserver')
yield from stream.close()
@property
def name(self):

View File

@@ -47,22 +47,32 @@ class FoscamCamera(Camera):
port = device_info.get(CONF_PORT)
self._base_url = 'http://{}:{}/'.format(ip_address, port)
uri_template = self._base_url \
+ 'cgi-bin/CGIProxy.fcgi?' \
+ 'cmd=snapPicture2&usr={}&pwd={}'
self._username = device_info.get(CONF_USERNAME)
self._password = device_info.get(CONF_PASSWORD)
self._snap_picture_url = self._base_url \
+ 'cgi-bin/CGIProxy.fcgi?cmd=snapPicture2&usr=' \
+ self._username + '&pwd=' + self._password
self._snap_picture_url = uri_template.format(
self._username,
self._password
)
self._name = device_info.get(CONF_NAME)
_LOGGER.info('Using the following URL for %s: %s',
self._name, self._snap_picture_url)
self._name, uri_template.format('***', '***'))
def camera_image(self):
"""Return a still image reponse from the camera."""
# Send the request to snap a picture and return raw jpg data
response = requests.get(self._snap_picture_url, timeout=10)
return response.content
# Handle exception if host is not reachable or url failed
try:
response = requests.get(self._snap_picture_url, timeout=10)
except requests.exceptions.ConnectionError:
return None
else:
return response.content
@property
def name(self):

View File

@@ -44,7 +44,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
# pylint: disable=unused-argument
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Setup a generic IP Camera."""
yield from async_add_devices([GenericCamera(hass, config)])
async_add_devices([GenericCamera(hass, config)])
class GenericCamera(Camera):
@@ -107,7 +107,6 @@ class GenericCamera(Camera):
None, fetch)
# async
else:
response = None
try:
websession = async_get_clientsession(self.hass)
with async_timeout.timeout(10, loop=self.hass.loop):
@@ -117,14 +116,9 @@ class GenericCamera(Camera):
except asyncio.TimeoutError:
_LOGGER.error('Timeout getting camera image')
return self._last_image
except (aiohttp.errors.ClientError,
aiohttp.errors.DisconnectedError,
aiohttp.errors.HttpProcessingError) as err:
except aiohttp.ClientError as err:
_LOGGER.error('Error getting new camera image: %s', err)
return self._last_image
finally:
if response is not None:
yield from response.release()
self._last_url = url
return self._last_image

View File

@@ -20,7 +20,7 @@ CONF_FILE_PATH = 'file_path'
DEFAULT_NAME = 'Local File'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_FILE_PATH): cv.isfile,
vol.Required(CONF_FILE_PATH): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string
})
@@ -31,8 +31,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
# check filepath given is readable
if not os.access(file_path, os.R_OK):
_LOGGER.error("file path is not readable")
return False
_LOGGER.warning("Could not read camera %s image from file: %s",
config[CONF_NAME], file_path)
add_devices([LocalFile(config[CONF_NAME], file_path)])
@@ -49,8 +49,12 @@ class LocalFile(Camera):
def camera_image(self):
"""Return image response."""
with open(self._file_path, 'rb') as file:
return file.read()
try:
with open(self._file_path, 'rb') as file:
return file.read()
except FileNotFoundError:
_LOGGER.warning("Could not read camera %s image from file: %s",
self._name, self._file_path)
@property
def name(self):

View File

@@ -19,7 +19,7 @@ from homeassistant.const import (
HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION)
from homeassistant.components.camera import (PLATFORM_SCHEMA, Camera)
from homeassistant.helpers.aiohttp_client import (
async_get_clientsession, async_aiohttp_proxy_stream)
async_get_clientsession, async_aiohttp_proxy_web)
from homeassistant.helpers import config_validation as cv
_LOGGER = logging.getLogger(__name__)
@@ -45,7 +45,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
# pylint: disable=unused-argument
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Setup a MJPEG IP Camera."""
yield from async_add_devices([MjpegCamera(hass, config)])
if discovery_info:
config = PLATFORM_SCHEMA(discovery_info)
async_add_devices([MjpegCamera(hass, config)])
def extract_image_from_mjpeg(stream):
@@ -91,7 +93,6 @@ class MjpegCamera(Camera):
return image
websession = async_get_clientsession(self.hass)
response = None
try:
with async_timeout.timeout(10, loop=self.hass.loop):
response = yield from websession.get(
@@ -103,14 +104,9 @@ class MjpegCamera(Camera):
except asyncio.TimeoutError:
_LOGGER.error('Timeout getting camera image')
except (aiohttp.errors.ClientError,
aiohttp.errors.ClientDisconnectedError) as err:
except aiohttp.ClientError as err:
_LOGGER.error('Error getting new camera image: %s', err)
finally:
if response is not None:
yield from response.release()
def camera_image(self):
"""Return a still image response from the camera."""
if self._username and self._password:
@@ -138,7 +134,7 @@ class MjpegCamera(Camera):
websession = async_get_clientsession(self.hass)
stream_coro = websession.get(self._mjpeg_url, auth=self._auth)
yield from async_aiohttp_proxy_stream(self.hass, request, stream_coro)
yield from async_aiohttp_proxy_web(self.hass, request, stream_coro)
@property
def name(self):

View File

@@ -14,12 +14,12 @@ import async_timeout
from homeassistant.const import (
CONF_NAME, CONF_USERNAME, CONF_PASSWORD,
CONF_URL, CONF_WHITELIST, CONF_VERIFY_SSL)
CONF_URL, CONF_WHITELIST, CONF_VERIFY_SSL, CONF_TIMEOUT)
from homeassistant.components.camera import (
Camera, PLATFORM_SCHEMA)
from homeassistant.helpers.aiohttp_client import (
async_get_clientsession, async_create_clientsession,
async_aiohttp_proxy_stream)
async_aiohttp_proxy_web)
import homeassistant.helpers.config_validation as cv
from homeassistant.util.async import run_coroutine_threadsafe
@@ -27,7 +27,7 @@ _LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'Synology Camera'
DEFAULT_STREAM_ID = '0'
TIMEOUT = 5
DEFAULT_TIMEOUT = 5
CONF_CAMERA_NAME = 'camera_name'
CONF_STREAM_ID = 'stream_id'
@@ -51,6 +51,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_URL): cv.string,
vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
vol.Optional(CONF_WHITELIST, default=[]): cv.ensure_list,
vol.Optional(CONF_VERIFY_SSL, default=True): cv.boolean,
})
@@ -60,6 +61,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Setup a Synology IP Camera."""
verify_ssl = config.get(CONF_VERIFY_SSL)
timeout = config.get(CONF_TIMEOUT)
websession_init = async_get_clientsession(hass, verify_ssl)
# Determine API to use for authentication
@@ -72,9 +74,8 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
'version': '1',
'query': 'SYNO.'
}
query_req = None
try:
with async_timeout.timeout(TIMEOUT, loop=hass.loop):
with async_timeout.timeout(timeout, loop=hass.loop):
query_req = yield from websession_init.get(
syno_api_url,
params=query_payload
@@ -86,14 +87,10 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
camera_path = query_resp['data'][CAMERA_API]['path']
streaming_path = query_resp['data'][STREAMING_API]['path']
except (asyncio.TimeoutError, aiohttp.errors.ClientError):
except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.exception("Error on %s", syno_api_url)
return False
finally:
if query_req is not None:
yield from query_req.release()
# Authticate to NAS to get a session id
syno_auth_url = SYNO_API_URL.format(
config.get(CONF_URL), WEBAPI_PATH, auth_path)
@@ -103,7 +100,8 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
websession_init,
config.get(CONF_USERNAME),
config.get(CONF_PASSWORD),
syno_auth_url
syno_auth_url,
timeout
)
# init websession
@@ -120,18 +118,17 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
'version': '1'
}
try:
with async_timeout.timeout(TIMEOUT, loop=hass.loop):
with async_timeout.timeout(timeout, loop=hass.loop):
camera_req = yield from websession.get(
syno_camera_url,
params=camera_payload
)
except (asyncio.TimeoutError, aiohttp.errors.ClientError):
except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.exception("Error on %s", syno_camera_url)
return False
camera_resp = yield from camera_req.json()
cameras = camera_resp['data']['cameras']
yield from camera_req.release()
# add cameras
devices = []
@@ -149,15 +146,16 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
snapshot_path,
streaming_path,
camera_path,
auth_path
auth_path,
timeout
)
devices.append(device)
yield from async_add_devices(devices)
async_add_devices(devices)
@asyncio.coroutine
def get_session_id(hass, websession, username, password, login_url):
def get_session_id(hass, websession, username, password, login_url, timeout):
"""Get a session id."""
auth_payload = {
'api': AUTH_API,
@@ -168,9 +166,8 @@ def get_session_id(hass, websession, username, password, login_url):
'session': 'SurveillanceStation',
'format': 'sid'
}
auth_req = None
try:
with async_timeout.timeout(TIMEOUT, loop=hass.loop):
with async_timeout.timeout(timeout, loop=hass.loop):
auth_req = yield from websession.get(
login_url,
params=auth_payload
@@ -178,21 +175,17 @@ def get_session_id(hass, websession, username, password, login_url):
auth_resp = yield from auth_req.json()
return auth_resp['data']['sid']
except (asyncio.TimeoutError, aiohttp.errors.ClientError):
except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.exception("Error on %s", login_url)
return False
finally:
if auth_req is not None:
yield from auth_req.release()
class SynologyCamera(Camera):
"""An implementation of a Synology NAS based IP camera."""
def __init__(self, hass, websession, config, camera_id,
camera_name, snapshot_path, streaming_path, camera_path,
auth_path):
auth_path, timeout):
"""Initialize a Synology Surveillance Station camera."""
super().__init__()
self.hass = hass
@@ -206,6 +199,7 @@ class SynologyCamera(Camera):
self._streaming_path = streaming_path
self._camera_path = camera_path
self._auth_path = auth_path
self._timeout = timeout
def camera_image(self):
"""Return bytes of camera image."""
@@ -225,17 +219,16 @@ class SynologyCamera(Camera):
'cameraId': self._camera_id
}
try:
with async_timeout.timeout(TIMEOUT, loop=self.hass.loop):
with async_timeout.timeout(self._timeout, loop=self.hass.loop):
response = yield from self._websession.get(
image_url,
params=image_payload
)
except (asyncio.TimeoutError, aiohttp.errors.ClientError):
_LOGGER.exception("Error on %s", image_url)
except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.error("Error fetching %s", image_url)
return None
image = yield from response.read()
yield from response.release()
return image
@@ -255,7 +248,7 @@ class SynologyCamera(Camera):
stream_coro = self._websession.get(
streaming_url, params=streaming_payload)
yield from async_aiohttp_proxy_stream(self.hass, request, stream_coro)
yield from async_aiohttp_proxy_web(self.hass, request, stream_coro)
@property
def name(self):

View File

@@ -19,6 +19,9 @@ _LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['zoneminder']
DOMAIN = 'zoneminder'
# From ZoneMinder's web/includes/config.php.in
ZM_STATE_ALARM = "2"
def _get_image_url(hass, monitor, mode):
zm_data = hass.data[DOMAIN]
@@ -69,10 +72,43 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
CONF_MJPEG_URL: _get_image_url(hass, monitor, 'jpeg'),
CONF_STILL_IMAGE_URL: _get_image_url(hass, monitor, 'single')
}
cameras.append(MjpegCamera(hass, device_info))
cameras.append(ZoneMinderCamera(hass, device_info, monitor))
if not cameras:
_LOGGER.warning('No active cameras found')
return
yield from async_add_devices(cameras)
async_add_devices(cameras)
class ZoneMinderCamera(MjpegCamera):
"""Representation of a ZoneMinder Monitor Stream."""
def __init__(self, hass, device_info, monitor):
"""Initialize as a subclass of MjpegCamera."""
super().__init__(hass, device_info)
self._monitor_id = int(monitor['Id'])
self._is_recording = None
@property
def should_poll(self):
"""Update the recording state periodically."""
return True
def update(self):
"""Update our recording state from the ZM API."""
_LOGGER.debug('Updating camera state for monitor %i', self._monitor_id)
status_response = zoneminder.get_state(
'api/monitors/alarm/id:%i/command:status.json' % self._monitor_id
)
if not status_response:
_LOGGER.warning('Could not get status for monitor %i',
self._monitor_id)
return
self._is_recording = status_response['status'] == ZM_STATE_ALARM
@property
def is_recording(self):
"""Return whether the monitor is in alarm mode."""
return self._is_recording

View File

@@ -224,7 +224,7 @@ def async_setup(hass, config):
if not climate.should_poll:
continue
update_coro = hass.loop.create_task(
update_coro = hass.async_add_job(
climate.async_update_ha_state(True))
if hasattr(climate, 'async_update'):
update_tasks.append(update_coro)
@@ -692,18 +692,16 @@ class ClimateDevice(Entity):
def _convert_for_display(self, temp):
"""Convert temperature into preferred units for display purposes."""
if (temp is None or not isinstance(temp, Number) or
self.temperature_unit == self.unit_of_measurement):
if temp is None or not isinstance(temp, Number):
return temp
value = convert_temperature(temp, self.temperature_unit,
self.unit_of_measurement)
if self.temperature_unit != self.unit_of_measurement:
temp = convert_temperature(temp, self.temperature_unit,
self.unit_of_measurement)
# Round in the units appropriate
if self.precision == PRECISION_HALVES:
return round(value * 2) / 2.0
return round(temp * 2) / 2.0
elif self.precision == PRECISION_TENTHS:
return round(value, 1)
return round(temp, 1)
else:
# PRECISION_WHOLE as a fall back
return round(value)
return round(temp)

View File

@@ -25,6 +25,8 @@ ATTR_FAN_MIN_ON_TIME = 'fan_min_on_time'
ATTR_RESUME_ALL = 'resume_all'
DEFAULT_RESUME_ALL = False
TEMPERATURE_HOLD = 'temp'
VACATION_HOLD = 'vacation'
DEPENDENCIES = ['ecobee']
@@ -112,6 +114,8 @@ class Thermostat(ClimateDevice):
self.thermostat_index)
self._name = self.thermostat['name']
self.hold_temp = hold_temp
self.vacation = None
self._climate_list = self.climate_list
self._operation_list = ['auto', 'auxHeatOnly', 'cool',
'heat', 'off']
self.update_without_throttle = False
@@ -187,29 +191,30 @@ class Thermostat(ClimateDevice):
def current_hold_mode(self):
"""Return current hold mode."""
events = self.thermostat['events']
if any((event['holdClimateRef'] == 'away' and
int(event['endDate'][0:4])-int(event['startDate'][0:4]) <= 1)
or event['type'] == 'autoAway'
for event in events):
# away hold is auto away or a temporary hold from away climate
hold = 'away'
elif any(event['holdClimateRef'] == 'away' and
int(event['endDate'][0:4])-int(event['startDate'][0:4]) > 1
for event in events):
# a permanent away is not considered a hold, but away_mode
hold = None
elif any(event['holdClimateRef'] == 'home' or
event['type'] == 'autoHome'
for event in events):
# home mode is auto home or any home hold
hold = 'home'
elif any(event['type'] == 'hold' and event['running']
for event in events):
hold = 'temp'
# temperature hold is any other hold not based on climate
else:
hold = None
return hold
for event in events:
if event['running']:
if event['type'] == 'hold':
if event['holdClimateRef'] == 'away':
if int(event['endDate'][0:4]) - \
int(event['startDate'][0:4]) <= 1:
# a temporary hold from away climate is a hold
return 'away'
else:
# a premanent hold from away climate is away_mode
return None
elif event['holdClimateRef'] != "":
# any other hold based on climate
return event['holdClimateRef']
else:
# any hold not based on a climate is a temp hold
return TEMPERATURE_HOLD
elif event['type'].startswith('auto'):
# all auto modes are treated as holds
return event['type'][4:].lower()
elif event['type'] == 'vacation':
self.vacation = event['name']
return VACATION_HOLD
return None
@property
def current_operation(self):
@@ -232,8 +237,11 @@ class Thermostat(ClimateDevice):
@property
def mode(self):
"""Return current mode ie. home, away, sleep."""
return self.thermostat['program']['currentClimateRef']
"""Return current mode, as the user-visible name."""
cur = self.thermostat['program']['currentClimateRef']
climates = self.thermostat['program']['climates']
current = list(filter(lambda x: x['climateRef'] == cur, climates))
return current[0]['name']
@property
def fan_min_on_time(self):
@@ -261,52 +269,44 @@ class Thermostat(ClimateDevice):
"fan": self.fan,
"mode": self.mode,
"operation": operation,
"climate_list": self.climate_list,
"fan_min_on_time": self.fan_min_on_time
}
def is_vacation_on(self):
"""Return true if vacation mode is on."""
events = self.thermostat['events']
return any(event['type'] == 'vacation' and event['running']
for event in events)
@property
def is_away_mode_on(self):
"""Return true if away mode is on."""
events = self.thermostat['events']
return any(event['holdClimateRef'] == 'away' and
int(event['endDate'][0:4])-int(event['startDate'][0:4]) > 1
for event in events)
return self.current_hold_mode == 'away'
def turn_away_mode_on(self):
"""Turn away on."""
self.data.ecobee.set_climate_hold(self.thermostat_index,
"away", 'indefinite')
self.update_without_throttle = True
self.set_hold_mode('away')
def turn_away_mode_off(self):
"""Turn away off."""
self.data.ecobee.resume_program(self.thermostat_index)
self.update_without_throttle = True
self.set_hold_mode(None)
def set_hold_mode(self, hold_mode):
"""Set hold mode (away, home, temp)."""
"""Set hold mode (away, home, temp, sleep, etc.)."""
hold = self.current_hold_mode
if hold == hold_mode:
# no change, so no action required
return
elif hold_mode == 'away':
self.data.ecobee.set_climate_hold(self.thermostat_index,
"away", self.hold_preference())
elif hold_mode == 'home':
self.data.ecobee.set_climate_hold(self.thermostat_index,
"home", self.hold_preference())
elif hold_mode == 'temp':
self.set_temp_hold(int(self.current_temperature))
elif hold_mode == 'None' or hold_mode is None:
if hold == VACATION_HOLD:
self.data.ecobee.delete_vacation(self.thermostat_index,
self.vacation)
else:
self.data.ecobee.resume_program(self.thermostat_index)
else:
self.data.ecobee.resume_program(self.thermostat_index)
self.update_without_throttle = True
if hold_mode == TEMPERATURE_HOLD:
self.set_temp_hold(int(self.current_temperature))
else:
self.data.ecobee.set_climate_hold(self.thermostat_index,
hold_mode,
self.hold_preference())
self.update_without_throttle = True
def set_auto_temp_hold(self, heat_temp, cool_temp):
"""Set temperature hold in auto mode."""
@@ -382,3 +382,9 @@ class Thermostat(ClimateDevice):
# as an indefinite away hold is interpreted as away_mode
else:
return 'nextTransition'
@property
def climate_list(self):
"""Return the list of climates currently available."""
climates = self.thermostat['program']['climates']
return list(map((lambda x: x['name']), climates))

View File

@@ -16,7 +16,8 @@ from homeassistant.components.climate import (
from homeassistant.const import (
ATTR_UNIT_OF_MEASUREMENT, STATE_ON, STATE_OFF, ATTR_TEMPERATURE)
from homeassistant.helpers import condition
from homeassistant.helpers.event import async_track_state_change
from homeassistant.helpers.event import (
async_track_state_change, async_track_time_interval)
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
@@ -35,6 +36,7 @@ CONF_TARGET_TEMP = 'target_temp'
CONF_AC_MODE = 'ac_mode'
CONF_MIN_DUR = 'min_cycle_duration'
CONF_TOLERANCE = 'tolerance'
CONF_KEEP_ALIVE = 'keep_alive'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@@ -47,6 +49,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_TOLERANCE, default=DEFAULT_TOLERANCE): vol.Coerce(float),
vol.Optional(CONF_TARGET_TEMP): vol.Coerce(float),
vol.Optional(CONF_KEEP_ALIVE): vol.All(
cv.time_period, cv.positive_timedelta),
})
@@ -62,10 +66,11 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
ac_mode = config.get(CONF_AC_MODE)
min_cycle_duration = config.get(CONF_MIN_DUR)
tolerance = config.get(CONF_TOLERANCE)
keep_alive = config.get(CONF_KEEP_ALIVE)
yield from async_add_devices([GenericThermostat(
async_add_devices([GenericThermostat(
hass, name, heater_entity_id, sensor_entity_id, min_temp, max_temp,
target_temp, ac_mode, min_cycle_duration, tolerance)])
target_temp, ac_mode, min_cycle_duration, tolerance, keep_alive)])
class GenericThermostat(ClimateDevice):
@@ -73,7 +78,7 @@ class GenericThermostat(ClimateDevice):
def __init__(self, hass, name, heater_entity_id, sensor_entity_id,
min_temp, max_temp, target_temp, ac_mode, min_cycle_duration,
tolerance):
tolerance, keep_alive):
"""Initialize the thermostat."""
self.hass = hass
self._name = name
@@ -81,6 +86,7 @@ class GenericThermostat(ClimateDevice):
self.ac_mode = ac_mode
self.min_cycle_duration = min_cycle_duration
self._tolerance = tolerance
self._keep_alive = keep_alive
self._active = False
self._cur_temp = None
@@ -94,6 +100,10 @@ class GenericThermostat(ClimateDevice):
async_track_state_change(
hass, heater_entity_id, self._async_switch_changed)
if self._keep_alive:
async_track_time_interval(
hass, self._async_keep_alive, self._keep_alive)
sensor_state = hass.states.get(sensor_entity_id)
if sensor_state:
self._async_update_temp(sensor_state)
@@ -180,6 +190,14 @@ class GenericThermostat(ClimateDevice):
return
self.hass.async_add_job(self.async_update_ha_state())
@callback
def _async_keep_alive(self, time):
"""Called at constant intervals for keep-alive purposes."""
if self.current_operation in [STATE_COOL, STATE_HEAT]:
switch.async_turn_on(self.hass, self.heater_entity_id)
else:
switch.async_turn_off(self.hass, self.heater_entity_id)
@callback
def _async_update_temp(self, state):
"""Update thermostat with latest state from sensor."""

View File

@@ -16,11 +16,15 @@ _LOGGER = logging.getLogger(__name__)
STATE_MANUAL = "manual"
STATE_BOOST = "boost"
STATE_COMFORT = "comfort"
STATE_LOWERING = "lowering"
HM_STATE_MAP = {
"AUTO_MODE": STATE_AUTO,
"MANU_MODE": STATE_MANUAL,
"BOOST_MODE": STATE_BOOST,
"COMFORT_MODE": STATE_COMFORT,
"LOWERING_MODE": STATE_LOWERING
}
HM_TEMP_MAP = [

View File

@@ -6,10 +6,15 @@ https://home-assistant.io/components/climate.honeywell/
"""
import logging
import socket
import datetime
import voluptuous as vol
import requests
from homeassistant.components.climate import (ClimateDevice, PLATFORM_SCHEMA)
from homeassistant.components.climate import (ClimateDevice, PLATFORM_SCHEMA,
ATTR_FAN_MODE, ATTR_FAN_LIST,
ATTR_OPERATION_MODE,
ATTR_OPERATION_LIST)
from homeassistant.const import (
CONF_PASSWORD, CONF_USERNAME, TEMP_CELSIUS, TEMP_FAHRENHEIT,
ATTR_TEMPERATURE)
@@ -21,27 +26,35 @@ REQUIREMENTS = ['evohomeclient==0.2.5',
_LOGGER = logging.getLogger(__name__)
ATTR_FAN = 'fan'
ATTR_FANMODE = 'fanmode'
ATTR_SYSTEM_MODE = 'system_mode'
ATTR_CURRENT_OPERATION = 'equipment_output_status'
CONF_AWAY_TEMPERATURE = 'away_temperature'
CONF_COOL_AWAY_TEMPERATURE = 'away_cool_temperature'
CONF_HEAT_AWAY_TEMPERATURE = 'away_heat_temperature'
CONF_REGION = 'region'
DEFAULT_AWAY_TEMPERATURE = 16
DEFAULT_COOL_AWAY_TEMPERATURE = 30
DEFAULT_HEAT_AWAY_TEMPERATURE = 16
DEFAULT_REGION = 'eu'
REGIONS = ['eu', 'us']
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_AWAY_TEMPERATURE, default=DEFAULT_AWAY_TEMPERATURE):
vol.Coerce(float),
vol.Optional(CONF_AWAY_TEMPERATURE,
default=DEFAULT_AWAY_TEMPERATURE): vol.Coerce(float),
vol.Optional(CONF_COOL_AWAY_TEMPERATURE,
default=DEFAULT_COOL_AWAY_TEMPERATURE): vol.Coerce(float),
vol.Optional(CONF_HEAT_AWAY_TEMPERATURE,
default=DEFAULT_HEAT_AWAY_TEMPERATURE): vol.Coerce(float),
vol.Optional(CONF_REGION, default=DEFAULT_REGION): vol.In(REGIONS),
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the HoneywelL thermostat."""
"""Setup the Honeywell thermostat."""
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
region = config.get(CONF_REGION)
@@ -88,8 +101,11 @@ def _setup_us(username, password, config, add_devices):
dev_id = config.get('thermostat')
loc_id = config.get('location')
cool_away_temp = config.get(CONF_COOL_AWAY_TEMPERATURE)
heat_away_temp = config.get(CONF_HEAT_AWAY_TEMPERATURE)
add_devices([HoneywellUSThermostat(client, device)
add_devices([HoneywellUSThermostat(client, device, cool_away_temp,
heat_away_temp, username, password)
for location in client.locations_by_id.values()
for device in location.devices_by_id.values()
if ((not loc_id or location.locationid == loc_id) and
@@ -160,7 +176,7 @@ class RoundThermostat(ClimateDevice):
def turn_away_mode_on(self):
"""Turn away on.
Evohome does have a proprietary away mode, but it doesn't really work
Honeywell does have a proprietary away mode, but it doesn't really work
the way it should. For example: If you set a temperature manually
it doesn't get overwritten when away mode is switched on.
"""
@@ -199,10 +215,16 @@ class RoundThermostat(ClimateDevice):
class HoneywellUSThermostat(ClimateDevice):
"""Representation of a Honeywell US Thermostat."""
def __init__(self, client, device):
def __init__(self, client, device, cool_away_temp,
heat_away_temp, username, password):
"""Initialize the thermostat."""
self._client = client
self._device = device
self._cool_away_temp = cool_away_temp
self._heat_away_temp = heat_away_temp
self._away = False
self._username = username
self._password = password
@property
def is_fan_on(self):
@@ -236,7 +258,10 @@ class HoneywellUSThermostat(ClimateDevice):
@property
def current_operation(self: ClimateDevice) -> str:
"""Return current operation ie. heat, cool, idle."""
return getattr(self._device, ATTR_SYSTEM_MODE, None)
oper = getattr(self._device, ATTR_CURRENT_OPERATION, None)
if oper == "off":
oper = "idle"
return oper
def set_temperature(self, **kwargs):
"""Set target temperature."""
@@ -245,29 +270,84 @@ class HoneywellUSThermostat(ClimateDevice):
return
import somecomfort
try:
if self._device.system_mode == 'cool':
self._device.setpoint_cool = temperature
else:
self._device.setpoint_heat = temperature
# Get current mode
mode = self._device.system_mode
# Set hold if this is not the case
if getattr(self._device, "hold_{}".format(mode)) is False:
# Get next period key
next_period_key = '{}NextPeriod'.format(mode.capitalize())
# Get next period raw value
next_period = self._device.raw_ui_data.get(next_period_key)
# Get next period time
hour, minute = divmod(next_period * 15, 60)
# Set hold time
setattr(self._device,
"hold_{}".format(mode),
datetime.time(hour, minute))
# Set temperature
setattr(self._device,
"setpoint_{}".format(mode),
temperature)
except somecomfort.SomeComfortError:
_LOGGER.error('Temperature %.1f out of range', temperature)
@property
def device_state_attributes(self):
"""Return the device specific state attributes."""
return {
import somecomfort
data = {
ATTR_FAN: (self.is_fan_on and 'running' or 'idle'),
ATTR_FANMODE: self._device.fan_mode,
ATTR_SYSTEM_MODE: self._device.system_mode,
ATTR_FAN_MODE: self._device.fan_mode,
ATTR_OPERATION_MODE: self._device.system_mode,
}
data[ATTR_FAN_LIST] = somecomfort.FAN_MODES
data[ATTR_OPERATION_LIST] = somecomfort.SYSTEM_MODES
return data
@property
def is_away_mode_on(self):
"""Return true if away mode is on."""
return self._away
def turn_away_mode_on(self):
"""Turn away on."""
pass
"""Turn away on.
Somecomfort does have a proprietary away mode, but it doesn't really
work the way it should. For example: If you set a temperature manually
it doesn't get overwritten when away mode is switched on.
"""
self._away = True
import somecomfort
try:
# Get current mode
mode = self._device.system_mode
except somecomfort.SomeComfortError:
_LOGGER.error('Can not get system mode')
return
try:
# Set permanent hold
setattr(self._device,
"hold_{}".format(mode),
True)
# Set temperature
setattr(self._device,
"setpoint_{}".format(mode),
getattr(self, "_{}_away_temp".format(mode)))
except somecomfort.SomeComfortError:
_LOGGER.error('Temperature %.1f out of range',
getattr(self, "_{}_away_temp".format(mode)))
def turn_away_mode_off(self):
"""Turn away off."""
pass
self._away = False
import somecomfort
try:
# Disabling all hold modes
self._device.hold_cool = False
self._device.hold_heat = False
except somecomfort.SomeComfortError:
_LOGGER.error('Can not stop hold mode')
def set_operation_mode(self: ClimateDevice, operation_mode: str) -> None:
"""Set the system mode (Cool, Heat, etc)."""
@@ -276,4 +356,49 @@ class HoneywellUSThermostat(ClimateDevice):
def update(self):
"""Update the state."""
self._device.refresh()
import somecomfort
retries = 3
while retries > 0:
try:
self._device.refresh()
break
except (somecomfort.client.APIRateLimited, OSError,
requests.exceptions.ReadTimeout) as exp:
retries -= 1
if retries == 0:
raise exp
if not self._retry():
raise exp
_LOGGER.error("SomeComfort update failed, Retrying "
"- Error: %s", exp)
def _retry(self):
"""Recreate a new somecomfort client.
When we got an error, the best way to be sure that the next query
will succeed, is to recreate a new somecomfort client.
"""
import somecomfort
try:
self._client = somecomfort.SomeComfort(self._username,
self._password)
except somecomfort.AuthError:
_LOGGER.error('Failed to login to honeywell account %s',
self._username)
return False
except somecomfort.SomeComfortError as ex:
_LOGGER.error('Failed to initialize honeywell client: %s',
str(ex))
return False
devices = [device
for location in self._client.locations_by_id.values()
for device in location.devices_by_id.values()
if device.name == self._device.name]
if len(devices) != 1:
_LOGGER.error('Failed to find device %s', self._device.name)
return False
self._device = devices[0]
return True

View File

@@ -0,0 +1,216 @@
"""
Support for MAX! Thermostats via MAX! Cube.
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/maxcube/
"""
import socket
import logging
from homeassistant.components.climate import ClimateDevice, STATE_AUTO
from homeassistant.components.maxcube import MAXCUBE_HANDLE
from homeassistant.const import TEMP_CELSIUS, ATTR_TEMPERATURE
from homeassistant.const import STATE_UNKNOWN
_LOGGER = logging.getLogger(__name__)
STATE_MANUAL = "manual"
STATE_BOOST = "boost"
STATE_VACATION = "vacation"
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Iterate through all MAX! Devices and add thermostats to HASS."""
cube = hass.data[MAXCUBE_HANDLE].cube
# List of devices
devices = []
for device in cube.devices:
# Create device name by concatenating room name + device name
name = "%s %s" % (cube.room_by_id(device.room_id).name, device.name)
# Only add thermostats and wallthermostats
if cube.is_thermostat(device) or cube.is_wallthermostat(device):
# Add device to HASS
devices.append(MaxCubeClimate(hass, name, device.rf_address))
# Add all devices at once
if len(devices) > 0:
add_devices(devices)
class MaxCubeClimate(ClimateDevice):
"""MAX! Cube ClimateDevice."""
def __init__(self, hass, name, rf_address):
"""Initialize MAX! Cube ClimateDevice."""
self._name = name
self._unit_of_measurement = TEMP_CELSIUS
self._operation_list = [STATE_AUTO, STATE_MANUAL, STATE_BOOST,
STATE_VACATION]
self._rf_address = rf_address
self._cubehandle = hass.data[MAXCUBE_HANDLE]
@property
def should_poll(self):
"""Polling is required."""
return True
@property
def name(self):
"""Return the name of the ClimateDevice."""
return self._name
@property
def min_temp(self):
"""Return the minimum temperature."""
# Get the device we want (does not do any IO, just reads from memory)
device = self._cubehandle.cube.device_by_rf(self._rf_address)
# Map and return minimum temperature
return self.map_temperature_max_hass(device.min_temperature)
@property
def max_temp(self):
"""Return the maximum temperature."""
# Get the device we want (does not do any IO, just reads from memory)
device = self._cubehandle.cube.device_by_rf(self._rf_address)
# Map and return maximum temperature
return self.map_temperature_max_hass(device.max_temperature)
@property
def temperature_unit(self):
"""Return the unit of measurement."""
return self._unit_of_measurement
@property
def current_temperature(self):
"""Return the current temperature."""
# Get the device we want (does not do any IO, just reads from memory)
device = self._cubehandle.cube.device_by_rf(self._rf_address)
# Map and return current temperature
return self.map_temperature_max_hass(device.actual_temperature)
@property
def current_operation(self):
"""Return current operation (auto, manual, boost, vacation)."""
# Get the device we want (does not do any IO, just reads from memory)
device = self._cubehandle.cube.device_by_rf(self._rf_address)
# Mode Mapping
return self.map_mode_max_hass(device.mode)
@property
def operation_list(self):
"""List of available operation modes."""
return self._operation_list
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
# Get the device we want (does not do any IO, just reads from memory)
device = self._cubehandle.cube.device_by_rf(self._rf_address)
# Map and return target temperature
return self.map_temperature_max_hass(device.target_temperature)
def set_temperature(self, **kwargs):
"""Set new target temperatures."""
# Fail is target temperature has not been supplied as argument
if kwargs.get(ATTR_TEMPERATURE) is None:
return False
# Determine the new target temperature
target_temperature = kwargs.get(ATTR_TEMPERATURE)
# Write the target temperature to the MAX! Cube.
device = self._cubehandle.cube.device_by_rf(self._rf_address)
cube = self._cubehandle.cube
with self._cubehandle.mutex:
try:
cube.set_target_temperature(device, target_temperature)
except (socket.timeout, socket.error):
_LOGGER.error("Setting target temperature failed")
return False
def set_operation_mode(self, operation_mode):
"""Set new operation mode."""
# Get the device we want to update
device = self._cubehandle.cube.device_by_rf(self._rf_address)
# Mode Mapping
mode = self.map_mode_hass_max(operation_mode)
# Write new mode to thermostat
if mode is None:
return False
with self._cubehandle.mutex:
try:
self._cubehandle.cube.set_mode(device, mode)
except (socket.timeout, socket.error):
_LOGGER.error("Setting operation mode failed")
return False
def update(self):
"""Get latest data from MAX! Cube."""
# Update the CubeHandle
self._cubehandle.update()
@staticmethod
def map_temperature_max_hass(temperature):
"""Map Temperature from MAX! to HASS."""
if temperature is None:
return STATE_UNKNOWN
return temperature
@staticmethod
def map_mode_hass_max(operation_mode):
"""Map HASS Operation Modes to MAX! Operation Modes."""
from maxcube.device import \
MAX_DEVICE_MODE_AUTOMATIC, \
MAX_DEVICE_MODE_MANUAL, \
MAX_DEVICE_MODE_VACATION, \
MAX_DEVICE_MODE_BOOST
if operation_mode == STATE_AUTO:
mode = MAX_DEVICE_MODE_AUTOMATIC
elif operation_mode == STATE_MANUAL:
mode = MAX_DEVICE_MODE_MANUAL
elif operation_mode == STATE_VACATION:
mode = MAX_DEVICE_MODE_VACATION
elif operation_mode == STATE_BOOST:
mode = MAX_DEVICE_MODE_BOOST
else:
mode = None
return mode
@staticmethod
def map_mode_max_hass(mode):
"""Map MAX! Operation Modes to HASS Operation Modes."""
from maxcube.device import \
MAX_DEVICE_MODE_AUTOMATIC, \
MAX_DEVICE_MODE_MANUAL, \
MAX_DEVICE_MODE_VACATION, \
MAX_DEVICE_MODE_BOOST
if mode == MAX_DEVICE_MODE_AUTOMATIC:
operation_mode = STATE_AUTO
elif mode == MAX_DEVICE_MODE_MANUAL:
operation_mode = STATE_MANUAL
elif mode == MAX_DEVICE_MODE_VACATION:
operation_mode = STATE_VACATION
elif mode == MAX_DEVICE_MODE_BOOST:
operation_mode = STATE_BOOST
else:
operation_mode = None
return operation_mode

View File

@@ -0,0 +1,292 @@
"""
Tado component to create a climate device for each zone.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.tado/
"""
import logging
from homeassistant.const import TEMP_CELSIUS
from homeassistant.components.climate import ClimateDevice
from homeassistant.const import ATTR_TEMPERATURE
from homeassistant.components.tado import DATA_TADO
_LOGGER = logging.getLogger(__name__)
CONST_MODE_SMART_SCHEDULE = 'SMART_SCHEDULE' # Default mytado mode
CONST_MODE_OFF = 'OFF' # Switch off heating in a zone
# When we change the temperature setting, we need an overlay mode
# wait until tado changes the mode automatic
CONST_OVERLAY_TADO_MODE = 'TADO_MODE'
# the user has change the temperature or mode manually
CONST_OVERLAY_MANUAL = 'MANUAL'
# the temperature will be reset after a timespan
CONST_OVERLAY_TIMER = 'TIMER'
OPERATION_LIST = {
CONST_OVERLAY_MANUAL: 'Manual',
CONST_OVERLAY_TIMER: 'Timer',
CONST_OVERLAY_TADO_MODE: 'Tado mode',
CONST_MODE_SMART_SCHEDULE: 'Smart schedule',
CONST_MODE_OFF: 'Off',
}
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Tado climate platform."""
tado = hass.data[DATA_TADO]
try:
zones = tado.get_zones()
except RuntimeError:
_LOGGER.error("Unable to get zone info from mytado")
return False
climate_devices = []
for zone in zones:
climate_devices.append(create_climate_device(
tado, hass, zone, zone['name'], zone['id']))
if len(climate_devices) > 0:
add_devices(climate_devices, True)
return True
else:
return False
def create_climate_device(tado, hass, zone, name, zone_id):
"""Create a Tado climate device."""
capabilities = tado.get_capabilities(zone_id)
unit = TEMP_CELSIUS
min_temp = float(capabilities['temperatures']['celsius']['min'])
max_temp = float(capabilities['temperatures']['celsius']['max'])
ac_mode = capabilities['type'] != 'HEATING'
data_id = 'zone {} {}'.format(name, zone_id)
device = TadoClimate(tado,
name, zone_id, data_id,
hass.config.units.temperature(min_temp, unit),
hass.config.units.temperature(max_temp, unit),
ac_mode)
tado.add_sensor(data_id, {
'id': zone_id,
'zone': zone,
'name': name,
'climate': device
})
return device
class TadoClimate(ClimateDevice):
"""Representation of a tado climate device."""
def __init__(self, store, zone_name, zone_id, data_id,
min_temp, max_temp, ac_mode,
tolerance=0.3):
"""Initialization of Tado climate device."""
self._store = store
self._data_id = data_id
self.zone_name = zone_name
self.zone_id = zone_id
self.ac_mode = ac_mode
self._active = False
self._device_is_active = False
self._unit = TEMP_CELSIUS
self._cur_temp = None
self._cur_humidity = None
self._is_away = False
self._min_temp = min_temp
self._max_temp = max_temp
self._target_temp = None
self._tolerance = tolerance
self._current_operation = CONST_MODE_SMART_SCHEDULE
self._overlay_mode = CONST_MODE_SMART_SCHEDULE
@property
def name(self):
"""Return the name of the sensor."""
return self.zone_name
@property
def current_humidity(self):
"""Return the current humidity."""
return self._cur_humidity
@property
def current_temperature(self):
"""Return the sensor temperature."""
return self._cur_temp
@property
def current_operation(self):
"""Return current readable operation mode."""
return OPERATION_LIST.get(self._current_operation)
@property
def operation_list(self):
"""List of available operation modes (readable)."""
return list(OPERATION_LIST.values())
@property
def temperature_unit(self):
"""The unit of measurement used by the platform."""
return self._unit
@property
def is_away_mode_on(self):
"""Return true if away mode is on."""
return self._is_away
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
return self._target_temp
def set_temperature(self, **kwargs):
"""Set new target temperature."""
temperature = kwargs.get(ATTR_TEMPERATURE)
if temperature is None:
return
self._current_operation = CONST_OVERLAY_TADO_MODE
self._overlay_mode = None
self._target_temp = temperature
self._control_heating()
def set_operation_mode(self, readable_operation_mode):
"""Set new operation mode."""
operation_mode = CONST_MODE_SMART_SCHEDULE
for mode, readable in OPERATION_LIST.items():
if readable == readable_operation_mode:
operation_mode = mode
break
self._current_operation = operation_mode
self._overlay_mode = None
self._control_heating()
@property
def min_temp(self):
"""Return the minimum temperature."""
if self._min_temp:
return self._min_temp
else:
# get default temp from super class
return super().min_temp
@property
def max_temp(self):
"""Return the maximum temperature."""
if self._max_temp:
return self._max_temp
else:
# Get default temp from super class
return super().max_temp
def update(self):
"""Update the state of this climate device."""
self._store.update()
data = self._store.get_data(self._data_id)
if data is None:
_LOGGER.debug("Recieved no data for zone %s", self.zone_name)
return
if 'sensorDataPoints' in data:
sensor_data = data['sensorDataPoints']
temperature = float(
sensor_data['insideTemperature']['celsius'])
humidity = float(
sensor_data['humidity']['percentage'])
setting = 0
# temperature setting will not exist when device is off
if 'temperature' in data['setting'] and \
data['setting']['temperature'] is not None:
setting = float(
data['setting']['temperature']['celsius'])
unit = TEMP_CELSIUS
self._cur_temp = self.hass.config.units.temperature(
temperature, unit)
self._target_temp = self.hass.config.units.temperature(
setting, unit)
self._cur_humidity = humidity
if 'tadoMode' in data:
mode = data['tadoMode']
self._is_away = mode == 'AWAY'
if 'setting' in data:
power = data['setting']['power']
if power == 'OFF':
self._current_operation = CONST_MODE_OFF
self._device_is_active = False
else:
self._device_is_active = True
if 'overlay' in data and data['overlay'] is not None:
overlay = True
termination = data['overlay']['termination']['type']
else:
overlay = False
termination = ""
# If you set mode manualy to off, there will be an overlay
# and a termination, but we want to see the mode "OFF"
if overlay and self._device_is_active:
# There is an overlay the device is on
self._overlay_mode = termination
self._current_operation = termination
else:
# There is no overlay, the mode will always be
# "SMART_SCHEDULE"
self._overlay_mode = CONST_MODE_SMART_SCHEDULE
self._current_operation = CONST_MODE_SMART_SCHEDULE
def _control_heating(self):
"""Send new target temperature to mytado."""
if not self._active and None not in (
self._cur_temp, self._target_temp):
self._active = True
_LOGGER.info("Obtained current and target temperature. "
"Tado thermostat active")
if not self._active or self._current_operation == self._overlay_mode:
return
if self._current_operation == CONST_MODE_SMART_SCHEDULE:
_LOGGER.info("Switching mytado.com to SCHEDULE (default) "
"for zone %s", self.zone_name)
self._store.reset_zone_overlay(self.zone_id)
self._overlay_mode = self._current_operation
return
if self._current_operation == CONST_MODE_OFF:
_LOGGER.info("Switching mytado.com to OFF for zone %s",
self.zone_name)
self._store.set_zone_overlay(self.zone_id, CONST_OVERLAY_MANUAL)
self._overlay_mode = self._current_operation
return
_LOGGER.info("Switching mytado.com to %s mode for zone %s",
self._current_operation, self.zone_name)
self._store.set_zone_overlay(
self.zone_id, self._current_operation, self._target_temp)
self._overlay_mode = self._current_operation

View File

@@ -7,14 +7,14 @@ https://home-assistant.io/components/switch.vera/
import logging
from homeassistant.util import convert
from homeassistant.components.climate import ClimateDevice
from homeassistant.components.climate import ClimateDevice, ENTITY_ID_FORMAT
from homeassistant.const import (
TEMP_FAHRENHEIT,
TEMP_CELSIUS,
ATTR_TEMPERATURE)
from homeassistant.components.vera import (
VeraDevice, VERA_DEVICES, VERA_CONTROLLER)
VERA_CONTROLLER, VERA_DEVICES, VeraDevice)
DEPENDENCIES = ['vera']
@@ -37,6 +37,7 @@ class VeraThermostat(VeraDevice, ClimateDevice):
def __init__(self, vera_device, controller):
"""Initialize the Vera device."""
VeraDevice.__init__(self, vera_device, controller)
self.entity_id = ENTITY_ID_FORMAT.format(self.vera_id)
@property
def current_operation(self):
@@ -84,11 +85,11 @@ class VeraThermostat(VeraDevice, ClimateDevice):
return self.vera_device.fan_cycle()
@property
def current_power_mwh(self):
"""Current power usage in mWh."""
def current_power_w(self):
"""Current power usage in W."""
power = self.vera_device.power
if power:
return convert(power, float, 0.0) * 1000
return convert(power, float, 0.0)
def update(self):
"""Called by the vera device callback to update state."""

View File

@@ -19,7 +19,6 @@ DEPENDENCIES = ['wink']
STATE_AUX = 'aux'
STATE_ECO = 'eco'
STATE_FAN = 'fan'
SPEED_LOWEST = 'lowest'
SPEED_LOW = 'low'
SPEED_MEDIUM = 'medium'
SPEED_HIGH = 'high'
@@ -400,7 +399,7 @@ class WinkAC(WinkDevice, ClimateDevice):
op_list.append(STATE_COOL)
if 'auto_eco' in modes:
op_list.append(STATE_ECO)
if 'fan_eco' in modes:
if 'fan_only' in modes:
op_list.append(STATE_FAN)
return op_list
@@ -439,9 +438,7 @@ class WinkAC(WinkDevice, ClimateDevice):
def current_fan_mode(self):
"""Return the current fan mode."""
speed = self.wink.current_fan_speed()
if speed <= 0.3 and speed >= 0.0:
return SPEED_LOWEST
elif speed <= 0.5 and speed > 0.3:
if speed <= 0.4 and speed > 0.3:
return SPEED_LOW
elif speed <= 0.8 and speed > 0.5:
return SPEED_MEDIUM
@@ -453,14 +450,12 @@ class WinkAC(WinkDevice, ClimateDevice):
@property
def fan_list(self):
"""List of available fan modes."""
return [SPEED_LOWEST, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]
return [SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]
def set_fan_mode(self, mode):
"""Set fan speed."""
if mode == SPEED_LOWEST:
speed = 0.3
elif mode == SPEED_LOW:
speed = 0.5
if mode == SPEED_LOW:
speed = 0.4
elif mode == SPEED_MEDIUM:
speed = 0.8
elif mode == SPEED_HIGH:

View File

@@ -10,7 +10,7 @@ import logging
from homeassistant.components.climate import DOMAIN
from homeassistant.components.climate import ClimateDevice
from homeassistant.components.zwave import ZWaveDeviceEntity
from homeassistant.components import zwave
from homeassistant.components.zwave import async_setup_platform # noqa # pylint: disable=unused-import
from homeassistant.const import (
TEMP_CELSIUS, TEMP_FAHRENHEIT, ATTR_TEMPERATURE)
@@ -32,29 +32,18 @@ DEVICE_MAPPINGS = {
}
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Z-Wave Climate devices."""
if discovery_info is None or zwave.NETWORK is None:
_LOGGER.debug("No discovery_info=%s or no NETWORK=%s",
discovery_info, zwave.NETWORK)
return
def get_device(hass, values, **kwargs):
"""Create zwave entity device."""
temp_unit = hass.config.units.temperature_unit
node = zwave.NETWORK.nodes[discovery_info[zwave.const.ATTR_NODE_ID]]
value = node.values[discovery_info[zwave.const.ATTR_VALUE_ID]]
value.set_change_verified(False)
add_devices([ZWaveClimate(value, temp_unit)])
_LOGGER.debug("discovery_info=%s and zwave.NETWORK=%s",
discovery_info, zwave.NETWORK)
return ZWaveClimate(values, temp_unit)
class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
"""Representation of a Z-Wave Climate device."""
def __init__(self, value, temp_unit):
def __init__(self, values, temp_unit):
"""Initialize the Z-Wave climate device."""
ZWaveDeviceEntity.__init__(self, value, DOMAIN)
self._index = value.index
self._node = value.node
ZWaveDeviceEntity.__init__(self, values, DOMAIN)
self._target_temperature = None
self._current_temperature = None
self._current_operation = None
@@ -69,10 +58,11 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
_LOGGER.debug("temp_unit is %s", self._unit)
self._zxt_120 = None
# 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 (self.node.manufacturer_id.strip() and
self.node.product_id.strip()):
specific_sensor_key = (
int(self.node.manufacturer_id, 16),
int(self.node.product_id, 16))
if specific_sensor_key in DEVICE_MAPPINGS:
if DEVICE_MAPPINGS[specific_sensor_key] == WORKAROUND_ZXT_120:
_LOGGER.debug("Remotec ZXT-120 Zwave Thermostat"
@@ -83,86 +73,59 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
def update_properties(self):
"""Callback on data changes for node values."""
# Operation Mode
self._current_operation = self.get_value(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_MODE, member='data')
operation_list = self.get_value(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_MODE,
member='data_items')
if operation_list:
self._operation_list = list(operation_list)
if self.values.mode:
self._current_operation = self.values.mode.data
operation_list = self.values.mode.data_items
if operation_list:
self._operation_list = list(operation_list)
_LOGGER.debug("self._operation_list=%s", self._operation_list)
_LOGGER.debug("self._current_operation=%s", self._current_operation)
# Current Temp
self._current_temperature = self.get_value(
class_id=zwave.const.COMMAND_CLASS_SENSOR_MULTILEVEL,
label=['Temperature'], member='data')
device_unit = self.get_value(
class_id=zwave.const.COMMAND_CLASS_SENSOR_MULTILEVEL,
label=['Temperature'], member='units')
if device_unit is not None:
self._unit = device_unit
if self.values.temperature:
self._current_temperature = self.values.temperature.data
device_unit = self.values.temperature.units
if device_unit is not None:
self._unit = device_unit
# Fan Mode
self._current_fan_mode = self.get_value(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_FAN_MODE,
member='data')
fan_list = self.get_value(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_FAN_MODE,
member='data_items')
if fan_list:
self._fan_list = list(fan_list)
if self.values.fan_mode:
self._current_fan_mode = self.values.fan_mode.data
fan_list = self.values.fan_mode.data_items
if fan_list:
self._fan_list = list(fan_list)
_LOGGER.debug("self._fan_list=%s", self._fan_list)
_LOGGER.debug("self._current_fan_mode=%s",
self._current_fan_mode)
# Swing mode
if self._zxt_120 == 1:
self._current_swing_mode = (
self.get_value(
class_id=zwave.const.COMMAND_CLASS_CONFIGURATION,
index=33,
member='data'))
swing_list = self.get_value(class_id=zwave.const
.COMMAND_CLASS_CONFIGURATION,
index=33,
member='data_items')
if swing_list:
self._swing_list = list(swing_list)
if self.values.zxt_120_swing_mode:
self._current_swing_mode = self.values.zxt_120_swing_mode.data
swing_list = self.values.zxt_120_swing_mode.data_items
if swing_list:
self._swing_list = list(swing_list)
_LOGGER.debug("self._swing_list=%s", self._swing_list)
_LOGGER.debug("self._current_swing_mode=%s",
self._current_swing_mode)
# Set point
temps = []
for value in (
self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_SETPOINT)
.values()):
temps.append((round(float(value.data)), 1))
if value.index == self._index:
if value.data == 0:
_LOGGER.debug("Setpoint is 0, setting default to "
"current_temperature=%s",
self._current_temperature)
self._target_temperature = (
round((float(self._current_temperature)), 1))
break
else:
self._target_temperature = round((float(value.data)), 1)
if self.values.primary.data == 0:
_LOGGER.debug("Setpoint is 0, setting default to "
"current_temperature=%s",
self._current_temperature)
if self._current_temperature is not None:
self._target_temperature = (
round((float(self._current_temperature)), 1))
else:
self._target_temperature = round(
(float(self.values.primary.data)), 1)
# Operating state
self._operating_state = self.get_value(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_OPERATING_STATE,
member='data')
if self.values.operating_state:
self._operating_state = self.values.operating_state.data
# Fan operating state
self._fan_state = self.get_value(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_FAN_STATE,
member='data')
@property
def should_poll(self):
"""No polling on Z-Wave."""
return False
if self.values.fan_state:
self._fan_state = self.values.fan_state.data
@property
def current_fan_mode(self):
@@ -221,36 +184,31 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
else:
return
self.set_value(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_SETPOINT,
index=self._index, data=temperature)
self.update_ha_state()
self.values.primary.data = temperature
def set_fan_mode(self, fan):
"""Set new target fan mode."""
self.set_value(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_FAN_MODE,
index=0, data=bytes(fan, 'utf-8'))
if self.values.fan_mode:
self.values.fan_mode.data = bytes(fan, 'utf-8')
def set_operation_mode(self, operation_mode):
"""Set new target operation mode."""
self.set_value(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_MODE,
index=0, data=bytes(operation_mode, 'utf-8'))
if self.values.mode:
self.values.mode.data = bytes(operation_mode, 'utf-8')
def set_swing_mode(self, swing_mode):
"""Set new target swing mode."""
if self._zxt_120 == 1:
self.set_value(
class_id=zwave.const.COMMAND_CLASS_CONFIGURATION,
index=33, data=bytes(swing_mode, 'utf-8'))
if self.values.zxt_120_swing_mode:
self.values.zxt_120_swing_mode.data = bytes(
swing_mode, 'utf-8')
@property
def device_state_attributes(self):
"""Return the device specific state attributes."""
data = super().device_state_attributes
if self._operating_state:
data[ATTR_OPERATING_STATE] = self._operating_state,
data[ATTR_OPERATING_STATE] = self._operating_state
if self._fan_state:
data[ATTR_FAN_STATE] = self._fan_state
return data

View File

@@ -6,7 +6,7 @@ import voluptuous as vol
from homeassistant.core import callback
from homeassistant.const import EVENT_COMPONENT_LOADED
from homeassistant.bootstrap import (
from homeassistant.setup import (
async_prepare_setup_platform, ATTR_COMPONENT)
from homeassistant.components.frontend import register_built_in_panel
from homeassistant.components.http import HomeAssistantView
@@ -129,5 +129,8 @@ def _read(path):
def _write(path, data):
"""Write YAML helper."""
# Do it before opening file. If dump causes error it will now not
# truncate the file.
data = dump(data)
with open(path, 'w', encoding='utf-8') as outfile:
outfile.write(dump(data))
outfile.write(data)

View File

@@ -166,7 +166,7 @@ def async_setup(hass, config):
if not cover.should_poll:
continue
update_coro = hass.loop.create_task(
update_coro = hass.async_add_job(
cover.async_update_ha_state(True))
if hasattr(cover, 'async_update'):
update_tasks.append(update_coro)

View File

@@ -54,7 +54,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
if value_template is not None:
value_template.hass = hass
yield from async_add_devices([MqttCover(
async_add_devices([MqttCover(
config.get(CONF_NAME),
config.get(CONF_STATE_TOPIC),
config.get(CONF_COMMAND_TOPIC),

View File

@@ -14,8 +14,8 @@ from homeassistant.const import (
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = [
'https://github.com/arraylabs/pymyq/archive/v0.0.6.zip'
'#pymyq==0.0.6']
'https://github.com/arraylabs/pymyq/archive/v0.0.8.zip'
'#pymyq==0.0.8']
COVER_SCHEMA = vol.Schema({
vol.Required(CONF_TYPE): cv.string,

View File

@@ -6,9 +6,9 @@ https://home-assistant.io/components/cover.vera/
"""
import logging
from homeassistant.components.cover import CoverDevice
from homeassistant.components.cover import CoverDevice, ENTITY_ID_FORMAT
from homeassistant.components.vera import (
VeraDevice, VERA_DEVICES, VERA_CONTROLLER)
VERA_CONTROLLER, VERA_DEVICES, VeraDevice)
DEPENDENCIES = ['vera']
@@ -28,6 +28,7 @@ class VeraCover(VeraDevice, CoverDevice):
def __init__(self, vera_device, controller):
"""Initialize the Vera device."""
VeraDevice.__init__(self, vera_device, controller)
self.entity_id = ENTITY_ID_FORMAT.format(self.vera_id)
@property
def current_cover_position(self):
@@ -46,6 +47,7 @@ class VeraCover(VeraDevice, CoverDevice):
def set_cover_position(self, position, **kwargs):
"""Move the cover to a specific position."""
self.vera_device.set_level(position)
self.schedule_update_ha_state()
@property
def is_closed(self):
@@ -59,11 +61,14 @@ class VeraCover(VeraDevice, CoverDevice):
def open_cover(self, **kwargs):
"""Open the cover."""
self.vera_device.open()
self.schedule_update_ha_state()
def close_cover(self, **kwargs):
"""Close the cover."""
self.vera_device.close()
self.schedule_update_ha_state()
def stop_cover(self, **kwargs):
"""Stop the cover."""
self.vera_device.stop()
self.schedule_update_ha_state()

View File

@@ -11,6 +11,7 @@ from homeassistant.components.cover import (
DOMAIN, SUPPORT_OPEN, SUPPORT_CLOSE)
from homeassistant.components.zwave import ZWaveDeviceEntity
from homeassistant.components import zwave
from homeassistant.components.zwave import async_setup_platform # noqa # pylint: disable=unused-import
from homeassistant.components.zwave import workaround
from homeassistant.components.cover import CoverDevice
@@ -19,42 +20,33 @@ _LOGGER = logging.getLogger(__name__)
SUPPORT_GARAGE = SUPPORT_OPEN | SUPPORT_CLOSE
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Find and return Z-Wave covers."""
if discovery_info is None or zwave.NETWORK is None:
return
node = zwave.NETWORK.nodes[discovery_info[zwave.const.ATTR_NODE_ID]]
value = node.values[discovery_info[zwave.const.ATTR_VALUE_ID]]
if (value.command_class == zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL
and value.index == 0):
value.set_change_verified(False)
add_devices([ZwaveRollershutter(value)])
elif (value.command_class == zwave.const.COMMAND_CLASS_SWITCH_BINARY or
value.command_class == zwave.const.COMMAND_CLASS_BARRIER_OPERATOR):
if (value.type != zwave.const.TYPE_BOOL and
value.genre != zwave.const.GENRE_USER):
return
value.set_change_verified(False)
add_devices([ZwaveGarageDoor(value)])
else:
return
def get_device(values, node_config, **kwargs):
"""Create zwave entity device."""
invert_buttons = node_config.get(zwave.CONF_INVERT_OPENCLOSE_BUTTONS)
if (values.primary.command_class ==
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL
and values.primary.index == 0):
return ZwaveRollershutter(values, invert_buttons)
elif (values.primary.command_class in [
zwave.const.COMMAND_CLASS_SWITCH_BINARY,
zwave.const.COMMAND_CLASS_BARRIER_OPERATOR]):
return ZwaveGarageDoor(values)
return None
class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice):
"""Representation of an Zwave roller shutter."""
def __init__(self, value):
def __init__(self, values, invert_buttons):
"""Initialize the zwave rollershutter."""
ZWaveDeviceEntity.__init__(self, value, DOMAIN)
ZWaveDeviceEntity.__init__(self, values, DOMAIN)
# pylint: disable=no-member
self._node = value.node
self._open_id = None
self._close_id = None
self._current_position = None
self._invert_buttons = invert_buttons
self._workaround = workaround.get_device_mapping(value)
self._workaround = workaround.get_device_mapping(values.primary)
if self._workaround:
_LOGGER.debug("Using workaround %s", self._workaround)
self.update_properties()
@@ -62,17 +54,16 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice):
def update_properties(self):
"""Callback on data changes for node values."""
# Position value
self._current_position = self.get_value(
class_id=zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL,
label=['Level'], member='data')
self._open_id = self.get_value(
class_id=zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL,
label=['Open', 'Up'], member='value_id')
self._close_id = self.get_value(
class_id=zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL,
label=['Close', 'Down'], member='value_id')
if self._workaround == workaround.WORKAROUND_REVERSE_OPEN_CLOSE:
self._open_id, self._close_id = self._close_id, self._open_id
self._current_position = self.values.primary.data
if self.values.open and self.values.close and \
self._open_id is None and self._close_id is None:
if self._invert_buttons:
self._open_id = self.values.close.value_id
self._close_id = self.values.open.value_id
else:
self._open_id = self.values.open.value_id
self._close_id = self.values.close.value_id
@property
def is_closed(self):
@@ -107,7 +98,7 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice):
def set_cover_position(self, position, **kwargs):
"""Move the roller shutter to a specific position."""
self._node.set_dimmer(self._value.value_id, position)
self.node.set_dimmer(self.values.primary.value_id, position)
def stop_cover(self, **kwargs):
"""Stop the roller shutter."""
@@ -117,14 +108,14 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice):
class ZwaveGarageDoor(zwave.ZWaveDeviceEntity, CoverDevice):
"""Representation of an Zwave garage door device."""
def __init__(self, value):
def __init__(self, values):
"""Initialize the zwave garage door."""
ZWaveDeviceEntity.__init__(self, value, DOMAIN)
ZWaveDeviceEntity.__init__(self, values, DOMAIN)
self.update_properties()
def update_properties(self):
"""Callback on data changes for node values."""
self._state = self._value.data
self._state = self.values.primary.data
@property
def is_closed(self):
@@ -133,11 +124,11 @@ class ZwaveGarageDoor(zwave.ZWaveDeviceEntity, CoverDevice):
def close_cover(self):
"""Close the garage door."""
self._value.data = False
self.values.primary.data = False
def open_cover(self):
"""Open the garage door."""
self._value.data = True
self.values.primary.data = True
@property
def device_class(self):

View File

@@ -4,6 +4,7 @@ Sets up a demo environment that mimics interaction with devices.
For more details about this component, please refer to the documentation
https://home-assistant.io/components/demo/
"""
import asyncio
import time
import homeassistant.bootstrap as bootstrap
@@ -34,7 +35,8 @@ COMPONENTS_WITH_DEMO_PLATFORM = [
]
def setup(hass, config):
@asyncio.coroutine
def async_setup(hass, config):
"""Setup a demo environment."""
group = loader.get_component('group')
configurator = loader.get_component('configurator')
@@ -44,7 +46,7 @@ def setup(hass, config):
config.setdefault(DOMAIN, {})
if config[DOMAIN].get('hide_demo_state') != 1:
hass.states.set('a.Demo_Mode', 'Enabled')
hass.states.async_set('a.Demo_Mode', 'Enabled')
# Setup sun
if not hass.config.latitude:
@@ -53,50 +55,71 @@ def setup(hass, config):
if not hass.config.longitude:
hass.config.longitude = 117.22743
bootstrap.setup_component(hass, 'sun')
tasks = [
bootstrap.async_setup_component(hass, 'sun')
]
# Setup demo platforms
demo_config = config.copy()
for component in COMPONENTS_WITH_DEMO_PLATFORM:
demo_config[component] = {CONF_PLATFORM: 'demo'}
bootstrap.setup_component(hass, component, demo_config)
tasks.append(
bootstrap.async_setup_component(hass, component, demo_config))
# Set up input select
tasks.append(bootstrap.async_setup_component(
hass, 'input_select',
{'input_select':
{'living_room_preset': {'options': ['Visitors',
'Visitors with kids',
'Home Alone']},
'who_cooks': {'icon': 'mdi:panda',
'initial': 'Anne Therese',
'name': 'Cook today',
'options': ['Paulus', 'Anne Therese']}}}))
# Set up input boolean
tasks.append(bootstrap.async_setup_component(
hass, 'input_boolean',
{'input_boolean': {'notify': {
'icon': 'mdi:car',
'initial': False,
'name': 'Notify Anne Therese is home'}}}))
# Set up input boolean
tasks.append(bootstrap.async_setup_component(
hass, 'input_slider',
{'input_slider': {
'noise_allowance': {'icon': 'mdi:bell-ring',
'min': 0,
'max': 10,
'name': 'Allowed Noise',
'unit_of_measurement': 'dB'}}}))
# Set up weblink
tasks.append(bootstrap.async_setup_component(
hass, 'weblink',
{'weblink': {'entities': [{'name': 'Router',
'url': 'http://192.168.1.1'}]}}))
results = yield from asyncio.gather(*tasks, loop=hass.loop)
if any(not result for result in results):
return False
# Setup example persistent notification
persistent_notification.create(
persistent_notification.async_create(
hass, 'This is an example of a persistent notification.',
title='Example Notification')
# Setup room groups
lights = sorted(hass.states.entity_ids('light'))
switches = sorted(hass.states.entity_ids('switch'))
media_players = sorted(hass.states.entity_ids('media_player'))
lights = sorted(hass.states.async_entity_ids('light'))
switches = sorted(hass.states.async_entity_ids('switch'))
media_players = sorted(hass.states.async_entity_ids('media_player'))
group.Group.create_group(hass, 'living room', [
lights[1], switches[0], 'input_select.living_room_preset',
'rollershutter.living_room_window', media_players[1],
'scene.romantic_lights'])
group.Group.create_group(hass, 'bedroom', [
lights[0], switches[1], media_players[0],
'input_slider.noise_allowance'])
group.Group.create_group(hass, 'kitchen', [
lights[2], 'rollershutter.kitchen_window', 'lock.kitchen_door'])
group.Group.create_group(hass, 'doors', [
'lock.front_door', 'lock.kitchen_door',
'garage_door.right_garage_door', 'garage_door.left_garage_door'])
group.Group.create_group(hass, 'automations', [
'input_select.who_cooks', 'input_boolean.notify', ])
group.Group.create_group(hass, 'people', [
'device_tracker.demo_anne_therese', 'device_tracker.demo_home_boy',
'device_tracker.demo_paulus'])
group.Group.create_group(hass, 'downstairs', [
'group.living_room', 'group.kitchen',
'scene.romantic_lights', 'rollershutter.kitchen_window',
'rollershutter.living_room_window', 'group.doors',
'thermostat.ecobee',
], view=True)
tasks2 = []
# Setup scripts
bootstrap.setup_component(
tasks2.append(bootstrap.async_setup_component(
hass, 'script',
{'script': {
'demo': {
@@ -115,10 +138,10 @@ def setup(hass, config):
'service': 'light.turn_off',
'data': {ATTR_ENTITY_ID: lights[0]}
}]
}}})
}}}))
# Setup scenes
bootstrap.setup_component(
tasks2.append(bootstrap.async_setup_component(
hass, 'scene',
{'scene': [
{'name': 'Romantic lights',
@@ -132,41 +155,37 @@ def setup(hass, config):
switches[0]: True,
switches[1]: False,
}},
]})
]}))
# Set up input select
bootstrap.setup_component(
hass, 'input_select',
{'input_select':
{'living_room_preset': {'options': ['Visitors',
'Visitors with kids',
'Home Alone']},
'who_cooks': {'icon': 'mdi:panda',
'initial': 'Anne Therese',
'name': 'Cook today',
'options': ['Paulus', 'Anne Therese']}}})
# Set up input boolean
bootstrap.setup_component(
hass, 'input_boolean',
{'input_boolean': {'notify': {'icon': 'mdi:car',
'initial': False,
'name': 'Notify Anne Therese is home'}}})
tasks2.append(group.Group.async_create_group(hass, 'living room', [
lights[1], switches[0], 'input_select.living_room_preset',
'rollershutter.living_room_window', media_players[1],
'scene.romantic_lights']))
tasks2.append(group.Group.async_create_group(hass, 'bedroom', [
lights[0], switches[1], media_players[0],
'input_slider.noise_allowance']))
tasks2.append(group.Group.async_create_group(hass, 'kitchen', [
lights[2], 'rollershutter.kitchen_window', 'lock.kitchen_door']))
tasks2.append(group.Group.async_create_group(hass, 'doors', [
'lock.front_door', 'lock.kitchen_door',
'garage_door.right_garage_door', 'garage_door.left_garage_door']))
tasks2.append(group.Group.async_create_group(hass, 'automations', [
'input_select.who_cooks', 'input_boolean.notify', ]))
tasks2.append(group.Group.async_create_group(hass, 'people', [
'device_tracker.demo_anne_therese', 'device_tracker.demo_home_boy',
'device_tracker.demo_paulus']))
tasks2.append(group.Group.async_create_group(hass, 'downstairs', [
'group.living_room', 'group.kitchen',
'scene.romantic_lights', 'rollershutter.kitchen_window',
'rollershutter.living_room_window', 'group.doors',
'thermostat.ecobee',
], view=True))
# Set up input boolean
bootstrap.setup_component(
hass, 'input_slider',
{'input_slider': {
'noise_allowance': {'icon': 'mdi:bell-ring',
'min': 0,
'max': 10,
'name': 'Allowed Noise',
'unit_of_measurement': 'dB'}}})
results = yield from asyncio.gather(*tasks2, loop=hass.loop)
if any(not result for result in results):
return False
# Set up weblink
bootstrap.setup_component(
hass, 'weblink',
{'weblink': {'entities': [{'name': 'Router',
'url': 'http://192.168.1.1'}]}})
# Setup configurator
configurator_ids = []
@@ -184,14 +203,17 @@ def setup(hass, config):
else:
configurator.request_done(configurator_ids[0])
request_id = configurator.request_config(
hass, "Philips Hue", hue_configuration_callback,
description=("Press the button on the bridge to register Philips Hue "
"with Home Assistant."),
description_image="/static/images/config_philips_hue.jpg",
submit_caption="I have pressed the button"
)
def setup_configurator():
"""Setup configurator."""
request_id = configurator.request_config(
hass, "Philips Hue", hue_configuration_callback,
description=("Press the button on the bridge to register Philips "
"Hue with Home Assistant."),
description_image="/static/images/config_philips_hue.jpg",
submit_caption="I have pressed the button"
)
configurator_ids.append(request_id)
configurator_ids.append(request_id)
hass.async_add_job(setup_configurator)
return True

View File

@@ -14,12 +14,11 @@ import aiohttp
import async_timeout
import voluptuous as vol
from homeassistant.bootstrap import (
async_prepare_setup_platform, async_log_exception)
from homeassistant.setup import async_prepare_setup_platform
from homeassistant.core import callback
from homeassistant.components import group, zone
from homeassistant.components.discovery import SERVICE_NETGEAR
from homeassistant.config import load_yaml_config_file
from homeassistant.config import load_yaml_config_file, async_log_exception
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers import config_per_platform, discovery
@@ -133,18 +132,6 @@ def async_setup(hass: HomeAssistantType, config: ConfigType):
devices = yield from async_load_config(yaml_path, hass, consider_home)
tracker = DeviceTracker(hass, consider_home, track_new, devices)
# added_to_hass
add_tasks = [device.async_added_to_hass() for device in devices
if device.track]
if add_tasks:
yield from asyncio.wait(add_tasks, loop=hass.loop)
# update tracked devices
update_tasks = [device.async_update_ha_state() for device in devices
if device.track]
if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)
@asyncio.coroutine
def async_setup_platform(p_type, p_config, disc_info=None):
"""Setup a device tracker platform."""
@@ -227,6 +214,8 @@ def async_setup(hass: HomeAssistantType, config: ConfigType):
hass.services.async_register(
DOMAIN, SERVICE_SEE, async_see_service, descriptions.get(SERVICE_SEE))
# restore
yield from tracker.async_setup_tracked_device()
return True
@@ -357,6 +346,27 @@ class DeviceTracker(object):
device.stale(now):
self.hass.async_add_job(device.async_update_ha_state(True))
@asyncio.coroutine
def async_setup_tracked_device(self):
"""Setup all not exists tracked devices.
This method is a coroutine.
"""
@asyncio.coroutine
def async_init_single_device(dev):
"""Init a single device_tracker entity."""
yield from dev.async_added_to_hass()
yield from dev.async_update_ha_state()
tasks = []
for device in self.devices.values():
if device.track and not device.last_seen:
tasks.append(self.hass.async_add_job(
async_init_single_device(device)))
if tasks:
yield from asyncio.wait(tasks, loop=self.hass.loop)
class Device(Entity):
"""Represent a tracked device."""
@@ -543,7 +553,6 @@ class Device(Entity):
# bytes like 00 get truncates to 0, API needs full bytes
oui = '{:02x}:{:02x}:{:02x}'.format(*[int(b, 16) for b in oui_bytes])
url = 'http://api.macvendors.com/' + oui
resp = None
try:
websession = async_get_clientsession(self.hass)
@@ -560,13 +569,9 @@ class Device(Entity):
# in the 'known_devices.yaml' file which only happens
# the first time the device is seen.
return 'unknown'
except (asyncio.TimeoutError, aiohttp.errors.ClientError,
aiohttp.errors.ClientDisconnectedError):
except (asyncio.TimeoutError, aiohttp.ClientError):
# same as above
return 'unknown'
finally:
if resp is not None:
yield from resp.release()
@asyncio.coroutine
def async_added_to_hass(self):

View File

@@ -110,7 +110,7 @@ def setup_scanner(hass, config, see, discovery_info=None):
_LOGGER.info("Discovered Bluetooth LE device %s", address)
see_device(address, devs[address], new_device=True)
track_point_in_utc_time(hass, update_ble, now + interval)
track_point_in_utc_time(hass, update_ble, dt_util.utcnow() + interval)
update_ble(dt_util.utcnow())

View File

@@ -82,7 +82,8 @@ def setup_scanner(hass, config, see, discovery_info=None):
see_device((mac, result))
except bluetooth.BluetoothError:
_LOGGER.exception('Error looking up bluetooth device!')
track_point_in_utc_time(hass, update_bluetooth, now + interval)
track_point_in_utc_time(
hass, update_bluetooth, dt_util.utcnow() + interval)
update_bluetooth(dt_util.utcnow())

View File

@@ -15,7 +15,7 @@ from homeassistant.components.device_tracker import (
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.util import Throttle
REQUIREMENTS = ['fritzconnection==0.6']
REQUIREMENTS = ['fritzconnection==0.6.3']
# Return cached results if last scan was less then this time ago.
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)

View File

@@ -197,13 +197,22 @@ class Icloud(DeviceScanner):
def icloud_trusted_device_callback(self, callback_data):
"""The trusted device is chosen."""
self._trusted_device = int(callback_data.get('0', '0'))
self._trusted_device = int(callback_data.get('trusted_device'))
self._trusted_device = self.api.trusted_devices[self._trusted_device]
if not self.api.send_verification_code(self._trusted_device):
_LOGGER.error('Failed to send verification code')
self._trusted_device = None
return
if self.accountname in _CONFIGURING:
request_id = _CONFIGURING.pop(self.accountname)
configurator = get_component('configurator')
configurator.request_done(request_id)
# Trigger the next step immediately
self.icloud_need_verification_code()
def icloud_need_trusted_device(self):
"""We need a trusted device."""
configurator = get_component('configurator')
@@ -213,7 +222,10 @@ class Icloud(DeviceScanner):
devicesstring = ''
devices = self.api.trusted_devices
for i, device in enumerate(devices):
devicesstring += "{}: {};".format(i, device.get('deviceName'))
devicename = device.get(
'deviceName',
'SMS to %s' % device.get('phoneNumber'))
devicesstring += "{}: {};".format(i, devicename)
_CONFIGURING[self.accountname] = configurator.request_config(
self.hass, 'iCloud {}'.format(self.accountname),
@@ -223,12 +235,27 @@ class Icloud(DeviceScanner):
' the index from this list: ' + devicesstring),
entity_picture="/static/images/config_icloud.png",
submit_caption='Confirm',
fields=[{'id': '0'}]
fields=[{'id': 'trusted_device', 'name': 'Trusted Device'}]
)
def icloud_verification_callback(self, callback_data):
"""The trusted device is chosen."""
self._verification_code = callback_data.get('0')
from pyicloud.exceptions import PyiCloudException
self._verification_code = callback_data.get('code')
try:
if not self.api.validate_verification_code(
self._trusted_device, self._verification_code):
raise PyiCloudException('Unknown failure')
except PyiCloudException as error:
# Reset to the inital 2FA state to allow the user to retry
_LOGGER.error('Failed to verify verification code: %s', error)
self._trusted_device = None
self._verification_code = None
# Trigger the next step immediately
self.icloud_need_trusted_device()
if self.accountname in _CONFIGURING:
request_id = _CONFIGURING.pop(self.accountname)
configurator = get_component('configurator')
@@ -240,22 +267,17 @@ class Icloud(DeviceScanner):
if self.accountname in _CONFIGURING:
return
if self.api.send_verification_code(self._trusted_device):
self._verification_code = 'waiting'
_CONFIGURING[self.accountname] = configurator.request_config(
self.hass, 'iCloud {}'.format(self.accountname),
self.icloud_verification_callback,
description=('Please enter the validation code:'),
entity_picture="/static/images/config_icloud.png",
submit_caption='Confirm',
fields=[{'code': '0'}]
fields=[{'id': 'code', 'name': 'code'}]
)
def keep_alive(self, now):
"""Keep the api alive."""
from pyicloud.exceptions import PyiCloud2FARequiredError
if self.api is None:
self.reset_account_icloud()
@@ -263,9 +285,8 @@ class Icloud(DeviceScanner):
return
if self.api.requires_2fa:
from pyicloud.exceptions import PyiCloudException
try:
self.api.authenticate()
except PyiCloud2FARequiredError:
if self._trusted_device is None:
self.icloud_need_trusted_device()
return
@@ -274,12 +295,14 @@ class Icloud(DeviceScanner):
self.icloud_need_verification_code()
return
if self._verification_code == 'waiting':
return
self.api.authenticate()
if self.api.requires_2fa:
raise Exception('Unknown failure')
if self.api.validate_verification_code(
self._trusted_device, self._verification_code):
self._verification_code = None
self._trusted_device = None
self._verification_code = None
except PyiCloudException as error:
_LOGGER.error("Error setting up 2fa: %s", error)
else:
self.api.authenticate()
@@ -397,13 +420,13 @@ class Icloud(DeviceScanner):
try:
if devicename is not None:
if devicename in self.devices:
self.devices[devicename].update_icloud()
self.devices[devicename].location()
else:
_LOGGER.error("devicename %s unknown for account %s",
devicename, self._attrs[ATTR_ACCOUNTNAME])
else:
for device in self.devices:
self.devices[device].update_icloud()
self.devices[device].location()
except PyiCloudNoDevicesException:
_LOGGER.error('No iCloud Devices found!')

View File

@@ -18,6 +18,7 @@ from homeassistant.components.device_tracker import ( # NOQA
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['http']
URL = '/api/locative'
def setup_scanner(hass, config, see, discovery_info=None):
@@ -30,7 +31,7 @@ def setup_scanner(hass, config, see, discovery_info=None):
class LocativeView(HomeAssistantView):
"""View to handle locative requests."""
url = '/api/locative'
url = URL
name = 'api:locative'
def __init__(self, see):

View File

@@ -14,6 +14,7 @@ import requests
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.exceptions import HomeAssistantError
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
@@ -31,6 +32,12 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
class InvalidLuciTokenError(HomeAssistantError):
"""When an invalid token is detected."""
pass
def get_scanner(hass, config):
"""Validate the configuration and return a Luci scanner."""
scanner = LuciDeviceScanner(config[DOMAIN])
@@ -46,8 +53,9 @@ class LuciDeviceScanner(DeviceScanner):
def __init__(self, config):
"""Initialize the scanner."""
host = config[CONF_HOST]
username, password = config[CONF_USERNAME], config[CONF_PASSWORD]
self.host = config[CONF_HOST]
self.username = config[CONF_USERNAME]
self.password = config[CONF_PASSWORD]
self.parse_api_pattern = re.compile(r"(?P<param>\w*) = (?P<value>.*);")
@@ -55,12 +63,15 @@ class LuciDeviceScanner(DeviceScanner):
self.last_results = {}
self.token = _get_token(host, username, password)
self.host = host
self.refresh_token()
self.mac2name = None
self.success_init = self.token is not None
def refresh_token(self):
"""Get a new token."""
self.token = _get_token(self.host, self.username, self.password)
def scan_devices(self):
"""Scan for new devices and return a list with found device IDs."""
self._update_info()
@@ -98,8 +109,15 @@ class LuciDeviceScanner(DeviceScanner):
_LOGGER.info('Checking ARP')
url = 'http://{}/cgi-bin/luci/rpc/sys'.format(self.host)
result = _req_json_rpc(url, 'net.arptable',
params={'auth': self.token})
try:
result = _req_json_rpc(url, 'net.arptable',
params={'auth': self.token})
except InvalidLuciTokenError:
_LOGGER.info('Refreshing token')
self.refresh_token()
return False
if result:
self.last_results = []
for device_entry in result:
@@ -116,6 +134,7 @@ class LuciDeviceScanner(DeviceScanner):
def _req_json_rpc(url, method, *args, **kwargs):
"""Perform one JSON RPC operation."""
data = json.dumps({'method': method, 'params': args})
try:
res = requests.post(url, data=data, timeout=5, **kwargs)
except requests.exceptions.Timeout:
@@ -139,6 +158,10 @@ def _req_json_rpc(url, method, *args, **kwargs):
"Failed to authenticate, "
"please check your username and password")
return
elif res.status_code == 403:
_LOGGER.error('Luci responded with a 403 Invalid token')
raise InvalidLuciTokenError
else:
_LOGGER.error('Invalid response from luci: %s', res)

View File

@@ -4,11 +4,13 @@ Support for tracking MQTT enabled devices.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.mqtt/
"""
import asyncio
import logging
import voluptuous as vol
import homeassistant.components.mqtt as mqtt
from homeassistant.core import callback
from homeassistant.const import CONF_DEVICES
from homeassistant.components.mqtt import CONF_QOS
from homeassistant.components.device_tracker import PLATFORM_SCHEMA
@@ -23,19 +25,23 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(mqtt.SCHEMA_BASE).extend({
})
def setup_scanner(hass, config, see, discovery_info=None):
@asyncio.coroutine
def async_setup_scanner(hass, config, async_see, discovery_info=None):
"""Setup the MQTT tracker."""
devices = config[CONF_DEVICES]
qos = config[CONF_QOS]
dev_id_lookup = {}
def device_tracker_message_received(topic, payload, qos):
@callback
def async_tracker_message_received(topic, payload, qos):
"""MQTT message received."""
see(dev_id=dev_id_lookup[topic], location_name=payload)
hass.async_add_job(
async_see(dev_id=dev_id_lookup[topic], location_name=payload))
for dev_id, topic in devices.items():
dev_id_lookup[topic] = dev_id
mqtt.subscribe(hass, topic, device_tracker_message_received, qos)
yield from mqtt.async_subscribe(
hass, topic, async_tracker_message_received, qos)
return True

View File

@@ -16,11 +16,11 @@ _LOGGER = logging.getLogger(__name__)
def setup_scanner(hass, config, see, discovery_info=None):
"""Setup the MySensors tracker."""
def mysensors_callback(gateway, node_id):
def mysensors_callback(gateway, msg):
"""Callback for mysensors platform."""
node = gateway.sensors[node_id]
node = gateway.sensors[msg.node_id]
if node.sketch_name is None:
_LOGGER.info('No sketch_name: node %s', node_id)
_LOGGER.info('No sketch_name: node %s', msg.node_id)
return
pres = gateway.const.Presentation
@@ -37,12 +37,12 @@ def setup_scanner(hass, config, see, discovery_info=None):
'latitude,longitude,altitude', position)
continue
name = '{} {} {}'.format(
node.sketch_name, node_id, child.id)
node.sketch_name, msg.node_id, child.id)
attr = {
mysensors.ATTR_CHILD_ID: child.id,
mysensors.ATTR_DESCRIPTION: child.description,
mysensors.ATTR_DEVICE: gateway.device,
mysensors.ATTR_NODE_ID: node_id,
mysensors.ATTR_NODE_ID: msg.node_id,
}
see(
dev_id=slugify(name),

View File

@@ -4,14 +4,15 @@ Support the OwnTracks platform.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.owntracks/
"""
import asyncio
import json
import logging
import threading
import base64
from collections import defaultdict
import voluptuous as vol
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
import homeassistant.components.mqtt as mqtt
from homeassistant.const import STATE_HOME
@@ -19,6 +20,7 @@ from homeassistant.util import convert, slugify
from homeassistant.components import zone as zone_comp
from homeassistant.components.device_tracker import PLATFORM_SCHEMA
DEPENDENCIES = ['mqtt']
REQUIREMENTS = ['libnacl==1.5.0']
_LOGGER = logging.getLogger(__name__)
@@ -30,16 +32,9 @@ CONF_SECRET = 'secret'
CONF_WAYPOINT_IMPORT = 'waypoints'
CONF_WAYPOINT_WHITELIST = 'waypoint_whitelist'
DEPENDENCIES = ['mqtt']
EVENT_TOPIC = 'owntracks/+/+/event'
LOCATION_TOPIC = 'owntracks/+/+'
LOCK = threading.Lock()
MOBILE_BEACONS_ACTIVE = defaultdict(list)
REGIONS_ENTERED = defaultdict(list)
VALIDATE_LOCATION = 'location'
VALIDATE_TRANSITION = 'transition'
@@ -61,7 +56,10 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
def get_cipher():
"""Return decryption function and length of key."""
"""Return decryption function and length of key.
Async friendly.
"""
from libnacl import crypto_secretbox_KEYBYTES as KEYLEN
from libnacl.secret import SecretBox
@@ -71,13 +69,17 @@ def get_cipher():
return (KEYLEN, decrypt)
def setup_scanner(hass, config, see, discovery_info=None):
@asyncio.coroutine
def async_setup_scanner(hass, config, async_see, discovery_info=None):
"""Set up an OwnTracks tracker."""
max_gps_accuracy = config.get(CONF_MAX_GPS_ACCURACY)
waypoint_import = config.get(CONF_WAYPOINT_IMPORT)
waypoint_whitelist = config.get(CONF_WAYPOINT_WHITELIST)
secret = config.get(CONF_SECRET)
mobile_beacons_active = defaultdict(list)
regions_entered = defaultdict(list)
def decrypt_payload(topic, ciphertext):
"""Decrypt encrypted payload."""
try:
@@ -154,7 +156,8 @@ def setup_scanner(hass, config, see, discovery_info=None):
return data
def owntracks_location_update(topic, payload, qos):
@callback
def async_owntracks_location_update(topic, payload, qos):
"""MQTT message received."""
# Docs on available data:
# http://owntracks.org/booklet/tech/json/#_typelocation
@@ -164,18 +167,17 @@ def setup_scanner(hass, config, see, discovery_info=None):
dev_id, kwargs = _parse_see_args(topic, data)
# Block updates if we're in a region
with LOCK:
if REGIONS_ENTERED[dev_id]:
_LOGGER.debug(
"location update ignored - inside region %s",
REGIONS_ENTERED[-1])
return
if regions_entered[dev_id]:
_LOGGER.debug(
"location update ignored - inside region %s",
regions_entered[-1])
return
see(**kwargs)
see_beacons(dev_id, kwargs)
hass.async_add_job(async_see(**kwargs))
async_see_beacons(dev_id, kwargs)
def owntracks_event_update(topic, payload, qos):
@callback
def async_owntracks_event_update(topic, payload, qos):
"""MQTT event (geofences) received."""
# Docs on available data:
# http://owntracks.org/booklet/tech/json/#_typetransition
@@ -199,67 +201,65 @@ def setup_scanner(hass, config, see, discovery_info=None):
def enter_event():
"""Execute enter event."""
zone = hass.states.get("zone.{}".format(slugify(location)))
with LOCK:
if zone is None and data.get('t') == 'b':
# Not a HA zone, and a beacon so assume mobile
beacons = MOBILE_BEACONS_ACTIVE[dev_id]
if location not in beacons:
beacons.append(location)
_LOGGER.info("Added beacon %s", location)
else:
# Normal region
regions = REGIONS_ENTERED[dev_id]
if location not in regions:
regions.append(location)
_LOGGER.info("Enter region %s", location)
_set_gps_from_zone(kwargs, location, zone)
if zone is None and data.get('t') == 'b':
# Not a HA zone, and a beacon so assume mobile
beacons = mobile_beacons_active[dev_id]
if location not in beacons:
beacons.append(location)
_LOGGER.info("Added beacon %s", location)
else:
# Normal region
regions = regions_entered[dev_id]
if location not in regions:
regions.append(location)
_LOGGER.info("Enter region %s", location)
_set_gps_from_zone(kwargs, location, zone)
see(**kwargs)
see_beacons(dev_id, kwargs)
hass.async_add_job(async_see(**kwargs))
async_see_beacons(dev_id, kwargs)
def leave_event():
"""Execute leave event."""
with LOCK:
regions = REGIONS_ENTERED[dev_id]
if location in regions:
regions.remove(location)
new_region = regions[-1] if regions else None
regions = regions_entered[dev_id]
if location in regions:
regions.remove(location)
new_region = regions[-1] if regions else None
if new_region:
# Exit to previous region
zone = hass.states.get(
"zone.{}".format(slugify(new_region)))
_set_gps_from_zone(kwargs, new_region, zone)
_LOGGER.info("Exit to %s", new_region)
see(**kwargs)
see_beacons(dev_id, kwargs)
if new_region:
# Exit to previous region
zone = hass.states.get(
"zone.{}".format(slugify(new_region)))
_set_gps_from_zone(kwargs, new_region, zone)
_LOGGER.info("Exit to %s", new_region)
hass.async_add_job(async_see(**kwargs))
async_see_beacons(dev_id, kwargs)
else:
_LOGGER.info("Exit to GPS")
# Check for GPS accuracy
valid_gps = True
if 'acc' in data:
if data['acc'] == 0.0:
valid_gps = False
_LOGGER.warning(
'Ignoring GPS in region exit because accuracy'
'is zero: %s',
payload)
if (max_gps_accuracy is not None and
data['acc'] > max_gps_accuracy):
valid_gps = False
_LOGGER.info(
'Ignoring GPS in region exit because expected '
'GPS accuracy %s is not met: %s',
max_gps_accuracy, payload)
if valid_gps:
see(**kwargs)
see_beacons(dev_id, kwargs)
else:
_LOGGER.info("Exit to GPS")
# Check for GPS accuracy
valid_gps = True
if 'acc' in data:
if data['acc'] == 0.0:
valid_gps = False
_LOGGER.warning(
'Ignoring GPS in region exit because accuracy'
'is zero: %s',
payload)
if (max_gps_accuracy is not None and
data['acc'] > max_gps_accuracy):
valid_gps = False
_LOGGER.info(
'Ignoring GPS in region exit because expected '
'GPS accuracy %s is not met: %s',
max_gps_accuracy, payload)
if valid_gps:
hass.async_add_job(async_see(**kwargs))
async_see_beacons(dev_id, kwargs)
beacons = MOBILE_BEACONS_ACTIVE[dev_id]
if location in beacons:
beacons.remove(location)
_LOGGER.info("Remove beacon %s", location)
beacons = mobile_beacons_active[dev_id]
if location in beacons:
beacons.remove(location)
_LOGGER.info("Remove beacon %s", location)
if data['event'] == 'enter':
enter_event()
@@ -271,7 +271,8 @@ def setup_scanner(hass, config, see, discovery_info=None):
data['event'])
return
def owntracks_waypoint_update(topic, payload, qos):
@callback
def async_owntracks_waypoint_update(topic, payload, qos):
"""List of waypoints published by a user."""
# Docs on available data:
# http://owntracks.org/booklet/tech/json/#_typewaypoints
@@ -298,36 +299,43 @@ def setup_scanner(hass, config, see, discovery_info=None):
zone = zone_comp.Zone(hass, pretty_name, lat, lon, rad,
zone_comp.ICON_IMPORT, False)
zone.entity_id = entity_id
zone.update_ha_state()
hass.async_add_job(zone.async_update_ha_state())
def see_beacons(dev_id, kwargs_param):
@callback
def async_see_beacons(dev_id, kwargs_param):
"""Set active beacons to the current location."""
kwargs = kwargs_param.copy()
# the battery state applies to the tracking device, not the beacon
kwargs.pop('battery', None)
for beacon in MOBILE_BEACONS_ACTIVE[dev_id]:
for beacon in mobile_beacons_active[dev_id]:
kwargs['dev_id'] = "{}_{}".format(BEACON_DEV_ID, beacon)
kwargs['host_name'] = beacon
see(**kwargs)
hass.async_add_job(async_see(**kwargs))
mqtt.subscribe(hass, LOCATION_TOPIC, owntracks_location_update, 1)
mqtt.subscribe(hass, EVENT_TOPIC, owntracks_event_update, 1)
yield from mqtt.async_subscribe(
hass, LOCATION_TOPIC, async_owntracks_location_update, 1)
yield from mqtt.async_subscribe(
hass, EVENT_TOPIC, async_owntracks_event_update, 1)
if waypoint_import:
if waypoint_whitelist is None:
mqtt.subscribe(hass, WAYPOINT_TOPIC.format('+', '+'),
owntracks_waypoint_update, 1)
yield from mqtt.async_subscribe(
hass, WAYPOINT_TOPIC.format('+', '+'),
async_owntracks_waypoint_update, 1)
else:
for whitelist_user in waypoint_whitelist:
mqtt.subscribe(hass, WAYPOINT_TOPIC.format(whitelist_user,
'+'),
owntracks_waypoint_update, 1)
yield from mqtt.async_subscribe(
hass, WAYPOINT_TOPIC.format(whitelist_user, '+'),
async_owntracks_waypoint_update, 1)
return True
def parse_topic(topic, pretty=False):
"""Parse an MQTT topic owntracks/user/dev, return (user, dev) tuple."""
"""Parse an MQTT topic owntracks/user/dev, return (user, dev) tuple.
Async friendly.
"""
parts = topic.split('/')
dev_id_format = ''
if pretty:
@@ -340,7 +348,10 @@ def parse_topic(topic, pretty=False):
def _parse_see_args(topic, data):
"""Parse the OwnTracks location parameters, into the format see expects."""
"""Parse the OwnTracks location parameters, into the format see expects.
Async friendly.
"""
(host_name, dev_id) = parse_topic(topic, False)
kwargs = {
'dev_id': dev_id,
@@ -355,7 +366,10 @@ def _parse_see_args(topic, data):
def _set_gps_from_zone(kwargs, location, zone):
"""Set the see parameters from the zone parameters."""
"""Set the see parameters from the zone parameters.
Async friendly.
"""
if zone is not None:
kwargs['gps'] = (
zone.attributes['latitude'],

View File

@@ -86,7 +86,7 @@ def setup_scanner(hass, config, see, discovery_info=None):
"""Update all the hosts on every interval time."""
for host in hosts:
host.update(see)
track_point_in_utc_time(hass, update, now + interval)
track_point_in_utc_time(hass, update, util.dt.utcnow() + interval)
return True
return update(util.dt.utcnow())

View File

@@ -19,7 +19,7 @@ from homeassistant.util import Throttle
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['pysnmp==4.3.3']
REQUIREMENTS = ['pysnmp==4.3.5']
CONF_COMMUNITY = 'community'
CONF_AUTHKEY = 'authkey'
@@ -125,7 +125,10 @@ class SnmpScanner(DeviceScanner):
for resrow in restable:
for _, val in resrow:
mac = binascii.hexlify(val.asOctets()).decode('utf-8')
try:
mac = binascii.hexlify(val.asOctets()).decode('utf-8')
except AttributeError:
continue
_LOGGER.debug("Found MAC %s", mac)
mac = ':'.join([mac[i:i+2] for i in range(0, len(mac), 2)])
devices.append({'mac': mac})

View File

@@ -105,8 +105,6 @@ class TadoDeviceScanner(DeviceScanner):
_LOGGER.debug("Requesting Tado")
last_results = []
response = None
tado_json = None
try:
with async_timeout.timeout(10, loop=self.hass.loop):
@@ -127,14 +125,10 @@ class TadoDeviceScanner(DeviceScanner):
tado_json = yield from response.json()
except (asyncio.TimeoutError, aiohttp.errors.ClientError):
except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.error("Cannot load Tado data")
return False
finally:
if response is not None:
yield from response.release()
# Without a home_id, we fetched an URL where the mobile devices can be
# found under the mobileDevices key.
if 'mobileDevices' in tado_json:
@@ -142,7 +136,7 @@ class TadoDeviceScanner(DeviceScanner):
# Find devices that have geofencing enabled, and are currently at home.
for mobile_device in tado_json:
if 'location' in mobile_device:
if mobile_device.get('location'):
if mobile_device['location']['atHome']:
device_id = mobile_device['id']
device_name = mobile_device['name']

View File

@@ -13,13 +13,15 @@ import homeassistant.loader as loader
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD
from homeassistant.const import CONF_VERIFY_SSL
# Unifi package doesn't list urllib3 as a requirement
REQUIREMENTS = ['urllib3', 'pyunifi==1.3']
REQUIREMENTS = ['pyunifi==2.0']
_LOGGER = logging.getLogger(__name__)
CONF_PORT = 'port'
CONF_SITE_ID = 'site_id'
DEFAULT_VERIFY_SSL = True
NOTIFICATION_ID = 'unifi_notification'
NOTIFICATION_TITLE = 'Unifi Device Tracker Setup'
@@ -29,7 +31,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_SITE_ID, default='default'): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PORT, default=8443): cv.port
vol.Required(CONF_PORT, default=8443): cv.port,
vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean,
})
@@ -42,10 +45,12 @@ def get_scanner(hass, config):
password = config[DOMAIN].get(CONF_PASSWORD)
site_id = config[DOMAIN].get(CONF_SITE_ID)
port = config[DOMAIN].get(CONF_PORT)
verify_ssl = config[DOMAIN].get(CONF_VERIFY_SSL)
persistent_notification = loader.get_component('persistent_notification')
try:
ctrl = Controller(host, username, password, port, 'v4', site_id)
ctrl = Controller(host, username, password, port, version='v4',
site_id=site_id, ssl_verify=verify_ssl)
except urllib.error.HTTPError as ex:
_LOGGER.error('Failed to connect to Unifi: %s', ex)
persistent_notification.create(

View File

@@ -101,7 +101,6 @@ class UPCDeviceScanner(DeviceScanner):
@asyncio.coroutine
def async_login(self):
"""Login into firmware and get first token."""
response = None
try:
# get first token
with async_timeout.timeout(10, loop=self.hass.loop):
@@ -109,7 +108,8 @@ class UPCDeviceScanner(DeviceScanner):
"http://{}/common_page/login.html".format(self.host)
)
yield from response.text()
yield from response.text()
self.token = response.cookies['sessionToken'].value
# login
@@ -119,18 +119,12 @@ class UPCDeviceScanner(DeviceScanner):
})
# successfull?
if data is not None:
return True
return False
return data is not None
except (asyncio.TimeoutError, aiohttp.errors.ClientError):
except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.error("Can not load login page from %s", self.host)
return False
finally:
if response is not None:
yield from response.release()
@asyncio.coroutine
def _async_ws_function(self, function, additional_form=None):
"""Execute a command on UPC firmware webservice."""
@@ -142,8 +136,7 @@ class UPCDeviceScanner(DeviceScanner):
if additional_form:
form_data.update(additional_form)
redirects = True if function != CMD_DEVICES else False
response = None
redirects = function != CMD_DEVICES
try:
with async_timeout.timeout(10, loop=self.hass.loop):
response = yield from self.websession.post(
@@ -163,10 +156,6 @@ class UPCDeviceScanner(DeviceScanner):
self.token = response.cookies['sessionToken'].value
return (yield from response.text())
except (asyncio.TimeoutError, aiohttp.errors.ClientError):
except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.error("Error on %s", function)
self.token = None
finally:
if response is not None:
yield from response.release()

View File

@@ -13,7 +13,7 @@ from homeassistant.const import CONF_ACCESS_TOKEN
from homeassistant.util import Throttle
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['python-digitalocean==1.10.1']
REQUIREMENTS = ['python-digitalocean==1.11']
_LOGGER = logging.getLogger(__name__)

View File

@@ -6,20 +6,25 @@ Will emit EVENT_PLATFORM_DISCOVERED whenever a new service has been discovered.
Knows which components handle certain types, will make sure they are
loaded before the EVENT_PLATFORM_DISCOVERED is fired.
"""
import asyncio
import json
from datetime import timedelta
import logging
import threading
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.core import callback
from homeassistant.const import EVENT_HOMEASSISTANT_START
from homeassistant.helpers.discovery import load_platform, discover
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import async_track_point_in_utc_time
from homeassistant.helpers.discovery import async_load_platform, async_discover
import homeassistant.util.dt as dt_util
REQUIREMENTS = ['netdisco==0.8.3']
REQUIREMENTS = ['netdisco==0.9.2']
DOMAIN = 'discovery'
SCAN_INTERVAL = 300 # seconds
SCAN_INTERVAL = timedelta(seconds=300)
SERVICE_NETGEAR = 'netgear_router'
SERVICE_WEMO = 'belkin_wemo'
SERVICE_HASS_IOS_APP = 'hass_ios'
@@ -42,24 +47,28 @@ SERVICE_HANDLERS = {
'yeelight': ('light', 'yeelight'),
'flux_led': ('light', 'flux_led'),
'apple_tv': ('media_player', 'apple_tv'),
'frontier_silicon': ('media_player', 'frontier_silicon'),
'openhome': ('media_player', 'openhome'),
}
CONF_IGNORE = 'ignore'
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(DOMAIN): vol.Schema({
vol.Optional(CONF_IGNORE, default=[]):
vol.All(cv.ensure_list, [vol.In(SERVICE_HANDLERS)])
}),
}, extra=vol.ALLOW_EXTRA)
def setup(hass, config):
@asyncio.coroutine
def async_setup(hass, config):
"""Start a discovery service."""
logger = logging.getLogger(__name__)
from netdisco.discovery import NetworkDiscovery
from netdisco.service import DiscoveryService
logger = logging.getLogger(__name__)
netdisco = NetworkDiscovery()
already_discovered = set()
# Disable zeroconf logging, it spams
logging.getLogger('zeroconf').setLevel(logging.CRITICAL)
@@ -67,37 +76,67 @@ def setup(hass, config):
# Platforms ignore by config
ignored_platforms = config[DOMAIN][CONF_IGNORE]
lock = threading.Lock()
def new_service_listener(service, info):
@asyncio.coroutine
def new_service_found(service, info):
"""Called when a new service is found."""
if service in ignored_platforms:
logger.info("Ignoring service: %s %s", service, info)
return
with lock:
logger.info("Found new service: %s %s", service, info)
comp_plat = SERVICE_HANDLERS.get(service)
comp_plat = SERVICE_HANDLERS.get(service)
# We do not know how to handle this service.
if not comp_plat:
return
# We do not know how to handle this service.
if not comp_plat:
return
discovery_hash = json.dumps([service, info], sort_keys=True)
if discovery_hash in already_discovered:
return
component, platform = comp_plat
already_discovered.add(discovery_hash)
if platform is None:
discover(hass, service, info, component, config)
else:
load_platform(hass, component, platform, info, config)
logger.info("Found new service: %s %s", service, info)
# pylint: disable=unused-argument
def start_discovery(event):
"""Start discovering."""
netdisco = DiscoveryService(SCAN_INTERVAL)
netdisco.add_listener(new_service_listener)
netdisco.start()
component, platform = comp_plat
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_discovery)
if platform is None:
yield from async_discover(hass, service, info, component, config)
else:
yield from async_load_platform(
hass, component, platform, info, config)
@asyncio.coroutine
def scan_devices(now):
"""Scan for devices."""
results = yield from hass.loop.run_in_executor(
None, _discover, netdisco)
for result in results:
hass.async_add_job(new_service_found(*result))
async_track_point_in_utc_time(hass, scan_devices,
dt_util.utcnow() + SCAN_INTERVAL)
@callback
def schedule_first(event):
"""Schedule the first discovery when Home Assistant starts up."""
async_track_point_in_utc_time(hass, scan_devices, dt_util.utcnow())
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, schedule_first)
return True
def _discover(netdisco):
"""Discover devices."""
results = []
try:
netdisco.scan()
for disc in netdisco.discover():
for service in netdisco.get_info(disc):
results.append((disc, service))
finally:
netdisco.stop()
return results

View File

@@ -18,7 +18,7 @@ from homeassistant.util import Throttle
REQUIREMENTS = [
'https://github.com/nkgilley/python-ecobee-api/archive/'
'4856a704670c53afe1882178a89c209b5f98533d.zip#python-ecobee==0.0.6']
'a4496b293956b2eac285305136a62ac78bef510d.zip#python-ecobee==0.0.7']
_CONFIGURING = {}
_LOGGER = logging.getLogger(__name__)

View File

@@ -218,7 +218,7 @@ def async_setup(hass, config: dict):
if not fan.should_poll:
continue
update_coro = hass.loop.create_task(
update_coro = hass.async_add_job(
fan.async_update_ha_state(True))
if hasattr(fan, 'async_update'):
update_tasks.append(update_coro)

View File

@@ -78,7 +78,7 @@ PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Setup MQTT fan platform."""
yield from async_add_devices([MqttFan(
async_add_devices([MqttFan(
config.get(CONF_NAME),
{
key: config.get(key) for key in (

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