Compare commits

...

355 Commits

Author SHA1 Message Date
Paulus Schoutsen d85ed8d0fe Merge pull request #12402 from home-assistant/release-0-63-2
0.63.2
2018-02-13 23:00:45 -08:00
Anders Melchiorsen c82ca62820 Downgrade limitlessled to 1.0.8 (#12403) 2018-02-13 22:58:49 -08:00
Sean Dague c414ecd4f0 Introduce zone_id to identify player+zone (#12382)
The yamaha component previously used a property named unique_id to
ensure that exactly 1 media_player was discovered per zone per
control_url. This was introduced so that hard coded devices wouldn't
be duplicated by automatically discovered devices.

In HA 0.63 unique_id became a reserved concept as part of the new
device registry, and the property was removed from the component. But
the default returns None, which had the side effect of only ever
registering a single unit + zone, the first one discovered. This was
typically the Main_Zone of the unit, but there is actually no
guaruntee of that.

This fix brings back the logic under a different property called
zone_id. This is not guarunteed to be globally stable like unique_id
is supposed to be, but it is suitable for the deduplication for yamaha
media players.
2018-02-13 22:11:05 -08:00
citruz 72f100723f Updated beacontools (#12368) 2018-02-13 22:11:04 -08:00
Otto Winter 9e4da37022 Fix WUnderground names (#12346)
* 📝 Fix WUnderground names

* 👻 Fix using event loop callback
2018-02-13 22:11:03 -08:00
Rene Nulsch e5f000f976 Fix MercedesMe - add check for unsupported features (#12342)
* Add check for unsupported features

* Lint fix

* change to guard clause
2018-02-13 22:11:03 -08:00
Paulus Schoutsen e2408cc804 Version bump to 0.63.2 2018-02-13 22:03:06 -08:00
Paulus Schoutsen 073126755c Merge pull request #12334 from home-assistant/release-0-63-1
0.63.1
2018-02-12 08:56:04 -08:00
Paulus Schoutsen 7a9ceb6f54 Fix platform dependencies (#12330) 2018-02-11 23:38:47 -08:00
Richard Lucas a06000c76d Always return lockState == LOCKED when handling Alexa.LockController (#12328) 2018-02-11 23:38:39 -08:00
Richard Lucas 2a0bd8d330 Fix Report State for Alexa Brightness Controller (#12318)
* Fix Report State for Alexa Brightness Controller

* Lint
2018-02-11 23:38:31 -08:00
Paulus Schoutsen ead158b68c Respect entity namespace for entity registry (#12313)
* Respect entity namespace for entity registry

* Lint
2018-02-11 23:38:24 -08:00
Paulus Schoutsen bfd9a5a863 Allow overriding name via entity registry (#12292)
* Allow overriding name via entity registry

* Update requirements
2018-02-11 23:38:15 -08:00
Paulus Schoutsen 56b185f7ab Remove unique ID from netatmo (#12317)
* Remove unique ID from netatmo

* Shame platform in error message
2018-02-11 23:31:40 -08:00
Russell Cloran 34ccfae565 zha: Update zigpy-xbee to 0.0.2
0.0.2 implements auto_form, so that configuring the radio to be a
controller is done automatically.
2018-02-11 23:31:30 -08:00
Richard Lucas dc8a0205ee Fix Alexa Step Volume (#12314) 2018-02-11 23:30:55 -08:00
Paulus Schoutsen 7471211b60 Version bump to 0.63.1 2018-02-11 23:27:38 -08:00
Paulus Schoutsen 0e16f7f307 Merge pull request #12267 from home-assistant/release-0-63
0.63
2018-02-10 14:00:29 -08:00
Slava 18aa1037dd Update limitlessled requirement to v1.0.9 (#12275)
* Update limitlessled requirement to v1.0.9

* trigger cla

* take back empty line
2018-02-10 13:11:05 -08:00
Paulus Schoutsen aad26599ae Retry keyset cloud (#12270)
* Use less threads in helpers.event tests

* Add helpers.event.async_call_later

* Cloud: retry fetching keyset
2018-02-10 13:11:05 -08:00
escoand a9412d27aa allow wildcards in subscription (#12247)
* allow wildcards in subscription

* remove whitespaces

* make function public

* also implement for mqtt_json

* avoid mqtt-outside topic matching

* add wildcard tests

* add not matching wildcard tests

* fix not-matching tests
2018-02-10 13:11:04 -08:00
Paulus Schoutsen 3333dcc6c2 Version bump to 0.63 2018-02-09 14:55:20 -08:00
Paulus Schoutsen 681dc72a15 Merge branch 'master' into dev 2018-02-09 14:54:27 -08:00
Anton Lundin b0780110c7 One bug fix and one improvement to the statistics sensor. (#12259)
* Correct time on recorder loaded values in statistics sensor

Previously, the current time was used when initial values was loaded
form the recorder component. This changes that to use the stored time
from recorder instead.

Signed-off-by: Anton Lundin <glance@acc.umu.se>

* Expose min / max age of values in the statistics sensor

This is very useful when doing derived calculations, for example in a
template sensor.

Signed-off-by: Anton Lundin <glance@acc.umu.se>
2018-02-09 23:50:05 +01:00
Paulus Schoutsen b087ea101d Update frontend to 20180209.0 2018-02-09 14:34:20 -08:00
David 129d720d8e Fix duplicate entity_ids in System Monitor (#12124)
* Fix duplicate entity_ids in System Monitor

This fix makes it so `_percent` is appended to the `entity_id` of the following 3 resources, in order to make the `entity_id` unique:
- disk_use_percent
- memory_use_percent
- swap_use_percent

* match entity_id to resource name

Match entity_id to resource name, to make resulting entity_id more predictable

* match entity_id to more resource names

match entity_id to more resource names

* Add unique_id property

* Revert "Add unique_id property"

This reverts commit c213ac360e.
2018-02-09 14:10:16 -08:00
Eleftherios Chamakiotis 6174c1754b Fix for iTunes media player not updating artwork (#12089)
* Added timestamp at the end of the iTunes API URL from where HA retrieves the artwork, so that it's not cached, as the URL exposed by the API never changes

* Rearranged imports according to pylint

* Added content_id in media file URL instead of timestamp, according to Paulus' suggestion
2018-02-09 14:09:34 -08:00
luca-angemi 4c11a3461f Update owntracks.py (#12260) 2018-02-09 23:06:31 +01:00
Daniel Perna 0b947882ac Update pyhomematic to 0.1.39 (#12265)
* Update pyhomematic
2018-02-09 21:59:10 +01:00
ChristianKuehnel 2014e42e4e miflora - fix for exception handling bug (#12149)
* updated to development branch of miflora

* updated requirements_all.txt

* upgraded to version 0.3

* updated requirements_all.txt
2018-02-09 21:31:49 +01:00
Otto Winter 2ae0c5653e Fix source code using Windows newline (#12248)
* 🚜 Fix usage of carriage return

* 🤝 Rebase and repeat

* 🚜 Fix file permissions
2018-02-09 08:11:47 +01:00
Pascal Vizeli e4874fd7c7 Update aiohttp 2.3.10 / yarl 1.1.0 (#12244)
* Update aiohttp 2.3.10 / yarl 1.1.0

* Update setup.py

* Update package_constraints.txt

* Update google.py

* Update static.py
2018-02-08 20:57:05 -08:00
Jon Maddox 18d027a10d Add Service Schema to Broadlink Switch (#12253)
* add service schema to ensure single values are wrapped into a list

Co-Authored-By: Paulus Schoutsen <paulus@paulusschoutsen.nl>

* 💐
2018-02-08 19:37:17 -08:00
ChristianKuehnel b08294386b added more debug logging for sensor.alpha_vantage (#12249)
* added more debug logging for sensor.alpha_vantage

* fixed typo in log statement, more fine grained logging

* Capitalized first character in log statement

* replaced quotes as proposed by @OttoWinter
2018-02-09 00:14:49 +01:00
Anders Melchiorsen acb521330c Add explicit first-time config for new purge_keep_days default (#12246) 2018-02-08 09:44:19 -08:00
cdce8p 231b62d043 Fix cover service description (#12243)
Parameter for `set_cover_tilt_position` is `tilt_position` not `position`.
2018-02-08 16:09:42 +01:00
Eu 905bb36e6a Added password mode to input_text (obscure content of text box) (#11849)
* Round values to one decimal

Temperature detection range: -20 - 60 Deg.C ( + / - 0.3 Deg.C )
Humidity detection range: 0 - 100pct RH ( + / - 0.3pct )
Atmospheric pressure detection range: 30 - 110KPa ( + / - 120Pa )

* Add password mode option

Hide the content of the input_text field

* Revert "Round values to one decimal"

This reverts commit a3124a6aaa.

* Added test for mode option

* Added newline (lint)
2018-02-08 12:29:33 +01:00
mkfink 25cbc8317f Force update support for mqtt binary sensor (#12092) 2018-02-08 12:28:12 +01:00
Anders Melchiorsen 6265d1b747 Avoid influxdb filling connection pool (#12182)
* Add a processing queue to influxdb

* Updates after reviews

* Remove lint

* Move retry loop to thread class

* Move constant calculation out of loop

* Deprecate retry_queue_limit
2018-02-08 12:25:26 +01:00
Thijs de Jong 702b1be985 Set tahoma cover update interval to default (#12232)
revert 35f35050ff158b423dc437c66bdea5ebcbabc009#diff-c65eb52376398392f1395f8127364078
2018-02-08 12:20:51 +01:00
Diogo Gomes 15368d4ca1 [SMALL PATCH] Sql sensor (#12242)
* Initial Commit

* Passed all checks

* Make DB_URL required

* addresses review comments from @fabaff

* unused variable

* return nothing
2018-02-08 12:17:54 +01:00
Paulus Schoutsen 5601fbdc7a Entity layer cleanup (#12237)
* Simplify entity update

* Split entity platform from entity component

* Decouple entity platform from entity component

* Always include unit of measurement again

* Lint

* Fix test
2018-02-08 12:16:51 +01:00
Sergey Isachenko 8523933605 Fixes for tesla. New sensors. (#12225)
* fixes #11970

* long line fix
2018-02-07 23:34:26 -08:00
Diogo Gomes 0300229085 SQL sensor (#12142)
* Initial Commit

* Passed all checks

* Make DB_URL required

* addresses review comments from @fabaff

* unused variable
2018-02-07 23:32:39 -08:00
Sergey Isachenko d0ffb1bc52 librouteros version bump (#12227) 2018-02-07 23:15:02 -08:00
Andrey 2b9bb7963d Update min js=latest version (#12091) 2018-02-07 23:14:31 -08:00
Anders Melchiorsen 945606238c Allow zero purge_interval to disable recorder purge (#12220) 2018-02-07 22:59:09 -08:00
Anders Melchiorsen aa9b5e6ea5 Return of entity_id in template platforms (#12234) 2018-02-07 22:46:14 -08:00
Phil Elson 9d5dee574a Specify the minimum python version in the setup.py. (#12144)
* Specify the minimum python version in the setup.py.

* Used the minimum python version defined in homeassistant.const.
2018-02-07 12:38:06 -08:00
Marc Egli ea35ffbc81 Add wake on lan capability to philips TV (#12065)
* Add wake on lan capability to philips TV

* Update requirements_all.txt

* Fix line length issues.

* Replace wake on lan with turn on script for philips TV

* rerun requirements script
2018-02-07 09:35:08 -08:00
Kevin Siml d05a1e35fc Update pushsafer.py (#11466)
* Update pushsafer.py

Now you can setup your pushsafer notification, and change the following parameters:
sound, vibration, icon, devices (targets), icon/led color, url, url title, time2live, picture

* Update pushsafer.py

* Update pushsafer.py

* Update pushsafer.py

* Update pushsafer.py

* Update pushsafer.py

* Update pushsafer.py

* Update pushsafer.py

* Update pushsafer.py

* Update pushsafer.py

* Update pushsafer.py

* Update pushsafer.py

* Update pushsafer.py

* Update pushsafer.py

* Update pushsafer.py

* Update pushsafer.py

* Update pushsafer.py

* Update pushsafer.py

* Update pushsafer.py

* Update pushsafer.py

* Update pushsafer.py

* Update pushsafer.py

* Update pushsafer.py

* Update pushsafer.py

* Update pushsafer.py

* Update pushsafer.py

* Update pushsafer.py

* Update pushsafer.py

* Update pushsafer.py

* Update pushsafer.py

* Update pushsafer.py

* Update pushsafer.py

* Update pushsafer.py

* Update pushsafer.py

* Update pushsafer.py

* fix lint
2018-02-07 17:23:10 +01:00
Phil Elson 5ba02c531e Catch concurrent.futures.CancelledError in websocket code. (#12150)
* Catch concurrent.futures.CancelledError in websocket code.

* Added a comment about the use of futures.CancelledError
2018-02-06 21:30:18 -08:00
Boyi C 7e246e4680 Fix logger bug on Windows: path contains '\'. (#12197)
* Fix logger bug on Windows: path contains '\'.

* update

* Update __init__.py
2018-02-06 12:56:31 -08:00
kennedyshead bd29cd2ba2 Fixes according to review from @MartinHjelmare Thank you. (#12171) 2018-02-06 21:27:35 +01:00
Sebastian Muszynski cee57aab24 Xiaomi MiIO Light: Brightness mapping improved (#12203)
* Mapping ([1,100] <-> [1,255]) of the brightness improved.

* The cast to int isn't needed for python3.
2018-02-06 19:59:49 +01:00
Jerad Meisner a2916a9c47 Fix Xeoma camera platform to allow different admin/viewer credentials (#12161) 2018-02-06 10:47:38 -08:00
Conrad Juhl Andersen 49c7b422f2 Add Xiaomi Universal IR Remote (Chuangmi IR) (#11891)
* First version of remote xiaomi-miio

* added to coveragerc

* fixed pylint error

* misc fixes and input validation

* address syssi's requests except device and async_service_handler

* forgot to run linter

* implemented async_service_handler

* fixed delay == None, honor timeout given by user, pythonic compare of None

* Added some whitespace for readability, added error message to turn_on and turn_off, fixed services.yaml examples

* fixed pylint errors

* readd pass for readability

* fixed small stuff

* Use RemoteDevice, Make send_command non-async

* Ready code for next version of python-miio (Support for pronto hex codes)

* cast command_optional to int, better input validation, fixed index out of bounds error.

* revert code now in python-miio

* ready for python-miio 0.3.5

* Removed unneccary return statements

* require 0.3.5

* Rebase and update requirements_all.txt
2018-02-06 19:47:24 +01:00
Thibault Cohen a1d586c793 Fix clear playlist in Volumio component (#12173) 2018-02-06 10:46:44 -08:00
Russell Cloran c7dad113d9 zha: Add support for XBee radios (#12205)
* zha: Add support for xbee radios

* Lint
2018-02-06 10:46:28 -08:00
Dan Nixon 844337ca42 Properly handle thresholds of zero (#12175)
Explicitly test for thresholds to be None rather than truth value
testing (which for number types returns False for zero values).
2018-02-06 10:32:56 -08:00
lance36 0fd17a7c35 Much nicer icon (#12212)
mdi:robot-vacuum will be renamed mdi:roomba 
https://github.com/Templarian/MaterialDesign/issues/2368
2018-02-06 18:52:55 +01:00
Sean Dague 6f74b672a3 Make waterfurnace recovery more robust (#12202)
This makes waterfurnace recovery more robust by catching any
understood exceptions by the library, and always doing another login.
2018-02-06 12:12:35 +01:00
Russell Cloran f58e5f442d zha: Update to bellows 0.5.0+zigpy (#12187) 2018-02-05 16:05:19 -08:00
tadly 98b47cecbd Upgrade wakeonlan to 1.0.0 (#12190) 2018-02-05 16:04:31 -08:00
Richard Lucas e7a0759e1c Add support for Alexa.StepSpeaker (#12183) 2018-02-05 16:02:08 -08:00
William Scanlon bdaf9cfae2 Bump pyeconet version to fix JSONDecodeError (#12204) 2018-02-05 15:59:17 -08:00
hawk259 4f0776de13 Binary Sensor Template: Add icon_template and entity_picture_template support (#12158)
* Binary Sensor Template: Add icon_template and entity_picture_template support

* fix white space

* Added else logging and return state
2018-02-05 23:30:56 +01:00
Rene Nulsch 323fe87b57 Change attributes in new Mercedes Me component (#12147)
* Fix wrong component doc URL

* Change attributes to lowercase snakecase

* pylint fix

* Remove test comments
2018-02-05 23:29:19 +01:00
Fabian Affolter c72460ccf0 Upgrade Sphinx to 1.6.7 (#12200) 2018-02-05 22:11:20 +01:00
Fabian Affolter 49343c9b02 Replace Gitter with Discord (#12199) 2018-02-05 21:25:37 +01:00
Joe Lu e35d4f0a2c Canary live stream (#11949)
* Added support for Canary live stream view

* Updated requirements

* - Fixed lint error

* Addressed PR comment

* - Disabled polling for Canary camera as suggested in PR comment
- Live session is now renewed every time camera is retrieved and min time between session renewal is 90 seconds
2018-02-05 14:02:43 +01:00
tbergo 86e89b7c26 Upgrade pytouchline to 0.7 (#12179) 2018-02-05 09:33:07 +01:00
Thibault Cohen 44cfd2999c fix ecobee is_aux_heat_on property (#12186) 2018-02-05 09:21:20 +01:00
Tod Schmidt f5030d9ebf Services (small_pr)(fix): Added missing return on data template error (#12184)
* Added return on data template error

* Rebased so not sure why spelling errors returned...
2018-02-05 09:19:56 +01:00
Sebastian Muszynski 137933a774 python-miio version bumped. Fixes all xiaomi_miio components. (Closes: #12017, Closes: #11948, Closes: #11200) (#12188) 2018-02-05 09:14:09 +01:00
Fabian Affolter 905a994972 Upgrade schiene to 0.21 (#12176) 2018-02-04 21:33:41 +01:00
Dan Nixon ec201f3458 Move TP-Link socket LED state setting to update() (#12170)
* Add error handling to TP-LInk LED state set

Handles errors when setting the LED state of TP-Link sockets.

If the socket is unavailable then the raised exception will cause the
compoent to not be added to HA.

* Move LED state setting out of __init__
2018-02-04 19:20:06 +01:00
akloeckner cff4f8ec9a add delay_arrival (#12169)
This adds the delay_arrival field from the schiene interface.
This field sometimes explains an ontime=false with delay=0...
2018-02-04 18:30:03 +01:00
Fabian Affolter 64cbfdfd77 Upgrade influxdb to 5.0.0 (#12156)
* Upgrade influxdb to 5.0.0

* UPdate sensor as well
2018-02-04 18:23:26 +01:00
kennedyshead 8fe339d2a8 Tests for samsungtv (#11933)
* Testing samsungtv

* Remove samsungtv from .coveragerc
2018-02-03 13:09:16 -05:00
Diogo Gomes c209c10887 [Mediaroom media_player] Follow up on PR #11864 (#12155)
* addresses @MartinHjelmare on #11864

* as requested by @MartinHjelmare
2018-02-03 17:58:34 +01:00
Diogo Gomes b33d89326f Panasonic viera new services (#11963)
* Implemented play_media

Panasonic Viera TV has a full blown Web Browser that can play any media

* media stop

* format checks

* added SUPPORT_*

* bump version

* Tks @rytilahti

* one too many
2018-02-03 11:51:34 -05:00
Per Osbäck 1aca6f922f update python-openzwave to 4.1.3 (#12057)
* update python-openzwave to 4.1.0

* 0.4.1.3
2018-02-03 11:08:48 -05:00
ErnstEeldert 9b0dbf3fbe Adding xy_color attribute support to deconz lights (#12106) 2018-02-03 17:08:00 +01:00
ruohan.chen 4ac9e7edf4 fix generic_thermostat bug when restore state from HA start up (#12134)
* fix generic_thermostat bug when restore state from HA start up

if you don't set "initial_operation_mode" in config, you will get
`self._enabled = True` when init GenericThermostat. And then you will
miss the `if self._current_operation != STATE_OFF` statement and the
self._enabled still keep `True`. That's the problem

* add a test to describe the restore case
2018-02-03 16:59:19 +01:00
Fabian Affolter c144a3339f Upgrade TwitterAPI to 2.4.8 (#12148) 2018-02-03 16:56:35 +01:00
Fabian Affolter acc767cdb1 Upgrade mutagen to 1.40.0 (#12152) 2018-02-03 16:55:19 +01:00
Diogo Gomes 880f18a37e Mediaroom (#11864)
* make port mapping optional

* dependencies + improvements

* Added bytes and packets sensors from IGD

* flake8 check

* new sensor with upnp counters

* checks

* whitespaces in blank line

* requirements update

* added sensor.upnp to .coveragerc

* downgrade miniupnpc

Latest version of miniupnpc is 2.0, but pypi only has 1.9

Fortunately it is enough

* revert to non async

miniupnpc will do network calls, so this component can’t be moved to
coroutine

* hof hof

forgot to remove import ot asyncio

* Add baudrate option

* merge

* Added Mediaroom media_player component

* Updated header

Works with MEO and VDF set-top boxes in Portugal

* formatting

* Development Checklist (done)

* fix formatting according to houndci-bot

* more format fixing (tks houndci-bot)

* more fixes

* too much cleanup...

* too much

* pylint check

* Initial commit

Basic configuration testing

* flake8 and lint
2018-02-03 08:29:55 -05:00
kennedyshead f7c9787418 Add Melissa (HVAC/climate) component (#11503)
* Adding component melissa

* Adding sensor component melissa

* Adding Melissa climate component

* Testing component

* Tests for Climate component

* Testing Melissa sensor

* Fixing review Thank you @rytilahti
2018-02-03 03:17:01 +01:00
Nigel Rook c204a7c787 Tado fixes (#11294)
* Fix tado overlay end state

Previously, when tado ended an overlay state itself, say because a timer
expired or a scheduled temperature change ocurred, the tado climate
component would not return to Smart Schedule mode. This change fixes
that issue

* Correct tado state after multiple rapid updates

Previosuly, making two changes to tado climate within 10 seconds, for
example setting operation mode to Tado mode, then changing the
temperature, would leave the entity showing the incorrect state for up
to a minute.

This change forces an unthrottled update after setting the climate
state, which fixes the issue

* Fix comment formatting
2018-02-02 17:28:54 -08:00
Ville Skyttä 2cbab48e1b Update flake8-docstrings to 1.0.3 (#12136) 2018-02-02 17:22:44 -08:00
Anthony Arnaud 86daec8c59 Fix #11875 Ubus broken since upgrade to 0 57 (#12141) 2018-02-02 17:22:29 -08:00
Anthony Arnaud 0f879a6c60 Fix #8475 device tracker ubus tracks unauthenticated and unassociated devices (#12140) 2018-02-02 17:22:14 -08:00
led-spb a3e36e6c66 Adding information about current TV channel to WebOS media player (#11339)
* Added Channel attribute to webos media pplayer

* webostv: Current TV channel display as media_title

* Added displaying information about the current TV channel for WebOS platform

* Fixed PEP8 requirements
2018-02-02 23:54:54 +01:00
Thibault Cohen 730e0a0094 Update volumio component (#12045) 2018-02-02 23:12:54 +01:00
Ville Skyttä 13ec8b143d Spelling fixes (#12138) 2018-02-02 22:35:34 +01:00
Rene Nulsch ed2d54ab45 Add Mercedes me component (#11743)
* Add Mercedes me component

* pump api version to 0.1.2

* Add Mercedes me component

* pump api version to 0.1.2

* clean up code

* Code cleanup

* Remove unneeded return statement

* Return statements added again

* Implement requested changes

* Rework component, move data load to component

* lint

* remove debug logging

* Change RainCloud comments, change from track_utc_time to track_time_interval

* Final cleanup for version 1
2018-02-02 10:56:58 -05:00
Marc Khouri 72c35468b3 Remove asyncio.test_utils to fix tests in Docker/Python 3.7 (#12102)
The module `asyncio.test_utils` has been removed from Python in the 3.7 branch, because it was intended to be a private module for internal testing of asyncio. For more information, see the upstream bug report at https://bugs.python.org/issue32273 and the upstream PR at https://github.com/python/cpython/pull/4785.

For this commit, I have migrated the small amount of functionality that was being used from the `asyncio.test_utils` directly into the `RunThreadsafeTests` Class. To see the original `asyncio.test_utils.TestCase` class, which I pulled some functionality from, please see: https://github.com/python/cpython/blob/3.6/Lib/asyncio/test_utils.py#L440

Note: In addition to being broken in 3.7, this test case also seems to be broken in Python 3.6.4 when using Docker. This PR fixes the test when run in docker.

To reproduce: `./script/test_docker -- tests/util/test_async.py`
failing output (prior to this commit):

```
... trimmed ...
py36 runtests: PYTHONHASHSEED='3262989550'
py36 runtests: commands[0] | py.test --timeout=9 --duration=10 --cov --cov-report= tests/util/test_async.py
Test session starts (platform: linux, Python 3.6.4, pytest 3.3.1, pytest-sugar 0.9.0)
rootdir: /usr/src/app, inifile: setup.cfg
plugins: timeout-1.2.1, sugar-0.9.0, cov-2.5.1, aiohttp-0.3.0
timeout: 9.0s method: signal

―――――――――――――――――― ERROR collecting tests/util/test_async.py ――――――――――――――――――――――――
ImportError while importing test module '/usr/src/app/tests/util/test_async.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
tests/util/test_async.py:3: in <module>
from asyncio import test_utils
/usr/local/lib/python3.6/asyncio/test_utils.py:36: in <module>
from test import support
E ImportError: cannot import name 'support'
```
2018-02-02 14:59:05 +00:00
Eleftherios Chamakiotis 87c0fd98c7 Add support for "off" function to iTunes (#12109)
fixes #7614
2018-02-02 14:54:47 +00:00
Rene Nulsch 1d2e930900 OpenALPR Cloud API - transfer image in body of POST request (#12112)
* Send image in body of POST request

* Fix tests

* Implement requested change
2018-02-02 14:19:13 +01:00
Paul Annekov ad24cbddcc fixed wrong check for valid range of 'rgb' values (#12132) 2018-02-02 14:02:11 +01:00
nordlead2005 65f22b09ae Dark sky precip accumulation (#12127)
* Added DarkSky Precipitation Accumulation as an hourly forecast field

* fixed spacing
2018-02-02 10:23:27 +01:00
hawk259 569f7e2fea Adds SUPPORT_TARGET_TEMPERATURE_HIGH and SUPPORT_TARGET_TEMPERATURE_LOW support (#12110)
Fixes: [Issue 12048]
2018-02-02 10:11:13 +01:00
Daniel Høyer Iversen 12dc0db856 except vol.MultipleInvalid in Broadlink #11795 (#12107)
* except vol.MultipleInvalid in Broadlink #11795

* typo
2018-02-02 09:05:54 +01:00
Marius 6d5a87afb6 Fixes away_mode error on startup (#12121)
* Fixes away_mode error on startup

* Updated based on feedback
2018-02-02 09:05:19 +01:00
Timmo 30ad591a59 Downgrade Sonarr and Radarr 'Host is not avaliable' errors to warnings (#12119) 2018-02-01 20:57:33 +01:00
Alok Saboo be37bb14b7 Update jinja2 to 2.10 (#12118) 2018-02-01 19:21:15 +01:00
Craig J. Ward 53a99dc9fa Goalfeed channel (#12086)
* fix event channel name

* I guess accidentally added timeout here too.
2018-02-01 11:24:02 +01:00
jodur 2f07ffc4e4 added media_stop (#12100)
* added media_stop

VLC was missing the media_stop. The pause was present, but starting the same file result in resuming the file instead of start over new

* Corrected style issues

Style issues and added Support Flag to Support VLC

* fixed other style issues

* remove trailing whitespace
2018-02-01 09:49:39 +01:00
Philip Kleimeyer 8991690d53 update tahoma api to version 0.0.11 (#12099) 2018-02-01 08:15:13 +01:00
c727 764343dbf8 Fix detection of mobile browsers (#12075)
* Fix detection of mobile browsers

* Move break

* Add Mobile browsers to list

`Mobile Safari` is covered by iOS and `Opera Mini` does not support es6
... And Edge doesn't even work on desktop for me...
2018-01-31 10:44:33 -08:00
Philip Kleimeyer e11e066684 updated sensor name (#12084)
* updated sensor name

* Lint
2018-01-31 10:10:35 -08:00
Phil Elson 40af9f2676 Correct use of middleware async handling. (#12078) 2018-01-31 09:04:32 -08:00
Fabian Affolter 424fe95ce4 Upgrade keyring to 11.0.0 (#12082)
* Upgrade keyring to 11.0.0

* Address the removal of 'keyring.__version__'
2018-01-31 09:03:20 -08:00
John Mihalic 81a6178931 Squeezebox Fix duplicate server from discovery (#12063)
* Fix duplicate server from discovery

* Use hass.data instead of global

* Simplify
2018-01-31 15:32:08 +01:00
escoand e9508405bc Add conditions to forecast (#12074)
* add conditions to forecast chart

* Fix pylint issues
2018-01-31 13:05:15 +01:00
Gerben Meijer 6ae3fa40cf Set flux default stop time to dusk (#12062)
This is more in line with how one would expect light temperature
transitions to take place, but still allows for a user defined
stop_time.
2018-01-31 12:00:47 +01:00
Taylor Peet 434d2afbfc Influx import improvements (#11988)
* Influx import improvements

* fix line length issues

* fixing pylint spaces

* Added refined except clause

* Fix progress bar and exclude issues

* fix travis lint too many blank lines

* Minor changes
2018-01-31 11:39:15 +01:00
Ville Skyttä 0376cc0917 Handle more file closing using context manager (#11942) 2018-01-31 11:30:48 +01:00
escoand 4cb1f93019 fixed timestamp problem in firefox (#12073) 2018-01-31 11:26:35 +01:00
Craig J. Ward ebfb380449 fix event channel name (#12077) 2018-01-30 21:46:47 -05:00
Marius 3e41422caa Fix demo platform support (#12070)
* Fixing demo platform to use support_flags

* Fixed tests as well

* Moved humidity low / high as always available based on defaults

* Updated demo platform to show more combinations
2018-01-30 14:59:43 -08:00
Ville Skyttä cab6c694c5 Flake8 bugbear fixes (#12072)
* Don't use mutable argument defaults (bugbear B006)

* Use callable(x) instead of hasattr(x, '__call__') (bugbear B004)

* Remove/mark unused loop control variables (bugbear B007)

* Fix stripping protocol from kodi host name (bugbear B005)

* Fix plant daily history add default date (bugbear B008)
2018-01-30 14:44:05 -08:00
Kane610 37034a7450 Deconz use entity registry (#12067)
* Support for entity registry

* Not everything is a sensor...
2018-01-30 14:42:24 -08:00
Anders Melchiorsen 990fbdf3ca Unique ID for LIFX lights (#12064) 2018-01-30 14:40:44 -08:00
Pierre Ståhl dfd2d631ae Publish errors on the event bus (#11964)
* Publish errors on the event bus

* Add block till done to test.

* Update test_system_log.py

* Remove old logger handlers
2018-01-30 11:25:58 -05:00
Alex Osadchyy 12182d6e49 Bumped up pymochad requirement to 0.2.0 as a fix for #11928 (#12014)
* Bumped up pymochad requirement to 0.2.0 as a fix for #11928

* requirements_all.txt updated to match pymochad requirements
2018-01-30 05:13:30 -08:00
Anders Melchiorsen d7017f2138 Prepare for recorder purge to be active by default (#11976) 2018-01-30 12:41:33 +01:00
Paulus Schoutsen ec1c395f09 Extract requirements (#12051) 2018-01-30 12:30:47 +01:00
Joe Lu 71cb4df817 Return all attributes that are not None in base lock entity class (#12049)
* Return all attributes that are not None in base lock entity class

* Update __init__.py
2018-01-30 12:20:20 +01:00
Paulus Schoutsen e51427b284 Entity registry (#11979)
* Entity#unique_id defaults to None

* Initial commit entity registry

* Clean up unique_id property

* Lint

* Add tests to entity component

* Lint

* Restore some unique ids

* Spelling

* Remove use of IP address for unique ID

* Add tests

* Add tests

* Fix tests

* Add some docs

* Add one more test

* Fix new test…
2018-01-30 10:39:39 +01:00
Phil Kates 8e441ba03b Refactor Google Assistant query_device (#12022)
* google_assistant: Refactor query_device

The previous code had issues where domains could break out and end up
with weird brightness values and we weren't enforcing the `on` and
`oneline` keys in the response.

* google_assistant: Add media_player to query test
2018-01-30 01:19:24 -08:00
freol35241 5b1c51bdf6 Handling of payload not for this entity. (#11836)
* Handling of payload not for this entity.

The update state-method should not be called if the payload is not intended for this entity.

* Fixing linter errors

* Adding warning for case when no matching payload is found
2018-01-30 10:18:45 +01:00
Phil Frost 8624799c45 Refactor alexa smart_home tests (#12044)
* Refactor alexa smart_home tests

The previous tests had a lot of copy pasta due to a lack of expressions
for higher-level assertions. I'm hoping this makes it more reasonable to
extend all interfaces to support properties.

* Lint
2018-01-29 20:33:39 -08:00
Paulus Schoutsen 1aaf49d0c1 Merge pull request #12047 from home-assistant/release-0-62-1
0.62.1
2018-01-29 16:35:59 -08:00
Fabian Affolter 10263230f7 Upgrade astral to 1.5 (#12042) 2018-01-29 16:08:01 -08:00
Paulus Schoutsen 6f84fa4ce5 Bump frontend to 20180130.0 2018-01-29 16:06:00 -08:00
Anders Melchiorsen a1c0544e35 Upgrade pyharmony to 1.0.20 (#12043) 2018-01-29 16:04:04 -08:00
c727 a59d26b1fa Fix 404 for Hass.io panel using frontend dev (#12039)
* Fix 404 for Hass.io panel using frontend dev

* Hound
2018-01-29 16:04:03 -08:00
Rene Nulsch 170a0c9888 Error handling, in case no connections are available (#12010)
* Error handling, in case no connections are available

* Fix elif to if
2018-01-29 16:04:03 -08:00
smoldaner e084a260c6 Fix parameter escaping (#12008)
From rfc3986: The characters slash ("/") and question mark ("?") may represent data within the query component

See https://tools.ietf.org/html/rfc3986#section-3.4
2018-01-29 16:04:03 -08:00
Frantz 8709a397f6 Set default values for Daikin devices that don't support fan direction and fan speed features (#12000) 2018-01-29 16:04:02 -08:00
Ville Skyttä 9fed3acc90 Fix asuswrt AttributeError on neigh for unknown device (#11960) 2018-01-29 16:04:02 -08:00
tschmidty69 0713dbc7a3 Snips - (fix/change) remove response when intent not handled (#11929)
* Remove snips endSession response on unknownIntent

* Removed snips_response for unknown and error.
2018-01-29 16:04:01 -08:00
Anders Melchiorsen 5609b42863 Upgrade pyharmony to 1.0.20 (#12043) 2018-01-29 15:54:49 -08:00
Paulus Schoutsen 24c6285567 Bump frontend to 20180130.0 2018-01-29 15:51:43 -08:00
Paulus Schoutsen 5bdbf3dfc0 Version bump to 0.62.1 2018-01-29 15:40:08 -08:00
smoldaner 6d59dad1ce Fix parameter escaping (#12008)
From rfc3986: The characters slash ("/") and question mark ("?") may represent data within the query component

See https://tools.ietf.org/html/rfc3986#section-3.4
2018-01-29 15:02:26 -08:00
Frantz 99c6a10b99 Set default values for Daikin devices that don't support fan direction and fan speed features (#12000) 2018-01-29 14:56:55 -08:00
kennedyshead 7ad870c4ff Online state for samsungtv is jumping when TV is idle (#11998)
* Set timeout to offline

* Have to rewrite to use ping instead.
2018-01-29 14:55:39 -08:00
Rene Nulsch 89e0b26b73 Error handling, in case no connections are available (#12010)
* Error handling, in case no connections are available

* Fix elif to if
2018-01-29 14:49:38 -08:00
Otto Winter 8dcfd35b8b Spelling fixes (#12041)
* Spelling fixes

*Lots* of them.

* Spelling breaking changes

* Fix lint errors
2018-01-29 23:37:19 +01:00
Otto Winter 38fd9b65bf Fix MQTT cover availability subscription (#12036) 2018-01-29 23:19:08 +01:00
c727 105522f03f Fix 404 for Hass.io panel using frontend dev (#12039)
* Fix 404 for Hass.io panel using frontend dev

* Hound
2018-01-29 23:18:33 +01:00
akloeckner 8b9dc71cde sensor.deutsche_bahn: add only_direct option (#11999)
* sensor.deutsche_bahn: add only_direct option

This adds the `only_direct` option to the HA interface as provided by the schiene class.

* fix line length

as requested by @houndci-bot and @fabaff

* No line breaks needed
2018-01-29 23:17:58 +01:00
Dan Cinnamon ff0fd71608 Bump upstream lib version. (#12021) 2018-01-29 12:35:13 +01:00
Andy Castille 12a53e2747 Fix DoorBird push notifications for installations with an API password (#12020) 2018-01-29 12:15:53 +01:00
Ville Skyttä 384f63dd1d Typing fixes (#12015)
* .gitignore: Add .mypy_cache

* Typing fixes
2018-01-29 00:24:08 -08:00
Ville Skyttä 78a3c01f27 Flake8 35 (#11972)
* Upgrade flake8 to 3.5

* Fix flake8 bare except errors

* Make flake8 and pylint cooperate
2018-01-29 00:23:53 -08:00
Jack Wilsdon 5426e5c875 emulated_hue: allow customization within emulated_hue configuration (#11981)
* emulated_hue: add entities configuration

* emulated_hue: update tests to include new entities attribute
2018-01-28 23:40:00 -08:00
Phil Kates 766875f702 alexa: Add media_player InputController support (#11946)
`media_player`s can support select_source so map that to an
InputController.

[1]: https://developer.amazon.com/docs/device-apis/alexa-inputcontroller.html
2018-01-28 22:22:04 -08:00
Phil Frost 7d6ef4445e Report states (#11973)
* Refactor Alexa Smart Home API

Having an object per interface will make it easier to support
properties.

Ideally, properties are reported in context in all responses. However
current implementation reports them only in response to a ReportState
request. This seems to work sufficiently. As long as the device is
opened in the Alexa app, Amazon will poll the device state every few
seconds with a ReportState request.

* Report properties for some Alexa interfaces

Fixes (mostly) #11874.

Other interfaces will need properties implemented as well.

Implementing properties for just PowerController seems sufficient to
eliminate the "There was a problem." error for any device that supports
it, even if other interfaces are supported. Of course the additional
properties will be reported incorrectly in the Alexa app.

Includes a minor bugfix: `reportable` was previously placed incorrectly
in the responses, so Amazon was ignoring it.
2018-01-28 17:00:34 -08:00
Phil Frost 84711aad90 Refactor Alexa Smart Home API (#12016)
Having an object per interface will make it easier to support
properties.

Ideally, properties are reported in context in all responses. However
current implementation reports them only in response to a ReportState
request. This seems to work sufficiently. As long as the device is
opened in the Alexa app, Amazon will poll the device state every few
seconds with a ReportState request.
2018-01-28 16:43:27 -08:00
Joe Lu b3bf6c4be2 Fixed Canary temperature sensor and remapped air quality value (#11355)
* Fixed Canary temperature sensor and remapped air quality value

* Addressed review comment

* - Fixed canary tests and added more tests
- Removed py-canary requirements from tests

* Noop to trigger a build again

* - Removed py-canary requirements from tests

* Addressed PR comment

* - Updated tests
- Removed py-canary from gen_requirements_all.py

* - Fixed hound violation

* Added back py-canary to gen_requirements_all.py as it's still need in tests

* Added back py-canary to test requirements as it's still need in tests

* Address PR comment
2018-01-28 15:30:46 -08:00
Fabian Affolter c7efe5b7dd Upgrade pyota to 2.0.4 (#11991) 2018-01-28 18:04:54 +01:00
Fabian Affolter 96f9a12541 Upgrade coinbase to 2.0.7 (#11992) 2018-01-28 18:04:40 +01:00
Kevin Goff 336bdb1889 Added support for hourly percent change in coinmarketcap component (#11996)
* Added support for hourly percent change (percent_change_1h)

* Fixed display of 1h percent change
2018-01-28 15:50:43 +01:00
Craig J. Ward 62d4f23833 Add Goalfeed platform (#11098)
* add goalfeed

* use pysher. begin auth

* auth!

* update params

* cleanup

* update library and gen requirements

* unused imports

* case-sensitive

* crazy train

* docstrings and some other fixes

* remove logging

* unused imports

* import in setup

* move import

* Update based on notes

* add timeout

* It's only a component

* Update docstrings
2018-01-28 14:04:40 +01:00
Emil Stjerneman 3c869c6ed6 Fix 11982 - uvc don't handle null as username (#11984) 2018-01-28 10:50:23 +01:00
Ville Skyttä a3fc2c7fee Update panasonic_viera to 0.3 (#11989) 2018-01-28 09:16:06 +01:00
Ville Skyttä 2d3034be11 panasonic_viera: Set device name from discovery info (#11990) 2018-01-28 09:15:39 +01:00
MGWGIT 1419005082 Update xiaomi_aqara.py (#11969)
Sensor can measure temperature below -20, but maybe not so accurate, but no need to discard measurements.
2018-01-27 22:01:48 +01:00
Fabian Affolter f43234b533 Bump dev to 0.63.0.dev0 (#11952) 2018-01-27 12:02:55 -08:00
Fabian Affolter f08fd8182c Upgrade coinmarketcap to 4.2.1 (#11953) 2018-01-27 11:59:33 -08:00
Fabian Affolter 0c008663ad Upgrade sphinx-autodoc-typehints to 1.2.4 (#11954) 2018-01-27 11:59:19 -08:00
Fabian Affolter 63ae275182 Upgrade youtube_dl to 2018.01.21 (#11955) 2018-01-27 11:59:07 -08:00
Fabian Affolter d8fde94763 Upgrade sqlalchemy to 1.2.2 (#11956) 2018-01-27 11:58:52 -08:00
Ville Skyttä 55ee8959ba Spelling fixes (#11940) 2018-01-27 11:58:27 -08:00
tschmidty69 94316f07a2 Snips - (fix/change) remove response when intent not handled (#11929)
* Remove snips endSession response on unknownIntent

* Removed snips_response for unknown and error.
2018-01-27 11:55:47 -08:00
Ville Skyttä e750428e9d huawei_router: Fix documentation link (#11961) 2018-01-27 16:33:33 +01:00
Ville Skyttä 3af7c67bf1 Fix asuswrt AttributeError on neigh for unknown device (#11960) 2018-01-27 15:20:28 +01:00
Ville Skyttä f1fc3c762a tests: Use assertEqual instead of deprecated assertEquals (#11943) 2018-01-27 12:18:02 +01:00
Ville Skyttä b4d682ca75 Python 3.6 invalid escape sequence deprecation fixes (#11941)
https://docs.python.org/3/whatsnew/3.6.html#deprecated-python-behavior
2018-01-27 12:16:30 +01:00
Rene Nulsch cad0bde95b Panel_Iframe - Allow relative urls in config (#11832)
* Panel_Iframe - Allow relative urls in config

* change regex to check for starting forward slash only

* Change error message and const name
2018-01-26 22:31:40 -08:00
Rene Nulsch 74b0740e1c Weblink - Allow relative urls in config (#11808)
* Allow relative url

* Allow absolute urls in config schema

* change after pylint build

* Add tests and change error message

* Change regex to check starting forward slash only

* Change error message and const name
2018-01-26 22:30:39 -08:00
Paulus Schoutsen 5bde72d490 Merge pull request #11921 from home-assistant/release-0-62
0.62
2018-01-26 21:07:57 -08:00
Daniel Perna af69a307a8 Update pyhomematic to 0.1.38 (#11936) 2018-01-26 20:32:08 -08:00
Phil Frost 7bbef68b2a Implement Alexa temperature sensors (#11930) 2018-01-26 20:32:08 -08:00
Bas Schipper abde8c40c9 Fixed rfxtrx binary_sensor KeyError on missing optional device_class (#11925)
* Fixed rfxtrx binary_sensor KeyError on missing optional device_class

* Fixed rfxtrx binary_sensor KeyError on missing optional device_class
2018-01-26 20:32:08 -08:00
Paulus Schoutsen 07b2f38046 Allow setting climate devices to AUTO mode via Google Assistant (#11923)
* Allow setting climate devices to AUTO mode via Google Assistant

* Remove cast to lower

* Clarify const name
2018-01-26 20:32:07 -08:00
kennedyshead 280c1601a2 fixes #11848 (#11915)
Adding tests to check the component after latest patch
2018-01-26 20:32:07 -08:00
Andrey df24ecf395 Add "write" service to system_log (#11901)
* Add API to write error log

* Move write_error api to system_log.write service call

* Restore empty line
2018-01-26 20:32:06 -08:00
Daniel Perna af5d0b3443 Update pyhomematic to 0.1.38 (#11936) 2018-01-26 21:41:43 +01:00
Robert Schütz 2b68bec428 check_config.py: allow colorlog==3.1. (#11927) 2018-01-26 10:43:55 -08:00
Phil Frost ffcc41d6ef Implement Alexa temperature sensors (#11930) 2018-01-26 10:40:39 -08:00
kennedyshead 2d8ef36a6c fixes #11848 (#11915)
Adding tests to check the component after latest patch
2018-01-26 10:30:48 -08:00
Aaron Bach 5af7666a61 Adds allergy/disease sensor platform from Pollen.com (#11573)
* Base platform in place

* Logic in place

* Requirements and coverage

* Fixed some linting issues

* Small attribute reorganization

* Collaborator-requested changes round 1

* Updated documentation
2018-01-26 18:40:02 +01:00
Bas Schipper bfe259f7a0 Fixed rfxtrx binary_sensor KeyError on missing optional device_class (#11925)
* Fixed rfxtrx binary_sensor KeyError on missing optional device_class

* Fixed rfxtrx binary_sensor KeyError on missing optional device_class
2018-01-26 13:45:02 +01:00
akloeckner 68d2851ecf Map media_stop to idle state (#11819)
adresses #11813
2018-01-26 12:57:54 +01:00
Andrey 8332d4e359 Add "write" service to system_log (#11901)
* Add API to write error log

* Move write_error api to system_log.write service call

* Restore empty line
2018-01-26 12:41:52 +01:00
Paulus Schoutsen 390b727869 Allow setting climate devices to AUTO mode via Google Assistant (#11923)
* Allow setting climate devices to AUTO mode via Google Assistant

* Remove cast to lower

* Clarify const name
2018-01-26 12:37:06 +01:00
Paulus Schoutsen deb10a1c4d Update frontend to 20180126.0 2018-01-25 23:26:14 -08:00
Paulus Schoutsen 3d8e425113 Update frontend to 20180126.0 2018-01-25 23:26:02 -08:00
Paulus Schoutsen a8ee11e732 Version 0.62 2018-01-25 23:17:07 -08:00
Nash Kaminski 748fff7ebc Allow separate command and state OIDs and payloads in SNMP switch (#11075)
* Allow separate command and state OIDs and payloads in SNMP switch

Follow PEP8 style properly

More PEP8 style fixes

Fully PEP8 compliant

* Fix pylint errors

* Remove comment to satisfy pylint

* Style changes/refactoring
2018-01-25 22:48:21 -08:00
Nolan Gilley 9a71717047 Add ERC20 tokens to etherscan.io sensor (#11916)
* upgrade python-etherscan-api

* add support for erc20 tokens
2018-01-25 21:08:08 -08:00
Phil Frost 920f9f132b Report scripts and groups as scenes to Alexa (#11900)
* Send Alexa Smart Home responses to debug log

* Report scripts and groups as scenes to Alexa

The Alexa API docs have a couple display categories that sound relevant
to scenes or scripts:

    ACTIVITY_TRIGGER: Describes a combination of devices set to a
    specific state, when the state change must occur in a specific
    order.  For example, a “watch Neflix” scene might require the: 1. TV
    to be powered on & 2. Input set to HDMI1.

    SCENE_TRIGGER: Describes a combination of devices set to a specific
    state, when the order of the state change is not important. For
    example a bedtime scene might include turning off lights and
    lowering the thermostat, but the order is unimportant.

Additionally, Alexa has a notion of scenes that support deactivation.
This is a natural fit for groups, and scripts with delays which can be
cancelled.

https://developer.amazon.com/docs/device-apis/alexa-discovery.html#display-categories

The mechanism to map entities to the Alexa Discovery response is
refactored since extending the data structures in MAPPING_COMPONENT to
implement supportsDeactivation would have added complication to what I
already found to be a confusing construct.
2018-01-25 21:06:57 -08:00
arcsur 3aa3130d05 Minor fix to configuration validation and related log line. (#11898) 2018-01-25 21:01:43 -08:00
David Fiel caa16da5c5 Multi-Room Support for Greenwave Reality (#11364)
* Multi-Room Support for Greenwave Reality

* PEP8 Line Too Long

* Update requirements_all.txt

* Shared State Object

Shared State Object added, and implemented new function of greenwavereality to abstract complex runtimes.

* Update requirements_all.txt

* Lint issues

* Rewrite method to not trigger "Method could be a function"

* Remove unnecessary arguments for update call
2018-01-26 00:00:32 +01:00
Jerad Meisner 94e270f828 Added Xeoma camera platform (#11619)
* Added Xeoma camera platform

* Switch to dict.get() for optional config values with no default

* Minor changes
2018-01-25 23:48:38 +01:00
i-am-shodan 502ebd2a31 Improve foscam library exception support (#11701)
* Improve foscam library exception support

Catches foscam exceptions that otherwise pollute the log. Many of these exception can safely be ignored

* Fixed line length

Fixed line length

* Changed exception and log handling

changed logging and catched only the TypeError effecting the library

* Removed unused var

Removed var

* Fix remaining issue
2018-01-25 23:47:53 +01:00
Johannes Innerbichler 0db9c04f21 Iota wallet (#11398)
* worked on first version of IOTA wallet

* added sensor for IRI node

* added requirements

* ignored new component in test coverage

* changed length of line

* fixed linter issues

* changed requested changes

* moved attributes of IRI node

* change coverage definitions

* changed loading of platforms

* changed loading of platforms

* changed initial state of wallet sensor

* changed handling of config

* changed attribute handling for IOTA node sensor

* Follow the component style

* Fix ordering and docstring

* Fix pylint issue
2018-01-25 19:46:48 +01:00
Dan Cinnamon 72bb0e97e0 New venstar climate component (#11639)
* New venstar climate component

* Corrected items raised during the PR process.

* Corrected flak8 issues.

* Add support for venstar models without humidity control.

* Changed the idle operation mode to off.  Few other adjustments.

* Minor changes (log messages, sorting, etc.)
2018-01-25 18:34:53 +01:00
William Scanlon 5123487705 Update python-wink version and multiple wink fixes/updates. (#11833) 2018-01-25 11:31:38 -05:00
Paulus Schoutsen 3d9ff372fc Use API to discover Hue if no bridges specified (#11909)
* Use API to discover Hue if no bridges specified

* hide config file
2018-01-25 14:55:14 +01:00
Paulus Schoutsen 9123dfce6d Clarify emulated hue warning (#11910) 2018-01-25 11:20:37 +01:00
tschmidty69 27edbe5c0f Snips - (fix) removed endSession mqtt response on error and unknown intents (#11908)
* Remove snips endSession response on unknownIntent

* Removed snips_response for unknown and error.
2018-01-24 22:50:36 -08:00
tschmidty69 ffe832763d Pushbullet (fix) invalid keyword, added unittests (#11804)
* Fixed unittests

* revert requirements

* added pushbullet.py to requirements

* updated requirements
2018-01-24 21:06:35 +01:00
lich becd94fe2f Add android option for linux_battery.py (#11852)
* Add android option for linux_battery.py

* Add system option for linux_battery.py, support android.
2018-01-24 13:51:39 +01:00
arcsur a0a001db71 Bme680 (#11892)
* Added documentation reference to code

* Some code clean-ups suggested by @MartinHjelmare in PR#: 11695 after merging.

* fixed minor typo in docstring

* fixed another minor typo in same docstring...

* fixed another minor typo in same docstring...
2018-01-24 03:44:29 -08:00
Gregory Dosh d65ac7421d device tracker - tomato https support (#11566)
* initial https support

* adding tests

* lint errors

* missing docstring

* fixing non-deterministic params

* fixing non-deterministic params

* Updating docstrings & added missing tests

* revert _LOGGER

* updating default port to reflect ssl/nonssl

* fixing docstrings for tests
2018-01-24 04:51:06 -05:00
Frantz b43b542667 Handle Daikin AC adapters without fan mode and swing mode support (#11840)
* Handle daikin AC devices without FAN_MODE and SWING_MODE support

* Handle daikin AC devices without FAN_MODE and SWING_MODE support
2018-01-24 09:39:25 +01:00
Frantz 676c95ed2d [SMALL-PR] Don't make climate calls if feature is not supported (#11841)
* Don't make climate calls if feature is not supported

* Implemented suggested code review changes & cleanup

* Fix demo climate unit tests
2018-01-24 09:35:31 +01:00
Anders Melchiorsen 5b0a475197 Postpone "Z-wave not ready" warning (#11807)
* Release worker thread while waiting for Z-wave startup

* Increase zwave startup timeout

* Adjust test

* Use asyncio.sleep in _check_awaked

* Remove lint

* Name loop parameter
2018-01-24 09:27:58 +01:00
Sean Dague 4521d59bec Bump waterfurnace library to 0.3.0 (#11888)
Fixes in the library to handle transaction id which has to roll over
otherwise the server gets confused.
2018-01-24 08:24:54 +01:00
Rendili 8f083e17f8 Fix for hive aux_heat target temperature display (#11884) 2018-01-23 16:16:38 -08:00
Nolan Gilley 9a3895c79e upgrade ecobee (#11878) 2018-01-23 23:03:54 +01:00
Paul Annekov eea7e2fa2d fixed async function called in a thread (#11882) 2018-01-23 22:44:07 +01:00
Phil Frost 990e076c2c Expose Alexa Smart Home via HTTP POST (#11859)
* Expose Alexa Smart Home via HTTP POST

Haaska uses the deprecated v2 Alexa Smart Home payload. Exposing the v3
implementation this way allows an easy path to upgrading Haaska and
reducing code duplication with Home Assistant Cloud.

* Expose Alexa Smart Home via HTTP POST

Haaska uses the deprecated v2 Alexa Smart Home payload. Exposing the v3
implementation this way allows an easy path to upgrading Haaska and
reducing code duplication with Home Assistant Cloud.
2018-01-23 10:45:28 -08:00
Sean Dague 73fa76d792 Add missing mock call for test_minimal_config (#11858)
In local testing py36 always fails on test_minimal_config on a wait
call that never completes. One difference between this test and others
in the class is the lack of a mock on `call`. With this added, the
test passes locally 100%.
2018-01-23 14:02:00 +01:00
Pascal Vizeli 6df1fae447 Hass.io panel v2 (#11860)
* Update hassio.py

* fix test

* Update test_hassio.py
2018-01-23 11:39:55 +01:00
Paul Annekov 3417c6ad8d Checking Xiaomi Aqara devices unavailability states (#11631)
* added unavailability tracker, updated sensor component

* change hass argument position according to position in binary_sensor

* added hass argument to binary_sensor, updated is_on(), it can be UNAVAILABLE now

* updated switch component to support unavailability feature

* updated light component to support unavailability feature

* updated cover component to support unavailability feature

* set _hass property

* added unavailability tracker, updated sensor component

* change hass argument position according to position in binary_sensor

* added hass argument to binary_sensor, updated is_on(), it can be UNAVAILABLE now

* updated switch component to support unavailability feature

* updated light component to support unavailability feature

* updated cover component to support unavailability feature

* set _hass property

* fixed error with wrong arguments number during callback call

* reset unavailability state on new message received from device

* use locks to fix race condition during managing _state property

* overriden state() method for some components to check for STATE_UNAVAILABLE and return it instead e.g. STATE_OFF

* fixed linter

* removed blank line

* use available() method instead of changing _state

* filter motion sensors 'heartbeat', was removed from PyXiaomiGateway

* remove self._hass, use self.hass set by HA on attach

* self.push_data now running in the event loop, use async_schedule_update_ha_state()

* merge fix

* removed accidentally added home-assistant-polymer

* bump PyXiaomiGateway version to 0.8.0

* bump PyXiaomiGateway to 0.8.0

* updated methods names and annotations
2018-01-23 10:22:43 +01:00
Phil Frost 95592d9283 Respond to Alexa scene activation correctly (#11869)
The API documentation[1] specifies that Alexa.SceneController Activate
must get a ActivationStarted response. Responding with just a `Response`
will elicit a "Hmm... $scene is not responding" from Alexa.

[1]: https://developer.amazon.com/docs/smarthome/provide-scenes-in-a-smart-home-skill.html
2018-01-23 00:01:18 -08:00
arcsur 09e3bf94eb BME680 Sensor Component (#11695)
* Adding BME680 Sensor Component

* Flake8 lint fixes

* PyLint fixes

* Fix for log line

* Updating requirements for testing

* Fix PyLint Log format errors and add to coveragerc ommisions as requires sensor connected

* Regenerated requirements_all.txt

* Added Pylint exception for import error of system specific library

* Refactored async_add_platform to move IO out to avoid heavy yield usage
2018-01-22 23:51:52 -08:00
Frantz bc13c9db83 Allow exposing sensors as temperature or humidity 'climate' devices to Google Assistant (#11095)
* Allow exposing sensors as temperature or humidity as 'climate' to Google Assistant

* Fixed hound

* Fixed hound

* Handled correctly unit of measurement to fix humidity

* Fixed temperature conversion for Google climate components

* Fixed temperature conversion for Google climate components

* Fixed indentation

* Fixed hound

* Fixed tests

* Fixed conversion and unit tests

* Fix sync for custom unit temperature

* Implemented requested changes

* Fix hound

* Fix linting errors

* Added success tests for sensors as climate

* Fix lint errors
2018-01-22 23:23:33 -08:00
Paulus Schoutsen 183e0543b4 Clean up entity component (#11691)
* Clean up entity component

* Lint

* List -> Tuple

* Add Entity.async_remove back

* Unflake setting up group test
2018-01-22 22:54:41 -08:00
Sean Dague d478517c51 Fix races on recorder test (#11857) 2018-01-22 15:21:56 +01:00
Pascal Vizeli b224fd324d Fix new iframe panel url (#11850)
* Fix new iframe panel url

* fix tests
2018-01-22 14:45:48 +01:00
Oleksii Serdiuk 8c627e2b8b maxcube: Set MAX! Window Sensor's class to 'window' (#11799)
The sensors are meant to be put on windows to shut down the heating when
windows are open. Having 'window' device class instead of 'opening' is
much more logical here.
2018-01-22 12:32:27 +01:00
Marius c8d26d99f0 Fix issues with generic thermostat (#11805)
* Fixes for #11757 #11798 #11763

* Adjustments based on feedback
2018-01-22 12:12:03 +01:00
kennedyshead 0f26ebe954 Use strict timeout when polling Samsung TV (Fix for #6375) (#11759) 2018-01-22 08:11:16 +00:00
andrewdolphin 5513ffc33c Change 'on' to 'heat' as fallback thermostat mode (#11377)
* Change 'on' to 'heat' as fallback thermostat mode

'on' isn't recognised as a mode by Google Assistant, rather is used as a method to return a thermostat to a previous mode. In the case where a thermostat doesn't support the standard google modes (e.g. A homematic radiator thermostat) this means the set_temperature doesn't get returned to the user on a request.

* Update test_google_assistant.py
2018-01-21 12:57:56 -08:00
William Scanlon 6a6ea263cf Fix Unifi direct errors caused by AP reboot. (#11835)
* Fix Unifi direct errors caused by AP reboot.

* Inverted logic in update
2018-01-21 18:13:28 +01:00
Teemu R 1a789a05db bump eq3bt version (#11834) 2018-01-21 11:45:20 +01:00
Fabian Affolter 47e31dc9ee Fixes for PEP257 (#11810)
* Fixes for PEP257

* More updates
2018-01-20 22:35:38 -08:00
Frantz 0100f87ff2 (Re)Enable Daikin autodiscovery (#11842) 2018-01-21 06:08:42 +01:00
Sean Dague 8c78a210ef Add waterfurnace platform (#11732)
Add waterfurnace platform

This adds support for waterfurnace geothermal systems. This is
implemented as a component as there will eventually be some active
control elements. This is not done as a climate platform because
geothermal systems work best when set at a constant temperature as
they are tuned to keep within 0.5 degrees F of a setpoint, and large
temperature shifts are slow and expensive.

This is done in the Data + Sensors model, with the Data component
having a regular update thread. That thread needs to call the read()
function at least every 30 seconds otherwise the underlying websocket
is closed by the server.
2018-01-20 16:51:59 -05:00
Sebastian Muszynski dd81af4cd5 python-miio version bumped. Fixes all xiaomi_miio components. (Closes: #11768) (#11837) 2018-01-20 21:27:52 +01:00
Dan Nixon 2cfbd0dc1d Add missing availability schema to MQTT alarm panel (#11829)
* Add missing availability schema to MQTT alarm panel

* Add tests for default MQTT availability payloads
2018-01-20 17:11:04 +01:00
Filip Bednárik a470cc212e Add more workday sensor countries and update holidays library to version 0.9.3 (#11826) 2018-01-20 17:09:05 +01:00
Michaël Arnauts f7b129d790 Change telldus domain to download.telldus.com (#11825) 2018-01-20 17:07:45 +01:00
Jens Østergaard Nielsen e02d5e7ff1 Ihc component and platforms (#10916)
* Added IHC platform

* Updated requirements for IHC platform

* Exclude IHC from test

* Correcting flake8 issues

* Fixing more flake8 issues

* Fixed flake8 issues

* Fixing pylint issues

* Fixed flake8 issues

* Changes from PR review.

* STATE_UNKNOWN changed to None

* Spelling mistake in comment

* Added IHC platform

* Updated requirements for IHC platform

* Exclude IHC from test

* Correcting flake8 issues

* Fixing more flake8 issues

* Fixed flake8 issues

* Fixing pylint issues

* Fixed flake8 issues

* Changes from PR review.

* STATE_UNKNOWN changed to None

* Spelling mistake in comment

* Updated requirements_all.txt with gen_requirements_app.py

* Pylint fix: No space allowed around keyword argument assignment

* PR review changes

* Moved auto setup from platforms to ihc component

* Do no auto setup if there are no IHC products found

* Changes from PR review
2018-01-20 16:29:50 +01:00
ChristianKuehnel 323992e224 MiFlora - use bluepy on linux systems (#11284)
* Updated bluepy to version 1.1.4 as some issues with the native code were fixed there.

* Miflora - Added support for bluepy backend.

* miflora - now using bluepy backend on linux platforms

* fixed pylint findings

* miflora now using bluepy as default bluetooth backend and gatttool as fallback

* fixed hound complaints

* fixed pylint warning

* updated requirements

* Update miflora.py
2018-01-20 08:50:25 +01:00
Spencer Oberstadt f744467c5d bump roku version (#11816) 2018-01-19 21:56:56 -08:00
robhuls 7cbe017932 Nad (#11800)
* Update nad_receiver to 0.0.9

* Update nad_receiver to 0.0.9
2018-01-19 21:41:12 -08:00
Paulus Schoutsen c1b0ab75e1 Update frontend to 20180119.0 2018-01-19 09:54:01 -08:00
Paulus Schoutsen 51c41ba4e3 Disable installing Telldus in Docker (#11806) 2018-01-19 09:47:45 -08:00
Lukas Barth 51dd9b6dde Fix recorder purge (#11802)
* Do proper largest-of query

* Fix error when event_id is NULL
2018-01-19 08:56:28 -08:00
Martin Rowan d697e8e677 Correct inadvertent change to file permissions (#11755) 2018-01-19 16:28:47 +01:00
Paul Rabahy d5df1c070d Make Google TTS secure (#11031)
* Make Google TTS secure

I noticed that my TTS queries were showing up in the log on my router, so I was curious if there was a way to make it secure. A quick search showed people using https instead of http (https://stackoverflow.com/questions/32053442/google-translate-tts-api-blocked), so I figure that should work. I am using hass.io, so I'm not actually sure how to test this, but its a pretty simple change.

* Fix the tts test.
2018-01-19 16:27:40 +02:00
Andrey 273db75248 Always load yr picture from https (#11796) 2018-01-19 13:06:34 +01:00
Andrey 5ef7a8d55a Fix sensibo function names (#11797) 2018-01-19 13:04:53 +01:00
starkillerOG 2ca4bde06a Hyperion: fix (#11793) 2018-01-19 10:50:12 +01:00
Daniel Høyer Iversen 4ceb13291f flux led version 0.20 (#11791) 2018-01-19 00:44:40 -08:00
Sebastian Muszynski 03a5d4e131 Additional attributes and services of the Xiaomi Air Purifier introduced (#11249)
* Attributes average_aqi and purify_volume introduced. Fixes https://github.com/syssi/xiaomi_airpurifier/issues/14.
New service light.xiaomi_miio_set_child_lock_{on,off} added. Fixes https://github.com/syssi/xiaomi_airpurifier/issues/13.

* Lazy loading of service descriptions.

* Merge conflict resolved.
2018-01-18 23:50:56 -08:00
Philip Kleimeyer 5de828d6e2 add generic rollershutter agian, was missing in last merge (#11788) 2018-01-18 23:47:35 -08:00
ChristianKuehnel b84e551aea plant - check history for min_brightness (#9534)
* Added option to create a group for the plant and all of it's sensors so that they appear together in the UI

* fixed warnings from the hound

* added check for min_brightness over several days

* fixed hound complaints

* 1) added missing dependency on recorder
2) using group.Group instead of hass.states.async_set as requested by @pvizeli

* fixed pylint error in docstring

* changed the way the groups are created

* fixed requirements issue

* Changed the way the groups are implemented. This is proposal number 4...

* Data read from recorder only on startup.

We now only store one data point per day. If a recorder is configured, this data is initialized from the database. If not the list is empty on startup.

* added missing documentation

* fixed typo in comment

* removed group fature

* added group dependency since it's still needed

* fixed bug: now "None" is no longer added to the DailyHistory

* now also outputting unit of measurement if defined by the sensors

* removed iconss

* fixed line length

* Implemented changes requested in code reviews.

These changes affect the interface to the UI:
* renamed attribute for units of measurement to "unit_of_measurement_dict"
* renamed attribute for maximum in brightness history to "max_brightness"

* only loading the history if a brightness sensor was configured

* fixed testcase

* fixed stupid bug in check of brightness history

Also added test for this bug

* added missing docstring

* Fixed sporadic failure in test case.

Sometimes the component was created before the data was stored in the history. This lead to an empty history being read.

* removed unused import statement in testcase

* reverted change to test case

* Changed startup behavior of the component.

No failed tests after 20 local test runs.

* added missing docstring

* fixed tests

* added hass.start() to Setup
* fixed call parameters in constructor
* added time.sleep

* removed sleep

* fixed typo in variable name

* disabled loading from database as it's not stable at the moment and nobody knows why :(

* fixed flake8

* now using pytest.mark.skipif to skip test
2018-01-18 23:46:12 -08:00
Steaff c48ef281ab Homematic ip tilt covers (#11650)
* add pyhomematic support for ip and tiltable covers

* use cached data in current_cover_tilt_position

* check for existance not for None

* reformatting

* check node for LEVEL_2
2018-01-18 23:20:05 -08:00
Sebastian Muszynski b10fd172fd Service for setting a fixed scene of Xiaomi MIIO lights (#10819)
* Service for setting a fixed scene introduced.

Fixes https://github.com/syssi/philipslight/issues/6.
Fixes https://github.com/home-assistant/home-assistant/issues/10458.

* Service description added.

* Typo fixed.

* Error message updated and naming improved.

* Name ("scene") of the method parameter aligned.

* Hound error fixed: Spaces removed.

* async_setup_platform method simplified.

* Lazy loading of service descriptions.

* Unused import removed.
2018-01-18 22:52:30 -08:00
William Scanlon 314582ef0c Support for performance mode on Rheem water heaters. (#11786) 2018-01-18 22:37:24 -08:00
Fabian Affolter b1fd9daf5f Fix typos (#11781) 2018-01-18 22:36:48 -08:00
Fabian Affolter 7fe2dafa04 Fix PEP8 and PEP257 issues (#11780) 2018-01-18 22:36:29 -08:00
tschmidty69 48619c9d7c Implemented event_data_template (new) (#11057)
* Implemented event_data_template

* The hound does not like my indentation

* Added passed variables to tests for event and svc template calls

* Moved recursive function to template.py

* Update template.py

* Update template.py

* Cleaned up service.py and fixed unit tests

* Blank lines

* Removed stray logger statement

* Blank lines again
2018-01-18 22:13:14 -08:00
tschmidty69 0e1cc05189 Snips: (change) Removed unknown intent speech response (#11776)
* Removed unknown intent speech response

* Fixed tests, doh!

* Woof
2018-01-18 22:01:04 -08:00
Anders Melchiorsen ce9673b06d Limit service description loading to a single thread (#11733) 2018-01-18 21:59:03 -08:00
Kane610 a9634199e6 Axis discovery fails to save conf (#11769)
* Signal callback isnt JSON serializable so it has to be removed before saving to conf

* Remove filtered events list which is not a part of component configuration
2018-01-18 14:51:01 -08:00
karlkar cf6f916ed4 Fix for Neato D3 and D5 (#11775) 2018-01-18 23:37:24 +01:00
Jean-Philippe Jodoin 8ca45acb4e Added support for TekSavvy bandwidth sensor (#11186)
* Added support for TekSavvy bandwidth sensor

* Variable name fix

* Units are Gigabytes and not Gigabits

* Change REST calls to use asyncio

* houndci violation fix

* pylint fix

* Fix for code review

* Import order asyncxxx:wq

* Tweak docstrings and log message
2018-01-18 23:15:13 +01:00
Fabian Affolter 23b2ca50e2 Update header and make it less verbose (#11774) 2018-01-18 23:04:18 +01:00
markferry 526405c83b Add a 'last' mode and attribute to min_max sensor (#11037)
* Add 'last' type to min/max sensor

Now supports types: min, max, mean, last
'last' is the most recently received value from all tracked entities.

* Min/max sensor 'last' type test

* Fix min/max sensor 'last' test
2018-01-18 23:03:41 +01:00
Conrad Juhl Andersen 536424b0c8 Owntracks: Use bluetooth_le as source_type if beacon was used for location change. (#11615)
* Use bluetooth_le source_type, if location was changed by beacon

* No reason to do nested ifs

* Added tests for source_type on owntracks

* Fixed The Hound

* Added test and fixed bug surfaced by test
2018-01-18 23:00:20 +01:00
Ioan Loosley 0859e38bd5 Unit should be mph as that is what the metoffices datapoint API returns (#11760) 2018-01-18 22:03:01 +01:00
Matthias Dötsch 46bbd78b23 Use localized forecast for openweathermap (#11770)
Use "detailed status" of forecast to get localized value
2018-01-18 21:59:50 +01:00
Fabian Affolter 5c474ec42d Update icon (fixes #11744) (#11758) 2018-01-18 21:48:21 +01:00
Alok Saboo 2df2f35423 Round off exchange rate (#11765) 2018-01-18 21:47:46 +01:00
Daniel Høyer Iversen 216075cc72 set default value for rfxtrx config (#11767) 2018-01-18 19:58:56 +01:00
Paulus Schoutsen f5fba333d9 Upgrade netdisco (#11752) 2018-01-18 12:51:01 +01:00
Rene Nulsch aad14b8b87 Xbox sensor - Extend error handling (#11637)
* Extend error handling fix #9016

* lint finding

* Implement requested changes

* Remove unneeded return statement
2018-01-17 21:26:23 -08:00
Kane610 8bcaf832ae Add deCONZ entities in a predicitive order (#11712)
* Make sure that entities names are created in a predicitive order

* Debug print for deconz config parameters

* Bump requirement to v25
2018-01-17 21:22:42 -08:00
Thijs de Jong aac01cb096 Fix Tahoma device class (#11745)
* fix device class

* edit description
2018-01-17 21:20:23 -08:00
iliketoprogram14 e72fefa74d Fixed universal media player templated source_select bug (issue #10981) and corrected typo in test_universal (#11746) 2018-01-17 21:19:34 -08:00
karlkar 7617b8af52 Fix for None object access attempt (#11748)
* Fix NoneType access error

* Update lib dependencies

* Fix for line length
2018-01-17 21:10:15 -08:00
Martin Rowan 33fd9c7c8f Fix effects not appearing in UI due to missing attribute (#11738) 2018-01-17 22:00:46 +01:00
Pierre Ståhl 8703124c76 Add enable_output service to Yamaha platform (#11103)
* Add enable_output service to Yamaha platform

* Fix lint issues

* Fix review comment

* Check entity_ids instead of device
2018-01-17 19:34:21 +01:00
Pascal Vizeli 4ee2c311a7 Don't use None inside header (#11725) 2018-01-17 15:20:26 +01:00
Dan Nixon 020593d509 Override default name for TP-Link devices (#11710)
Fixes #11706

Corrects a bug introduced that prevents the names of TP-LInk devices
from being pulled from the devices themselves.
2018-01-17 00:52:32 -08:00
Åskar Andersson 1f118c4b84 Update mold_indicator.py (#11715) 2018-01-17 09:12:50 +01:00
Paulus Schoutsen abbc6a2587 Merge branch 'master' into dev 2018-01-16 14:49:03 -08:00
Paulus Schoutsen 37eb6c90b6 Merge pull request #11716 from home-assistant/release-0-61-1
0.61.1
2018-01-16 14:33:12 -08:00
Lukas Barth fc302186b2 Fix purge with MariaDB / MySQL (#11713) 2018-01-16 13:37:38 -08:00
Bob Anderson 903ca567c5 History order bugfix and opt-in option (#11686)
* make history view re-ordering optional and opt-in, also fix type bug

* use python false for default value

* whitespace cleanup
2018-01-16 13:37:37 -08:00
Daniel Høyer Iversen fd75f157d6 Rfxtrx fix (#11678)
* fix voluptuous bug in rfxtrx

* fix voluptuous bug in rfxtrx

* fix voluptuous bug in rfxtrx
2018-01-16 13:37:37 -08:00
Anders Melchiorsen f86224a64e Move several local services to their right domain (#11677)
* Move several local services to their right domain

* Fix lint
2018-01-16 13:37:37 -08:00
Russell Cloran ed41252207 Fix zha color probe (#11670) 2018-01-16 13:37:36 -08:00
Paulus Schoutsen 6ec411b4bb Version bump to 0.61.1 2018-01-16 13:37:19 -08:00
Lukas Barth 5a26d4c039 Fix purge with MariaDB / MySQL (#11713) 2018-01-16 13:35:23 -08:00
Bob Anderson 94950cccc8 History order bugfix and opt-in option (#11686)
* make history view re-ordering optional and opt-in, also fix type bug

* use python false for default value

* whitespace cleanup
2018-01-16 11:48:10 -08:00
Sergey Isachenko 3a00077305 Tesla bug fix #11598 (#11707) 2018-01-16 17:34:41 +01:00
Wolf-Bastian Pöttner 4a82606ffb Feature/fritzdect errorhandling (#11490)
* Update to fritzhome 1.0.4

* Improved error handling, added autoreconnect on connection failure

* Move from STATE_UNKNOWN to None
2018-01-16 15:55:22 +01:00
Fabian Affolter d5f63ebac4 Add attributes (#11698) 2018-01-16 12:32:08 +01:00
Åskar Andersson dce079e711 fixed not to include spaces or dots in attribute names (#11694)
mold_indicator should be fixed not to include spaces or dots in attribute names

https://community.home-assistant.io/t/how-to-show-sensor-attribute-est-crit-temp-in-customize-template-and-extra-badge/39451
2018-01-16 10:59:20 +01:00
Fabian Affolter 632525f4d0 Enable probot move (#11690) 2018-01-16 09:25:13 +01:00
karlkar 98692523bf Added extra arguments to onvif platform config (#11680) 2018-01-16 09:23:48 +01:00
Anders Melchiorsen 87534692d0 Move several local services to their right domain (#11677)
* Move several local services to their right domain

* Fix lint
2018-01-15 14:53:56 -08:00
Daniel Høyer Iversen a358174536 Rfxtrx fix (#11678)
* fix voluptuous bug in rfxtrx

* fix voluptuous bug in rfxtrx

* fix voluptuous bug in rfxtrx
2018-01-15 14:26:27 -08:00
Russell Cloran ff32f90a29 Fix zha color probe (#11670) 2018-01-15 14:25:59 -08:00
Fabian Affolter fb69620e49 Upgrade pylast to 2.1.0 (#11668) 2018-01-15 14:25:00 -08:00
Fabian Affolter d219f244d2 Upgrade sqlalchemy to 1.2.1 (#11666) 2018-01-15 14:24:34 -08:00
Fabian Affolter b0860ce5f0 Change line separator to LN (#11662) 2018-01-15 14:24:12 -08:00
Fabian Affolter 56a2c587ad Upgrade youtube_dl to 2018.01.14 (#11661) 2018-01-15 14:23:53 -08:00
Thom Troy c43eceb2cb add temperature controls to eph-ember (#11400)
* add temperature controls to eph-ember

* fix linting
2018-01-15 23:13:48 +01:00
angel12 799e1f0469 Wemo Dimmer Support (#10882)
* Wemo Dimmer Support

Add support for the Wemo Dimmer Switch

* Add newline at end of file

Re: findings from hound

* Syntax for the hound

Sorry for the excess edits, new to python

* Change order of Models

Fixed order back to ABC order

* Changes as requested

I made the changes I was comfortable with at this point, the dimmer addition now very closely mirrors what is under the switch component, at least as far as the parts necessary for the dimmer.

Any changes past these with regards to the subscription registry / callback info is probably going to be over my head, but I will try to look deeper at them if required.

* Remove unnecessary lines

Removed self.schedule_update_ha_state() from turn off / turn on

* Remove update(self)

Removed update method

* Move subscription to async_added_to_hass

* Move subscription.
* Clean up.

* Wait until the job in the executor is done

* Run gen_requirements_all script

* Only update instance attributes via callback
2018-01-15 23:08:48 +01:00
Heiko Thiery fdcf332a8a Add support for configuring jeelink RF parameters (#11620)
* add support for configuring jeelink ŔF parameters

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* fix houndci-bot error

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>
2018-01-15 22:49:17 +01:00
Ben bf3329e9a9 update mypy commandline arguments (#11638) 2018-01-15 22:41:49 +01:00
Sean Dague 6b26154077 Add basic mychevy component (#11409)
* mychevy: added basic mychevy component

This implements a component using the mychevy library (which utilizes
selenium to webscrape the mychevy website for onstar for your
car). The architecture works by having a background thread doing
periodic polling of the website, and updating the sensors when new
data is found.

This requires rather more setup than most platforms, as you need
working selenium. Instructions will be provided on the component
list. All the sensors are spawned and coordinated from a single "hub"
as they are really just attributes of the same web scraping session.

* mychevy: only poll every 30 minutes

* mychevy: update sensors

* mychevy: better error handling

* mychevy: tweaking for refactor

* mychevy: bump version to handle odometer > 1000

* mychevy: great sensor reorg

* mychevy: add binary sensors

* mychevy: bump mychevy requirement

* mychevy: use dispatcher

Instead of directly modifying the sensors, this lets us use a
dispatcher to have sensors pull information from the car object when
there is a relevant update for them.

* mychevy: remove from coverage

* mychevy: dedicated constants for dispatch signals

This makes the dispatch signals dedicated topics, and fixes updating
the state on the sensors so that they are correctly updated.

* mychevy: updated with comments from martinhjelmare

* mychevy: set battery icon based with helper function

* Address additional review feedback

* Address additional review comments
2018-01-15 21:50:56 +01:00
covrig 5546ecd637 Round values to one decimal (#11673)
Temperature detection range: -20 - 60 Deg.C ( + / - 0.3 Deg.C )
Humidity detection range: 0 - 100pct RH ( + / - 0.3pct )
Atmospheric pressure detection range: 30 - 110KPa ( + / - 120Pa )
2018-01-15 20:40:12 +01:00
heydonms 079d4039a1 Add ability to specify a sender in the clicksend notification (#11046)
* Add ability to specify a sender in the clicksend notification

* Style fixes

* Fix remaining issue

* Add sender validation
2018-01-15 18:38:58 +01:00
Fabian Affolter 1ed0c7d85d Bump dev to 0.62.0.dev0 (#11652) 2018-01-15 11:42:31 +01:00
Kane610 3979387c80 Add Deconz support for Zigbee green power devices like Hue Tap (#11455)
* Add support for Zigbee green power devices such as the Hue Tap

* Clarify that imported SWITCH is a DECONZ_REMOTE
2018-01-15 10:47:55 +01:00
Fabian Affolter f86b645417 Merge branch 'master' into dev 2018-01-15 10:42:20 +01:00
Fabian Affolter 688d706449 Upgrade coinmarketcap to 4.1.2 (#11634) 2018-01-14 22:31:47 +01:00
Pascal Vizeli 028597f774 Update CODEOWNERS 2018-01-14 15:03:39 +01:00
Lukas Barth 0550baaf4f Add templates to MQTT climate (#11623)
* Add templates

* Make pylint happy
2018-01-14 12:55:19 +01:00
Daniel Høyer Iversen c33b171043 upgrade xiaomi lib (#11629) 2018-01-13 21:34:56 +01:00
Giuseppe 9d67d229fa Fixes and enhancements for the Tahoma platform (#11547)
* Strip off the RTS/IO ID from the entity ID

* Ignore exception thrown when the device does not provide an active state

* Send actions with a label for easier identification in the Tahoma log

* Linting

* Strip off the RTS/IO ID from the entity ID, take 2

As per suggestions, let HA do the standard initialization and assign
an appropriate entity_id, instead of overriding it with the lengthy
unique_id coming from the TaHoma devices.
2018-01-13 21:24:10 +01:00
starkillerOG cdbf2f9293 Hyperion: Add brightness, HDMI and effect support (#11543)
* Hyperion: Add brightness, HDMI and effect support

- added brightness support to dim the hyperion light
- changed the "OFF" command to set the color to [0,0,0] after clearing all priorities.
  This is neccesary to keep the light turned off when an HDMI grabber is used for ambilight with hyperion.
- added HDMI ambilight mode recognition and control.
  by setting the "hdmi_priority" in your "configuration.yaml" file (defaults to 880), home assistant will now be able to recognize when the hyperion light is in HDMI ambilight mode and will change its icon to an HDMI symbol and set the status to ON.
Switching the hyperion light to HDMI ambilight mode can be done through the effect option (clears all priorities such that the HDMI grabber remains).
- added effect support for the default effects of hyperion, a custom list can be defined in the "configuration.yaml" file by using the "effect_list" option.

* Hyperion: Add brightness, HDMI and effect support

- added brightness support to dim the hyperion light
- changed the "OFF" command to set the color to [0,0,0] after clearing all priorities.
  This is neccesary to keep the light turned off when an HDMI grabber is used for ambilight with hyperion.
- added HDMI ambilight mode recognition and control.
  by setting the "hdmi_priority" in your "configuration.yaml" file (defaults to 880), home assistant will now be able to recognize when the hyperion light is in HDMI ambilight mode and will change its icon to an HDMI symbol and set the status to ON.
Switching the hyperion light to HDMI ambilight mode can be done through the effect option (clears all priorities such that the HDMI grabber remains).
- added effect support for the default effects of hyperion, a custom list can be defined in the "configuration.yaml" file by using the "effect_list" option.
- fixed some style issues with too long lines

* Hyperion: Add brightness, HDMI and effect support 

 - fixed some more indentation style issues

* Hyperion: Add brightness, HDMI and effect support 

- yet more fixed visuel indent issues

* Hyperion: Add brightness, HDMI and effect support 

- more visuel indents

* Hyperion: Add brightness, HDMI and effect support

- fixed invalid variable "A"

* Hyperion: Add brightness, HDMI and effect support

- remove unnececary brackets
- specify specific exceptions

* correct changing state holding attributes during a service method

Proccesed the comments of @MartinHjelmare: https://github.com/home-assistant/home-assistant/pull/11543#pullrequestreview-88328659

* indent correction

corrected tab instead of 4 spaces

* Hyperion: Add brightness, HDMI and effect support 

- changed 'none' to None
- renamed "self._skip_check" to "self._skip_update"

* Add brightness, HDMI and effect support 

changed checking if a list is empty from "list == []" to "not list"
2018-01-13 21:06:34 +01:00
Jesse Hills 5def6ebc3b Use kelvin/mireds correctly for setting iglo white (#11622)
* Use kelvin/mireds correctly for setting iglo white

* Update requirements_all.txt

* Fix line lengths
2018-01-13 20:07:39 +01:00
Alok Saboo ea62deda59 Update Pyarlo to 0.1.2 (#11626) 2018-01-13 20:00:04 +01:00
Nicko van Someren 3e43f4e58e Adding support for Lutron covers (#11602)
* Adding support for Lutron Radio RA2 shades as cover components.

* Adding initial version of the Lutron shades component.

* Fixed line-length violation detected by Hound.
2018-01-13 19:11:20 +01:00
Jason Kölker 2e08766cb1 light/mqqt_json: allow brightness scaling (#11613) 2018-01-13 18:58:21 +01:00
Kees Schollaart e476b9d225 Update Xiaomi Miio compontent broken URI (#11621) 2018-01-13 12:16:09 +01:00
Pascal Vizeli b2b836d4c1 small sonos cleanup (#11607) 2018-01-13 09:59:50 +01:00
Thijs de Jong 5e81736f88 patch stop command (#11612) 2018-01-13 09:06:37 +01:00
Fabian Affolter 37427d052e Upgrad youtube_dl to 2017.12.31 (#11610) 2018-01-13 09:02:51 +01:00
Fabian Affolter 5656b0eb2c Upgrade keyring to 10.6.0 (#11608) 2018-01-13 09:02:25 +01:00
Fabian Affolter b854cdb95b Upgrade yarl to 0.18.0 (#11609) 2018-01-13 09:01:05 +01:00
Daniel Høyer Iversen be31a860d1 Xiaomi lib upgrade (#11603)
* upgrade xiaomi lib

* xiaomi lib
2018-01-12 13:35:32 -08:00
Daniel Høyer Iversen 0409192e64 Bugfix and cleanup for Rfxtrx (#11600)
* rfxtrx clean up

* rfxtrx clean up

* rfxtrx clean up
2018-01-12 11:52:53 -08:00
tschmidty69 c036141b37 Pushbullet email support (fix) (#11590)
* Simplified push calls

* Cleaned up and added unittests

* Fixed email parameter

* Fixed email parameter
2018-01-12 11:06:42 -08:00
tschmidty69 b8e4c2ff69 Snips add say and say_actions services (new) (#11596)
* Added snips.say and snips.say_action services

* Added snips.say and snips.say_action services

* Merged services.yaml changes I missed

* added tests for new service configs

* Woof

* Woof Woof

* Changed attribute names to follow hass standards.

* updated test_snips with new attribute names
2018-01-12 10:19:43 -08:00
Thijs de Jong cc236529c4 Fix Tahoma stop command for 2 types of shutters (#11588)
* add working stop command

This fixes the stop command for 2 types of roller shutters

* fix line too long

* fix indentation

* fix indentation
2018-01-12 16:04:44 +01:00
Pascal Vizeli dacd7cd8a4 Core support for hass.io calls & Bugfix check_config (#11571)
* Initial overwrites

* Add check_config function.

* Update hassio.py

* Address comments

* add hassio support

* add more tests

* revert core changes

* Address check_config

* Address comment with api_bool

* Bugfix check_config

* Update core.py

* Update test_core.py

* Update config.py

* Update hassio.py

* Update config.py

* Update test_config.py
2018-01-12 15:29:58 +01:00
Bob Anderson 8a301c6c59 Concord232 alarm arm away fix (#11597)
* fix arming away cmd for concord232 client

* bump required version of concord232 to 0.15
2018-01-12 08:45:01 +01:00
Adam Mills 6918993c75 Fix state for trigger with forced updates (#11595) 2018-01-11 22:06:09 -08:00
Jack Fan 88161cd5c9 Avoid returning empty media_image_url string (#11557)
This relates to issue
https://github.com/home-assistant/home-assistant/issues/11556
2018-01-11 20:14:37 -08:00
640 changed files with 19690 additions and 12612 deletions
+23 -2
View File
@@ -97,6 +97,9 @@ omit =
homeassistant/components/homematic/__init__.py
homeassistant/components/*/homematic.py
homeassistant/components/ihc/*
homeassistant/components/*/ihc.py
homeassistant/components/insteon_local.py
homeassistant/components/*/insteon_local.py
@@ -106,6 +109,9 @@ omit =
homeassistant/components/ios.py
homeassistant/components/*/ios.py
homeassistant/components/iota.py
homeassistant/components/*/iota.py
homeassistant/components/isy994.py
homeassistant/components/*/isy994.py
@@ -139,12 +145,18 @@ omit =
homeassistant/components/maxcube.py
homeassistant/components/*/maxcube.py
homeassistant/components/mercedesme.py
homeassistant/components/*/mercedesme.py
homeassistant/components/mochad.py
homeassistant/components/*/mochad.py
homeassistant/components/modbus.py
homeassistant/components/*/modbus.py
homeassistant/components/mychevy.py
homeassistant/components/*/mychevy.py
homeassistant/components/mysensors.py
homeassistant/components/*/mysensors.py
@@ -241,6 +253,9 @@ omit =
homeassistant/components/volvooncall.py
homeassistant/components/*/volvooncall.py
homeassistant/components/waterfurnace.py
homeassistant/components/*/waterfurnace.py
homeassistant/components/*/webostv.py
homeassistant/components/wemo.py
@@ -304,6 +319,7 @@ omit =
homeassistant/components/camera/ring.py
homeassistant/components/camera/rpi_camera.py
homeassistant/components/camera/synology.py
homeassistant/components/camera/xeoma.py
homeassistant/components/camera/yi.py
homeassistant/components/climate/econet.py
homeassistant/components/climate/ephember.py
@@ -318,6 +334,7 @@ omit =
homeassistant/components/climate/radiotherm.py
homeassistant/components/climate/sensibo.py
homeassistant/components/climate/touchline.py
homeassistant/components/climate/venstar.py
homeassistant/components/cover/garadget.py
homeassistant/components/cover/homematic.py
homeassistant/components/cover/knx.py
@@ -327,7 +344,6 @@ omit =
homeassistant/components/cover/scsgate.py
homeassistant/components/device_tracker/actiontec.py
homeassistant/components/device_tracker/aruba.py
homeassistant/components/device_tracker/asuswrt.py
homeassistant/components/device_tracker/automatic.py
homeassistant/components/device_tracker/bbox.py
homeassistant/components/device_tracker/bluetooth_le_tracker.py
@@ -364,6 +380,7 @@ omit =
homeassistant/components/fan/xiaomi_miio.py
homeassistant/components/feedreader.py
homeassistant/components/foursquare.py
homeassistant/components/goalfeed.py
homeassistant/components/ifttt.py
homeassistant/components/image_processing/dlib_face_detect.py
homeassistant/components/image_processing/dlib_face_identify.py
@@ -422,6 +439,7 @@ omit =
homeassistant/components/media_player/kodi.py
homeassistant/components/media_player/lg_netcast.py
homeassistant/components/media_player/liveboxplaytv.py
homeassistant/components/media_player/mediaroom.py
homeassistant/components/media_player/mpchc.py
homeassistant/components/media_player/mpd.py
homeassistant/components/media_player/nad.py
@@ -436,7 +454,6 @@ omit =
homeassistant/components/media_player/roku.py
homeassistant/components/media_player/russound_rio.py
homeassistant/components/media_player/russound_rnet.py
homeassistant/components/media_player/samsungtv.py
homeassistant/components/media_player/snapcast.py
homeassistant/components/media_player/sonos.py
homeassistant/components/media_player/spotify.py
@@ -493,6 +510,7 @@ omit =
homeassistant/components/remember_the_milk/__init__.py
homeassistant/components/remote/harmony.py
homeassistant/components/remote/itach.py
homeassistant/components/remote/xiaomi_miio.py
homeassistant/components/scene/hunterdouglas_powerview.py
homeassistant/components/scene/lifx_cloud.py
homeassistant/components/sensor/airvisual.py
@@ -504,6 +522,7 @@ omit =
homeassistant/components/sensor/bitcoin.py
homeassistant/components/sensor/blockchain.py
homeassistant/components/sensor/bme280.py
homeassistant/components/sensor/bme680.py
homeassistant/components/sensor/bom.py
homeassistant/components/sensor/broadlink.py
homeassistant/components/sensor/buienradar.py
@@ -578,6 +597,7 @@ omit =
homeassistant/components/sensor/pi_hole.py
homeassistant/components/sensor/plex.py
homeassistant/components/sensor/pocketcasts.py
homeassistant/components/sensor/pollen.py
homeassistant/components/sensor/pushbullet.py
homeassistant/components/sensor/pvoutput.py
homeassistant/components/sensor/pyload.py
@@ -606,6 +626,7 @@ omit =
homeassistant/components/sensor/sytadin.py
homeassistant/components/sensor/tank_utility.py
homeassistant/components/sensor/ted5000.py
homeassistant/components/sensor/teksavvy.py
homeassistant/components/sensor/temper.py
homeassistant/components/sensor/tibber.py
homeassistant/components/sensor/time_date.py
+13
View File
@@ -0,0 +1,13 @@
# Configuration for move-issues - https://github.com/dessant/move-issues
# Delete the command comment. Ignored when the comment also contains other content
deleteCommand: true
# Close the source issue after moving
closeSourceIssue: true
# Lock the source issue after moving
lockSourceIssue: false
# Set custom aliases for targets
# aliases:
# r: repo
# or: owner/repo
+5
View File
@@ -16,7 +16,9 @@ Icon
# Thumbnails
._*
# IntelliJ IDEA
.idea
*.iml
# pytest
.cache
@@ -98,3 +100,6 @@ desktop.ini
/home-assistant.pyproj
/home-assistant.sln
/.vs/*
# mypy
/.mypy_cache/*
+5 -1
View File
@@ -32,13 +32,16 @@ homeassistant/components/zone.py @home-assistant/core
# To monitor non-pypi additions
requirements_all.txt @andrey-git
# HomeAssistant developer Teams
Dockerfile @home-assistant/docker
virtualization/Docker/* @home-assistant/docker
homeassistant/components/zwave/* @home-assistant/z-wave
homeassistant/components/*/zwave.py @home-assistant/z-wave
# Indiviudal components
homeassistant/components/hassio.py @home-assistant/hassio
# Individual components
homeassistant/components/alarm_control_panel/egardia.py @jeroenterheerdt
homeassistant/components/camera/yi.py @bachya
homeassistant/components/climate/ephember.py @ttroy50
@@ -58,6 +61,7 @@ homeassistant/components/sensor/airvisual.py @bachya
homeassistant/components/sensor/gearbest.py @HerrHofrat
homeassistant/components/sensor/irish_rail_transport.py @ttroy50
homeassistant/components/sensor/miflora.py @danielhiversen @ChristianKuehnel
homeassistant/components/sensor/pollen.py @bachya
homeassistant/components/sensor/sytadin.py @gautric
homeassistant/components/sensor/tibber.py @danielhiversen
homeassistant/components/sensor/waqi.py @andrey-git
+1 -1
View File
@@ -2,5 +2,5 @@
<li><a href="https://home-assistant.io/">Homepage</a></li>
<li><a href="https://community.home-assistant.io">Community Forums</a></li>
<li><a href="https://github.com/home-assistant/home-assistant">GitHub</a></li>
<li><a href="https://gitter.im/home-assistant/home-assistant">Gitter</a></li>
<li><a href="https://discord.gg/c5DvZ4e">Discord</a></li>
</ul>
+4 -2
View File
@@ -182,7 +182,8 @@ def check_pid(pid_file: str) -> None:
"""Check that Home Assistant is not already running."""
# Check pid file
try:
pid = int(open(pid_file, 'r').readline())
with open(pid_file, 'r') as file:
pid = int(file.readline())
except IOError:
# PID File does not exist
return
@@ -204,7 +205,8 @@ def write_pid(pid_file: str) -> None:
"""Create a PID File."""
pid = os.getpid()
try:
open(pid_file, 'w').write(str(pid))
with open(pid_file, 'w') as file:
file.write(str(pid))
except IOError:
print('Fatal Error: Unable to write pid file {}'.format(pid_file))
sys.exit(1)
+1 -1
View File
@@ -133,7 +133,7 @@ def async_setup(hass, config):
# have been processed. If a service does not exist it causes a 10
# second delay while we're blocking waiting for a response.
# But services can be registered on other HA instances that are
# listening to the bus too. So as a in between solution, we'll
# listening to the bus too. So as an in between solution, we'll
# block only if the service is defined in the current HA instance.
blocking = hass.services.has_service(domain, service.service)
+21 -35
View File
@@ -1,9 +1,8 @@
"""
ADS Component.
Support for Automation Device Specification (ADS).
For more details about this component, please refer to the documentation.
https://home-assistant.io/components/ads/
"""
import threading
import struct
@@ -29,7 +28,6 @@ ADSTYPE_BOOL = 'bool'
DOMAIN = 'ads'
# config variable names
CONF_ADS_VAR = 'adsvar'
CONF_ADS_VAR_BRIGHTNESS = 'adsvar_brightness'
CONF_ADS_TYPE = 'adstype'
@@ -47,10 +45,10 @@ CONFIG_SCHEMA = vol.Schema({
}, extra=vol.ALLOW_EXTRA)
SCHEMA_SERVICE_WRITE_DATA_BY_NAME = vol.Schema({
vol.Required(CONF_ADS_TYPE):
vol.In([ADSTYPE_INT, ADSTYPE_UINT, ADSTYPE_BYTE]),
vol.Required(CONF_ADS_VALUE): cv.match_all,
vol.Required(CONF_ADS_VAR): cv.string,
vol.Required(CONF_ADS_TYPE): vol.In([ADSTYPE_INT, ADSTYPE_UINT,
ADSTYPE_BYTE]),
vol.Required(CONF_ADS_VALUE): cv.match_all
})
@@ -59,15 +57,12 @@ def setup(hass, config):
import pyads
conf = config[DOMAIN]
# get ads connection parameters from config
net_id = conf.get(CONF_DEVICE)
ip_address = conf.get(CONF_IP_ADDRESS)
port = conf.get(CONF_PORT)
# create a new ads connection
client = pyads.Connection(net_id, port, ip_address)
# add some constants to AdsHub
AdsHub.ADS_TYPEMAP = {
ADSTYPE_BOOL: pyads.PLCTYPE_BOOL,
ADSTYPE_BYTE: pyads.PLCTYPE_BYTE,
@@ -81,16 +76,13 @@ def setup(hass, config):
AdsHub.PLCTYPE_UINT = pyads.PLCTYPE_UINT
AdsHub.ADSError = pyads.ADSError
# connect to ads client and try to connect
try:
ads = AdsHub(client)
except pyads.pyads.ADSError:
_LOGGER.error(
'Could not connect to ADS host (netid=%s, port=%s)', net_id, port
)
"Could not connect to ADS host (netid=%s, port=%s)", net_id, port)
return False
# add ads hub to hass data collection, listen to shutdown
hass.data[DATA_ADS] = ads
hass.bus.listen(EVENT_HOMEASSISTANT_STOP, ads.shutdown)
@@ -107,43 +99,41 @@ def setup(hass, config):
hass.services.register(
DOMAIN, SERVICE_WRITE_DATA_BY_NAME, handle_write_data_by_name,
schema=SCHEMA_SERVICE_WRITE_DATA_BY_NAME
)
schema=SCHEMA_SERVICE_WRITE_DATA_BY_NAME)
return True
# tuple to hold data needed for notification
# Tuple to hold data needed for notification
NotificationItem = namedtuple(
'NotificationItem', 'hnotify huser name plc_datatype callback'
)
class AdsHub:
"""Representation of a PyADS connection."""
class AdsHub(object):
"""Representation of an ADS connection."""
def __init__(self, ads_client):
"""Initialize the ADS Hub."""
"""Initialize the ADS hub."""
self._client = ads_client
self._client.open()
# all ADS devices are registered here
# All ADS devices are registered here
self._devices = []
self._notification_items = {}
self._lock = threading.Lock()
def shutdown(self, *args, **kwargs):
"""Shutdown ADS connection."""
_LOGGER.debug('Shutting down ADS')
_LOGGER.debug("Shutting down ADS")
for notification_item in self._notification_items.values():
self._client.del_device_notification(
notification_item.hnotify,
notification_item.huser
)
_LOGGER.debug(
'Deleting device notification %d, %d',
notification_item.hnotify, notification_item.huser
)
"Deleting device notification %d, %d",
notification_item.hnotify, notification_item.huser)
self._client.close()
def register_device(self, device):
@@ -167,33 +157,30 @@ class AdsHub:
with self._lock:
hnotify, huser = self._client.add_device_notification(
name, attr, self._device_notification_callback
)
name, attr, self._device_notification_callback)
hnotify = int(hnotify)
_LOGGER.debug(
'Added Device Notification %d for variable %s', hnotify, name
)
"Added device notification %d for variable %s", hnotify, name)
self._notification_items[hnotify] = NotificationItem(
hnotify, huser, name, plc_datatype, callback
)
hnotify, huser, name, plc_datatype, callback)
def _device_notification_callback(self, addr, notification, huser):
"""Handle device notifications."""
contents = notification.contents
hnotify = int(contents.hNotification)
_LOGGER.debug('Received Notification %d', hnotify)
_LOGGER.debug("Received notification %d", hnotify)
data = contents.data
try:
notification_item = self._notification_items[hnotify]
except KeyError:
_LOGGER.debug('Unknown Device Notification handle: %d', hnotify)
_LOGGER.debug("Unknown device notification handle: %d", hnotify)
return
# parse data to desired datatype
# Parse data to desired datatype
if notification_item.plc_datatype == self.PLCTYPE_BOOL:
value = bool(struct.unpack('<?', bytearray(data)[:1])[0])
elif notification_item.plc_datatype == self.PLCTYPE_INT:
@@ -204,7 +191,6 @@ class AdsHub:
value = struct.unpack('<H', bytearray(data)[:2])[0]
else:
value = bytearray(data)
_LOGGER.warning('No callback available for this datatype.')
_LOGGER.warning("No callback available for this datatype")
# execute callback
notification_item.callback(notification_item.name, value)
@@ -6,12 +6,12 @@ https://home-assistant.io/components/alarm_control_panel.abode/
"""
import logging
from homeassistant.components.abode import (
AbodeDevice, DOMAIN as ABODE_DOMAIN, CONF_ATTRIBUTION)
from homeassistant.components.alarm_control_panel import (AlarmControlPanel)
from homeassistant.const import (ATTR_ATTRIBUTION, STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED)
from homeassistant.components.abode import CONF_ATTRIBUTION, AbodeDevice
from homeassistant.components.abode import DOMAIN as ABODE_DOMAIN
from homeassistant.components.alarm_control_panel import AlarmControlPanel
from homeassistant.const import (
ATTR_ATTRIBUTION, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME,
STATE_ALARM_DISARMED)
DEPENDENCIES = ['abode']
@@ -21,7 +21,7 @@ ICON = 'mdi:security'
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up a sensor for an Abode device."""
"""Set up an alarm control panel for an Abode device."""
data = hass.data[ABODE_DOMAIN]
alarm_devices = [AbodeAlarm(data, data.abode.get_alarm(), data.name)]
@@ -41,7 +41,7 @@ class AbodeAlarm(AbodeDevice, AlarmControlPanel):
@property
def icon(self):
"""Return icon."""
"""Return the icon."""
return ICON
@property
@@ -81,5 +81,5 @@ class AbodeAlarm(AbodeDevice, AlarmControlPanel):
ATTR_ATTRIBUTION: CONF_ATTRIBUTION,
'device_id': self._device.device_id,
'battery_backup': self._device.battery,
'cellular_backup': self._device.is_cellular
'cellular_backup': self._device.is_cellular,
}
@@ -10,12 +10,11 @@ import logging
import voluptuous as vol
import homeassistant.components.alarm_control_panel as alarm
import homeassistant.helpers.config_validation as cv
from homeassistant.components.alarmdecoder import (
DATA_AD, SIGNAL_PANEL_MESSAGE)
from homeassistant.components.alarmdecoder import DATA_AD, SIGNAL_PANEL_MESSAGE
from homeassistant.const import (
ATTR_CODE, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME,
STATE_ALARM_DISARMED, STATE_ALARM_TRIGGERED)
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
@@ -67,6 +66,7 @@ class AlarmDecoderAlarmPanel(alarm.AlarmControlPanel):
SIGNAL_PANEL_MESSAGE, self._message_callback)
def _message_callback(self, message):
"""Handle received messages."""
if message.alarm_sounding or message.fire_alarm:
self._state = STATE_ALARM_TRIGGERED
elif message.armed_away:
@@ -120,7 +120,7 @@ class AlarmDecoderAlarmPanel(alarm.AlarmControlPanel):
'entry_delay_off': self._entry_delay_off,
'programming_mode': self._programming_mode,
'ready': self._ready,
'zone_bypassed': self._zone_bypassed
'zone_bypassed': self._zone_bypassed,
}
def alarm_disarm(self, code=None):
@@ -4,17 +4,18 @@ 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 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_CODE,
CONF_NAME)
import homeassistant.helpers.config_validation as cv
CONF_CODE, CONF_NAME, CONF_PASSWORD, CONF_USERNAME, STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_UNKNOWN)
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pyalarmdotcom==0.3.0']
@@ -44,7 +45,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
class AlarmDotCom(alarm.AlarmControlPanel):
"""Represent an Alarm.com status."""
"""Representation of an Alarm.com status."""
def __init__(self, hass, name, code, username, password):
"""Initialize the Alarm.com status."""
@@ -57,10 +58,8 @@ class AlarmDotCom(alarm.AlarmControlPanel):
self._password = password
self._websession = async_get_clientsession(self._hass)
self._state = STATE_UNKNOWN
self._alarm = Alarmdotcom(username,
password,
self._websession,
hass.loop)
self._alarm = Alarmdotcom(
username, password, self._websession, hass.loop)
@asyncio.coroutine
def async_login(self):
@@ -80,7 +79,7 @@ class AlarmDotCom(alarm.AlarmControlPanel):
@property
def code_format(self):
"""One or more characters if code is defined."""
"""Return one or more characters if code is defined."""
return None if self._code is None else '.+'
@property
@@ -116,5 +115,5 @@ class AlarmDotCom(alarm.AlarmControlPanel):
"""Validate given code."""
check = self._code is None or code == self._code
if not check:
_LOGGER.warning('Wrong code entered.')
_LOGGER.warning("Wrong code entered")
return check
@@ -29,9 +29,9 @@ DEFAULT_PORT = 5007
SCAN_INTERVAL = timedelta(seconds=1)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
})
@@ -47,7 +47,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
add_devices([Concord232Alarm(hass, url, name)])
except requests.exceptions.ConnectionError as ex:
_LOGGER.error("Unable to connect to Concord232: %s", str(ex))
return False
return
class Concord232Alarm(alarm.AlarmControlPanel):
@@ -107,7 +107,7 @@ class Concord232Alarm(alarm.AlarmControlPanel):
newstate = STATE_ALARM_ARMED_AWAY
if not newstate == self._state:
_LOGGER.info("State Change from %s to %s", self._state, newstate)
_LOGGER.info("State change from %s to %s", self._state, newstate)
self._state = newstate
return self._state
@@ -10,13 +10,13 @@ import requests
import voluptuous as vol
import homeassistant.components.alarm_control_panel as alarm
import homeassistant.exceptions as exc
import homeassistant.helpers.config_validation as cv
from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA
from homeassistant.const import (
CONF_PORT, CONF_HOST, CONF_PASSWORD, CONF_USERNAME, STATE_UNKNOWN,
CONF_NAME, STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_AWAY, STATE_ALARM_TRIGGERED, EVENT_HOMEASSISTANT_STOP)
CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_PORT, CONF_USERNAME,
EVENT_HOMEASSISTANT_STOP, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME,
STATE_ALARM_DISARMED, STATE_ALARM_TRIGGERED, STATE_UNKNOWN)
import homeassistant.exceptions as exc
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pythonegardia==1.0.26']
@@ -35,6 +35,7 @@ DEFAULT_REPORT_SERVER_PORT = 52010
DEFAULT_VERSION = 'GATE-01'
DOMAIN = 'egardia'
D_EGARDIASRV = 'egardiaserver'
NOTIFICATION_ID = 'egardia_notification'
NOTIFICATION_TITLE = 'Egardia'
@@ -97,8 +98,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
server = egardiaserver.EgardiaServer('', rs_port)
bound = server.bind()
if not bound:
raise IOError("Binding error occurred while " +
"starting EgardiaServer")
raise IOError(
"Binding error occurred while starting EgardiaServer")
hass.data[D_EGARDIASRV] = server
server.start()
except IOError:
@@ -106,22 +107,19 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
hass.data[D_EGARDIASRV].register_callback(eg_dev.handle_status_event)
def handle_stop_event(event):
"""Callback function for HA stop event."""
"""Call function for Home Assistant stop event."""
hass.data[D_EGARDIASRV].stop()
# listen to home assistant stop event
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, handle_stop_event)
# add egardia alarm device
add_devices([eg_dev], True)
class EgardiaAlarm(alarm.AlarmControlPanel):
"""Representation of a Egardia alarm."""
def __init__(self, name, egardiasystem,
rs_enabled=False, rs_codes=None):
"""Initialize object."""
def __init__(self, name, egardiasystem, rs_enabled=False, rs_codes=None):
"""Initialize the Egardia alarm."""
self._name = name
self._egardiasystem = egardiasystem
self._status = None
@@ -149,7 +147,7 @@ class EgardiaAlarm(alarm.AlarmControlPanel):
return False
def handle_status_event(self, event):
"""Handle egardia_system_status_event."""
"""Handle the Egardia system status event."""
statuscode = event.get('status')
if statuscode is not None:
status = self.lookupstatusfromcode(statuscode)
@@ -8,12 +8,12 @@ import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
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, CONF_HOST, STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, CONF_NAME)
CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNAME, STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pyialarm==0.2']
@@ -33,9 +33,9 @@ def no_application_protocol(value):
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): vol.All(cv.string, no_application_protocol),
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_HOST): vol.All(cv.string, no_application_protocol),
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
})
@@ -53,7 +53,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class IAlarmPanel(alarm.AlarmControlPanel):
"""Represent an iAlarm status."""
"""Representation of an iAlarm status."""
def __init__(self, name, username, password, url):
"""Initialize the iAlarm status."""
@@ -11,15 +11,17 @@ import logging
import voluptuous as vol
import homeassistant.components.alarm_control_panel as alarm
import homeassistant.util.dt as dt_util
from homeassistant.const import (
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT,
STATE_ALARM_ARMED_CUSTOM_BYPASS, STATE_ALARM_DISARMED, STATE_ALARM_PENDING,
STATE_ALARM_TRIGGERED, CONF_PLATFORM, CONF_NAME, CONF_CODE,
CONF_DELAY_TIME, CONF_PENDING_TIME, CONF_TRIGGER_TIME,
CONF_DISARM_AFTER_TRIGGER)
CONF_CODE, CONF_DELAY_TIME, CONF_DISARM_AFTER_TRIGGER, CONF_NAME,
CONF_PENDING_TIME, CONF_PLATFORM, CONF_TRIGGER_TIME,
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_CUSTOM_BYPASS,
STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT, STATE_ALARM_DISARMED,
STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import track_point_in_time
import homeassistant.util.dt as dt_util
_LOGGER = logging.getLogger(__name__)
CONF_CODE_TEMPLATE = 'code_template'
@@ -44,6 +46,7 @@ ATTR_POST_PENDING_STATE = 'post_pending_state'
def _state_validator(config):
"""Validate the state."""
config = copy.deepcopy(config)
for state in SUPPORTED_PRETRIGGER_STATES:
if CONF_DELAY_TIME not in config[state]:
@@ -58,6 +61,7 @@ def _state_validator(config):
def _state_schema(state):
"""Validate the state."""
schema = {}
if state in SUPPORTED_PRETRIGGER_STATES:
schema[vol.Optional(CONF_DELAY_TIME)] = vol.All(
@@ -97,8 +101,6 @@ PLATFORM_SCHEMA = vol.Schema(vol.All({
_state_schema(STATE_ALARM_TRIGGERED),
}, _state_validator))
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the manual alarm platform."""
@@ -151,7 +153,7 @@ class ManualAlarm(alarm.AlarmControlPanel):
@property
def should_poll(self):
"""Return the plling state."""
"""Return the polling state."""
return False
@property
@@ -182,23 +184,26 @@ class ManualAlarm(alarm.AlarmControlPanel):
@property
def _active_state(self):
"""Get the current state."""
if self.state == STATE_ALARM_PENDING:
return self._previous_state
else:
return self._state
def _pending_time(self, state):
"""Get the pending time."""
pending_time = self._pending_time_by_state[state]
if state == STATE_ALARM_TRIGGERED:
pending_time += self._delay_time_by_state[self._previous_state]
return pending_time
def _within_pending_time(self, state):
"""Get if the action is in the pending time window."""
return self._state_ts + self._pending_time(state) > dt_util.utcnow()
@property
def code_format(self):
"""One or more characters."""
"""Return one or more characters."""
return None if self._code is None else '.+'
def alarm_disarm(self, code=None):
@@ -250,6 +255,7 @@ class ManualAlarm(alarm.AlarmControlPanel):
self._update_state(STATE_ALARM_TRIGGERED)
def _update_state(self, state):
"""Update the state."""
if self._state == state:
return
@@ -26,6 +26,8 @@ from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import track_point_in_time
_LOGGER = logging.getLogger(__name__)
CONF_CODE_TEMPLATE = 'code_template'
CONF_PAYLOAD_DISARM = 'payload_disarm'
@@ -58,6 +60,7 @@ ATTR_POST_PENDING_STATE = 'post_pending_state'
def _state_validator(config):
"""Validate the state."""
config = copy.deepcopy(config)
for state in SUPPORTED_PRETRIGGER_STATES:
if CONF_DELAY_TIME not in config[state]:
@@ -72,6 +75,7 @@ def _state_validator(config):
def _state_schema(state):
"""Validate the state."""
schema = {}
if state in SUPPORTED_PRETRIGGER_STATES:
schema[vol.Optional(CONF_DELAY_TIME)] = vol.All(
@@ -117,8 +121,6 @@ PLATFORM_SCHEMA = vol.Schema(vol.All(mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_PAYLOAD_DISARM, default=DEFAULT_DISARM): cv.string,
}), _state_validator))
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the manual MQTT alarm platform."""
@@ -150,11 +152,10 @@ class ManualMQTTAlarm(alarm.AlarmControlPanel):
A trigger_time of zero disables the alarm_trigger service.
"""
def __init__(self, hass, name, code, code_template,
disarm_after_trigger,
state_topic, command_topic, qos,
payload_disarm, payload_arm_home, payload_arm_away,
payload_arm_night, config):
def __init__(self, hass, name, code, code_template, disarm_after_trigger,
state_topic, command_topic, qos, payload_disarm,
payload_arm_home, payload_arm_away, payload_arm_night,
config):
"""Init the manual MQTT alarm panel."""
self._state = STATE_ALARM_DISARMED
self._hass = hass
@@ -219,23 +220,26 @@ class ManualMQTTAlarm(alarm.AlarmControlPanel):
@property
def _active_state(self):
"""Get the current state."""
if self.state == STATE_ALARM_PENDING:
return self._previous_state
else:
return self._state
def _pending_time(self, state):
"""Get the pending time."""
pending_time = self._pending_time_by_state[state]
if state == STATE_ALARM_TRIGGERED:
pending_time += self._delay_time_by_state[self._previous_state]
return pending_time
def _within_pending_time(self, state):
"""Get if the action is in the pending time window."""
return self._state_ts + self._pending_time(state) > dt_util.utcnow()
@property
def code_format(self):
"""One or more characters."""
"""Return one or more characters."""
return None if self._code is None else '.+'
def alarm_disarm(self, code=None):
@@ -280,6 +284,7 @@ class ManualMQTTAlarm(alarm.AlarmControlPanel):
self._update_state(STATE_ALARM_TRIGGERED)
def _update_state(self, state):
"""Update the state."""
if self._state == state:
return
@@ -329,7 +334,7 @@ class ManualMQTTAlarm(alarm.AlarmControlPanel):
return state_attr
def async_added_to_hass(self):
"""Subscribe mqtt events.
"""Subscribe to MQTT events.
This method must be run in the event loop and returns a coroutine.
"""
@@ -358,5 +363,5 @@ class ManualMQTTAlarm(alarm.AlarmControlPanel):
@asyncio.coroutine
def _async_state_changed_listener(self, entity_id, old_state, new_state):
"""Publish state change to MQTT."""
mqtt.async_publish(self.hass, self._state_topic, new_state.state,
self._qos, True)
mqtt.async_publish(
self.hass, self._state_topic, new_state.state, self._qos, True)
@@ -42,7 +42,7 @@ PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_PAYLOAD_ARM_AWAY, default=DEFAULT_ARM_AWAY): cv.string,
vol.Optional(CONF_PAYLOAD_ARM_HOME, default=DEFAULT_ARM_HOME): cv.string,
vol.Optional(CONF_PAYLOAD_DISARM, default=DEFAULT_DISARM): cv.string,
})
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema)
@asyncio.coroutine
@@ -12,8 +12,8 @@ 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 (
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED,
STATE_UNKNOWN, CONF_NAME, CONF_HOST, CONF_PORT)
CONF_HOST, CONF_NAME, CONF_PORT, STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_UNKNOWN)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pynx584==0.4']
@@ -25,14 +25,14 @@ DEFAULT_NAME = 'NX584'
DEFAULT_PORT = 5007
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the nx584 platform."""
"""Set up the NX584 platform."""
name = config.get(CONF_NAME)
host = config.get(CONF_HOST)
port = config.get(CONF_PORT)
@@ -88,7 +88,7 @@ class NX584Alarm(alarm.AlarmControlPanel):
self._state = STATE_UNKNOWN
zones = []
except IndexError:
_LOGGER.error("nx584 reports no partitions")
_LOGGER.error("NX584 reports no partitions")
self._state = STATE_UNKNOWN
zones = []
@@ -8,9 +8,8 @@ import asyncio
import logging
import homeassistant.components.alarm_control_panel as alarm
from homeassistant.components.satel_integra import (CONF_ARM_HOME_MODE,
DATA_SATEL,
SIGNAL_PANEL_MESSAGE)
from homeassistant.components.satel_integra import (
CONF_ARM_HOME_MODE, DATA_SATEL, SIGNAL_PANEL_MESSAGE)
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
@@ -21,12 +20,12 @@ DEPENDENCIES = ['satel_integra']
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up for AlarmDecoder alarm panels."""
"""Set up for Satel Integra alarm panels."""
if not discovery_info:
return
device = SatelIntegraAlarmPanel("Alarm Panel",
discovery_info.get(CONF_ARM_HOME_MODE))
device = SatelIntegraAlarmPanel(
"Alarm Panel", discovery_info.get(CONF_ARM_HOME_MODE))
async_add_devices([device])
@@ -47,7 +46,7 @@ class SatelIntegraAlarmPanel(alarm.AlarmControlPanel):
@callback
def _message_callback(self, message):
"""Handle received messages."""
if message != self._state:
self._state = message
self.async_schedule_update_ha_state()
@@ -90,5 +89,5 @@ class SatelIntegraAlarmPanel(alarm.AlarmControlPanel):
def async_alarm_arm_home(self, code=None):
"""Send arm home command."""
if code:
yield from self.hass.data[DATA_SATEL].arm(code,
self._arm_home_mode)
yield from self.hass.data[DATA_SATEL].arm(
code, self._arm_home_mode)
@@ -11,9 +11,9 @@ 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_UNKNOWN, CONF_CODE, CONF_NAME,
STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_AWAY,
EVENT_HOMEASSISTANT_STOP)
CONF_CODE, CONF_NAME, CONF_PASSWORD, CONF_USERNAME,
EVENT_HOMEASSISTANT_STOP, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME,
STATE_ALARM_DISARMED, STATE_UNKNOWN)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['simplisafe-python==1.0.5']
@@ -22,6 +22,7 @@ _LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'SimpliSafe'
DOMAIN = 'simplisafe'
NOTIFICATION_ID = 'simplisafe_notification'
NOTIFICATION_TITLE = 'SimpliSafe Setup'
@@ -65,7 +66,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class SimpliSafeAlarm(alarm.AlarmControlPanel):
"""Representation a SimpliSafe alarm."""
"""Representation of a SimpliSafe alarm."""
def __init__(self, simplisafe, name, code):
"""Initialize the SimpliSafe alarm."""
@@ -82,7 +83,7 @@ class SimpliSafeAlarm(alarm.AlarmControlPanel):
@property
def code_format(self):
"""One or more characters if code is defined."""
"""Return one or more characters if code is defined."""
return None if self._code is None else '.+'
@property
@@ -103,12 +104,12 @@ class SimpliSafeAlarm(alarm.AlarmControlPanel):
def device_state_attributes(self):
"""Return the state attributes."""
return {
'temperature': self.simplisafe.temperature(),
'alarm': self.simplisafe.alarm(),
'co': self.simplisafe.carbon_monoxide(),
'fire': self.simplisafe.fire(),
'alarm': self.simplisafe.alarm(),
'flood': self.simplisafe.flood(),
'last_event': self.simplisafe.last_event(),
'flood': self.simplisafe.flood()
'temperature': self.simplisafe.temperature(),
}
def update(self):
@@ -9,26 +9,27 @@ import logging
import homeassistant.components.alarm_control_panel as alarm
from homeassistant.components.spc import (
SpcWebGateway, ATTR_DISCOVER_AREAS, DATA_API, DATA_REGISTRY)
ATTR_DISCOVER_AREAS, DATA_API, DATA_REGISTRY, SpcWebGateway)
from homeassistant.const import (
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED,
STATE_UNKNOWN)
_LOGGER = logging.getLogger(__name__)
SPC_AREA_MODE_TO_STATE = {'0': STATE_ALARM_DISARMED,
'1': STATE_ALARM_ARMED_HOME,
'3': STATE_ALARM_ARMED_AWAY}
SPC_AREA_MODE_TO_STATE = {
'0': STATE_ALARM_DISARMED,
'1': STATE_ALARM_ARMED_HOME,
'3': STATE_ALARM_ARMED_AWAY,
}
def _get_alarm_state(spc_mode):
"""Get the alarm state."""
return SPC_AREA_MODE_TO_STATE.get(spc_mode, STATE_UNKNOWN)
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices,
discovery_info=None):
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up the SPC alarm control panel platform."""
if (discovery_info is None or
discovery_info[ATTR_DISCOVER_AREAS] is None):
@@ -42,7 +43,7 @@ def async_setup_platform(hass, config, async_add_devices,
class SpcAlarm(alarm.AlarmControlPanel):
"""Represents the SPC alarm panel."""
"""Representation of the SPC alarm panel."""
def __init__(self, api, area):
"""Initialize the SPC alarm panel."""
@@ -57,7 +58,7 @@ class SpcAlarm(alarm.AlarmControlPanel):
@asyncio.coroutine
def async_added_to_hass(self):
"""Calbback for init handlers."""
"""Call for adding new entities."""
self.hass.data[DATA_REGISTRY].register_alarm_device(
self._area_id, self)
@@ -8,8 +8,8 @@ import logging
from time import sleep
import homeassistant.components.alarm_control_panel as alarm
from homeassistant.components.verisure import CONF_ALARM, CONF_CODE_DIGITS
from homeassistant.components.verisure import HUB as hub
from homeassistant.components.verisure import (CONF_ALARM, CONF_CODE_DIGITS)
from homeassistant.const import (
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED,
STATE_UNKNOWN)
@@ -43,7 +43,7 @@ class VerisureAlarm(alarm.AlarmControlPanel):
"""Representation of a Verisure alarm status."""
def __init__(self):
"""Initalize the Verisure alarm panel."""
"""Initialize the Verisure alarm panel."""
self._state = STATE_UNKNOWN
self._digits = hub.config.get(CONF_CODE_DIGITS)
self._changed_by = None
@@ -8,11 +8,10 @@ import asyncio
import logging
import homeassistant.components.alarm_control_panel as alarm
from homeassistant.const import (STATE_UNKNOWN,
STATE_ALARM_DISARMED,
STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_AWAY)
from homeassistant.components.wink import WinkDevice, DOMAIN
from homeassistant.components.wink import DOMAIN, WinkDevice
from homeassistant.const import (
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED,
STATE_UNKNOWN)
_LOGGER = logging.getLogger(__name__)
@@ -41,7 +40,7 @@ class WinkCameraDevice(WinkDevice, alarm.AlarmControlPanel):
@asyncio.coroutine
def async_added_to_hass(self):
"""Callback when entity is added to hass."""
"""Call when entity is added to hass."""
self.hass.data[DOMAIN]['entities']['alarm_control_panel'].append(self)
@property
+1 -1
View File
@@ -277,7 +277,7 @@ class Alert(ToggleEntity):
yield from self.async_update_ha_state()
@asyncio.coroutine
def async_toggle(self):
def async_toggle(self, **kwargs):
"""Async toggle alert."""
if self._ack:
return self.async_turn_on()
+30 -4
View File
@@ -10,18 +10,33 @@ import logging
import voluptuous as vol
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers import entityfilter
from . import flash_briefings, intent, smart_home
from .const import (
DOMAIN, CONF_UID, CONF_TITLE, CONF_AUDIO, CONF_TEXT, CONF_DISPLAY_URL)
from . import flash_briefings, intent
CONF_AUDIO, CONF_DISPLAY_URL, CONF_TEXT, CONF_TITLE, CONF_UID, DOMAIN,
CONF_FILTER, CONF_ENTITY_CONFIG)
_LOGGER = logging.getLogger(__name__)
CONF_FLASH_BRIEFINGS = 'flash_briefings'
CONF_SMART_HOME = 'smart_home'
DEPENDENCIES = ['http']
CONF_FLASH_BRIEFINGS = 'flash_briefings'
ALEXA_ENTITY_SCHEMA = vol.Schema({
vol.Optional(smart_home.CONF_DESCRIPTION): cv.string,
vol.Optional(smart_home.CONF_DISPLAY_CATEGORIES): cv.string,
vol.Optional(smart_home.CONF_NAME): cv.string,
})
SMART_HOME_SCHEMA = vol.Schema({
vol.Optional(
CONF_FILTER,
default=lambda: entityfilter.generate_filter([], [], [], [])
): entityfilter.FILTER_SCHEMA,
vol.Optional(CONF_ENTITY_CONFIG): {cv.entity_id: ALEXA_ENTITY_SCHEMA}
})
CONFIG_SCHEMA = vol.Schema({
DOMAIN: {
@@ -33,7 +48,10 @@ CONFIG_SCHEMA = vol.Schema({
vol.Required(CONF_TEXT, default=""): cv.template,
vol.Optional(CONF_DISPLAY_URL): cv.template,
}]),
}
},
# vol.Optional here would mean we couldn't distinguish between an empty
# smart_home: and none at all.
CONF_SMART_HOME: vol.Any(SMART_HOME_SCHEMA, None),
}
}, extra=vol.ALLOW_EXTRA)
@@ -49,4 +67,12 @@ def async_setup(hass, config):
if flash_briefings_config:
flash_briefings.async_setup(hass, flash_briefings_config)
try:
smart_home_config = config[CONF_SMART_HOME]
except KeyError:
pass
else:
smart_home_config = smart_home_config or SMART_HOME_SCHEMA({})
smart_home.async_setup(hass, smart_home_config)
return True
+3
View File
@@ -8,6 +8,9 @@ CONF_AUDIO = 'audio'
CONF_TEXT = 'text'
CONF_DISPLAY_URL = 'display_url'
CONF_FILTER = 'filter'
CONF_ENTITY_CONFIG = 'entity_config'
ATTR_UID = 'uid'
ATTR_UPDATE_DATE = 'updateDate'
ATTR_TITLE_TEXT = 'titleText'
@@ -5,19 +5,18 @@ For more details about this component, please refer to the documentation at
https://home-assistant.io/components/alexa/
"""
import copy
import logging
from datetime import datetime
import logging
import uuid
from homeassistant.components import http
from homeassistant.core import callback
from homeassistant.helpers import template
from homeassistant.components import http
from .const import (
CONF_UID, CONF_TITLE, CONF_AUDIO, CONF_TEXT, CONF_DISPLAY_URL, ATTR_UID,
ATTR_UPDATE_DATE, ATTR_TITLE_TEXT, ATTR_STREAM_URL, ATTR_MAIN_TEXT,
ATTR_REDIRECTION_URL, DATE_FORMAT)
ATTR_MAIN_TEXT, ATTR_REDIRECTION_URL, ATTR_STREAM_URL, ATTR_TITLE_TEXT,
ATTR_UID, ATTR_UPDATE_DATE, CONF_AUDIO, CONF_DISPLAY_URL, CONF_TEXT,
CONF_TITLE, CONF_UID, DATE_FORMAT)
_LOGGER = logging.getLogger(__name__)
@@ -46,11 +45,11 @@ class AlexaFlashBriefingView(http.HomeAssistantView):
@callback
def get(self, request, briefing_id):
"""Handle Alexa Flash Briefing request."""
_LOGGER.debug('Received Alexa flash briefing request for: %s',
_LOGGER.debug("Received Alexa flash briefing request for: %s",
briefing_id)
if self.flash_briefings.get(briefing_id) is None:
err = 'No configured Alexa flash briefing was found for: %s'
err = "No configured Alexa flash briefing was found for: %s"
_LOGGER.error(err, briefing_id)
return b'', 404
+15 -12
View File
@@ -3,30 +3,31 @@ Support for Alexa skill service end point.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/alexa/
"""
import asyncio
import enum
import logging
from homeassistant.exceptions import HomeAssistantError
from homeassistant.core import callback
from homeassistant.helpers import intent
from homeassistant.components import http
from homeassistant.core import callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import intent
from homeassistant.util.decorator import Registry
from .const import DOMAIN, SYN_RESOLUTION_MATCH
INTENTS_API_ENDPOINT = '/api/alexa'
HANDLERS = Registry()
_LOGGER = logging.getLogger(__name__)
HANDLERS = Registry()
INTENTS_API_ENDPOINT = '/api/alexa'
class SpeechType(enum.Enum):
"""The Alexa speech types."""
plaintext = "PlainText"
ssml = "SSML"
plaintext = 'PlainText'
ssml = 'SSML'
SPEECH_MAPPINGS = {
@@ -38,8 +39,8 @@ SPEECH_MAPPINGS = {
class CardType(enum.Enum):
"""The Alexa card types."""
simple = "Simple"
link_account = "LinkAccount"
simple = 'Simple'
link_account = 'LinkAccount'
@callback
@@ -64,7 +65,7 @@ class AlexaIntentsView(http.HomeAssistantView):
hass = request.app['hass']
message = yield from request.json()
_LOGGER.debug('Received Alexa request: %s', message)
_LOGGER.debug("Received Alexa request: %s", message)
try:
response = yield from async_handle_message(hass, message)
@@ -81,7 +82,7 @@ class AlexaIntentsView(http.HomeAssistantView):
"This intent is not yet configured within Home Assistant."))
except intent.InvalidSlotInfo as err:
_LOGGER.error('Received invalid slot data from Alexa: %s', err)
_LOGGER.error("Received invalid slot data from Alexa: %s", err)
return self.json(intent_error_response(
hass, message,
"Invalid slot information received for this intent."))
@@ -109,6 +110,7 @@ def async_handle_message(hass, message):
- intent.UnknownIntent
- intent.InvalidSlotInfo
- intent.IntentError
"""
req = message.get('request')
req_type = req['type']
@@ -138,6 +140,7 @@ def async_handle_intent(hass, message):
- intent.UnknownIntent
- intent.InvalidSlotInfo
- intent.IntentError
"""
req = message.get('request')
alexa_intent_info = req.get('intent')
+758 -100
View File
@@ -2,73 +2,582 @@
import asyncio
import logging
import math
from datetime import datetime
from uuid import uuid4
from homeassistant.components import (
alert, automation, cover, fan, group, input_boolean, light, lock,
media_player, scene, script, switch, http, sensor)
import homeassistant.core as ha
import homeassistant.util.color as color_util
from homeassistant.util.decorator import Registry
from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, SERVICE_LOCK,
ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, CONF_NAME, SERVICE_LOCK,
SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_PLAY,
SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_STOP,
SERVICE_SET_COVER_POSITION, SERVICE_TURN_OFF, SERVICE_TURN_ON,
SERVICE_UNLOCK, SERVICE_VOLUME_SET)
from homeassistant.components import (
alert, automation, cover, fan, group, input_boolean, light, lock,
media_player, scene, script, switch)
import homeassistant.util.color as color_util
from homeassistant.util.decorator import Registry
SERVICE_UNLOCK, SERVICE_VOLUME_SET, TEMP_FAHRENHEIT, TEMP_CELSIUS,
CONF_UNIT_OF_MEASUREMENT, STATE_LOCKED, STATE_UNLOCKED, STATE_ON)
from .const import CONF_FILTER, CONF_ENTITY_CONFIG
HANDLERS = Registry()
_LOGGER = logging.getLogger(__name__)
API_DIRECTIVE = 'directive'
API_ENDPOINT = 'endpoint'
API_EVENT = 'event'
API_CONTEXT = 'context'
API_HEADER = 'header'
API_PAYLOAD = 'payload'
API_TEMP_UNITS = {
TEMP_FAHRENHEIT: 'FAHRENHEIT',
TEMP_CELSIUS: 'CELSIUS',
}
SMART_HOME_HTTP_ENDPOINT = '/api/alexa/smart_home'
CONF_DESCRIPTION = 'description'
CONF_DISPLAY_CATEGORIES = 'display_categories'
CONF_NAME = 'name'
HANDLERS = Registry()
ENTITY_ADAPTERS = Registry()
MAPPING_COMPONENT = {
alert.DOMAIN: ['OTHER', ('Alexa.PowerController',), None],
automation.DOMAIN: ['OTHER', ('Alexa.PowerController',), None],
cover.DOMAIN: [
'DOOR', ('Alexa.PowerController',), {
cover.SUPPORT_SET_POSITION: 'Alexa.PercentageController',
class _DisplayCategory(object):
"""Possible display categories for Discovery response.
https://developer.amazon.com/docs/device-apis/alexa-discovery.html#display-categories
"""
# Describes a combination of devices set to a specific state, when the
# state change must occur in a specific order. For example, a "watch
# Netflix" scene might require the: 1. TV to be powered on & 2. Input set
# to HDMI1. Applies to Scenes
ACTIVITY_TRIGGER = "ACTIVITY_TRIGGER"
# Indicates media devices with video or photo capabilities.
CAMERA = "CAMERA"
# Indicates a door.
DOOR = "DOOR"
# Indicates light sources or fixtures.
LIGHT = "LIGHT"
# An endpoint that cannot be described in on of the other categories.
OTHER = "OTHER"
# Describes a combination of devices set to a specific state, when the
# order of the state change is not important. For example a bedtime scene
# might include turning off lights and lowering the thermostat, but the
# order is unimportant. Applies to Scenes
SCENE_TRIGGER = "SCENE_TRIGGER"
# Indicates an endpoint that locks.
SMARTLOCK = "SMARTLOCK"
# Indicates modules that are plugged into an existing electrical outlet.
# Can control a variety of devices.
SMARTPLUG = "SMARTPLUG"
# Indicates the endpoint is a speaker or speaker system.
SPEAKER = "SPEAKER"
# Indicates in-wall switches wired to the electrical system. Can control a
# variety of devices.
SWITCH = "SWITCH"
# Indicates endpoints that report the temperature only.
TEMPERATURE_SENSOR = "TEMPERATURE_SENSOR"
# Indicates endpoints that control temperature, stand-alone air
# conditioners, or heaters with direct temperature control.
THERMOSTAT = "THERMOSTAT"
# Indicates the endpoint is a television.
# pylint: disable=invalid-name
TV = "TV"
def _capability(interface,
version=3,
supports_deactivation=None,
retrievable=None,
properties_supported=None,
cap_type='AlexaInterface'):
"""Return a Smart Home API capability object.
https://developer.amazon.com/docs/device-apis/alexa-discovery.html#capability-object
There are some additional fields allowed but not implemented here since
we've no use case for them yet:
- proactively_reported
`supports_deactivation` applies only to scenes.
"""
result = {
'type': cap_type,
'interface': interface,
'version': version,
}
if supports_deactivation is not None:
result['supportsDeactivation'] = supports_deactivation
if retrievable is not None:
result['retrievable'] = retrievable
if properties_supported is not None:
result['properties'] = {'supported': properties_supported}
return result
class _UnsupportedInterface(Exception):
"""This entity does not support the requested Smart Home API interface."""
class _UnsupportedProperty(Exception):
"""This entity does not support the requested Smart Home API property."""
class _AlexaEntity(object):
"""An adaptation of an entity, expressed in Alexa's terms.
The API handlers should manipulate entities only through this interface.
"""
def __init__(self, config, entity):
self.config = config
self.entity = entity
self.entity_conf = config.entity_config.get(entity.entity_id, {})
def friendly_name(self):
"""Return the Alexa API friendly name."""
return self.entity_conf.get(CONF_NAME, self.entity.name)
def description(self):
"""Return the Alexa API description."""
return self.entity_conf.get(CONF_DESCRIPTION, self.entity.entity_id)
def entity_id(self):
"""Return the Alexa API entity id."""
return self.entity.entity_id.replace('.', '#')
def display_categories(self):
"""Return a list of display categories."""
entity_conf = self.config.entity_config.get(self.entity.entity_id, {})
if CONF_DISPLAY_CATEGORIES in entity_conf:
return [entity_conf[CONF_DISPLAY_CATEGORIES]]
return self.default_display_categories()
def default_display_categories(self):
"""Return a list of default display categories.
This can be overridden by the user in the Home Assistant configuration.
See also _DisplayCategory.
"""
raise NotImplementedError
def get_interface(self, capability):
"""Return the given _AlexaInterface.
Raises _UnsupportedInterface.
"""
pass
def interfaces(self):
"""Return a list of supported interfaces.
Used for discovery. The list should contain _AlexaInterface instances.
If the list is empty, this entity will not be discovered.
"""
raise NotImplementedError
class _AlexaInterface(object):
def __init__(self, entity):
self.entity = entity
def name(self):
"""Return the Alexa API name of this interface."""
raise NotImplementedError
@staticmethod
def properties_supported():
"""Return what properties this entity supports."""
return []
@staticmethod
def properties_proactively_reported():
"""Return True if properties asynchronously reported."""
return False
@staticmethod
def properties_retrievable():
"""Return True if properties can be retrieved."""
return False
@staticmethod
def get_property(name):
"""Read and return a property.
Return value should be a dict, or raise _UnsupportedProperty.
Properties can also have a timeOfSample and uncertaintyInMilliseconds,
but returning those metadata is not yet implemented.
"""
raise _UnsupportedProperty(name)
@staticmethod
def supports_deactivation():
"""Applicable only to scenes."""
return None
def serialize_discovery(self):
"""Serialize according to the Discovery API."""
result = {
'type': 'AlexaInterface',
'interface': self.name(),
'version': '3',
'properties': {
'supported': self.properties_supported(),
'proactivelyReported': self.properties_proactively_reported(),
'retrievable': self.properties_retrievable(),
},
}
],
fan.DOMAIN: [
'OTHER', ('Alexa.PowerController',), {
fan.SUPPORT_SET_SPEED: 'Alexa.PercentageController',
# pylint: disable=assignment-from-none
supports_deactivation = self.supports_deactivation()
if supports_deactivation is not None:
result['supportsDeactivation'] = supports_deactivation
return result
def serialize_properties(self):
"""Return properties serialized for an API response."""
for prop in self.properties_supported():
prop_name = prop['name']
yield {
'name': prop_name,
'namespace': self.name(),
'value': self.get_property(prop_name),
}
class _AlexaPowerController(_AlexaInterface):
def name(self):
return 'Alexa.PowerController'
def properties_supported(self):
return [{'name': 'powerState'}]
def properties_retrievable(self):
return True
def get_property(self, name):
if name != 'powerState':
raise _UnsupportedProperty(name)
if self.entity.state == STATE_ON:
return 'ON'
return 'OFF'
class _AlexaLockController(_AlexaInterface):
def name(self):
return 'Alexa.LockController'
def properties_supported(self):
return [{'name': 'lockState'}]
def properties_retrievable(self):
return True
def get_property(self, name):
if name != 'lockState':
raise _UnsupportedProperty(name)
if self.entity.state == STATE_LOCKED:
return 'LOCKED'
elif self.entity.state == STATE_UNLOCKED:
return 'UNLOCKED'
return 'JAMMED'
class _AlexaSceneController(_AlexaInterface):
def __init__(self, entity, supports_deactivation):
_AlexaInterface.__init__(self, entity)
self.supports_deactivation = lambda: supports_deactivation
def name(self):
return 'Alexa.SceneController'
class _AlexaBrightnessController(_AlexaInterface):
def name(self):
return 'Alexa.BrightnessController'
def properties_supported(self):
return [{'name': 'brightness'}]
def properties_retrievable(self):
return True
def get_property(self, name):
if name != 'brightness':
raise _UnsupportedProperty(name)
if 'brightness' in self.entity.attributes:
return round(self.entity.attributes['brightness'] / 255.0 * 100)
return 0
class _AlexaColorController(_AlexaInterface):
def name(self):
return 'Alexa.ColorController'
class _AlexaColorTemperatureController(_AlexaInterface):
def name(self):
return 'Alexa.ColorTemperatureController'
class _AlexaPercentageController(_AlexaInterface):
def name(self):
return 'Alexa.PercentageController'
class _AlexaSpeaker(_AlexaInterface):
def name(self):
return 'Alexa.Speaker'
class _AlexaStepSpeaker(_AlexaInterface):
def name(self):
return 'Alexa.StepSpeaker'
class _AlexaPlaybackController(_AlexaInterface):
def name(self):
return 'Alexa.PlaybackController'
class _AlexaInputController(_AlexaInterface):
def name(self):
return 'Alexa.InputController'
class _AlexaTemperatureSensor(_AlexaInterface):
def name(self):
return 'Alexa.TemperatureSensor'
def properties_supported(self):
return [{'name': 'temperature'}]
def properties_retrievable(self):
return True
def get_property(self, name):
if name != 'temperature':
raise _UnsupportedProperty(name)
unit = self.entity.attributes[CONF_UNIT_OF_MEASUREMENT]
return {
'value': float(self.entity.state),
'scale': API_TEMP_UNITS[unit],
}
],
group.DOMAIN: ['OTHER', ('Alexa.PowerController',), None],
input_boolean.DOMAIN: ['OTHER', ('Alexa.PowerController',), None],
light.DOMAIN: [
'LIGHT', ('Alexa.PowerController',), {
light.SUPPORT_BRIGHTNESS: 'Alexa.BrightnessController',
light.SUPPORT_RGB_COLOR: 'Alexa.ColorController',
light.SUPPORT_XY_COLOR: 'Alexa.ColorController',
light.SUPPORT_COLOR_TEMP: 'Alexa.ColorTemperatureController',
}
],
lock.DOMAIN: ['SMARTLOCK', ('Alexa.LockController',), None],
media_player.DOMAIN: [
'TV', ('Alexa.PowerController',), {
media_player.SUPPORT_VOLUME_SET: 'Alexa.Speaker',
media_player.SUPPORT_PLAY: 'Alexa.PlaybackController',
media_player.SUPPORT_PAUSE: 'Alexa.PlaybackController',
media_player.SUPPORT_STOP: 'Alexa.PlaybackController',
media_player.SUPPORT_NEXT_TRACK: 'Alexa.PlaybackController',
media_player.SUPPORT_PREVIOUS_TRACK: 'Alexa.PlaybackController',
}
],
scene.DOMAIN: ['ACTIVITY_TRIGGER', ('Alexa.SceneController',), None],
script.DOMAIN: ['OTHER', ('Alexa.PowerController',), None],
switch.DOMAIN: ['SWITCH', ('Alexa.PowerController',), None],
}
@ENTITY_ADAPTERS.register(alert.DOMAIN)
@ENTITY_ADAPTERS.register(automation.DOMAIN)
@ENTITY_ADAPTERS.register(input_boolean.DOMAIN)
class _GenericCapabilities(_AlexaEntity):
"""A generic, on/off device.
The choice of last resort.
"""
def default_display_categories(self):
return [_DisplayCategory.OTHER]
def interfaces(self):
return [_AlexaPowerController(self.entity)]
@ENTITY_ADAPTERS.register(switch.DOMAIN)
class _SwitchCapabilities(_AlexaEntity):
def default_display_categories(self):
return [_DisplayCategory.SWITCH]
def interfaces(self):
return [_AlexaPowerController(self.entity)]
@ENTITY_ADAPTERS.register(cover.DOMAIN)
class _CoverCapabilities(_AlexaEntity):
def default_display_categories(self):
return [_DisplayCategory.DOOR]
def interfaces(self):
yield _AlexaPowerController(self.entity)
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
if supported & cover.SUPPORT_SET_POSITION:
yield _AlexaPercentageController(self.entity)
@ENTITY_ADAPTERS.register(light.DOMAIN)
class _LightCapabilities(_AlexaEntity):
def default_display_categories(self):
return [_DisplayCategory.LIGHT]
def interfaces(self):
yield _AlexaPowerController(self.entity)
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
if supported & light.SUPPORT_BRIGHTNESS:
yield _AlexaBrightnessController(self.entity)
if supported & light.SUPPORT_RGB_COLOR:
yield _AlexaColorController(self.entity)
if supported & light.SUPPORT_XY_COLOR:
yield _AlexaColorController(self.entity)
if supported & light.SUPPORT_COLOR_TEMP:
yield _AlexaColorTemperatureController(self.entity)
@ENTITY_ADAPTERS.register(fan.DOMAIN)
class _FanCapabilities(_AlexaEntity):
def default_display_categories(self):
return [_DisplayCategory.OTHER]
def interfaces(self):
yield _AlexaPowerController(self.entity)
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
if supported & fan.SUPPORT_SET_SPEED:
yield _AlexaPercentageController(self.entity)
@ENTITY_ADAPTERS.register(lock.DOMAIN)
class _LockCapabilities(_AlexaEntity):
def default_display_categories(self):
return [_DisplayCategory.SMARTLOCK]
def interfaces(self):
return [_AlexaLockController(self.entity)]
@ENTITY_ADAPTERS.register(media_player.DOMAIN)
class _MediaPlayerCapabilities(_AlexaEntity):
def default_display_categories(self):
return [_DisplayCategory.TV]
def interfaces(self):
yield _AlexaPowerController(self.entity)
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
if supported & media_player.SUPPORT_VOLUME_SET:
yield _AlexaSpeaker(self.entity)
step_volume_features = (media_player.SUPPORT_VOLUME_MUTE |
media_player.SUPPORT_VOLUME_STEP)
if supported & step_volume_features:
yield _AlexaStepSpeaker(self.entity)
playback_features = (media_player.SUPPORT_PLAY |
media_player.SUPPORT_PAUSE |
media_player.SUPPORT_STOP |
media_player.SUPPORT_NEXT_TRACK |
media_player.SUPPORT_PREVIOUS_TRACK)
if supported & playback_features:
yield _AlexaPlaybackController(self.entity)
if supported & media_player.SUPPORT_SELECT_SOURCE:
yield _AlexaInputController(self.entity)
@ENTITY_ADAPTERS.register(scene.DOMAIN)
class _SceneCapabilities(_AlexaEntity):
def description(self):
# Required description as per Amazon Scene docs
scene_fmt = '{} (Scene connected via Home Assistant)'
return scene_fmt.format(_AlexaEntity.description(self))
def default_display_categories(self):
return [_DisplayCategory.SCENE_TRIGGER]
def interfaces(self):
return [_AlexaSceneController(self.entity,
supports_deactivation=False)]
@ENTITY_ADAPTERS.register(script.DOMAIN)
class _ScriptCapabilities(_AlexaEntity):
def default_display_categories(self):
return [_DisplayCategory.ACTIVITY_TRIGGER]
def interfaces(self):
can_cancel = bool(self.entity.attributes.get('can_cancel'))
return [_AlexaSceneController(self.entity,
supports_deactivation=can_cancel)]
@ENTITY_ADAPTERS.register(group.DOMAIN)
class _GroupCapabilities(_AlexaEntity):
def default_display_categories(self):
return [_DisplayCategory.SCENE_TRIGGER]
def interfaces(self):
return [_AlexaSceneController(self.entity,
supports_deactivation=True)]
@ENTITY_ADAPTERS.register(sensor.DOMAIN)
class _SensorCapabilities(_AlexaEntity):
def default_display_categories(self):
# although there are other kinds of sensors, all but temperature
# sensors are currently ignored.
return [_DisplayCategory.TEMPERATURE_SENSOR]
def interfaces(self):
attrs = self.entity.attributes
if attrs.get(CONF_UNIT_OF_MEASUREMENT) in (
TEMP_FAHRENHEIT,
TEMP_CELSIUS,
):
yield _AlexaTemperatureSensor(self.entity)
class _Cause(object):
"""Possible causes for property changes.
https://developer.amazon.com/docs/smarthome/state-reporting-for-a-smart-home-skill.html#cause-object
"""
# Indicates that the event was caused by a customer interaction with an
# application. For example, a customer switches on a light, or locks a door
# using the Alexa app or an app provided by a device vendor.
APP_INTERACTION = 'APP_INTERACTION'
# Indicates that the event was caused by a physical interaction with an
# endpoint. For example manually switching on a light or manually locking a
# door lock
PHYSICAL_INTERACTION = 'PHYSICAL_INTERACTION'
# Indicates that the event was caused by the periodic poll of an appliance,
# which found a change in value. For example, you might poll a temperature
# sensor every hour, and send the updated temperature to Alexa.
PERIODIC_POLL = 'PERIODIC_POLL'
# Indicates that the event was caused by the application of a device rule.
# For example, a customer configures a rule to switch on a light if a
# motion sensor detects motion. In this case, Alexa receives an event from
# the motion sensor, and another event from the light to indicate that its
# state change was caused by the rule.
RULE_TRIGGER = 'RULE_TRIGGER'
# Indicates that the event was caused by a voice interaction with Alexa.
# For example a user speaking to their Echo device.
VOICE_INTERACTION = 'VOICE_INTERACTION'
class Config:
@@ -80,6 +589,52 @@ class Config:
self.entity_config = entity_config or {}
@ha.callback
def async_setup(hass, config):
"""Activate Smart Home functionality of Alexa component.
This is optional, triggered by having a `smart_home:` sub-section in the
alexa configuration.
Even if that's disabled, the functionality in this module may still be used
by the cloud component which will call async_handle_message directly.
"""
smart_home_config = Config(
should_expose=config[CONF_FILTER],
entity_config=config.get(CONF_ENTITY_CONFIG),
)
hass.http.register_view(SmartHomeView(smart_home_config))
class SmartHomeView(http.HomeAssistantView):
"""Expose Smart Home v3 payload interface via HTTP POST."""
url = SMART_HOME_HTTP_ENDPOINT
name = 'api:alexa:smart_home'
def __init__(self, smart_home_config):
"""Initialize."""
self.smart_home_config = smart_home_config
@asyncio.coroutine
def post(self, request):
"""Handle Alexa Smart Home requests.
The Smart Home API requires the endpoint to be implemented in AWS
Lambda, which will need to forward the requests to here and pass back
the response.
"""
hass = request.app['hass']
message = yield from request.json()
_LOGGER.debug("Received Alexa Smart Home request: %s", message)
response = yield from async_handle_message(
hass, self.smart_home_config, message)
_LOGGER.debug("Sending Alexa Smart Home response: %s", response)
return b'' if response is None else self.json(response)
@asyncio.coroutine
def async_handle_message(hass, config, message):
"""Handle incoming API messages."""
@@ -100,7 +655,11 @@ def async_handle_message(hass, config, message):
return (yield from funct_ref(hass, config, message))
def api_message(request, name='Response', namespace='Alexa', payload=None):
def api_message(request,
name='Response',
namespace='Alexa',
payload=None,
context=None):
"""Create a API formatted response message.
Async friendly.
@@ -119,7 +678,7 @@ def api_message(request, name='Response', namespace='Alexa', payload=None):
}
}
# If a correlation token exsits, add it to header / Need by Async requests
# If a correlation token exists, add it to header / Need by Async requests
token = request[API_HEADER].get('correlationToken')
if token:
response[API_EVENT][API_HEADER]['correlationToken'] = token
@@ -128,6 +687,9 @@ def api_message(request, name='Response', namespace='Alexa', payload=None):
if API_ENDPOINT in request:
response[API_EVENT][API_ENDPOINT] = request[API_ENDPOINT].copy()
if context is not None:
response[API_CONTEXT] = context
return response
@@ -159,55 +721,26 @@ def async_api_discovery(hass, config, request):
entity.entity_id)
continue
class_data = MAPPING_COMPONENT.get(entity.domain)
if not class_data:
if entity.domain not in ENTITY_ADAPTERS:
continue
entity_conf = config.entity_config.get(entity.entity_id, {})
friendly_name = entity_conf.get(CONF_NAME, entity.name)
description = entity_conf.get(CONF_DESCRIPTION, entity.entity_id)
# Required description as per Amazon Scene docs
if entity.domain == scene.DOMAIN:
scene_fmt = '{} (Scene connected via Home Assistant)'
description = scene_fmt.format(description)
display_categories = entity_conf.get(CONF_DISPLAY_CATEGORIES,
class_data[0])
alexa_entity = ENTITY_ADAPTERS[entity.domain](config, entity)
endpoint = {
'displayCategories': [display_categories],
'displayCategories': alexa_entity.display_categories(),
'additionalApplianceDetails': {},
'endpointId': entity.entity_id.replace('.', '#'),
'friendlyName': friendly_name,
'description': description,
'endpointId': alexa_entity.entity_id(),
'friendlyName': alexa_entity.friendly_name(),
'description': alexa_entity.description(),
'manufacturerName': 'Home Assistant',
}
actions = set()
# static actions
if class_data[1]:
actions |= set(class_data[1])
endpoint['capabilities'] = [
i.serialize_discovery() for i in alexa_entity.interfaces()]
# dynamic actions
if class_data[2]:
supported = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
for feature, action_name in class_data[2].items():
if feature & supported > 0:
actions.add(action_name)
# Write action into capabilities
capabilities = []
for action in actions:
capabilities.append({
'type': 'AlexaInterface',
'interface': action,
'version': 3,
})
endpoint['capabilities'] = capabilities
if not endpoint['capabilities']:
_LOGGER.debug("Not exposing %s because it has no capabilities",
entity.entity_id)
continue
discovery_endpoints.append(endpoint)
return api_message(
@@ -216,7 +749,7 @@ def async_api_discovery(hass, config, request):
def extract_entity(funct):
"""Decorator for extract entity object from request."""
"""Decorate for extract entity object from request."""
@asyncio.coroutine
def async_api_entity_wrapper(hass, config, request):
"""Process a turn on request."""
@@ -240,8 +773,6 @@ def extract_entity(funct):
def async_api_turn_on(hass, config, request, entity):
"""Process a turn on request."""
domain = entity.domain
if entity.domain == group.DOMAIN:
domain = ha.DOMAIN
service = SERVICE_TURN_ON
if entity.domain == cover.DOMAIN:
@@ -293,7 +824,7 @@ def async_api_set_brightness(hass, config, request, entity):
@extract_entity
@asyncio.coroutine
def async_api_adjust_brightness(hass, config, request, entity):
"""Process a adjust brightness request."""
"""Process an adjust brightness request."""
brightness_delta = int(request[API_PAYLOAD]['brightnessDelta'])
# read current state
@@ -379,7 +910,7 @@ def async_api_decrease_color_temp(hass, config, request, entity):
@extract_entity
@asyncio.coroutine
def async_api_increase_color_temp(hass, config, request, entity):
"""Process a increase color temperature request."""
"""Process an increase color temperature request."""
current = int(entity.attributes.get(light.ATTR_COLOR_TEMP))
min_mireds = int(entity.attributes.get(light.ATTR_MIN_MIREDS))
@@ -396,12 +927,54 @@ def async_api_increase_color_temp(hass, config, request, entity):
@extract_entity
@asyncio.coroutine
def async_api_activate(hass, config, request, entity):
"""Process a activate request."""
yield from hass.services.async_call(entity.domain, SERVICE_TURN_ON, {
"""Process an activate request."""
if entity.domain == group.DOMAIN:
domain = ha.DOMAIN
else:
domain = entity.domain
yield from hass.services.async_call(domain, SERVICE_TURN_ON, {
ATTR_ENTITY_ID: entity.entity_id
}, blocking=False)
return api_message(request)
payload = {
'cause': {'type': _Cause.VOICE_INTERACTION},
'timestamp': '%sZ' % (datetime.utcnow().isoformat(),)
}
return api_message(
request,
name='ActivationStarted',
namespace='Alexa.SceneController',
payload=payload,
)
@HANDLERS.register(('Alexa.SceneController', 'Deactivate'))
@extract_entity
@asyncio.coroutine
def async_api_deactivate(hass, config, request, entity):
"""Process a deactivate request."""
if entity.domain == group.DOMAIN:
domain = ha.DOMAIN
else:
domain = entity.domain
yield from hass.services.async_call(domain, SERVICE_TURN_OFF, {
ATTR_ENTITY_ID: entity.entity_id
}, blocking=False)
payload = {
'cause': {'type': _Cause.VOICE_INTERACTION},
'timestamp': '%sZ' % (datetime.utcnow().isoformat(),)
}
return api_message(
request,
name='DeactivationStarted',
namespace='Alexa.SceneController',
payload=payload,
)
@HANDLERS.register(('Alexa.PercentageController', 'SetPercentage'))
@@ -439,7 +1012,7 @@ def async_api_set_percentage(hass, config, request, entity):
@extract_entity
@asyncio.coroutine
def async_api_adjust_percentage(hass, config, request, entity):
"""Process a adjust percentage request."""
"""Process an adjust percentage request."""
percentage_delta = int(request[API_PAYLOAD]['percentageDelta'])
service = None
data = {ATTR_ENTITY_ID: entity.entity_id}
@@ -492,7 +1065,16 @@ def async_api_lock(hass, config, request, entity):
ATTR_ENTITY_ID: entity.entity_id
}, blocking=False)
return api_message(request)
# Alexa expects a lockState in the response, we don't know the actual
# lockState at this point but assume it is locked. It is reported
# correctly later when ReportState is called. The alt. to this approach
# is to implement DeferredResponse
properties = [{
'name': 'lockState',
'namespace': 'Alexa.LockController',
'value': 'LOCKED'
}]
return api_message(request, context={'properties': properties})
# Not supported by Alexa yet
@@ -500,7 +1082,7 @@ def async_api_lock(hass, config, request, entity):
@extract_entity
@asyncio.coroutine
def async_api_unlock(hass, config, request, entity):
"""Process a unlock request."""
"""Process an unlock request."""
yield from hass.services.async_call(entity.domain, SERVICE_UNLOCK, {
ATTR_ENTITY_ID: entity.entity_id
}, blocking=False)
@@ -527,11 +1109,46 @@ def async_api_set_volume(hass, config, request, entity):
return api_message(request)
@HANDLERS.register(('Alexa.InputController', 'SelectInput'))
@extract_entity
@asyncio.coroutine
def async_api_select_input(hass, config, request, entity):
"""Process a set input request."""
media_input = request[API_PAYLOAD]['input']
# attempt to map the ALL UPPERCASE payload name to a source
source_list = entity.attributes[media_player.ATTR_INPUT_SOURCE_LIST] or []
for source in source_list:
# response will always be space separated, so format the source in the
# most likely way to find a match
formatted_source = source.lower().replace('-', ' ').replace('_', ' ')
if formatted_source in media_input.lower():
media_input = source
break
else:
msg = 'failed to map input {} to a media source on {}'.format(
media_input, entity.entity_id)
_LOGGER.error(msg)
return api_error(
request, error_type='INVALID_VALUE', error_message=msg)
data = {
ATTR_ENTITY_ID: entity.entity_id,
media_player.ATTR_INPUT_SOURCE: media_input,
}
yield from hass.services.async_call(
entity.domain, media_player.SERVICE_SELECT_SOURCE,
data, blocking=False)
return api_message(request)
@HANDLERS.register(('Alexa.Speaker', 'AdjustVolume'))
@extract_entity
@asyncio.coroutine
def async_api_adjust_volume(hass, config, request, entity):
"""Process a adjust volume request."""
"""Process an adjust volume request."""
volume_delta = int(request[API_PAYLOAD]['volume'])
current_level = entity.attributes.get(media_player.ATTR_MEDIA_VOLUME_LEVEL)
@@ -556,6 +1173,30 @@ def async_api_adjust_volume(hass, config, request, entity):
return api_message(request)
@HANDLERS.register(('Alexa.StepSpeaker', 'AdjustVolume'))
@extract_entity
@asyncio.coroutine
def async_api_adjust_volume_step(hass, config, request, entity):
"""Process an adjust volume step request."""
volume_step = round(float(request[API_PAYLOAD]['volumeSteps'] / 100), 2)
current_level = entity.attributes.get(media_player.ATTR_MEDIA_VOLUME_LEVEL)
volume = current_level + volume_step
data = {
ATTR_ENTITY_ID: entity.entity_id,
media_player.ATTR_MEDIA_VOLUME_LEVEL: volume,
}
yield from hass.services.async_call(
entity.domain, media_player.SERVICE_VOLUME_SET,
data, blocking=False)
return api_message(request)
@HANDLERS.register(('Alexa.StepSpeaker', 'SetMute'))
@HANDLERS.register(('Alexa.Speaker', 'SetMute'))
@extract_entity
@asyncio.coroutine
@@ -653,3 +1294,20 @@ def async_api_previous(hass, config, request, entity):
data, blocking=False)
return api_message(request)
@HANDLERS.register(('Alexa', 'ReportState'))
@extract_entity
@asyncio.coroutine
def async_api_reportstate(hass, config, request, entity):
"""Process a ReportState request."""
alexa_entity = ENTITY_ADAPTERS[entity.domain](config, entity)
properties = []
for interface in alexa_entity.interfaces():
properties.extend(interface.serialize_properties())
return api_message(
request,
name='StateReport',
context={'properties': properties}
)
@@ -251,7 +251,7 @@ class AndroidIPCamEntity(Entity):
"""The Android device running IP Webcam."""
def __init__(self, host, ipcam):
"""Initialize the data oject."""
"""Initialize the data object."""
self._host = host
self._ipcam = ipcam
+1 -1
View File
@@ -66,7 +66,7 @@ class APCUPSdData(object):
"""
def __init__(self, host, port):
"""Initialize the data oject."""
"""Initialize the data object."""
from apcaccess import status
self._host = host
self._port = port
+11 -10
View File
@@ -7,13 +7,14 @@ https://home-assistant.io/components/apple_tv/
import asyncio
import logging
from typing import Sequence, TypeVar, Union
import voluptuous as vol
from typing import Union, TypeVar, Sequence
from homeassistant.const import (CONF_HOST, CONF_NAME, ATTR_ENTITY_ID)
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers import discovery
from homeassistant.components.discovery import SERVICE_APPLE_TV
from homeassistant.const import ATTR_ENTITY_ID, CONF_HOST, CONF_NAME
from homeassistant.helpers import discovery
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pyatv==0.3.9']
@@ -59,9 +60,9 @@ CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.All(ensure_list, [vol.Schema({
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_LOGIN_ID): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_CREDENTIALS, default=None): cv.string,
vol.Optional(CONF_START_OFF, default=False): cv.boolean
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_START_OFF, default=False): cv.boolean,
})])
}, extra=vol.ALLOW_EXTRA)
@@ -140,7 +141,7 @@ def async_setup(hass, config):
@asyncio.coroutine
def async_service_handler(service):
"""Handler for service calls."""
"""Handle service calls."""
entity_ids = service.data.get(ATTR_ENTITY_ID)
if service.service == SERVICE_SCAN:
@@ -167,7 +168,7 @@ def async_setup(hass, config):
@asyncio.coroutine
def atv_discovered(service, info):
"""Setup an Apple TV that was auto discovered."""
"""Set up an Apple TV that was auto discovered."""
yield from _setup_atv(hass, {
CONF_NAME: info['name'],
CONF_HOST: info['host'],
@@ -194,7 +195,7 @@ def async_setup(hass, config):
@asyncio.coroutine
def _setup_atv(hass, atv_config):
"""Setup an Apple TV."""
"""Set up an Apple TV."""
import pyatv
name = atv_config.get(CONF_NAME)
host = atv_config.get(CONF_HOST)
@@ -245,7 +246,7 @@ class AppleTVPowerManager:
@property
def turned_on(self):
"""If device is on or off."""
"""Return true if device is on or off."""
return self._is_on
def set_power_on(self, value):
+1 -1
View File
@@ -47,7 +47,7 @@ def setup(hass, config):
return False
hass.data[DATA_ARLO] = arlo
except (ConnectTimeout, HTTPError) as ex:
_LOGGER.error("Unable to connect to Netgar Arlo: %s", str(ex))
_LOGGER.error("Unable to connect to Netgear Arlo: %s", str(ex))
hass.components.persistent_notification.create(
'Error: {}<br />'
'You will need to restart hass after fixing.'
+21 -22
View File
@@ -1,34 +1,34 @@
"""Support for Asterisk Voicemail interface."""
"""
Support for Asterisk Voicemail interface.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/asterisk_mbox/
"""
import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers import discovery
from homeassistant.const import (CONF_HOST,
CONF_PORT, CONF_PASSWORD)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import (async_dispatcher_connect,
async_dispatcher_send)
from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import (
async_dispatcher_connect, async_dispatcher_send)
REQUIREMENTS = ['asterisk_mbox==0.4.0']
SIGNAL_MESSAGE_UPDATE = 'asterisk_mbox.message_updated'
SIGNAL_MESSAGE_REQUEST = 'asterisk_mbox.message_request'
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'asterisk_mbox'
_LOGGER = logging.getLogger(__name__)
SIGNAL_MESSAGE_REQUEST = 'asterisk_mbox.message_request'
SIGNAL_MESSAGE_UPDATE = 'asterisk_mbox.message_updated'
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_PORT): int,
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_PORT): int,
}),
}, extra=vol.ALLOW_EXTRA)
@@ -43,7 +43,7 @@ def setup(hass, config):
hass.data[DOMAIN] = AsteriskData(hass, host, port, password)
discovery.load_platform(hass, "mailbox", DOMAIN, {}, config)
discovery.load_platform(hass, 'mailbox', DOMAIN, {}, config)
return True
@@ -68,15 +68,14 @@ class AsteriskData(object):
from asterisk_mbox.commands import CMD_MESSAGE_LIST
if command == CMD_MESSAGE_LIST:
_LOGGER.info("AsteriskVM sent updated message list")
self.messages = sorted(msg,
key=lambda item: item['info']['origtime'],
reverse=True)
async_dispatcher_send(self.hass, SIGNAL_MESSAGE_UPDATE,
self.messages)
_LOGGER.debug("AsteriskVM sent updated message list")
self.messages = sorted(
msg, key=lambda item: item['info']['origtime'], reverse=True)
async_dispatcher_send(
self.hass, SIGNAL_MESSAGE_UPDATE, self.messages)
@callback
def _request_messages(self):
"""Handle changes to the mailbox."""
_LOGGER.info("Requesting message list")
_LOGGER.debug("Requesting message list")
self.client.messages()
@@ -338,10 +338,9 @@ class AutomationEntity(ToggleEntity):
yield from self.async_update_ha_state()
@asyncio.coroutine
def async_remove(self):
"""Remove automation from HASS."""
def async_will_remove_from_hass(self):
"""Remove listeners when removing automation from HASS."""
yield from self.async_turn_off()
yield from super().async_remove()
@asyncio.coroutine
def async_enable(self):
+32 -41
View File
@@ -4,24 +4,21 @@ Support for Axis devices.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/axis/
"""
import logging
import voluptuous as vol
from homeassistant.components.discovery import SERVICE_AXIS
from homeassistant.const import (ATTR_LOCATION, ATTR_TRIPPED,
CONF_EVENT, CONF_HOST, CONF_INCLUDE,
CONF_NAME, CONF_PASSWORD, CONF_PORT,
CONF_TRIGGER_TIME, CONF_USERNAME,
EVENT_HOMEASSISTANT_STOP)
from homeassistant.const import (
ATTR_LOCATION, ATTR_TRIPPED, CONF_EVENT, CONF_HOST, CONF_INCLUDE,
CONF_NAME, CONF_PASSWORD, CONF_PORT, CONF_TRIGGER_TIME, CONF_USERNAME,
EVENT_HOMEASSISTANT_STOP)
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers import discovery
from homeassistant.helpers.dispatcher import dispatcher_send
from homeassistant.helpers.entity import Entity
from homeassistant.util.json import load_json, save_json
REQUIREMENTS = ['axis==14']
_LOGGER = logging.getLogger(__name__)
@@ -81,10 +78,10 @@ def request_configuration(hass, config, name, host, serialnumber):
configurator = hass.components.configurator
def configuration_callback(callback_data):
"""Called when config is submitted."""
"""Call when configuration is submitted."""
if CONF_INCLUDE not in callback_data:
configurator.notify_errors(request_id,
"Functionality mandatory.")
configurator.notify_errors(
request_id, "Functionality mandatory.")
return False
callback_data[CONF_INCLUDE] = callback_data[CONF_INCLUDE].split()
@@ -96,18 +93,20 @@ def request_configuration(hass, config, name, host, serialnumber):
try:
device_config = DEVICE_SCHEMA(callback_data)
except vol.Invalid:
configurator.notify_errors(request_id,
"Bad input, please check spelling.")
configurator.notify_errors(
request_id, "Bad input, please check spelling.")
return False
if setup_device(hass, config, device_config):
del device_config['events']
del device_config['signal']
config_file = load_json(hass.config.path(CONFIG_FILE))
config_file[serialnumber] = dict(device_config)
save_json(hass.config.path(CONFIG_FILE), config_file)
configurator.request_done(request_id)
else:
configurator.notify_errors(request_id,
"Failed to register, please try again.")
configurator.notify_errors(
request_id, "Failed to register, please try again.")
return False
title = '{} ({})'.format(name, host)
@@ -145,7 +144,7 @@ def request_configuration(hass, config, name, host, serialnumber):
def setup(hass, config):
"""Common setup for Axis devices."""
"""Set up for Axis devices."""
def _shutdown(call): # pylint: disable=unused-argument
"""Stop the event stream on shutdown."""
for serialnumber, device in AXIS_DEVICES.items():
@@ -155,7 +154,7 @@ def setup(hass, config):
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, _shutdown)
def axis_device_discovered(service, discovery_info):
"""Called when axis devices has been found."""
"""Call when axis devices has been found."""
host = discovery_info[CONF_HOST]
name = discovery_info['hostname']
serialnumber = discovery_info['properties']['macaddress']
@@ -171,8 +170,8 @@ def setup(hass, config):
_LOGGER.error("Bad data from %s. %s", CONFIG_FILE, err)
return False
if not setup_device(hass, config, device_config):
_LOGGER.error("Couldn\'t set up %s",
device_config[CONF_NAME])
_LOGGER.error(
"Couldn't set up %s", device_config[CONF_NAME])
else:
# New device, create configuration request for UI
request_configuration(hass, config, name, host, serialnumber)
@@ -191,7 +190,7 @@ def setup(hass, config):
if CONF_NAME not in device_config:
device_config[CONF_NAME] = device
if not setup_device(hass, config, device_config):
_LOGGER.error("Couldn\'t set up %s", device_config[CONF_NAME])
_LOGGER.error("Couldn't set up %s", device_config[CONF_NAME])
def vapix_service(call):
"""Service to send a message."""
@@ -203,23 +202,21 @@ def setup(hass, config):
call.data[SERVICE_PARAM])
hass.bus.fire(SERVICE_VAPIX_CALL_RESPONSE, response)
return True
_LOGGER.info("Couldn\'t find device %s", call.data[CONF_NAME])
_LOGGER.info("Couldn't find device %s", call.data[CONF_NAME])
return False
# Register service with Home Assistant.
hass.services.register(DOMAIN,
SERVICE_VAPIX_CALL,
vapix_service,
schema=SERVICE_SCHEMA)
hass.services.register(
DOMAIN, SERVICE_VAPIX_CALL, vapix_service, schema=SERVICE_SCHEMA)
return True
def setup_device(hass, config, device_config):
"""Set up device."""
"""Set up an Axis device."""
from axis import AxisDevice
def signal_callback(action, event):
"""Callback to configure events when initialized on event stream."""
"""Call to configure events when initialized on event stream."""
if action == 'add':
event_config = {
CONF_EVENT: event,
@@ -228,11 +225,8 @@ def setup_device(hass, config, device_config):
CONF_TRIGGER_TIME: device_config[CONF_TRIGGER_TIME]
}
component = event.event_platform
discovery.load_platform(hass,
component,
DOMAIN,
event_config,
config)
discovery.load_platform(
hass, component, DOMAIN, event_config, config)
event_types = list(filter(lambda x: x in device_config[CONF_INCLUDE],
EVENT_TYPES))
@@ -243,7 +237,7 @@ def setup_device(hass, config, device_config):
if device.serial_number is None:
# If there is no serial number a connection could not be made
_LOGGER.error("Couldn\'t connect to %s", device_config[CONF_HOST])
_LOGGER.error("Couldn't connect to %s", device_config[CONF_HOST])
return False
for component in device_config[CONF_INCLUDE]:
@@ -255,11 +249,8 @@ def setup_device(hass, config, device_config):
CONF_USERNAME: device_config[CONF_USERNAME],
CONF_PASSWORD: device_config[CONF_PASSWORD]
}
discovery.load_platform(hass,
component,
DOMAIN,
camera_config,
config)
discovery.load_platform(
hass, component, DOMAIN, camera_config, config)
AXIS_DEVICES[device.serial_number] = device
if event_types:
@@ -273,9 +264,9 @@ class AxisDeviceEvent(Entity):
def __init__(self, event_config):
"""Initialize the event."""
self.axis_event = event_config[CONF_EVENT]
self._name = '{}_{}_{}'.format(event_config[CONF_NAME],
self.axis_event.event_type,
self.axis_event.id)
self._name = '{}_{}_{}'.format(
event_config[CONF_NAME], self.axis_event.event_type,
self.axis_event.id)
self.location = event_config[ATTR_LOCATION]
self.axis_event.callback = self._update_callback
@@ -296,7 +287,7 @@ class AxisDeviceEvent(Entity):
@property
def should_poll(self):
"""No polling needed."""
"""Return the polling state. No polling needed."""
return False
@property
+11 -14
View File
@@ -3,23 +3,22 @@ Support for ADS binary sensors.
For more details about this platform, please refer to the documentation.
https://home-assistant.io/components/binary_sensor.ads/
"""
import asyncio
import logging
import voluptuous as vol
from homeassistant.components.binary_sensor import BinarySensorDevice, \
PLATFORM_SCHEMA, DEVICE_CLASSES_SCHEMA
from homeassistant.components.ads import DATA_ADS, CONF_ADS_VAR
from homeassistant.const import CONF_NAME, CONF_DEVICE_CLASS
import homeassistant.helpers.config_validation as cv
import voluptuous as vol
from homeassistant.components.ads import CONF_ADS_VAR, DATA_ADS
from homeassistant.components.binary_sensor import (
DEVICE_CLASSES_SCHEMA, PLATFORM_SCHEMA, BinarySensorDevice)
from homeassistant.const import CONF_DEVICE_CLASS, CONF_NAME
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['ads']
DEFAULT_NAME = 'ADS binary sensor'
DEPENDENCIES = ['ads']
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_ADS_VAR): cv.string,
@@ -44,7 +43,7 @@ class AdsBinarySensor(BinarySensorDevice):
"""Representation of ADS binary sensors."""
def __init__(self, ads_hub, name, ads_var, device_class):
"""Initialize AdsBinarySensor entity."""
"""Initialize ADS binary sensor."""
self._name = name
self._state = False
self._device_class = device_class or 'moving'
@@ -56,15 +55,13 @@ class AdsBinarySensor(BinarySensorDevice):
"""Register device notification."""
def update(name, value):
"""Handle device notifications."""
_LOGGER.debug('Variable %s changed its value to %d',
name, value)
_LOGGER.debug('Variable %s changed its value to %d', name, value)
self._state = value
self.schedule_update_ha_state()
self.hass.async_add_job(
self._ads_hub.add_device_notification,
self.ads_var, self._ads_hub.PLCTYPE_BOOL, update
)
self.ads_var, self._ads_hub.PLCTYPE_BOOL, update)
@property
def name(self):
@@ -4,13 +4,12 @@ Support for Axis binary sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.axis/
"""
import logging
from datetime import timedelta
import logging
from homeassistant.components.binary_sensor import (BinarySensorDevice)
from homeassistant.components.axis import (AxisDeviceEvent)
from homeassistant.const import (CONF_TRIGGER_TIME)
from homeassistant.components.axis import AxisDeviceEvent
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.const import CONF_TRIGGER_TIME
from homeassistant.helpers.event import track_point_in_utc_time
from homeassistant.util.dt import utcnow
@@ -20,7 +19,7 @@ _LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup Axis device event."""
"""Set up the Axis binary devices."""
add_devices([AxisBinarySensor(hass, discovery_info)], True)
@@ -28,7 +27,7 @@ class AxisBinarySensor(AxisDeviceEvent, BinarySensorDevice):
"""Representation of a binary Axis event."""
def __init__(self, hass, event_config):
"""Initialize the binary sensor."""
"""Initialize the Axis binary sensor."""
self.hass = hass
self._state = False
self._delay = event_config[CONF_TRIGGER_TIME]
@@ -56,7 +55,7 @@ class AxisBinarySensor(AxisDeviceEvent, BinarySensorDevice):
# Set timer to wait until updating the state
def _delay_update(now):
"""Timer callback for sensor update."""
_LOGGER.debug("%s Called delayed (%s sec) update.",
_LOGGER.debug("%s called delayed (%s sec) update",
self._name, self._delay)
self.schedule_update_ha_state()
self._timer = None
@@ -50,7 +50,6 @@ class BloomSkySensor(BinarySensorDevice):
self._device_id = device['DeviceID']
self._sensor_name = sensor_name
self._name = '{} {}'.format(device['DeviceName'], sensor_name)
self._unique_id = 'bloomsky_binary_sensor {}'.format(self._name)
self._state = None
@property
@@ -58,11 +57,6 @@ class BloomSkySensor(BinarySensorDevice):
"""Return the name of the BloomSky device and this sensor."""
return self._name
@property
def unique_id(self):
"""Return the unique ID for this sensor."""
return self._unique_id
@property
def device_class(self):
"""Return the class of this sensor, from DEVICE_CLASSES."""
@@ -53,7 +53,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
sensors = []
try:
_LOGGER.debug("Initializing Client")
_LOGGER.debug("Initializing client")
client = concord232_client.Client('http://{}:{}'.format(host, port))
client.zones = client.list_zones()
client.last_zone_update = datetime.datetime.now()
@@ -4,7 +4,6 @@ Support for deCONZ binary sensor.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.deconz/
"""
import asyncio
from homeassistant.components.binary_sensor import BinarySensorDevice
@@ -17,7 +16,7 @@ DEPENDENCIES = ['deconz']
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Setup binary sensor for deCONZ component."""
"""Set up the deCONZ binary sensor."""
if discovery_info is None:
return
@@ -25,8 +24,9 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
sensors = hass.data[DECONZ_DATA].sensors
entities = []
for sensor in sensors.values():
if sensor.type in DECONZ_BINARY_SENSOR:
for key in sorted(sensors.keys(), key=int):
sensor = sensors[key]
if sensor and sensor.type in DECONZ_BINARY_SENSOR:
entities.append(DeconzBinarySensor(sensor))
async_add_devices(entities, True)
@@ -35,7 +35,7 @@ class DeconzBinarySensor(BinarySensorDevice):
"""Representation of a binary sensor."""
def __init__(self, sensor):
"""Setup sensor and add update callback to get data from websocket."""
"""Set up sensor and add update callback to get data from websocket."""
self._sensor = sensor
@asyncio.coroutine
@@ -65,9 +65,14 @@ class DeconzBinarySensor(BinarySensorDevice):
"""Return the name of the sensor."""
return self._sensor.name
@property
def unique_id(self):
"""Return a unique identifier for this sensor."""
return self._sensor.uniqueid
@property
def device_class(self):
"""Class of the sensor."""
"""Return the class of the sensor."""
return self._sensor.sensor_class
@property
@@ -50,11 +50,6 @@ class EcobeeBinarySensor(BinarySensorDevice):
"""Return the status of the sensor."""
return self._state == 'true'
@property
def unique_id(self):
"""Return the unique ID of this sensor."""
return "binary_sensor_ecobee_{}_{}".format(self._name, self.index)
@property
def device_class(self):
"""Return the class of this sensor, from DEVICE_CLASSES."""
@@ -48,7 +48,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up the FFmpeg binary moition sensor."""
"""Set up the FFmpeg binary motion sensor."""
manager = hass.data[DATA_FFMPEG]
if not manager.async_run_test(config.get(CONF_INPUT)):
@@ -238,6 +238,5 @@ class FlicButton(BinarySensorDevice):
import pyflic
if connection_status == pyflic.ConnectionStatus.Disconnected:
_LOGGER.info("Button (%s) disconnected. Reason: %s",
self.address, disconnect_reason)
self.remove()
_LOGGER.warning("Button (%s) disconnected. Reason: %s",
self.address, disconnect_reason)
@@ -118,7 +118,7 @@ class HikvisionData(object):
"""Hikvision device event stream object."""
def __init__(self, hass, url, port, name, username, password):
"""Initialize the data oject."""
"""Initialize the data object."""
from pyhik.hikvision import HikCamera
self._url = url
self._port = port
@@ -212,7 +212,7 @@ class HikvisionBinarySensor(BinarySensorDevice):
@property
def unique_id(self):
"""Return an unique ID."""
return '{}.{}'.format(self.__class__, self._id)
return self._id
@property
def is_on(self):
+63 -63
View File
@@ -1,63 +1,63 @@
"""
Support for the Hive devices.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.hive/
"""
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.hive import DATA_HIVE
DEPENDENCIES = ['hive']
DEVICETYPE_DEVICE_CLASS = {'motionsensor': 'motion',
'contactsensor': 'opening'}
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up Hive sensor devices."""
if discovery_info is None:
return
session = hass.data.get(DATA_HIVE)
add_devices([HiveBinarySensorEntity(session, discovery_info)])
class HiveBinarySensorEntity(BinarySensorDevice):
"""Representation of a Hive binary sensor."""
def __init__(self, hivesession, hivedevice):
"""Initialize the hive sensor."""
self.node_id = hivedevice["Hive_NodeID"]
self.node_name = hivedevice["Hive_NodeName"]
self.device_type = hivedevice["HA_DeviceType"]
self.node_device_type = hivedevice["Hive_DeviceType"]
self.session = hivesession
self.data_updatesource = '{}.{}'.format(self.device_type,
self.node_id)
self.session.entities.append(self)
def handle_update(self, updatesource):
"""Handle the new update request."""
if '{}.{}'.format(self.device_type, self.node_id) not in updatesource:
self.schedule_update_ha_state()
@property
def device_class(self):
"""Return the class of this sensor."""
return DEVICETYPE_DEVICE_CLASS.get(self.node_device_type)
@property
def name(self):
"""Return the name of the binary sensor."""
return self.node_name
@property
def is_on(self):
"""Return true if the binary sensor is on."""
return self.session.sensor.get_state(self.node_id,
self.node_device_type)
def update(self):
"""Update all Node data frome Hive."""
self.session.core.update_data(self.node_id)
"""
Support for the Hive devices.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.hive/
"""
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.hive import DATA_HIVE
DEPENDENCIES = ['hive']
DEVICETYPE_DEVICE_CLASS = {'motionsensor': 'motion',
'contactsensor': 'opening'}
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up Hive sensor devices."""
if discovery_info is None:
return
session = hass.data.get(DATA_HIVE)
add_devices([HiveBinarySensorEntity(session, discovery_info)])
class HiveBinarySensorEntity(BinarySensorDevice):
"""Representation of a Hive binary sensor."""
def __init__(self, hivesession, hivedevice):
"""Initialize the hive sensor."""
self.node_id = hivedevice["Hive_NodeID"]
self.node_name = hivedevice["Hive_NodeName"]
self.device_type = hivedevice["HA_DeviceType"]
self.node_device_type = hivedevice["Hive_DeviceType"]
self.session = hivesession
self.data_updatesource = '{}.{}'.format(self.device_type,
self.node_id)
self.session.entities.append(self)
def handle_update(self, updatesource):
"""Handle the new update request."""
if '{}.{}'.format(self.device_type, self.node_id) not in updatesource:
self.schedule_update_ha_state()
@property
def device_class(self):
"""Return the class of this sensor."""
return DEVICETYPE_DEVICE_CLASS.get(self.node_device_type)
@property
def name(self):
"""Return the name of the binary sensor."""
return self.node_name
@property
def is_on(self):
"""Return true if the binary sensor is on."""
return self.session.sensor.get_state(self.node_id,
self.node_device_type)
def update(self):
"""Update all Node data from Hive."""
self.session.core.update_data(self.node_id)
@@ -0,0 +1,96 @@
"""IHC binary sensor platform.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.ihc/
"""
from xml.etree.ElementTree import Element
import voluptuous as vol
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA, DEVICE_CLASSES_SCHEMA)
from homeassistant.components.ihc import (
validate_name, IHC_DATA, IHC_CONTROLLER, IHC_INFO)
from homeassistant.components.ihc.const import CONF_INVERTING
from homeassistant.components.ihc.ihcdevice import IHCDevice
from homeassistant.const import (
CONF_NAME, CONF_TYPE, CONF_ID, CONF_BINARY_SENSORS)
import homeassistant.helpers.config_validation as cv
DEPENDENCIES = ['ihc']
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_BINARY_SENSORS, default=[]):
vol.All(cv.ensure_list, [
vol.All({
vol.Required(CONF_ID): cv.positive_int,
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_TYPE, default=None): DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_INVERTING, default=False): cv.boolean,
}, validate_name)
])
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the IHC binary sensor platform."""
ihc_controller = hass.data[IHC_DATA][IHC_CONTROLLER]
info = hass.data[IHC_DATA][IHC_INFO]
devices = []
if discovery_info:
for name, device in discovery_info.items():
ihc_id = device['ihc_id']
product_cfg = device['product_cfg']
product = device['product']
sensor = IHCBinarySensor(ihc_controller, name, ihc_id, info,
product_cfg[CONF_TYPE],
product_cfg[CONF_INVERTING],
product)
devices.append(sensor)
else:
binary_sensors = config[CONF_BINARY_SENSORS]
for sensor_cfg in binary_sensors:
ihc_id = sensor_cfg[CONF_ID]
name = sensor_cfg[CONF_NAME]
sensor_type = sensor_cfg[CONF_TYPE]
inverting = sensor_cfg[CONF_INVERTING]
sensor = IHCBinarySensor(ihc_controller, name, ihc_id, info,
sensor_type, inverting)
devices.append(sensor)
add_devices(devices)
class IHCBinarySensor(IHCDevice, BinarySensorDevice):
"""IHC Binary Sensor.
The associated IHC resource can be any in or output from a IHC product
or function block, but it must be a boolean ON/OFF resources.
"""
def __init__(self, ihc_controller, name, ihc_id: int, info: bool,
sensor_type: str, inverting: bool,
product: Element=None) -> None:
"""Initialize the IHC binary sensor."""
super().__init__(ihc_controller, name, ihc_id, info, product)
self._state = None
self._sensor_type = sensor_type
self.inverting = inverting
@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 on_ihc_change(self, ihc_id, value):
"""IHC resource has changed."""
if self.inverting:
self._state = not value
else:
self._state = value
self.schedule_update_ha_state()
@@ -83,5 +83,5 @@ class InsteonPLMBinarySensorDevice(BinarySensorDevice):
@callback
def async_binarysensor_update(self, message):
"""Receive notification from transport that new data exists."""
_LOGGER.info("Received update calback from PLM for %s", self._address)
_LOGGER.info("Received update callback from PLM for %s", self._address)
self._hass.async_add_job(self.async_update_ha_state())
@@ -67,8 +67,8 @@ def setup_platform(hass, config: ConfigType,
elif subnode_id == 2:
parent_device.add_negative_node(node)
elif device_type == 'moisture':
# Moisure nodes have a subnode 2, but we ignore it because it's
# just the inverse of the primary node.
# Moisture nodes have a subnode 2, but we ignore it because
# it's just the inverse of the primary node.
if subnode_id == 4:
# Heartbeat node
device = ISYBinarySensorHeartbeat(node, parent_device)
+11 -14
View File
@@ -5,12 +5,13 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.knx/
"""
import asyncio
import voluptuous as vol
from homeassistant.components.knx import DATA_KNX, ATTR_DISCOVER_DEVICES, \
KNXAutomation
from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, \
BinarySensorDevice
from homeassistant.components.binary_sensor import (
PLATFORM_SCHEMA, BinarySensorDevice)
from homeassistant.components.knx import (
ATTR_DISCOVER_DEVICES, DATA_KNX, KNXAutomation)
from homeassistant.const import CONF_NAME
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
@@ -53,20 +54,16 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices,
discovery_info=None):
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up binary sensor(s) for KNX platform."""
if DATA_KNX not in hass.data \
or not hass.data[DATA_KNX].initialized:
return False
if DATA_KNX not in hass.data or not hass.data[DATA_KNX].initialized:
return
if discovery_info is not None:
async_add_devices_discovery(hass, discovery_info, async_add_devices)
else:
async_add_devices_config(hass, config, async_add_devices)
return True
@callback
def async_add_devices_discovery(hass, discovery_info, async_add_devices):
@@ -80,7 +77,7 @@ def async_add_devices_discovery(hass, discovery_info, async_add_devices):
@callback
def async_add_devices_config(hass, config, async_add_devices):
"""Set up binary senor for KNX platform configured within plattform."""
"""Set up binary senor for KNX platform configured within platform."""
name = config.get(CONF_NAME)
import xknx
binary_sensor = xknx.devices.BinarySensor(
@@ -108,7 +105,7 @@ class KNXBinarySensor(BinarySensorDevice):
"""Representation of a KNX binary sensor."""
def __init__(self, hass, device):
"""Initialization of KNXBinarySensor."""
"""Initialize of KNX binary sensor."""
self.device = device
self.hass = hass
self.async_register_callbacks()
@@ -119,7 +116,7 @@ class KNXBinarySensor(BinarySensorDevice):
"""Register callbacks to update hass after device was changed."""
@asyncio.coroutine
def after_update_callback(device):
"""Callback after device was updated."""
"""Call after device was updated."""
# pylint: disable=unused-argument
yield from self.async_update_ha_state()
self.device.register_device_updated_cb(after_update_callback)
@@ -36,7 +36,7 @@ class MaxCubeShutter(BinarySensorDevice):
def __init__(self, hass, name, rf_address):
"""Initialize MAX! Cube BinarySensorDevice."""
self._name = name
self._sensor_type = 'opening'
self._sensor_type = 'window'
self._rf_address = rf_address
self._cubehandle = hass.data[MAXCUBE_HANDLE]
self._state = STATE_UNKNOWN
@@ -0,0 +1,97 @@
"""
Support for Mercedes cars with Mercedes ME.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.mercedesme/
"""
import logging
import datetime
from homeassistant.components.binary_sensor import (BinarySensorDevice)
from homeassistant.components.mercedesme import (
DATA_MME, FEATURE_NOT_AVAILABLE, MercedesMeEntity, BINARY_SENSORS)
DEPENDENCIES = ['mercedesme']
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the sensor platform."""
data = hass.data[DATA_MME].data
if not data.cars:
_LOGGER.error("No cars found. Check component log.")
return
devices = []
for car in data.cars:
for key, value in sorted(BINARY_SENSORS.items()):
if car['availabilities'].get(key, 'INVALID') == 'VALID':
devices.append(MercedesMEBinarySensor(
data, key, value[0], car["vin"], None))
else:
_LOGGER.warning(FEATURE_NOT_AVAILABLE, key, car["license"])
add_devices(devices, True)
class MercedesMEBinarySensor(MercedesMeEntity, BinarySensorDevice):
"""Representation of a Sensor."""
@property
def is_on(self):
"""Return the state of the binary sensor."""
return self._state
@property
def device_state_attributes(self):
"""Return the state attributes."""
if self._internal_name == "windowsClosed":
return {
"window_front_left": self._car["windowStatusFrontLeft"],
"window_front_right": self._car["windowStatusFrontRight"],
"window_rear_left": self._car["windowStatusRearLeft"],
"window_rear_right": self._car["windowStatusRearRight"],
"original_value": self._car[self._internal_name],
"last_update": datetime.datetime.fromtimestamp(
self._car["lastUpdate"]).strftime('%Y-%m-%d %H:%M:%S'),
"car": self._car["license"]
}
elif self._internal_name == "tireWarningLight":
return {
"front_right_tire_pressure_kpa":
self._car["frontRightTirePressureKpa"],
"front_left_tire_pressure_kpa":
self._car["frontLeftTirePressureKpa"],
"rear_right_tire_pressure_kpa":
self._car["rearRightTirePressureKpa"],
"rear_left_tire_pressure_kpa":
self._car["rearLeftTirePressureKpa"],
"original_value": self._car[self._internal_name],
"last_update": datetime.datetime.fromtimestamp(
self._car["lastUpdate"]
).strftime('%Y-%m-%d %H:%M:%S'),
"car": self._car["license"],
}
return {
"original_value": self._car[self._internal_name],
"last_update": datetime.datetime.fromtimestamp(
self._car["lastUpdate"]).strftime('%Y-%m-%d %H:%M:%S'),
"car": self._car["license"]
}
def update(self):
"""Fetch new state data for the sensor."""
self._car = next(
car for car in self._data.cars if car["vin"] == self._vin)
if self._internal_name == "windowsClosed":
self._state = bool(self._car[self._internal_name] == "CLOSED")
elif self._internal_name == "tireWarningLight":
self._state = bool(self._car[self._internal_name] != "INACTIVE")
else:
self._state = self._car[self._internal_name] is True
_LOGGER.debug("Updated %s Value: %s IsOn: %s",
self._internal_name, self._state, self.is_on)
+18 -3
View File
@@ -14,8 +14,8 @@ import homeassistant.components.mqtt as mqtt
from homeassistant.components.binary_sensor import (
BinarySensorDevice, DEVICE_CLASSES_SCHEMA)
from homeassistant.const import (
CONF_NAME, CONF_VALUE_TEMPLATE, CONF_PAYLOAD_ON, CONF_PAYLOAD_OFF,
CONF_DEVICE_CLASS)
CONF_FORCE_UPDATE, CONF_NAME, CONF_VALUE_TEMPLATE, CONF_PAYLOAD_ON,
CONF_PAYLOAD_OFF, CONF_DEVICE_CLASS)
from homeassistant.components.mqtt import (
CONF_STATE_TOPIC, CONF_AVAILABILITY_TOPIC, CONF_PAYLOAD_AVAILABLE,
CONF_PAYLOAD_NOT_AVAILABLE, CONF_QOS, MqttAvailability)
@@ -24,8 +24,10 @@ import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'MQTT Binary sensor'
DEFAULT_PAYLOAD_OFF = 'OFF'
DEFAULT_PAYLOAD_ON = 'ON'
DEFAULT_FORCE_UPDATE = False
DEPENDENCIES = ['mqtt']
@@ -34,6 +36,7 @@ PLATFORM_SCHEMA = mqtt.MQTT_RO_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string,
vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string,
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_FORCE_UPDATE, default=DEFAULT_FORCE_UPDATE): cv.boolean,
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema)
@@ -53,6 +56,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
config.get(CONF_AVAILABILITY_TOPIC),
config.get(CONF_DEVICE_CLASS),
config.get(CONF_QOS),
config.get(CONF_FORCE_UPDATE),
config.get(CONF_PAYLOAD_ON),
config.get(CONF_PAYLOAD_OFF),
config.get(CONF_PAYLOAD_AVAILABLE),
@@ -65,7 +69,7 @@ class MqttBinarySensor(MqttAvailability, BinarySensorDevice):
"""Representation a binary sensor that is updated by MQTT."""
def __init__(self, name, state_topic, availability_topic, device_class,
qos, payload_on, payload_off, payload_available,
qos, force_update, payload_on, payload_off, payload_available,
payload_not_available, value_template):
"""Initialize the MQTT binary sensor."""
super().__init__(availability_topic, qos, payload_available,
@@ -77,6 +81,7 @@ class MqttBinarySensor(MqttAvailability, BinarySensorDevice):
self._payload_on = payload_on
self._payload_off = payload_off
self._qos = qos
self._force_update = force_update
self._template = value_template
@asyncio.coroutine
@@ -94,6 +99,11 @@ class MqttBinarySensor(MqttAvailability, BinarySensorDevice):
self._state = True
elif payload == self._payload_off:
self._state = False
else: # Payload is not for this entity
_LOGGER.warning('No matching payload found'
' for entity: %s with state_topic: %s',
self._name, self._state_topic)
return
self.async_schedule_update_ha_state()
@@ -119,3 +129,8 @@ class MqttBinarySensor(MqttAvailability, BinarySensorDevice):
def device_class(self):
"""Return the class of this sensor."""
return self._device_class
@property
def force_update(self):
"""Force update."""
return self._force_update
@@ -0,0 +1,85 @@
"""Support for MyChevy sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.mychevy/
"""
import asyncio
import logging
from homeassistant.components.mychevy import (
EVBinarySensorConfig, DOMAIN as MYCHEVY_DOMAIN, UPDATE_TOPIC
)
from homeassistant.components.binary_sensor import (
ENTITY_ID_FORMAT, BinarySensorDevice)
from homeassistant.core import callback
from homeassistant.util import slugify
_LOGGER = logging.getLogger(__name__)
SENSORS = [
EVBinarySensorConfig("Plugged In", "plugged_in", "plug")
]
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up the MyChevy sensors."""
if discovery_info is None:
return
sensors = []
hub = hass.data[MYCHEVY_DOMAIN]
for sconfig in SENSORS:
sensors.append(EVBinarySensor(hub, sconfig))
async_add_devices(sensors)
class EVBinarySensor(BinarySensorDevice):
"""Base EVSensor class.
The only real difference between sensors is which units and what
attribute from the car object they are returning. All logic can be
built with just setting subclass attributes.
"""
def __init__(self, connection, config):
"""Initialize sensor with car connection."""
self._conn = connection
self._name = config.name
self._attr = config.attr
self._type = config.device_class
self._is_on = None
self.entity_id = ENTITY_ID_FORMAT.format(
'{}_{}'.format(MYCHEVY_DOMAIN, slugify(self._name)))
@property
def name(self):
"""Return the name."""
return self._name
@property
def is_on(self):
"""Return if on."""
return self._is_on
@asyncio.coroutine
def async_added_to_hass(self):
"""Register callbacks."""
self.hass.helpers.dispatcher.async_dispatcher_connect(
UPDATE_TOPIC, self.async_update_callback)
@callback
def async_update_callback(self):
"""Update state."""
if self._conn.car is not None:
self._is_on = getattr(self._conn.car, self._attr, None)
self.async_schedule_update_ha_state()
@property
def should_poll(self):
"""Return the polling state."""
return False
@@ -5,21 +5,20 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.mysensors/
"""
from homeassistant.components import mysensors
from homeassistant.components.binary_sensor import (DEVICE_CLASSES, DOMAIN,
BinarySensorDevice)
from homeassistant.components.binary_sensor import (
DEVICE_CLASSES, DOMAIN, BinarySensorDevice)
from homeassistant.const import STATE_ON
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the mysensors platform for binary sensors."""
"""Set up the MySensors platform for binary sensors."""
mysensors.setup_mysensors_platform(
hass, DOMAIN, discovery_info, MySensorsBinarySensor,
add_devices=add_devices)
class MySensorsBinarySensor(
mysensors.MySensorsEntity, BinarySensorDevice):
"""Represent the value of a MySensors Binary Sensor child node."""
class MySensorsBinarySensor(mysensors.MySensorsEntity, BinarySensorDevice):
"""Representation of a MySensors Binary Sensor child node."""
@property
def is_on(self):
@@ -7,7 +7,7 @@ https://home-assistant.io/components/binary_sensor.mystrom/
import asyncio
import logging
from homeassistant.components.binary_sensor import (BinarySensorDevice, DOMAIN)
from homeassistant.components.binary_sensor import DOMAIN, BinarySensorDevice
from homeassistant.components.http import HomeAssistantView
from homeassistant.const import HTTP_UNPROCESSABLE_ENTITY
@@ -37,7 +37,7 @@ class MyStromView(HomeAssistantView):
@asyncio.coroutine
def get(self, request):
"""The GET request received from a myStrom button."""
"""Handle the GET request received from a myStrom button."""
res = yield from self._handle(request.app['hass'], request.query)
return res
@@ -131,10 +131,6 @@ class NetatmoBinarySensor(BinarySensorDevice):
self._name += ' / ' + module_name
self._sensor_name = sensor
self._name += ' ' + sensor
camera_id = data.camera_data.cameraByName(
camera=camera_name, home=home)['id']
self._unique_id = "Netatmo_binary_sensor {0} - {1}".format(
self._name, camera_id)
self._cameratype = camera_type
self._state = None
@@ -143,11 +139,6 @@ class NetatmoBinarySensor(BinarySensorDevice):
"""Return the name of the Netatmo device and this sensor."""
return self._name
@property
def unique_id(self):
"""Return the unique ID for this sensor."""
return self._unique_id
@property
def device_class(self):
"""Return the class of this sensor, from DEVICE_CLASSES."""
@@ -97,7 +97,7 @@ class PilightBinarySensor(BinarySensorDevice):
def _handle_code(self, call):
"""Handle received code by the pilight-daemon.
If the code matches the defined playload
If the code matches the defined payload
of this sensor the sensor state is changed accordingly.
"""
# Check if received code matches defined playoad
@@ -162,10 +162,10 @@ class PilightTriggerSensor(BinarySensorDevice):
def _handle_code(self, call):
"""Handle received code by the pilight-daemon.
If the code matches the defined playload
If the code matches the defined payload
of this sensor the sensor state is changed accordingly.
"""
# Check if received code matches defined playoad
# Check if received code matches defined payload
# True if payload is contained in received code dict
payload_ok = True
for key in self._payload:
@@ -39,7 +39,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
sensor_type))
else:
# create an sensor for each zone managed by faucet
# create a sensor for each zone managed by faucet
for zone in raincloud.controller.faucet.zones:
sensors.append(RainCloudBinarySensor(zone, sensor_type))
@@ -5,18 +5,17 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.raspihats/
"""
import logging
import voluptuous as vol
from homeassistant.const import (
CONF_NAME, CONF_DEVICE_CLASS, DEVICE_DEFAULT_NAME
)
import homeassistant.helpers.config_validation as cv
from homeassistant.components.binary_sensor import (
PLATFORM_SCHEMA, BinarySensorDevice
)
PLATFORM_SCHEMA, BinarySensorDevice)
from homeassistant.components.raspihats import (
CONF_I2C_HATS, CONF_BOARD, CONF_ADDRESS, CONF_CHANNELS, CONF_INDEX,
CONF_INVERT_LOGIC, I2C_HAT_NAMES, I2C_HATS_MANAGER, I2CHatsException
)
CONF_ADDRESS, CONF_BOARD, CONF_CHANNELS, CONF_I2C_HATS, CONF_INDEX,
CONF_INVERT_LOGIC, I2C_HAT_NAMES, I2C_HATS_MANAGER, I2CHatsException)
from homeassistant.const import (
CONF_DEVICE_CLASS, CONF_NAME, DEVICE_DEFAULT_NAME)
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
@@ -45,7 +44,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the raspihats binary_sensor devices."""
"""Set up the raspihats binary_sensor devices."""
I2CHatBinarySensor.I2C_HATS_MANAGER = hass.data[I2C_HATS_MANAGER]
binary_sensors = []
i2c_hat_configs = config.get(CONF_I2C_HATS)
@@ -65,39 +64,32 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
)
)
except I2CHatsException as ex:
_LOGGER.error(
"Failed to register " + board + "I2CHat@" + hex(address) + " "
+ str(ex)
)
_LOGGER.error("Failed to register %s I2CHat@%s %s",
board, hex(address), str(ex))
add_devices(binary_sensors)
class I2CHatBinarySensor(BinarySensorDevice):
"""Represents a binary sensor that uses a I2C-HAT digital input."""
"""Representation of a binary sensor that uses a I2C-HAT digital input."""
I2C_HATS_MANAGER = None
def __init__(self, address, channel, name, invert_logic, device_class):
"""Initialize sensor."""
"""Initialize the raspihats sensor."""
self._address = address
self._channel = channel
self._name = name or DEVICE_DEFAULT_NAME
self._invert_logic = invert_logic
self._device_class = device_class
self._state = self.I2C_HATS_MANAGER.read_di(
self._address,
self._channel
)
self._address, self._channel)
def online_callback():
"""Callback fired when board is online."""
"""Call fired when board is online."""
self.schedule_update_ha_state()
self.I2C_HATS_MANAGER.register_online_callback(
self._address,
self._channel,
online_callback
)
self._address, self._channel, online_callback)
def edge_callback(state):
"""Read digital input state."""
@@ -105,10 +97,7 @@ class I2CHatBinarySensor(BinarySensorDevice):
self.schedule_update_ha_state()
self.I2C_HATS_MANAGER.register_di_callback(
self._address,
self._channel,
edge_callback
)
self._address, self._channel, edge_callback)
@property
def device_class(self):
@@ -122,7 +111,7 @@ class I2CHatBinarySensor(BinarySensorDevice):
@property
def should_poll(self):
"""Polling not needed for this sensor."""
"""No polling needed for this sensor."""
return False
@property
@@ -1,51 +1,50 @@
"""
Support for RFXtrx binary sensors.
Lighting4 devices (sensors based on PT2262 encoder) are supported and
tested. Other types may need some work.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.rfxtrx/
"""
import logging
import voluptuous as vol
from homeassistant.const import (
CONF_DEVICE_CLASS, CONF_COMMAND_ON, CONF_COMMAND_OFF, CONF_NAME)
from homeassistant.components import rfxtrx
from homeassistant.helpers import event as evt
from homeassistant.helpers import config_validation as cv
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA)
PLATFORM_SCHEMA, DEVICE_CLASSES_SCHEMA, BinarySensorDevice)
from homeassistant.components.rfxtrx import (
ATTR_NAME, ATTR_DATA_BITS, ATTR_OFF_DELAY, ATTR_FIRE_EVENT,
CONF_AUTOMATIC_ADD, CONF_FIRE_EVENT,
CONF_DATA_BITS, CONF_DEVICES)
from homeassistant.util import slugify
ATTR_NAME, CONF_AUTOMATIC_ADD, CONF_DATA_BITS, CONF_DEVICES,
CONF_FIRE_EVENT, CONF_OFF_DELAY)
from homeassistant.const import (
CONF_COMMAND_OFF, CONF_COMMAND_ON, CONF_DEVICE_CLASS, CONF_NAME)
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers import event as evt
from homeassistant.util import dt as dt_util
DEPENDENCIES = ["rfxtrx"]
from homeassistant.util import slugify
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['rfxtrx']
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_DEVICES, default={}): {
cv.string: vol.Schema({
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_DEVICE_CLASS): cv.string,
vol.Optional(CONF_NAME, default=None): cv.string,
vol.Optional(CONF_DEVICE_CLASS, default=None):
DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_FIRE_EVENT, default=False): cv.boolean,
vol.Optional(CONF_DATA_BITS): cv.positive_int,
vol.Optional(CONF_COMMAND_ON): cv.byte,
vol.Optional(CONF_COMMAND_OFF): cv.byte
vol.Optional(CONF_OFF_DELAY, default=None):
vol.Any(cv.time_period, cv.positive_timedelta),
vol.Optional(CONF_DATA_BITS, default=None): cv.positive_int,
vol.Optional(CONF_COMMAND_ON, default=None): cv.byte,
vol.Optional(CONF_COMMAND_OFF, default=None): cv.byte
})
},
vol.Optional(CONF_AUTOMATIC_ADD, default=False): cv.boolean,
}, extra=vol.ALLOW_EXTRA)
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Setup the Binary Sensor platform to rfxtrx."""
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Binary Sensor platform to RFXtrx."""
import RFXtrx as rfxtrxmod
sensors = []
@@ -57,29 +56,26 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
continue
if entity[CONF_DATA_BITS] is not None:
_LOGGER.debug("Masked device id: %s",
rfxtrx.get_pt2262_deviceid(device_id,
entity[ATTR_DATA_BITS]))
_LOGGER.debug(
"Masked device id: %s", rfxtrx.get_pt2262_deviceid(
device_id, entity[CONF_DATA_BITS]))
_LOGGER.debug("Add %s rfxtrx.binary_sensor (class %s)",
entity[ATTR_NAME], entity[CONF_DEVICE_CLASS])
device = RfxtrxBinarySensor(event, entity[ATTR_NAME],
entity[CONF_DEVICE_CLASS],
entity[ATTR_FIRE_EVENT],
entity[ATTR_OFF_DELAY],
entity[ATTR_DATA_BITS],
entity[CONF_COMMAND_ON],
entity[CONF_COMMAND_OFF])
device = RfxtrxBinarySensor(
event, entity[ATTR_NAME], entity[CONF_DEVICE_CLASS],
entity[CONF_FIRE_EVENT], entity[CONF_OFF_DELAY],
entity[CONF_DATA_BITS], entity[CONF_COMMAND_ON],
entity[CONF_COMMAND_OFF])
device.hass = hass
sensors.append(device)
rfxtrx.RFX_DEVICES[device_id] = device
add_devices_callback(sensors)
add_devices(sensors)
# pylint: disable=too-many-branches
def binary_sensor_update(event):
"""Callback for control updates from the RFXtrx gateway."""
"""Call for control updates from the RFXtrx gateway."""
if not isinstance(event, rfxtrxmod.ControlEvent):
return
@@ -99,29 +95,26 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
poss_dev = rfxtrx.find_possible_pt2262_device(device_id)
if poss_dev is not None:
poss_id = slugify(poss_dev.event.device.id_string.lower())
_LOGGER.debug("Found possible matching deviceid %s.",
poss_id)
_LOGGER.debug(
"Found possible matching device ID: %s", poss_id)
pkt_id = "".join("{0:02x}".format(x) for x in event.data)
sensor = RfxtrxBinarySensor(event, pkt_id)
sensor.hass = hass
rfxtrx.RFX_DEVICES[device_id] = sensor
add_devices_callback([sensor])
_LOGGER.info("Added binary sensor %s "
"(Device_id: %s Class: %s Sub: %s)",
pkt_id,
slugify(event.device.id_string.lower()),
event.device.__class__.__name__,
event.device.subtype)
add_devices([sensor])
_LOGGER.info(
"Added binary sensor %s (Device ID: %s Class: %s Sub: %s)",
pkt_id, slugify(event.device.id_string.lower()),
event.device.__class__.__name__, event.device.subtype)
elif not isinstance(sensor, RfxtrxBinarySensor):
return
else:
_LOGGER.debug("Binary sensor update "
"(Device_id: %s Class: %s Sub: %s)",
slugify(event.device.id_string.lower()),
event.device.__class__.__name__,
event.device.subtype)
_LOGGER.debug(
"Binary sensor update (Device ID: %s Class: %s Sub: %s)",
slugify(event.device.id_string.lower()),
event.device.__class__.__name__, event.device.subtype)
if sensor.is_lighting4:
if sensor.data_bits is not None:
@@ -141,22 +134,20 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
sensor.update_state(False)
sensor.delay_listener = evt.track_point_in_time(
hass, off_delay_listener, dt_util.utcnow() + sensor.off_delay
)
hass, off_delay_listener, dt_util.utcnow() + sensor.off_delay)
# Subscribe to main rfxtrx events
# Subscribe to main RFXtrx events
if binary_sensor_update not in rfxtrx.RECEIVED_EVT_SUBSCRIBERS:
rfxtrx.RECEIVED_EVT_SUBSCRIBERS.append(binary_sensor_update)
# pylint: disable=too-many-instance-attributes,too-many-arguments
class RfxtrxBinarySensor(BinarySensorDevice):
"""An Rfxtrx binary sensor."""
"""A representation of a RFXtrx binary sensor."""
def __init__(self, event, name, device_class=None,
should_fire=False, off_delay=None, data_bits=None,
cmd_on=None, cmd_off=None):
"""Initialize the sensor."""
"""Initialize the RFXtrx sensor."""
self.event = event
self._name = name
self._should_fire_event = should_fire
@@ -171,8 +162,7 @@ class RfxtrxBinarySensor(BinarySensorDevice):
if data_bits is not None:
self._masked_id = rfxtrx.get_pt2262_deviceid(
event.device.id_string.lower(),
data_bits)
event.device.id_string.lower(), data_bits)
else:
self._masked_id = None
@@ -8,18 +8,17 @@ import logging
import voluptuous as vol
import homeassistant.components.rpi_pfio as rpi_pfio
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA)
from homeassistant.const import DEVICE_DEFAULT_NAME
PLATFORM_SCHEMA, BinarySensorDevice)
import homeassistant.components.rpi_pfio as rpi_pfio
from homeassistant.const import CONF_NAME, DEVICE_DEFAULT_NAME
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
ATTR_NAME = 'name'
ATTR_INVERT_LOGIC = 'invert_logic'
ATTR_SETTLE_TIME = 'settle_time'
CONF_INVERT_LOGIC = 'invert_logic'
CONF_PORTS = 'ports'
CONF_SETTLE_TIME = 'settle_time'
DEFAULT_INVERT_LOGIC = False
DEFAULT_SETTLE_TIME = 20
@@ -27,27 +26,27 @@ DEFAULT_SETTLE_TIME = 20
DEPENDENCIES = ['rpi_pfio']
PORT_SCHEMA = vol.Schema({
vol.Optional(ATTR_NAME, default=None): cv.string,
vol.Optional(ATTR_SETTLE_TIME, default=DEFAULT_SETTLE_TIME):
vol.Optional(CONF_NAME, default=None): cv.string,
vol.Optional(CONF_SETTLE_TIME, default=DEFAULT_SETTLE_TIME):
cv.positive_int,
vol.Optional(ATTR_INVERT_LOGIC, default=DEFAULT_INVERT_LOGIC): cv.boolean
vol.Optional(CONF_INVERT_LOGIC, default=DEFAULT_INVERT_LOGIC): cv.boolean,
})
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_PORTS, default={}): vol.Schema({
cv.positive_int: PORT_SCHEMA
cv.positive_int: PORT_SCHEMA,
})
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the PiFace Digital Input devices."""
"""Set up the PiFace Digital Input devices."""
binary_sensors = []
ports = config.get('ports')
ports = config.get(CONF_PORTS)
for port, port_entity in ports.items():
name = port_entity[ATTR_NAME]
settle_time = port_entity[ATTR_SETTLE_TIME] / 1000
invert_logic = port_entity[ATTR_INVERT_LOGIC]
name = port_entity[CONF_NAME]
settle_time = port_entity[CONF_SETTLE_TIME] / 1000
invert_logic = port_entity[CONF_INVERT_LOGIC]
binary_sensors.append(RPiPFIOBinarySensor(
hass, port, name, settle_time, invert_logic))
+26 -23
View File
@@ -4,46 +4,49 @@ Support for Vanderbilt (formerly Siemens) SPC alarm systems.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.spc/
"""
import logging
import asyncio
import logging
from homeassistant.components.spc import (
ATTR_DISCOVER_DEVICES, DATA_REGISTRY)
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.const import (STATE_UNAVAILABLE, STATE_ON, STATE_OFF)
from homeassistant.components.spc import ATTR_DISCOVER_DEVICES, DATA_REGISTRY
from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE
_LOGGER = logging.getLogger(__name__)
SPC_TYPE_TO_DEVICE_CLASS = {'0': 'motion',
'1': 'opening',
'3': 'smoke'}
SPC_TYPE_TO_DEVICE_CLASS = {
'0': 'motion',
'1': 'opening',
'3': 'smoke',
}
SPC_INPUT_TO_SENSOR_STATE = {'0': STATE_OFF,
'1': STATE_ON}
SPC_INPUT_TO_SENSOR_STATE = {
'0': STATE_OFF,
'1': STATE_ON,
}
def _get_device_class(spc_type):
"""Get the device class."""
return SPC_TYPE_TO_DEVICE_CLASS.get(spc_type, None)
def _get_sensor_state(spc_input):
"""Get the sensor state."""
return SPC_INPUT_TO_SENSOR_STATE.get(spc_input, STATE_UNAVAILABLE)
def _create_sensor(hass, zone):
return SpcBinarySensor(zone_id=zone['id'],
name=zone['zone_name'],
state=_get_sensor_state(zone['input']),
device_class=_get_device_class(zone['type']),
spc_registry=hass.data[DATA_REGISTRY])
"""Create a SPC sensor."""
return SpcBinarySensor(
zone_id=zone['id'], name=zone['zone_name'],
state=_get_sensor_state(zone['input']),
device_class=_get_device_class(zone['type']),
spc_registry=hass.data[DATA_REGISTRY])
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices,
discovery_info=None):
"""Initialize the platform."""
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up the SPC binary sensor."""
if (discovery_info is None or
discovery_info[ATTR_DISCOVER_DEVICES] is None):
return
@@ -55,7 +58,7 @@ def async_setup_platform(hass, config, async_add_devices,
class SpcBinarySensor(BinarySensorDevice):
"""Represents a sensor based on an SPC zone."""
"""Representation of a sensor based on a SPC zone."""
def __init__(self, zone_id, name, state, device_class, spc_registry):
"""Initialize the sensor device."""
@@ -74,7 +77,7 @@ class SpcBinarySensor(BinarySensorDevice):
@property
def name(self):
"""The name of the device."""
"""Return the name of the device."""
return self._name
@property
@@ -85,7 +88,7 @@ class SpcBinarySensor(BinarySensorDevice):
@property
def hidden(self) -> bool:
"""Whether the device is hidden by default."""
# these type of sensors are probably mainly used for automations
# These type of sensors are probably mainly used for automations
return True
@property
@@ -95,5 +98,5 @@ class SpcBinarySensor(BinarySensorDevice):
@property
def device_class(self):
"""The device class."""
"""Return the device class."""
return self._device_class
@@ -4,15 +4,15 @@ Support for Taps Affs.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.tapsaff/
"""
import logging
from datetime import timedelta
import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA)
from homeassistant.const import (CONF_NAME)
PLATFORM_SCHEMA, BinarySensorDevice)
from homeassistant.const import CONF_NAME
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['tapsaff==0.1.3']
@@ -67,7 +67,7 @@ class TapsAffData(object):
"""Class for handling the data retrieval for pins."""
def __init__(self, location):
"""Initialize the sensor."""
"""Initialize the data object."""
from tapsaff import TapsAff
self._is_taps_aff = None
@@ -15,6 +15,7 @@ from homeassistant.components.binary_sensor import (
DEVICE_CLASSES_SCHEMA)
from homeassistant.const import (
ATTR_FRIENDLY_NAME, ATTR_ENTITY_ID, CONF_VALUE_TEMPLATE,
CONF_ICON_TEMPLATE, CONF_ENTITY_PICTURE_TEMPLATE,
CONF_SENSORS, CONF_DEVICE_CLASS, EVENT_HOMEASSISTANT_START)
from homeassistant.exceptions import TemplateError
import homeassistant.helpers.config_validation as cv
@@ -29,6 +30,8 @@ CONF_DELAY_OFF = 'delay_off'
SENSOR_SCHEMA = vol.Schema({
vol.Required(CONF_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_ICON_TEMPLATE): cv.template,
vol.Optional(CONF_ENTITY_PICTURE_TEMPLATE): cv.template,
vol.Optional(ATTR_FRIENDLY_NAME): cv.string,
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
@@ -38,11 +41,6 @@ SENSOR_SCHEMA = vol.Schema({
vol.All(cv.time_period, cv.positive_timedelta),
})
SENSOR_SCHEMA = vol.All(
cv.deprecated(ATTR_ENTITY_ID),
SENSOR_SCHEMA,
)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_SENSORS): vol.Schema({cv.slug: SENSOR_SCHEMA}),
})
@@ -55,6 +53,9 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
for device, device_config in config[CONF_SENSORS].items():
value_template = device_config[CONF_VALUE_TEMPLATE]
icon_template = device_config.get(CONF_ICON_TEMPLATE)
entity_picture_template = device_config.get(
CONF_ENTITY_PICTURE_TEMPLATE)
entity_ids = (device_config.get(ATTR_ENTITY_ID) or
value_template.extract_entities())
friendly_name = device_config.get(ATTR_FRIENDLY_NAME, device)
@@ -65,10 +66,17 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
if value_template is not None:
value_template.hass = hass
if icon_template is not None:
icon_template.hass = hass
if entity_picture_template is not None:
entity_picture_template.hass = hass
sensors.append(
BinarySensorTemplate(
hass, device, friendly_name, device_class, value_template,
entity_ids, delay_on, delay_off)
icon_template, entity_picture_template, entity_ids,
delay_on, delay_off)
)
if not sensors:
_LOGGER.error("No sensors added")
@@ -82,7 +90,8 @@ class BinarySensorTemplate(BinarySensorDevice):
"""A virtual binary sensor that triggers from another sensor."""
def __init__(self, hass, device, friendly_name, device_class,
value_template, entity_ids, delay_on, delay_off):
value_template, icon_template, entity_picture_template,
entity_ids, delay_on, delay_off):
"""Initialize the Template binary sensor."""
self.hass = hass
self.entity_id = async_generate_entity_id(
@@ -91,6 +100,10 @@ class BinarySensorTemplate(BinarySensorDevice):
self._device_class = device_class
self._template = value_template
self._state = None
self._icon_template = icon_template
self._entity_picture_template = entity_picture_template
self._icon = None
self._entity_picture = None
self._entities = entity_ids
self._delay_on = delay_on
self._delay_off = delay_off
@@ -119,6 +132,16 @@ class BinarySensorTemplate(BinarySensorDevice):
"""Return the name of the sensor."""
return self._name
@property
def icon(self):
"""Return the icon to use in the frontend, if any."""
return self._icon
@property
def entity_picture(self):
"""Return the entity_picture to use in the frontend, if any."""
return self._entity_picture
@property
def is_on(self):
"""Return true if sensor is on."""
@@ -137,8 +160,9 @@ class BinarySensorTemplate(BinarySensorDevice):
@callback
def _async_render(self):
"""Get the state of template."""
state = None
try:
return self._template.async_render().lower() == 'true'
state = (self._template.async_render().lower() == 'true')
except TemplateError as ex:
if ex.args and ex.args[0].startswith(
"UndefinedError: 'None' has no attribute"):
@@ -148,6 +172,29 @@ class BinarySensorTemplate(BinarySensorDevice):
return
_LOGGER.error("Could not render template %s: %s", self._name, ex)
for property_name, template in (
('_icon', self._icon_template),
('_entity_picture', self._entity_picture_template)):
if template is None:
continue
try:
setattr(self, property_name, template.async_render())
except TemplateError as ex:
friendly_property_name = property_name[1:].replace('_', ' ')
if ex.args and ex.args[0].startswith(
"UndefinedError: 'None' has no attribute"):
# Common during HA startup - so just a warning
_LOGGER.warning('Could not render %s template %s,'
' the state is unknown.',
friendly_property_name, self._name)
else:
_LOGGER.error('Could not render %s template %s: %s',
friendly_property_name, self._name, ex)
return state
return state
@callback
def async_check_state(self):
"""Update the state from the template."""
@@ -28,7 +28,7 @@ class TeslaBinarySensor(TeslaDevice, BinarySensorDevice):
"""Implement an Tesla binary sensor for parking and charger."""
def __init__(self, tesla_device, controller, sensor_type):
"""Initialisation of binary sensor."""
"""Initialise of a Tesla binary sensor."""
super().__init__(tesla_device, controller)
self._state = False
self.entity_id = ENTITY_ID_FORMAT.format(self.tesla_id)
@@ -126,11 +126,12 @@ class ThresholdSensor(BinarySensorDevice):
@property
def threshold_type(self):
"""Return the type of threshold this sensor represents."""
if self._threshold_lower and self._threshold_upper:
if self._threshold_lower is not None and \
self._threshold_upper is not None:
return TYPE_RANGE
elif self._threshold_lower:
elif self._threshold_lower is not None:
return TYPE_LOWER
elif self._threshold_upper:
elif self._threshold_upper is not None:
return TYPE_UPPER
@property
@@ -6,15 +6,15 @@ https://home-assistant.io/components/binary_sensor.verisure/
"""
import logging
from homeassistant.components.verisure import HUB as hub
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.verisure import CONF_DOOR_WINDOW
from homeassistant.components.verisure import HUB as hub
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup Verisure binary sensors."""
"""Set up the Verisure binary sensors."""
sensors = []
hub.update_overview()
@@ -27,10 +27,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class VerisureDoorWindowSensor(BinarySensorDevice):
"""Verisure door window sensor."""
"""Representation of a Verisure door window sensor."""
def __init__(self, device_label):
"""Initialize the modbus coil sensor."""
"""Initialize the Verisure door window sensor."""
self._device_label = device_label
@property
@@ -31,7 +31,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Vultr subscription (server) sensor."""
"""Set up the Vultr subscription (server) binary sensor."""
vultr = hass.data[DATA_VULTR]
subscription = config.get(CONF_SUBSCRIPTION)
@@ -39,7 +39,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
if subscription not in vultr.data:
_LOGGER.error("Subscription %s not found", subscription)
return False
return
add_devices([VultrBinarySensor(vultr, subscription, name)], True)
@@ -48,7 +48,7 @@ class VultrBinarySensor(BinarySensorDevice):
"""Representation of a Vultr subscription sensor."""
def __init__(self, vultr, subscription, name):
"""Initialize a new Vultr sensor."""
"""Initialize a new Vultr binary sensor."""
self._vultr = vultr
self._name = name
@@ -58,11 +58,11 @@ class WemoBinarySensor(BinarySensorDevice):
@property
def unique_id(self):
"""Return the id of this WeMo device."""
return '{}.{}'.format(self.__class__, self.wemo.serialnumber)
return self.wemo.serialnumber
@property
def name(self):
"""Return the name of the sevice if any."""
"""Return the name of the service if any."""
return self.wemo.name
@property
+21 -14
View File
@@ -8,7 +8,7 @@ import asyncio
import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.wink import WinkDevice, DOMAIN
from homeassistant.components.wink import DOMAIN, WinkDevice
_LOGGER = logging.getLogger(__name__)
@@ -16,18 +16,18 @@ DEPENDENCIES = ['wink']
# These are the available sensors mapped to binary_sensor class
SENSOR_TYPES = {
'opened': 'opening',
'brightness': 'light',
'vibration': 'vibration',
'loudness': 'sound',
'noise': 'sound',
'capturing_audio': 'sound',
'liquid_detected': 'moisture',
'motion': 'motion',
'presence': 'occupancy',
'capturing_video': None,
'co_detected': 'gas',
'liquid_detected': 'moisture',
'loudness': 'sound',
'motion': 'motion',
'noise': 'sound',
'opened': 'opening',
'presence': 'occupancy',
'smoke_detected': 'smoke',
'capturing_video': None
'vibration': 'vibration',
}
@@ -103,7 +103,7 @@ class WinkBinarySensorDevice(WinkDevice, BinarySensorDevice):
@asyncio.coroutine
def async_added_to_hass(self):
"""Callback when entity is added to hass."""
"""Call when entity is added to hass."""
self.hass.data[DOMAIN]['entities']['binary_sensor'].append(self)
@property
@@ -118,7 +118,7 @@ class WinkBinarySensorDevice(WinkDevice, BinarySensorDevice):
@property
def device_state_attributes(self):
"""Return the state attributes."""
"""Return the device state attributes."""
return super().device_state_attributes
@@ -127,7 +127,7 @@ class WinkSmokeDetector(WinkBinarySensorDevice):
@property
def device_state_attributes(self):
"""Return the state attributes."""
"""Return the device state attributes."""
_attributes = super().device_state_attributes
_attributes['test_activated'] = self.wink.test_activated()
return _attributes
@@ -138,11 +138,18 @@ class WinkHub(WinkBinarySensorDevice):
@property
def device_state_attributes(self):
"""Return the state attributes."""
"""Return the device state attributes."""
_attributes = super().device_state_attributes
_attributes['update_needed'] = self.wink.update_needed()
_attributes['firmware_version'] = self.wink.firmware_version()
_attributes['pairing_mode'] = self.wink.pairing_mode()
_kidde_code = self.wink.kidde_radio_code()
if _kidde_code is not None:
# The service call to set the Kidde code
# takes a string of 1s and 0s so it makes
# sense to display it to the user that way
_formatted_kidde_code = "{:b}".format(_kidde_code).zfill(8)
_attributes['kidde_radio_code'] = _formatted_kidde_code
return _attributes
@@ -170,7 +177,7 @@ class WinkButton(WinkBinarySensorDevice):
@property
def device_state_attributes(self):
"""Return the state attributes."""
"""Return the device state attributes."""
_attributes = super().device_state_attributes
_attributes['pressed'] = self.wink.pressed()
_attributes['long_pressed'] = self.wink.long_pressed()
@@ -17,17 +17,21 @@ import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['holidays==0.8.1']
REQUIREMENTS = ['holidays==0.9.3']
# 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']
ALL_COUNTRIES = ['Australia', 'AU', 'Austria', 'AT', 'Belgium', 'BE', 'Canada',
'CA', 'Colombia', 'CO', 'Czech', 'CZ', 'Denmark', 'DK',
'England', 'EuropeanCentralBank', 'ECB', 'TAR', 'Finland',
'FI', 'France', 'FRA', 'Germany', 'DE', 'Ireland',
'Isle of Man', 'Italy', 'IT', 'Japan', 'JP', 'Mexico', 'MX',
'Netherlands', 'NL', 'NewZealand', 'NZ', 'Northern Ireland',
'Norway', 'NO', 'Polish', 'PL', 'Portugal', 'PT',
'PortugalExt', 'PTE', 'Scotland', 'Slovenia', 'SI',
'Slovakia', 'SK', 'South Africa', 'ZA', 'Spain', 'ES',
'Sweden', 'SE', 'UnitedKingdom', 'UK', 'UnitedStates', 'US',
'Wales']
CONF_COUNTRY = 'country'
CONF_PROVINCE = 'province'
CONF_WORKDAYS = 'workdays'
@@ -101,7 +101,7 @@ class XiaomiNatgasSensor(XiaomiBinarySensor):
attrs.update(super().device_state_attributes)
return attrs
def parse_data(self, data):
def parse_data(self, data, raw_data):
"""Parse data sent by gateway."""
if DENSITY in data:
self._density = int(data.get(DENSITY))
@@ -139,8 +139,16 @@ class XiaomiMotionSensor(XiaomiBinarySensor):
attrs.update(super().device_state_attributes)
return attrs
def parse_data(self, data):
def parse_data(self, data, raw_data):
"""Parse data sent by gateway."""
if raw_data['cmd'] == 'heartbeat':
_LOGGER.debug(
'Skipping heartbeat of the motion sensor. '
'It can introduce an incorrect state because of a firmware '
'bug (https://github.com/home-assistant/home-assistant/pull/'
'11631#issuecomment-357507744).')
return
self._should_poll = False
if NO_MOTION in data: # handle push from the hub
self._no_motion_since = data[NO_MOTION]
@@ -186,7 +194,7 @@ class XiaomiDoorSensor(XiaomiBinarySensor):
attrs.update(super().device_state_attributes)
return attrs
def parse_data(self, data):
def parse_data(self, data, raw_data):
"""Parse data sent by gateway."""
self._should_poll = False
if NO_CLOSE in data: # handle push from the hub
@@ -219,7 +227,7 @@ class XiaomiWaterLeakSensor(XiaomiBinarySensor):
XiaomiBinarySensor.__init__(self, device, 'Water Leak Sensor',
xiaomi_hub, 'status', 'moisture')
def parse_data(self, data):
def parse_data(self, data, raw_data):
"""Parse data sent by gateway."""
self._should_poll = False
@@ -256,7 +264,7 @@ class XiaomiSmokeSensor(XiaomiBinarySensor):
attrs.update(super().device_state_attributes)
return attrs
def parse_data(self, data):
def parse_data(self, data, raw_data):
"""Parse data sent by gateway."""
if DENSITY in data:
self._density = int(data.get(DENSITY))
@@ -293,7 +301,7 @@ class XiaomiButton(XiaomiBinarySensor):
attrs.update(super().device_state_attributes)
return attrs
def parse_data(self, data):
def parse_data(self, data, raw_data):
"""Parse data sent by gateway."""
value = data.get(self._data_key)
if value is None:
@@ -343,7 +351,7 @@ class XiaomiCube(XiaomiBinarySensor):
attrs.update(super().device_state_attributes)
return attrs
def parse_data(self, data):
def parse_data(self, data, raw_data):
"""Parse data sent by gateway."""
if 'status' in data:
self._hass.bus.fire('cube_action', {
@@ -32,7 +32,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
if discovery_info is None:
return
from bellows.zigbee.zcl.clusters.security import IasZone
from zigpy.zcl.clusters.security import IasZone
in_clusters = discovery_info['in_clusters']
@@ -63,7 +63,7 @@ class BinarySensor(zha.Entity, BinarySensorDevice):
"""Initialize the ZHA binary sensor."""
super().__init__(**kwargs)
self._device_class = device_class
from bellows.zigbee.zcl.clusters.security import IasZone
from zigpy.zcl.clusters.security import IasZone
self._ias_zone_cluster = self._in_clusters[IasZone.cluster_id]
@property
@@ -78,7 +78,7 @@ class BinarySensor(zha.Entity, BinarySensorDevice):
"""Return the class of this device, from component DEVICE_CLASSES."""
return self._device_class
def cluster_command(self, aps_frame, tsn, command_id, args):
def cluster_command(self, tsn, command_id, args):
"""Handle commands received to this cluster."""
if command_id == 0:
self._state = args[0] & 3
+18 -29
View File
@@ -4,18 +4,18 @@ Support for WebDav Calendar.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/calendar.caldav/
"""
from datetime import datetime, timedelta
import logging
import re
from datetime import datetime, timedelta
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.calendar import (
CalendarEventDevice, PLATFORM_SCHEMA)
PLATFORM_SCHEMA, CalendarEventDevice)
from homeassistant.const import (
CONF_NAME, CONF_PASSWORD, CONF_URL, CONF_USERNAME)
from homeassistant.util import dt, Throttle
import homeassistant.helpers.config_validation as cv
from homeassistant.util import Throttle, dt
REQUIREMENTS = ['caldav==0.5.0']
@@ -39,9 +39,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_CUSTOM_CALENDARS, default=[]):
vol.All(cv.ensure_list, vol.Schema([
vol.Schema({
vol.Required(CONF_NAME): cv.string,
vol.Required(CONF_CALENDAR): cv.string,
vol.Required(CONF_SEARCH): cv.string
vol.Required(CONF_NAME): cv.string,
vol.Required(CONF_SEARCH): cv.string,
})
]))
})
@@ -53,12 +53,12 @@ def setup_platform(hass, config, add_devices, disc_info=None):
"""Set up the WebDav Calendar platform."""
import caldav
client = caldav.DAVClient(config.get(CONF_URL),
None,
config.get(CONF_USERNAME),
config.get(CONF_PASSWORD))
url = config.get(CONF_URL)
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
client = caldav.DAVClient(url, None, username, password)
# Retrieve all the remote calendars
calendars = client.principal().calendars()
calendar_devices = []
@@ -70,8 +70,7 @@ def setup_platform(hass, config, add_devices, disc_info=None):
_LOGGER.debug("Ignoring calendar '%s'", calendar.name)
continue
# Create additional calendars based on custom filtering
# rules
# Create additional calendars based on custom filtering rules
for cust_calendar in config.get(CONF_CUSTOM_CALENDARS):
# Check that the base calendar matches
if cust_calendar.get(CONF_CALENDAR) != calendar.name:
@@ -85,12 +84,9 @@ def setup_platform(hass, config, add_devices, disc_info=None):
}
calendar_devices.append(
WebDavCalendarEventDevice(hass,
device_data,
calendar,
True,
cust_calendar.get(CONF_SEARCH))
)
WebDavCalendarEventDevice(
hass, device_data, calendar, True,
cust_calendar.get(CONF_SEARCH)))
# Create a default calendar if there was no custom one
if not config.get(CONF_CUSTOM_CALENDARS):
@@ -102,18 +98,13 @@ def setup_platform(hass, config, add_devices, disc_info=None):
WebDavCalendarEventDevice(hass, device_data, calendar)
)
# Finally add all the calendars we've created
add_devices(calendar_devices)
class WebDavCalendarEventDevice(CalendarEventDevice):
"""A device for getting the next Task from a WebDav Calendar."""
def __init__(self,
hass,
device_data,
calendar,
all_day=False,
def __init__(self, hass, device_data, calendar, all_day=False,
search=None):
"""Create the WebDav Calendar Event Device."""
self.data = WebDavCalendarData(calendar, all_day, search)
@@ -167,9 +158,7 @@ class WebDavCalendarData(object):
if vevent is None:
_LOGGER.debug(
"No matching event found in the %d results for %s",
len(results),
self.calendar.name,
)
len(results), self.calendar.name)
self.event = None
return True
@@ -185,7 +174,7 @@ class WebDavCalendarData(object):
@staticmethod
def is_matching(vevent, search):
"""Return if the event matches the filter critera."""
"""Return if the event matches the filter criteria."""
if search is None:
return True
+18 -27
View File
@@ -4,29 +4,27 @@ Support for Todoist task management (https://todoist.com).
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/calendar.todoist/
"""
from datetime import datetime
from datetime import timedelta
from datetime import datetime, timedelta
import logging
import voluptuous as vol
from homeassistant.components.calendar import (
CalendarEventDevice, PLATFORM_SCHEMA)
from homeassistant.components.google import (
CONF_DEVICE_ID)
from homeassistant.const import (
CONF_ID, CONF_NAME, CONF_TOKEN)
DOMAIN, PLATFORM_SCHEMA, CalendarEventDevice)
from homeassistant.components.google import CONF_DEVICE_ID
from homeassistant.const import CONF_ID, CONF_NAME, CONF_TOKEN
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.template import DATE_STR_FORMAT
from homeassistant.util import dt
from homeassistant.util import Throttle
from homeassistant.util import Throttle, dt
REQUIREMENTS = ['todoist-python==7.0.17']
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'todoist'
CONF_EXTRA_PROJECTS = 'custom_projects'
CONF_PROJECT_DUE_DATE = 'due_date_days'
CONF_PROJECT_LABEL_WHITELIST = 'labels'
CONF_PROJECT_WHITELIST = 'include_projects'
# Calendar Platform: Does this calendar event last all day?
ALL_DAY = 'all_day'
@@ -78,21 +76,16 @@ SUMMARY = 'summary'
# Todoist API: Fetch all Tasks
TASKS = 'items'
SERVICE_NEW_TASK = 'new_task'
SERVICE_NEW_TASK = 'todoist_new_task'
NEW_TASK_SERVICE_SCHEMA = vol.Schema({
vol.Required(CONTENT): cv.string,
vol.Optional(PROJECT_NAME, default='inbox'): vol.All(cv.string, vol.Lower),
vol.Optional(LABELS): cv.ensure_list_csv,
vol.Optional(PRIORITY): vol.All(vol.Coerce(int),
vol.Range(min=1, max=4)),
vol.Optional(DUE_DATE): cv.string
vol.Optional(PRIORITY): vol.All(vol.Coerce(int), vol.Range(min=1, max=4)),
vol.Optional(DUE_DATE): cv.string,
})
CONF_EXTRA_PROJECTS = 'custom_projects'
CONF_PROJECT_DUE_DATE = 'due_date_days'
CONF_PROJECT_WHITELIST = 'include_projects'
CONF_PROJECT_LABEL_WHITELIST = 'labels'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_TOKEN): cv.string,
vol.Optional(CONF_EXTRA_PROJECTS, default=[]):
@@ -112,8 +105,7 @@ MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=15)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Todoist platform."""
# Check token:
"""Set up the Todoist platform."""
token = config.get(CONF_TOKEN)
# Look up IDs based on (lowercase) names.
@@ -177,7 +169,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
add_devices(project_devices)
def handle_new_task(call):
"""Called when a user creates a new Todoist Task from HASS."""
"""Call when a user creates a new Todoist Task from HASS."""
project_name = call.data[PROJECT_NAME]
project_id = project_id_lookup[project_name]
@@ -419,7 +411,7 @@ class TodoistProjectData(object):
The "best" event is determined by the following criteria:
* A proposed event must not be completed
* A proposed event must have a end date (otherwise we go with
* A proposed event must have an end date (otherwise we go with
the event at index 0, selected above)
* A proposed event must be on the same day or earlier as our
current event
@@ -529,8 +521,7 @@ class TodoistProjectData(object):
# Let's set our "due date" to tomorrow
self.event[END] = {
DATETIME: (
datetime.utcnow() +
timedelta(days=1)
datetime.utcnow() + timedelta(days=1)
).strftime(DATE_STR_FORMAT)
}
_LOGGER.debug("Updated %s", self._name)
+8 -8
View File
@@ -91,13 +91,13 @@ def async_snapshot(hass, filename, entity_id=None):
@bind_hass
@asyncio.coroutine
def async_get_image(hass, entity_id, timeout=10):
"""Fetch a image from a camera entity."""
"""Fetch an image from a camera entity."""
websession = async_get_clientsession(hass)
state = hass.states.get(entity_id)
if state is None:
raise HomeAssistantError(
"No entity '{0}' for grab a image".format(entity_id))
"No entity '{0}' for grab an image".format(entity_id))
url = "{0}{1}".format(
hass.config.api.base_url,
@@ -124,15 +124,15 @@ def async_setup(hass, config):
"""Set up the camera component."""
component = EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL)
hass.http.register_view(CameraImageView(component.entities))
hass.http.register_view(CameraMjpegStream(component.entities))
hass.http.register_view(CameraImageView(component))
hass.http.register_view(CameraMjpegStream(component))
yield from component.async_setup(config)
@callback
def update_tokens(time):
"""Update tokens of the entities."""
for entity in component.entities.values():
for entity in component.entities:
entity.async_update_token()
hass.async_add_job(entity.async_update_ha_state())
@@ -358,14 +358,14 @@ class CameraView(HomeAssistantView):
requires_auth = False
def __init__(self, entities):
def __init__(self, component):
"""Initialize a basic camera view."""
self.entities = entities
self.component = component
@asyncio.coroutine
def get(self, request, entity_id):
"""Start a GET request."""
camera = self.entities.get(entity_id)
camera = self.component.get_entity(entity_id)
if camera is None:
status = 404 if request[KEY_AUTHENTICATED] else 401
+1 -1
View File
@@ -22,7 +22,7 @@ MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=90)
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discoveryy_info=None):
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up Abode camera devices."""
import abodepy.helpers.constants as CONST
import abodepy.helpers.timeline as TIMELINE
+3 -1
View File
@@ -75,7 +75,9 @@ class ArloCam(Camera):
self._ffmpeg = hass.data[DATA_FFMPEG]
self._ffmpeg_arguments = device_info.get(CONF_FFMPEG_ARGUMENTS)
self._last_refresh = None
self._camera.base_station.refresh_rate = SCAN_INTERVAL.total_seconds()
if self._camera.base_station:
self._camera.base_station.refresh_rate = \
SCAN_INTERVAL.total_seconds()
self.attrs = {}
def camera_image(self):
+16 -17
View File
@@ -6,11 +6,11 @@ https://home-assistant.io/components/camera.axis/
"""
import logging
from homeassistant.const import (
CONF_HOST, CONF_NAME, CONF_USERNAME, CONF_PASSWORD, CONF_PORT,
CONF_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION)
from homeassistant.components.camera.mjpeg import (
CONF_MJPEG_URL, CONF_STILL_IMAGE_URL, MjpegCamera)
from homeassistant.const import (
CONF_AUTHENTICATION, CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_PORT,
CONF_USERNAME, HTTP_DIGEST_AUTHENTICATION)
from homeassistant.helpers.dispatcher import dispatcher_connect
_LOGGER = logging.getLogger(__name__)
@@ -20,6 +20,7 @@ DEPENDENCIES = [DOMAIN]
def _get_image_url(host, port, mode):
"""Set the URL to get the image."""
if mode == 'mjpeg':
return 'http://{}:{}/axis-cgi/mjpg/video.cgi'.format(host, port)
elif mode == 'single':
@@ -27,34 +28,32 @@ def _get_image_url(host, port, mode):
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup Axis camera."""
"""Set up the Axis camera."""
camera_config = {
CONF_NAME: discovery_info[CONF_NAME],
CONF_USERNAME: discovery_info[CONF_USERNAME],
CONF_PASSWORD: discovery_info[CONF_PASSWORD],
CONF_MJPEG_URL: _get_image_url(discovery_info[CONF_HOST],
str(discovery_info[CONF_PORT]),
'mjpeg'),
CONF_STILL_IMAGE_URL: _get_image_url(discovery_info[CONF_HOST],
str(discovery_info[CONF_PORT]),
'single'),
CONF_MJPEG_URL: _get_image_url(
discovery_info[CONF_HOST], str(discovery_info[CONF_PORT]),
'mjpeg'),
CONF_STILL_IMAGE_URL: _get_image_url(
discovery_info[CONF_HOST], str(discovery_info[CONF_PORT]),
'single'),
CONF_AUTHENTICATION: HTTP_DIGEST_AUTHENTICATION,
}
add_devices([AxisCamera(hass,
camera_config,
str(discovery_info[CONF_PORT]))])
add_devices([AxisCamera(
hass, camera_config, str(discovery_info[CONF_PORT]))])
class AxisCamera(MjpegCamera):
"""AxisCamera class."""
"""Representation of a Axis camera."""
def __init__(self, hass, config, port):
"""Initialize Axis Communications camera component."""
super().__init__(hass, config)
self.port = port
dispatcher_connect(hass,
DOMAIN + '_' + config[CONF_NAME] + '_new_ip',
self._new_ip)
dispatcher_connect(
hass, DOMAIN + '_' + config[CONF_NAME] + '_new_ip', self._new_ip)
def _new_ip(self, host):
"""Set new IP for video stream."""
+6 -6
View File
@@ -4,21 +4,21 @@ Support for Blink system camera.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.blink/
"""
from datetime import timedelta
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
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['blink']
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=90)
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up a Blink Camera."""
@@ -45,7 +45,7 @@ class BlinkCamera(Camera):
self.notifications = self.data.cameras[self._name].notifications
self.response = None
_LOGGER.info("Initialized blink camera %s", self._name)
_LOGGER.debug("Initialized blink camera %s", self._name)
@property
def name(self):
@@ -55,7 +55,7 @@ class BlinkCamera(Camera):
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def request_image(self):
"""Request a new image from Blink servers."""
_LOGGER.info("Requesting new image from blink servers")
_LOGGER.debug("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)
@@ -68,7 +68,7 @@ class BlinkCamera(Camera):
# We detected motion at some point
self.data.last_motion()
self.notifications = notifs
# returning motion image currently not working
# Returning motion image currently not working
# return self.data.cameras[self._name].motion['image']
elif notifs < self.notifications:
self.notifications = notifs
+65 -48
View File
@@ -4,19 +4,30 @@ Support for Canary camera.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.canary/
"""
import asyncio
import logging
from datetime import timedelta
import requests
import voluptuous as vol
from homeassistant.components.camera import Camera
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
from homeassistant.components.canary import DATA_CANARY, DEFAULT_TIMEOUT
from homeassistant.components.ffmpeg import DATA_FFMPEG
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream
from homeassistant.util import Throttle
DEPENDENCIES = ['canary']
CONF_FFMPEG_ARGUMENTS = 'ffmpeg_arguments'
DEPENDENCIES = ['canary', 'ffmpeg']
_LOGGER = logging.getLogger(__name__)
ATTR_MOTION_START_TIME = "motion_start_time"
ATTR_MOTION_END_TIME = "motion_end_time"
MIN_TIME_BETWEEN_SESSION_RENEW = timedelta(seconds=90)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_FFMPEG_ARGUMENTS): cv.string,
})
def setup_platform(hass, config, add_devices, discovery_info=None):
@@ -25,10 +36,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
devices = []
for location in data.locations:
entries = data.get_motion_entries(location.location_id)
if entries:
devices.append(CanaryCamera(data, location.location_id,
DEFAULT_TIMEOUT))
for device in location.devices:
if device.is_online:
devices.append(
CanaryCamera(hass, data, location, device, DEFAULT_TIMEOUT,
config.get(CONF_FFMPEG_ARGUMENTS)))
add_devices(devices, True)
@@ -36,60 +48,65 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class CanaryCamera(Camera):
"""An implementation of a Canary security camera."""
def __init__(self, data, location_id, timeout):
def __init__(self, hass, data, location, device, timeout, ffmpeg_args):
"""Initialize a Canary security camera."""
super().__init__()
self._ffmpeg = hass.data[DATA_FFMPEG]
self._ffmpeg_arguments = ffmpeg_args
self._data = data
self._location_id = location_id
self._location = location
self._device = device
self._timeout = timeout
self._location = None
self._motion_entry = None
self._image_content = None
def camera_image(self):
"""Update the status of the camera and return bytes of camera image."""
self.update()
return self._image_content
self._live_stream_session = None
@property
def name(self):
"""Return the name of this device."""
return self._location.name
return self._device.name
@property
def is_recording(self):
"""Return true if the device is recording."""
return self._location.is_recording
@property
def device_state_attributes(self):
"""Return device specific state attributes."""
if self._motion_entry is None:
return None
return {
ATTR_MOTION_START_TIME: self._motion_entry.start_time,
ATTR_MOTION_END_TIME: self._motion_entry.end_time,
}
def update(self):
"""Update the status of the camera."""
self._data.update()
self._location = self._data.get_location(self._location_id)
entries = self._data.get_motion_entries(self._location_id)
if entries:
current = entries[0]
previous = self._motion_entry
if previous is None or previous.entry_id != current.entry_id:
self._motion_entry = current
self._image_content = requests.get(
current.thumbnails[0].image_url,
timeout=self._timeout).content
@property
def motion_detection_enabled(self):
"""Return the camera motion detection status."""
return not self._location.is_recording
@asyncio.coroutine
def async_camera_image(self):
"""Return a still image response from the camera."""
self.renew_live_stream_session()
from haffmpeg import ImageFrame, IMAGE_JPEG
ffmpeg = ImageFrame(self._ffmpeg.binary, loop=self.hass.loop)
image = yield from asyncio.shield(ffmpeg.get_image(
self._live_stream_session.live_stream_url,
output_format=IMAGE_JPEG,
extra_cmd=self._ffmpeg_arguments), loop=self.hass.loop)
return image
@asyncio.coroutine
def handle_async_mjpeg_stream(self, request):
"""Generate an HTTP MJPEG stream from the camera."""
if self._live_stream_session is None:
return
from haffmpeg import CameraMjpeg
stream = CameraMjpeg(self._ffmpeg.binary, loop=self.hass.loop)
yield from stream.open_camera(
self._live_stream_session.live_stream_url,
extra_cmd=self._ffmpeg_arguments)
yield from async_aiohttp_proxy_stream(
self.hass, request, stream,
'multipart/x-mixed-replace;boundary=ffserver')
yield from stream.close()
@Throttle(MIN_TIME_BETWEEN_SESSION_RENEW)
def renew_live_stream_session(self):
"""Renew live stream session."""
self._live_stream_session = self._data.get_live_stream_session(
self._device)
+14 -14
View File
@@ -44,6 +44,8 @@ class FoscamCam(Camera):
def __init__(self, device_info):
"""Initialize a Foscam camera."""
from libpyfoscam import FoscamCamera
super(FoscamCam, self).__init__()
ip_address = device_info.get(CONF_IP)
@@ -53,10 +55,8 @@ class FoscamCam(Camera):
self._name = device_info.get(CONF_NAME)
self._motion_status = False
from libpyfoscam import FoscamCamera
self._foscam_session = FoscamCamera(ip_address, port, self._username,
self._password, verbose=False)
self._foscam_session = FoscamCamera(
ip_address, port, self._username, self._password, verbose=False)
def camera_image(self):
"""Return a still image response from the camera."""
@@ -75,20 +75,20 @@ class FoscamCam(Camera):
def enable_motion_detection(self):
"""Enable motion detection in camera."""
ret, err = self._foscam_session.enable_motion_detection()
if ret == FOSCAM_COMM_ERROR:
_LOGGER.debug("Unable to communicate with Foscam Camera: %s", err)
self._motion_status = True
else:
try:
ret = self._foscam_session.enable_motion_detection()
self._motion_status = ret == FOSCAM_COMM_ERROR
except TypeError:
_LOGGER.debug("Communication problem")
self._motion_status = False
def disable_motion_detection(self):
"""Disable motion detection."""
ret, err = self._foscam_session.disable_motion_detection()
if ret == FOSCAM_COMM_ERROR:
_LOGGER.debug("Unable to communicate with Foscam Camera: %s", err)
self._motion_status = True
else:
try:
ret = self._foscam_session.disable_motion_detection()
self._motion_status = ret == FOSCAM_COMM_ERROR
except TypeError:
_LOGGER.debug("Communication problem")
self._motion_status = False
@property
@@ -64,10 +64,6 @@ class NetatmoCamera(Camera):
self._name = home + ' / ' + camera_name
else:
self._name = camera_name
camera_id = data.camera_data.cameraByName(
camera=camera_name, home=home)['id']
self._unique_id = "Welcome_camera {0} - {1}".format(
self._name, camera_id)
self._vpnurl, self._localurl = self._data.camera_data.cameraUrls(
camera=camera_name
)
@@ -114,8 +110,3 @@ class NetatmoCamera(Camera):
elif self._cameratype == "NACamera":
return "Welcome"
return None
@property
def unique_id(self):
"""Return the unique ID for this sensor."""
return self._unique_id
+4 -2
View File
@@ -14,7 +14,7 @@ from homeassistant.const import (
CONF_NAME, CONF_HOST, CONF_USERNAME, CONF_PASSWORD, CONF_PORT)
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
from homeassistant.components.ffmpeg import (
DATA_FFMPEG)
DATA_FFMPEG, CONF_EXTRA_ARGUMENTS)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.aiohttp_client import (
async_aiohttp_proxy_stream)
@@ -31,6 +31,7 @@ DEFAULT_NAME = 'ONVIF Camera'
DEFAULT_PORT = 5000
DEFAULT_USERNAME = 'admin'
DEFAULT_PASSWORD = '888888'
DEFAULT_ARGUMENTS = '-q:v 2'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
@@ -38,6 +39,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_PASSWORD, default=DEFAULT_PASSWORD): cv.string,
vol.Optional(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_EXTRA_ARGUMENTS, default=DEFAULT_ARGUMENTS): cv.string,
})
@@ -59,7 +61,7 @@ class ONVIFCamera(Camera):
super().__init__()
self._name = config.get(CONF_NAME)
self._ffmpeg_arguments = '-q:v 2'
self._ffmpeg_arguments = config.get(CONF_EXTRA_ARGUMENTS)
media = ONVIFService(
'http://{}:{}/onvif/device_service'.format(
config.get(CONF_HOST), config.get(CONF_PORT)),
@@ -28,7 +28,7 @@ CONF_VERTICAL_FLIP = 'vertical_flip'
DEFAULT_HORIZONTAL_FLIP = 0
DEFAULT_IMAGE_HEIGHT = 480
DEFAULT_IMAGE_QUALITIY = 7
DEFAULT_IMAGE_QUALITY = 7
DEFAULT_IMAGE_ROTATION = 0
DEFAULT_IMAGE_WIDTH = 640
DEFAULT_NAME = 'Raspberry Pi Camera'
@@ -41,7 +41,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.All(vol.Coerce(int), vol.Range(min=0, max=1)),
vol.Optional(CONF_IMAGE_HEIGHT, default=DEFAULT_IMAGE_HEIGHT):
vol.Coerce(int),
vol.Optional(CONF_IMAGE_QUALITY, default=DEFAULT_IMAGE_QUALITIY):
vol.Optional(CONF_IMAGE_QUALITY, default=DEFAULT_IMAGE_QUALITY):
vol.All(vol.Coerce(int), vol.Range(min=0, max=100)),
vol.Optional(CONF_IMAGE_ROTATION, default=DEFAULT_IMAGE_ROTATION):
vol.All(vol.Coerce(int), vol.Range(min=0, max=359)),
@@ -131,7 +131,7 @@ class RaspberryCamera(Camera):
stderr=subprocess.STDOUT)
def camera_image(self):
"""Return raspstill image response."""
"""Return raspistill image response."""
with open(self._config[CONF_FILE_PATH], 'rb') as file:
return file.read()
+3
View File
@@ -127,6 +127,9 @@ class UnifiVideoCamera(Camera):
else:
client_cls = uvc_camera.UVCCameraClient
if caminfo['username'] is None:
caminfo['username'] = 'ubnt'
camera = None
for addr in addrs:
try:
+121
View File
@@ -0,0 +1,121 @@
"""
Support for Xeoma Cameras.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.xeoma/
"""
import asyncio
import logging
import voluptuous as vol
from homeassistant.components.camera import PLATFORM_SCHEMA, Camera
from homeassistant.const import (
CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNAME)
from homeassistant.helpers import config_validation as cv
REQUIREMENTS = ['pyxeoma==1.3']
_LOGGER = logging.getLogger(__name__)
CONF_CAMERAS = 'cameras'
CONF_HIDE = 'hide'
CONF_IMAGE_NAME = 'image_name'
CONF_NEW_VERSION = 'new_version'
CONF_VIEWER_PASSWORD = 'viewer_password'
CONF_VIEWER_USERNAME = 'viewer_username'
CAMERAS_SCHEMA = vol.Schema({
vol.Required(CONF_IMAGE_NAME): cv.string,
vol.Optional(CONF_HIDE, default=False): cv.boolean,
vol.Optional(CONF_NAME): cv.string,
}, required=False)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_CAMERAS, default={}):
vol.Schema(vol.All(cv.ensure_list, [CAMERAS_SCHEMA])),
vol.Optional(CONF_NEW_VERSION, default=True): cv.boolean,
vol.Optional(CONF_PASSWORD): cv.string,
vol.Optional(CONF_USERNAME): cv.string,
})
@asyncio.coroutine
# pylint: disable=unused-argument
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Discover and setup Xeoma Cameras."""
from pyxeoma.xeoma import Xeoma, XeomaError
host = config[CONF_HOST]
login = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
xeoma = Xeoma(host, login, password)
try:
yield from xeoma.async_test_connection()
discovered_image_names = yield from xeoma.async_get_image_names()
discovered_cameras = [
{
CONF_IMAGE_NAME: image_name,
CONF_HIDE: False,
CONF_NAME: image_name,
CONF_VIEWER_USERNAME: username,
CONF_VIEWER_PASSWORD: pw
}
for image_name, username, pw in discovered_image_names
]
for cam in config[CONF_CAMERAS]:
camera = next(
(dc for dc in discovered_cameras
if dc[CONF_IMAGE_NAME] == cam[CONF_IMAGE_NAME]), None)
if camera is not None:
if CONF_NAME in cam:
camera[CONF_NAME] = cam[CONF_NAME]
if CONF_HIDE in cam:
camera[CONF_HIDE] = cam[CONF_HIDE]
cameras = list(filter(lambda c: not c[CONF_HIDE], discovered_cameras))
async_add_devices(
[XeomaCamera(xeoma, camera[CONF_IMAGE_NAME], camera[CONF_NAME],
camera[CONF_VIEWER_USERNAME],
camera[CONF_VIEWER_PASSWORD]) for camera in cameras])
except XeomaError as err:
_LOGGER.error("Error: %s", err.message)
return
class XeomaCamera(Camera):
"""Implementation of a Xeoma camera."""
def __init__(self, xeoma, image, name, username, password):
"""Initialize a Xeoma camera."""
super().__init__()
self._xeoma = xeoma
self._name = name
self._image = image
self._username = username
self._password = password
self._last_image = None
@asyncio.coroutine
def async_camera_image(self):
"""Return a still image response from the camera."""
from pyxeoma.xeoma import XeomaError
try:
image = yield from self._xeoma.async_get_camera_image(
self._image, self._username, self._password)
self._last_image = image
except XeomaError as err:
_LOGGER.error("Error fetching image: %s", err.message)
return self._last_image
@property
def name(self):
"""Return the name of this device."""
return self._name
+12 -1
View File
@@ -15,7 +15,7 @@ from homeassistant.const import CONF_USERNAME, CONF_PASSWORD, CONF_TIMEOUT
from homeassistant.helpers import discovery
from homeassistant.util import Throttle
REQUIREMENTS = ['py-canary==0.2.3']
REQUIREMENTS = ['py-canary==0.4.0']
_LOGGER = logging.getLogger(__name__)
@@ -111,7 +111,18 @@ class CanaryData(object):
"""Return a list of readings based on device_id."""
return self._readings_by_device_id.get(device_id, [])
def get_reading(self, device_id, sensor_type):
"""Return reading for device_id and sensor type."""
readings = self._readings_by_device_id.get(device_id, [])
return next((
reading.value for reading in readings
if reading.sensor_type == sensor_type), None)
def set_location_mode(self, location_id, mode_name, is_private=False):
"""Set location mode."""
self._api.set_location_mode(location_id, mode_name, is_private)
self.update(no_throttle=True)
def get_live_stream_session(self, device):
"""Return live stream session."""
return self._api.get_live_stream_session(device)
+24 -23
View File
@@ -499,53 +499,54 @@ class ClimateDevice(Entity):
self.precision),
}
supported_features = self.supported_features
if self.target_temperature_step is not None:
data[ATTR_TARGET_TEMP_STEP] = self.target_temperature_step
target_temp_high = self.target_temperature_high
if target_temp_high is not None:
if supported_features & SUPPORT_TARGET_TEMPERATURE_HIGH:
data[ATTR_TARGET_TEMP_HIGH] = show_temp(
self.hass, self.target_temperature_high, self.temperature_unit,
self.precision)
if supported_features & SUPPORT_TARGET_TEMPERATURE_LOW:
data[ATTR_TARGET_TEMP_LOW] = show_temp(
self.hass, self.target_temperature_low, self.temperature_unit,
self.precision)
humidity = self.target_humidity
if humidity is not None:
data[ATTR_HUMIDITY] = humidity
if supported_features & SUPPORT_TARGET_HUMIDITY:
data[ATTR_HUMIDITY] = self.target_humidity
data[ATTR_CURRENT_HUMIDITY] = self.current_humidity
data[ATTR_MIN_HUMIDITY] = self.min_humidity
data[ATTR_MAX_HUMIDITY] = self.max_humidity
fan_mode = self.current_fan_mode
if fan_mode is not None:
data[ATTR_FAN_MODE] = fan_mode
if supported_features & SUPPORT_TARGET_HUMIDITY_LOW:
data[ATTR_MIN_HUMIDITY] = self.min_humidity
if supported_features & SUPPORT_TARGET_HUMIDITY_HIGH:
data[ATTR_MAX_HUMIDITY] = self.max_humidity
if supported_features & SUPPORT_FAN_MODE:
data[ATTR_FAN_MODE] = self.current_fan_mode
if self.fan_list:
data[ATTR_FAN_LIST] = self.fan_list
operation_mode = self.current_operation
if operation_mode is not None:
data[ATTR_OPERATION_MODE] = operation_mode
if supported_features & SUPPORT_OPERATION_MODE:
data[ATTR_OPERATION_MODE] = self.current_operation
if self.operation_list:
data[ATTR_OPERATION_LIST] = self.operation_list
is_hold = self.current_hold_mode
if is_hold is not None:
data[ATTR_HOLD_MODE] = is_hold
if supported_features & SUPPORT_HOLD_MODE:
data[ATTR_HOLD_MODE] = self.current_hold_mode
swing_mode = self.current_swing_mode
if swing_mode is not None:
data[ATTR_SWING_MODE] = swing_mode
if supported_features & SUPPORT_SWING_MODE:
data[ATTR_SWING_MODE] = self.current_swing_mode
if self.swing_list:
data[ATTR_SWING_LIST] = self.swing_list
is_away = self.is_away_mode_on
if is_away is not None:
if supported_features & SUPPORT_AWAY_MODE:
is_away = self.is_away_mode_on
data[ATTR_AWAY_MODE] = STATE_ON if is_away else STATE_OFF
is_aux_heat = self.is_aux_heat_on
if is_aux_heat is not None:
if supported_features & SUPPORT_AUX_HEAT:
is_aux_heat = self.is_aux_heat_on
data[ATTR_AUX_HEAT] = STATE_ON if is_aux_heat else STATE_OFF
return data
+46 -38
View File
@@ -9,35 +9,23 @@ import re
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.climate import (
ATTR_OPERATION_MODE, ATTR_FAN_MODE, ATTR_SWING_MODE,
ATTR_CURRENT_TEMPERATURE, ClimateDevice, PLATFORM_SCHEMA,
SUPPORT_TARGET_TEMPERATURE, SUPPORT_FAN_MODE, SUPPORT_OPERATION_MODE,
SUPPORT_SWING_MODE, STATE_OFF, STATE_AUTO, STATE_HEAT, STATE_COOL,
STATE_DRY, STATE_FAN_ONLY
)
ATTR_CURRENT_TEMPERATURE, ATTR_FAN_MODE, ATTR_OPERATION_MODE,
ATTR_SWING_MODE, PLATFORM_SCHEMA, STATE_AUTO, STATE_COOL, STATE_DRY,
STATE_FAN_ONLY, STATE_HEAT, STATE_OFF, SUPPORT_FAN_MODE,
SUPPORT_OPERATION_MODE, SUPPORT_SWING_MODE, SUPPORT_TARGET_TEMPERATURE,
ClimateDevice)
from homeassistant.components.daikin import (
daikin_api_setup,
ATTR_TARGET_TEMPERATURE,
ATTR_INSIDE_TEMPERATURE,
ATTR_OUTSIDE_TEMPERATURE
)
ATTR_INSIDE_TEMPERATURE, ATTR_OUTSIDE_TEMPERATURE, ATTR_TARGET_TEMPERATURE,
daikin_api_setup)
from homeassistant.const import (
CONF_HOST, CONF_NAME,
TEMP_CELSIUS,
ATTR_TEMPERATURE
)
ATTR_TEMPERATURE, CONF_HOST, CONF_NAME, TEMP_CELSIUS)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pydaikin==0.4']
_LOGGER = logging.getLogger(__name__)
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE |
SUPPORT_FAN_MODE |
SUPPORT_OPERATION_MODE |
SUPPORT_SWING_MODE)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_NAME, default=None): cv.string,
@@ -56,19 +44,22 @@ HA_ATTR_TO_DAIKIN = {
ATTR_OPERATION_MODE: 'mode',
ATTR_FAN_MODE: 'f_rate',
ATTR_SWING_MODE: 'f_dir',
ATTR_INSIDE_TEMPERATURE: 'htemp',
ATTR_OUTSIDE_TEMPERATURE: 'otemp',
ATTR_TARGET_TEMPERATURE: 'stemp'
}
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Daikin HVAC platform."""
"""Set up the Daikin HVAC platform."""
if discovery_info is not None:
host = discovery_info.get('ip')
name = None
_LOGGER.info("Discovered a Daikin AC on %s", host)
_LOGGER.debug("Discovered a Daikin AC on %s", host)
else:
host = config.get(CONF_HOST)
name = config.get(CONF_NAME)
_LOGGER.info("Added Daikin AC on %s", host)
_LOGGER.debug("Added Daikin AC on %s", host)
api = daikin_api_setup(hass, host, name)
add_devices([DaikinClimate(api)], True)
@@ -101,6 +92,23 @@ class DaikinClimate(ClimateDevice):
),
}
self._supported_features = SUPPORT_TARGET_TEMPERATURE \
| SUPPORT_OPERATION_MODE
daikin_attr = HA_ATTR_TO_DAIKIN[ATTR_FAN_MODE]
if self._api.device.values.get(daikin_attr) is not None:
self._supported_features |= SUPPORT_FAN_MODE
else:
# even devices without support must have a default valid value
self._api.device.values[daikin_attr] = 'A'
daikin_attr = HA_ATTR_TO_DAIKIN[ATTR_SWING_MODE]
if self._api.device.values.get(daikin_attr) is not None:
self._supported_features |= SUPPORT_SWING_MODE
else:
# even devices without support must have a default valid value
self._api.device.values[daikin_attr] = '0'
def get(self, key):
"""Retrieve device settings from API library cache."""
value = None
@@ -108,29 +116,34 @@ class DaikinClimate(ClimateDevice):
if key in [ATTR_TEMPERATURE, ATTR_INSIDE_TEMPERATURE,
ATTR_CURRENT_TEMPERATURE]:
value = self._api.device.values.get('htemp')
key = ATTR_INSIDE_TEMPERATURE
daikin_attr = HA_ATTR_TO_DAIKIN.get(key)
if key == ATTR_INSIDE_TEMPERATURE:
value = self._api.device.values.get(daikin_attr)
cast_to_float = True
if key == ATTR_TARGET_TEMPERATURE:
value = self._api.device.values.get('stemp')
elif key == ATTR_TARGET_TEMPERATURE:
value = self._api.device.values.get(daikin_attr)
cast_to_float = True
elif key == ATTR_OUTSIDE_TEMPERATURE:
value = self._api.device.values.get('otemp')
value = self._api.device.values.get(daikin_attr)
cast_to_float = True
elif key == ATTR_FAN_MODE:
value = self._api.device.represent('f_rate')[1].title()
value = self._api.device.represent(daikin_attr)[1].title()
elif key == ATTR_SWING_MODE:
value = self._api.device.represent('f_dir')[1].title()
value = self._api.device.represent(daikin_attr)[1].title()
elif key == ATTR_OPERATION_MODE:
# Daikin can return also internal states auto-1 or auto-7
# and we need to translate them as AUTO
value = re.sub(
'[^a-z]',
'',
self._api.device.represent('mode')[1]
self._api.device.represent(daikin_attr)[1]
).title()
if value is None:
_LOGGER.warning("Invalid value requested for key %s", key)
_LOGGER.error("Invalid value requested for key %s", key)
else:
if value == "-" or value == "--":
value = None
@@ -170,15 +183,10 @@ class DaikinClimate(ClimateDevice):
self._force_refresh = True
self._api.device.set(values)
@property
def unique_id(self):
"""Return the ID of this AC."""
return "{}.{}".format(self.__class__, self._api.ip_address)
@property
def supported_features(self):
"""Return the list of supported features."""
return SUPPORT_FLAGS
return self._supported_features
@property
def name(self):
+39 -13
View File
@@ -7,28 +7,26 @@ https://home-assistant.io/components/demo/
from homeassistant.components.climate import (
ClimateDevice, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_HUMIDITY,
SUPPORT_TARGET_HUMIDITY_LOW, SUPPORT_TARGET_HUMIDITY_HIGH,
SUPPORT_AWAY_MODE, SUPPORT_HOLD_MODE, SUPPORT_FAN_MODE,
SUPPORT_OPERATION_MODE, SUPPORT_AUX_HEAT, SUPPORT_SWING_MODE,
SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW,
SUPPORT_ON_OFF)
from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT, ATTR_TEMPERATURE
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_TARGET_HUMIDITY |
SUPPORT_AWAY_MODE | SUPPORT_HOLD_MODE | SUPPORT_FAN_MODE |
SUPPORT_OPERATION_MODE | SUPPORT_AUX_HEAT |
SUPPORT_SWING_MODE | SUPPORT_TARGET_TEMPERATURE_HIGH |
SUPPORT_TARGET_TEMPERATURE_LOW | SUPPORT_ON_OFF)
SUPPORT_FLAGS = SUPPORT_TARGET_HUMIDITY_LOW | SUPPORT_TARGET_HUMIDITY_HIGH
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Demo climate devices."""
add_devices([
DemoClimate('HeatPump', 68, TEMP_FAHRENHEIT, None, None, 77,
'Auto Low', None, None, 'Auto', 'heat', None, None, None),
None, None, None, None, 'heat', None, None,
None, True),
DemoClimate('Hvac', 21, TEMP_CELSIUS, True, None, 22, 'On High',
67, 54, 'Off', 'cool', False, None, None),
DemoClimate('Ecobee', None, TEMP_CELSIUS, None, None, 23, 'Auto Low',
None, None, 'Auto', 'auto', None, 24, 21)
67, 54, 'Off', 'cool', False, None, None, None),
DemoClimate('Ecobee', None, TEMP_CELSIUS, None, 'home', 23, 'Auto Low',
None, None, 'Auto', 'auto', None, 24, 21, None)
])
@@ -38,9 +36,37 @@ class DemoClimate(ClimateDevice):
def __init__(self, name, target_temperature, unit_of_measurement,
away, hold, current_temperature, current_fan_mode,
target_humidity, current_humidity, current_swing_mode,
current_operation, aux, target_temp_high, target_temp_low):
current_operation, aux, target_temp_high, target_temp_low,
is_on):
"""Initialize the climate device."""
self._name = name
self._support_flags = SUPPORT_FLAGS
if target_temperature is not None:
self._support_flags = \
self._support_flags | SUPPORT_TARGET_TEMPERATURE
if away is not None:
self._support_flags = self._support_flags | SUPPORT_AWAY_MODE
if hold is not None:
self._support_flags = self._support_flags | SUPPORT_HOLD_MODE
if current_fan_mode is not None:
self._support_flags = self._support_flags | SUPPORT_FAN_MODE
if target_humidity is not None:
self._support_flags = \
self._support_flags | SUPPORT_TARGET_HUMIDITY
if current_swing_mode is not None:
self._support_flags = self._support_flags | SUPPORT_SWING_MODE
if current_operation is not None:
self._support_flags = self._support_flags | SUPPORT_OPERATION_MODE
if aux is not None:
self._support_flags = self._support_flags | SUPPORT_AUX_HEAT
if target_temp_high is not None:
self._support_flags = \
self._support_flags | SUPPORT_TARGET_TEMPERATURE_HIGH
if target_temp_low is not None:
self._support_flags = \
self._support_flags | SUPPORT_TARGET_TEMPERATURE_LOW
if is_on is not None:
self._support_flags = self._support_flags | SUPPORT_ON_OFF
self._target_temperature = target_temperature
self._target_humidity = target_humidity
self._unit_of_measurement = unit_of_measurement
@@ -57,12 +83,12 @@ class DemoClimate(ClimateDevice):
self._swing_list = ['Auto', '1', '2', '3', 'Off']
self._target_temperature_high = target_temp_high
self._target_temperature_low = target_temp_low
self._on = True
self._on = is_on
@property
def supported_features(self):
"""Return the list of supported features."""
return SUPPORT_FLAGS
return self._support_flags
@property
def should_poll(self):
@@ -205,7 +231,7 @@ class DemoClimate(ClimateDevice):
self.schedule_update_ha_state()
def turn_aux_heat_on(self):
"""Turn auxillary heater on."""
"""Turn auxiliary heater on."""
self._aux = True
self.schedule_update_ha_state()
+6 -2
View File
@@ -13,7 +13,9 @@ from homeassistant.components.climate import (
DOMAIN, STATE_COOL, STATE_HEAT, STATE_AUTO, STATE_IDLE, ClimateDevice,
ATTR_TARGET_TEMP_LOW, ATTR_TARGET_TEMP_HIGH, SUPPORT_TARGET_TEMPERATURE,
SUPPORT_AWAY_MODE, SUPPORT_HOLD_MODE, SUPPORT_OPERATION_MODE,
SUPPORT_TARGET_HUMIDITY_LOW, SUPPORT_TARGET_HUMIDITY_HIGH)
SUPPORT_TARGET_HUMIDITY_LOW, SUPPORT_TARGET_HUMIDITY_HIGH,
SUPPORT_AUX_HEAT, SUPPORT_TARGET_TEMPERATURE_HIGH,
SUPPORT_TARGET_TEMPERATURE_LOW)
from homeassistant.const import (
ATTR_ENTITY_ID, STATE_OFF, STATE_ON, ATTR_TEMPERATURE, TEMP_FAHRENHEIT)
import homeassistant.helpers.config_validation as cv
@@ -46,7 +48,9 @@ RESUME_PROGRAM_SCHEMA = vol.Schema({
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_AWAY_MODE |
SUPPORT_HOLD_MODE | SUPPORT_OPERATION_MODE |
SUPPORT_TARGET_HUMIDITY_LOW | SUPPORT_TARGET_HUMIDITY_HIGH)
SUPPORT_TARGET_HUMIDITY_LOW | SUPPORT_TARGET_HUMIDITY_HIGH |
SUPPORT_AUX_HEAT | SUPPORT_TARGET_TEMPERATURE_HIGH |
SUPPORT_TARGET_TEMPERATURE_LOW)
def setup_platform(hass, config, add_devices, discovery_info=None):
+15 -21
View File
@@ -10,19 +10,15 @@ import logging
import voluptuous as vol
from homeassistant.components.climate import (
DOMAIN,
PLATFORM_SCHEMA,
STATE_ECO, STATE_GAS, STATE_ELECTRIC,
STATE_HEAT_PUMP, STATE_HIGH_DEMAND,
STATE_OFF, SUPPORT_TARGET_TEMPERATURE,
SUPPORT_OPERATION_MODE,
ClimateDevice)
from homeassistant.const import (ATTR_ENTITY_ID,
CONF_PASSWORD, CONF_USERNAME, TEMP_FAHRENHEIT,
ATTR_TEMPERATURE)
DOMAIN, PLATFORM_SCHEMA, STATE_ECO, STATE_ELECTRIC, STATE_GAS,
STATE_HEAT_PUMP, STATE_HIGH_DEMAND, STATE_OFF, STATE_PERFORMANCE,
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, ClimateDevice)
from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_TEMPERATURE, CONF_PASSWORD, CONF_USERNAME,
TEMP_FAHRENHEIT)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pyeconet==0.0.4']
REQUIREMENTS = ['pyeconet==0.0.5']
_LOGGER = logging.getLogger(__name__)
@@ -59,6 +55,7 @@ HA_STATE_TO_ECONET = {
STATE_GAS: 'gas',
STATE_HIGH_DEMAND: 'High Demand',
STATE_OFF: 'Off',
STATE_PERFORMANCE: 'Performance'
}
ECONET_STATE_TO_HA = {value: key for key, value in HA_STATE_TO_ECONET.items()}
@@ -87,7 +84,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
hass.data[ECONET_DATA]['water_heaters'].extend(hass_water_heaters)
def service_handle(service):
"""Handler for services."""
"""Handle the service calls."""
entity_ids = service.data.get('entity_id')
all_heaters = hass.data[ECONET_DATA]['water_heaters']
_heaters = [
@@ -105,12 +102,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
_water_heater.schedule_update_ha_state(True)
hass.services.register(DOMAIN, SERVICE_ADD_VACATION,
service_handle,
hass.services.register(DOMAIN, SERVICE_ADD_VACATION, service_handle,
schema=ADD_VACATION_SCHEMA)
hass.services.register(DOMAIN, SERVICE_DELETE_VACATION,
service_handle,
hass.services.register(DOMAIN, SERVICE_DELETE_VACATION, service_handle,
schema=DELETE_VACATION_SCHEMA)
@@ -138,7 +133,7 @@ class EcoNetWaterHeater(ClimateDevice):
@property
def device_state_attributes(self):
"""Return the optional state attributes."""
"""Return the optional device state attributes."""
data = {}
vacations = self.water_heater.get_vacations()
if vacations:
@@ -157,8 +152,7 @@ class EcoNetWaterHeater(ClimateDevice):
"""
Return current operation as one of the following.
["eco", "heat_pump",
"high_demand", "electric_only"]
["eco", "heat_pump", "high_demand", "electric_only"]
"""
current_op = ECONET_STATE_TO_HA.get(self.water_heater.mode)
return current_op
@@ -189,7 +183,7 @@ class EcoNetWaterHeater(ClimateDevice):
if target_temp is not None:
self.water_heater.set_target_set_point(target_temp)
else:
_LOGGER.error("A target temperature must be provided.")
_LOGGER.error("A target temperature must be provided")
def set_operation_mode(self, operation_mode):
"""Set operation mode."""
@@ -197,7 +191,7 @@ class EcoNetWaterHeater(ClimateDevice):
if op_mode_to_set is not None:
self.water_heater.set_mode(op_mode_to_set)
else:
_LOGGER.error("An operation mode must be provided.")
_LOGGER.error("An operation mode must be provided")
def add_vacation(self, start, end):
"""Add a vacation to this water heater."""
+39 -6
View File
@@ -9,9 +9,10 @@ from datetime import timedelta
import voluptuous as vol
from homeassistant.components.climate import (
ClimateDevice, PLATFORM_SCHEMA, STATE_HEAT, STATE_IDLE, SUPPORT_AUX_HEAT)
ClimateDevice, PLATFORM_SCHEMA, STATE_HEAT, STATE_IDLE, SUPPORT_AUX_HEAT,
SUPPORT_TARGET_TEMPERATURE)
from homeassistant.const import (
TEMP_CELSIUS, CONF_USERNAME, CONF_PASSWORD)
TEMP_CELSIUS, CONF_USERNAME, CONF_PASSWORD, ATTR_TEMPERATURE)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pyephember==0.1.1']
@@ -59,7 +60,10 @@ class EphEmberThermostat(ClimateDevice):
@property
def supported_features(self):
"""Return the list of supported features."""
return SUPPORT_AUX_HEAT
if self._hot_water:
return SUPPORT_AUX_HEAT
return SUPPORT_TARGET_TEMPERATURE | SUPPORT_AUX_HEAT
@property
def name(self):
@@ -81,6 +85,14 @@ class EphEmberThermostat(ClimateDevice):
"""Return the temperature we try to reach."""
return self._zone['targetTemperature']
@property
def target_temperature_step(self):
"""Return the supported step of target temperature."""
if self._hot_water:
return None
return 1
@property
def current_operation(self):
"""Return current operation ie. heat, cool, idle."""
@@ -105,17 +117,38 @@ class EphEmberThermostat(ClimateDevice):
def set_temperature(self, **kwargs):
"""Set new target temperature."""
return
temperature = kwargs.get(ATTR_TEMPERATURE)
if temperature is None:
return
if self._hot_water:
return
if temperature == self.target_temperature:
return
if temperature > self.max_temp or temperature < self.min_temp:
return
self._ember.set_target_temperture_by_name(self._zone_name,
int(temperature))
@property
def min_temp(self):
"""Return the minimum temperature."""
return self._zone['targetTemperature']
# Hot water temp doesn't support being changed
if self._hot_water:
return self._zone['targetTemperature']
return 5
@property
def max_temp(self):
"""Return the maximum temperature."""
return self._zone['targetTemperature']
if self._hot_water:
return self._zone['targetTemperature']
return 35
def update(self):
"""Get the latest data."""
@@ -15,7 +15,7 @@ from homeassistant.const import (
CONF_MAC, CONF_DEVICES, TEMP_CELSIUS, ATTR_TEMPERATURE, PRECISION_HALVES)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['python-eq3bt==0.1.6']
REQUIREMENTS = ['python-eq3bt==0.1.8']
_LOGGER = logging.getLogger(__name__)
@@ -55,7 +55,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
# pylint: disable=import-error
class EQ3BTSmartThermostat(ClimateDevice):
"""Representation of a eQ-3 Bluetooth Smart thermostat."""
"""Representation of an eQ-3 Bluetooth Smart thermostat."""
def __init__(self, _mac, _name):
"""Initialize the thermostat."""
@@ -17,7 +17,8 @@ from homeassistant.components.climate import (
SUPPORT_AWAY_MODE, SUPPORT_TARGET_TEMPERATURE, PLATFORM_SCHEMA)
from homeassistant.const import (
ATTR_UNIT_OF_MEASUREMENT, STATE_ON, STATE_OFF, ATTR_TEMPERATURE,
CONF_NAME, ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF)
CONF_NAME, ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF,
STATE_UNKNOWN)
from homeassistant.helpers import condition
from homeassistant.helpers.event import (
async_track_state_change, async_track_time_interval)
@@ -30,7 +31,6 @@ DEPENDENCIES = ['switch', 'sensor']
DEFAULT_TOLERANCE = 0.3
DEFAULT_NAME = 'Generic Thermostat'
DEFAULT_AWAY_TEMP = 16
CONF_HEATER = 'heater'
CONF_SENSOR = 'target_sensor'
@@ -44,7 +44,7 @@ CONF_HOT_TOLERANCE = 'hot_tolerance'
CONF_KEEP_ALIVE = 'keep_alive'
CONF_INITIAL_OPERATION_MODE = 'initial_operation_mode'
CONF_AWAY_TEMP = 'away_temp'
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_AWAY_MODE |
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE |
SUPPORT_OPERATION_MODE)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@@ -64,8 +64,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
cv.time_period, cv.positive_timedelta),
vol.Optional(CONF_INITIAL_OPERATION_MODE):
vol.In([STATE_AUTO, STATE_OFF]),
vol.Optional(CONF_AWAY_TEMP,
default=DEFAULT_AWAY_TEMP): vol.Coerce(float)
vol.Optional(CONF_AWAY_TEMP): vol.Coerce(float)
})
@@ -119,6 +118,7 @@ class GenericThermostat(ClimateDevice):
self._operation_list = [STATE_HEAT, STATE_OFF]
if initial_operation_mode == STATE_OFF:
self._enabled = False
self._current_operation = STATE_OFF
else:
self._enabled = True
self._active = False
@@ -127,6 +127,9 @@ class GenericThermostat(ClimateDevice):
self._max_temp = max_temp
self._target_temp = target_temp
self._unit = hass.config.units.temperature_unit
self._support_flags = SUPPORT_FLAGS
if away_temp is not None:
self._support_flags = SUPPORT_FLAGS | SUPPORT_AWAY_MODE
self._away_temp = away_temp
self._is_away = False
@@ -139,6 +142,10 @@ class GenericThermostat(ClimateDevice):
async_track_time_interval(
hass, self._async_keep_alive, self._keep_alive)
sensor_state = hass.states.get(sensor_entity_id)
if sensor_state and sensor_state.state != STATE_UNKNOWN:
self._async_update_temp(sensor_state)
@asyncio.coroutine
def async_added_to_hass(self):
"""Run when entity about to be added."""
@@ -149,24 +156,34 @@ class GenericThermostat(ClimateDevice):
# If we have no initial temperature, restore
if self._target_temp is None:
# If we have a previously saved temperature
if old_state.attributes[ATTR_TEMPERATURE] is None:
if old_state.attributes.get(ATTR_TEMPERATURE) is None:
if self.ac_mode:
self._target_temp = self.max_temp
else:
self._target_temp = self.min_temp
_LOGGER.warning('Undefined target temperature, \
falling back to %s', self._target_temp)
_LOGGER.warning("Undefined target temperature,"
"falling back to %s", self._target_temp)
else:
self._target_temp = float(
old_state.attributes[ATTR_TEMPERATURE])
self._is_away = True if str(
old_state.attributes[ATTR_AWAY_MODE]) == STATE_ON else False
if old_state.attributes[ATTR_OPERATION_MODE] == STATE_OFF:
self._current_operation = STATE_OFF
self._enabled = False
if self._initial_operation_mode is None:
if old_state.attributes[ATTR_OPERATION_MODE] == STATE_OFF:
self._enabled = False
if old_state.attributes.get(ATTR_AWAY_MODE) is not None:
self._is_away = str(
old_state.attributes[ATTR_AWAY_MODE]) == STATE_ON
if (self._initial_operation_mode is None and
old_state.attributes[ATTR_OPERATION_MODE] is not None):
self._current_operation = \
old_state.attributes[ATTR_OPERATION_MODE]
self._enabled = self._current_operation != STATE_OFF
else:
# No previous state, try and restore defaults
if self._target_temp is None:
if self.ac_mode:
self._target_temp = self.max_temp
else:
self._target_temp = self.min_temp
_LOGGER.warning("No previously saved temperature, setting to %s",
self._target_temp)
@property
def state(self):
@@ -230,9 +247,9 @@ class GenericThermostat(ClimateDevice):
if self._is_device_active:
self._heater_turn_off()
else:
_LOGGER.error('Unrecognized operation mode: %s', operation_mode)
_LOGGER.error("Unrecognized operation mode: %s", operation_mode)
return
# Ensure we updae the current operation after changing the mode
# Ensure we update the current operation after changing the mode
self.schedule_update_ha_state()
@asyncio.coroutine
@@ -299,7 +316,7 @@ class GenericThermostat(ClimateDevice):
self._cur_temp = self.hass.config.units.temperature(
float(state.state), unit)
except ValueError as ex:
_LOGGER.error('Unable to update from sensor: %s', ex)
_LOGGER.error("Unable to update from sensor: %s", ex)
@callback
def _async_control_heating(self):
@@ -307,8 +324,9 @@ class GenericThermostat(ClimateDevice):
if not self._active and None not in (self._cur_temp,
self._target_temp):
self._active = True
_LOGGER.info('Obtained current and target temperature. '
'Generic thermostat active.')
_LOGGER.info("Obtained current and target temperature. "
"Generic thermostat active. %s, %s",
self._cur_temp, self._target_temp)
if not self._active:
return
@@ -333,13 +351,13 @@ class GenericThermostat(ClimateDevice):
too_cold = self._target_temp - self._cur_temp >= \
self._cold_tolerance
if too_cold:
_LOGGER.info('Turning off AC %s', self.heater_entity_id)
_LOGGER.info("Turning off AC %s", self.heater_entity_id)
self._heater_turn_off()
else:
too_hot = self._cur_temp - self._target_temp >= \
self._hot_tolerance
if too_hot:
_LOGGER.info('Turning on AC %s', self.heater_entity_id)
_LOGGER.info("Turning on AC %s", self.heater_entity_id)
self._heater_turn_on()
else:
is_heating = self._is_device_active
@@ -347,14 +365,14 @@ class GenericThermostat(ClimateDevice):
too_hot = self._cur_temp - self._target_temp >= \
self._hot_tolerance
if too_hot:
_LOGGER.info('Turning off heater %s',
_LOGGER.info("Turning off heater %s",
self.heater_entity_id)
self._heater_turn_off()
else:
too_cold = self._target_temp - self._cur_temp >= \
self._cold_tolerance
if too_cold:
_LOGGER.info('Turning on heater %s', self.heater_entity_id)
_LOGGER.info("Turning on heater %s", self.heater_entity_id)
self._heater_turn_on()
@property
@@ -365,7 +383,7 @@ class GenericThermostat(ClimateDevice):
@property
def supported_features(self):
"""Return the list of supported features."""
return SUPPORT_FLAGS
return self._support_flags
@callback
def _heater_turn_on(self):
@@ -46,7 +46,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
serport = connection.connection(ipaddress, port)
serport.open()
for thermostat, tstat in tstats.items():
for tstat in tstats.values():
add_devices([
HeatmiserV3Thermostat(
heatmiser, tstat.get(CONF_ID), tstat.get(CONF_NAME), serport)
+178 -178
View File
@@ -1,178 +1,178 @@
"""
Support for the Hive devices.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.hive/
"""
from homeassistant.components.climate import (
ClimateDevice, STATE_AUTO, STATE_HEAT, STATE_OFF, STATE_ON,
SUPPORT_AUX_HEAT, SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE)
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
from homeassistant.components.hive import DATA_HIVE
DEPENDENCIES = ['hive']
HIVE_TO_HASS_STATE = {'SCHEDULE': STATE_AUTO, 'MANUAL': STATE_HEAT,
'ON': STATE_ON, 'OFF': STATE_OFF}
HASS_TO_HIVE_STATE = {STATE_AUTO: 'SCHEDULE', STATE_HEAT: 'MANUAL',
STATE_ON: 'ON', STATE_OFF: 'OFF'}
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE |
SUPPORT_OPERATION_MODE |
SUPPORT_AUX_HEAT)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up Hive climate devices."""
if discovery_info is None:
return
session = hass.data.get(DATA_HIVE)
add_devices([HiveClimateEntity(session, discovery_info)])
class HiveClimateEntity(ClimateDevice):
"""Hive Climate Device."""
def __init__(self, hivesession, hivedevice):
"""Initialize the Climate device."""
self.node_id = hivedevice["Hive_NodeID"]
self.node_name = hivedevice["Hive_NodeName"]
self.device_type = hivedevice["HA_DeviceType"]
self.session = hivesession
self.data_updatesource = '{}.{}'.format(self.device_type,
self.node_id)
if self.device_type == "Heating":
self.modes = [STATE_AUTO, STATE_HEAT, STATE_OFF]
elif self.device_type == "HotWater":
self.modes = [STATE_AUTO, STATE_ON, STATE_OFF]
self.session.entities.append(self)
@property
def supported_features(self):
"""Return the list of supported features."""
return SUPPORT_FLAGS
def handle_update(self, updatesource):
"""Handle the new update request."""
if '{}.{}'.format(self.device_type, self.node_id) not in updatesource:
self.schedule_update_ha_state()
@property
def name(self):
"""Return the name of the Climate device."""
friendly_name = "Climate Device"
if self.device_type == "Heating":
friendly_name = "Heating"
if self.node_name is not None:
friendly_name = '{} {}'.format(self.node_name, friendly_name)
elif self.device_type == "HotWater":
friendly_name = "Hot Water"
return friendly_name
@property
def temperature_unit(self):
"""Return the unit of measurement."""
return TEMP_CELSIUS
@property
def current_temperature(self):
"""Return the current temperature."""
if self.device_type == "Heating":
return self.session.heating.current_temperature(self.node_id)
@property
def target_temperature(self):
"""Return the target temperature."""
if self.device_type == "Heating":
return self.session.heating.get_target_temperature(self.node_id)
@property
def min_temp(self):
"""Return minimum temperature."""
if self.device_type == "Heating":
return self.session.heating.min_temperature(self.node_id)
@property
def max_temp(self):
"""Return the maximum temperature."""
if self.device_type == "Heating":
return self.session.heating.max_temperature(self.node_id)
@property
def operation_list(self):
"""List of the operation modes."""
return self.modes
@property
def current_operation(self):
"""Return current mode."""
if self.device_type == "Heating":
currentmode = self.session.heating.get_mode(self.node_id)
elif self.device_type == "HotWater":
currentmode = self.session.hotwater.get_mode(self.node_id)
return HIVE_TO_HASS_STATE.get(currentmode)
def set_operation_mode(self, operation_mode):
"""Set new Heating mode."""
new_mode = HASS_TO_HIVE_STATE.get(operation_mode)
if self.device_type == "Heating":
self.session.heating.set_mode(self.node_id, new_mode)
elif self.device_type == "HotWater":
self.session.hotwater.set_mode(self.node_id, new_mode)
for entity in self.session.entities:
entity.handle_update(self.data_updatesource)
def set_temperature(self, **kwargs):
"""Set new target temperature."""
new_temperature = kwargs.get(ATTR_TEMPERATURE)
if new_temperature is not None:
if self.device_type == "Heating":
self.session.heating.set_target_temperature(self.node_id,
new_temperature)
for entity in self.session.entities:
entity.handle_update(self.data_updatesource)
@property
def is_aux_heat_on(self):
"""Return true if auxiliary heater is on."""
boost_status = None
if self.device_type == "Heating":
boost_status = self.session.heating.get_boost(self.node_id)
elif self.device_type == "HotWater":
boost_status = self.session.hotwater.get_boost(self.node_id)
return boost_status == "ON"
def turn_aux_heat_on(self):
"""Turn auxiliary heater on."""
target_boost_time = 30
if self.device_type == "Heating":
curtemp = self.session.heating.current_temperature(self.node_id)
curtemp = round(curtemp * 2) / 2
target_boost_temperature = curtemp + 0.5
self.session.heating.turn_boost_on(self.node_id,
target_boost_time,
target_boost_temperature)
elif self.device_type == "HotWater":
self.session.hotwater.turn_boost_on(self.node_id,
target_boost_time)
for entity in self.session.entities:
entity.handle_update(self.data_updatesource)
def turn_aux_heat_off(self):
"""Turn auxiliary heater off."""
if self.device_type == "Heating":
self.session.heating.turn_boost_off(self.node_id)
elif self.device_type == "HotWater":
self.session.hotwater.turn_boost_off(self.node_id)
for entity in self.session.entities:
entity.handle_update(self.data_updatesource)
def update(self):
"""Update all Node data frome Hive."""
self.session.core.update_data(self.node_id)
"""
Support for the Hive devices.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.hive/
"""
from homeassistant.components.climate import (
ClimateDevice, STATE_AUTO, STATE_HEAT, STATE_OFF, STATE_ON,
SUPPORT_AUX_HEAT, SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE)
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
from homeassistant.components.hive import DATA_HIVE
DEPENDENCIES = ['hive']
HIVE_TO_HASS_STATE = {'SCHEDULE': STATE_AUTO, 'MANUAL': STATE_HEAT,
'ON': STATE_ON, 'OFF': STATE_OFF}
HASS_TO_HIVE_STATE = {STATE_AUTO: 'SCHEDULE', STATE_HEAT: 'MANUAL',
STATE_ON: 'ON', STATE_OFF: 'OFF'}
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE |
SUPPORT_OPERATION_MODE |
SUPPORT_AUX_HEAT)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up Hive climate devices."""
if discovery_info is None:
return
session = hass.data.get(DATA_HIVE)
add_devices([HiveClimateEntity(session, discovery_info)])
class HiveClimateEntity(ClimateDevice):
"""Hive Climate Device."""
def __init__(self, hivesession, hivedevice):
"""Initialize the Climate device."""
self.node_id = hivedevice["Hive_NodeID"]
self.node_name = hivedevice["Hive_NodeName"]
self.device_type = hivedevice["HA_DeviceType"]
self.session = hivesession
self.data_updatesource = '{}.{}'.format(self.device_type,
self.node_id)
if self.device_type == "Heating":
self.modes = [STATE_AUTO, STATE_HEAT, STATE_OFF]
elif self.device_type == "HotWater":
self.modes = [STATE_AUTO, STATE_ON, STATE_OFF]
self.session.entities.append(self)
@property
def supported_features(self):
"""Return the list of supported features."""
return SUPPORT_FLAGS
def handle_update(self, updatesource):
"""Handle the new update request."""
if '{}.{}'.format(self.device_type, self.node_id) not in updatesource:
self.schedule_update_ha_state()
@property
def name(self):
"""Return the name of the Climate device."""
friendly_name = "Climate Device"
if self.device_type == "Heating":
friendly_name = "Heating"
if self.node_name is not None:
friendly_name = '{} {}'.format(self.node_name, friendly_name)
elif self.device_type == "HotWater":
friendly_name = "Hot Water"
return friendly_name
@property
def temperature_unit(self):
"""Return the unit of measurement."""
return TEMP_CELSIUS
@property
def current_temperature(self):
"""Return the current temperature."""
if self.device_type == "Heating":
return self.session.heating.current_temperature(self.node_id)
@property
def target_temperature(self):
"""Return the target temperature."""
if self.device_type == "Heating":
return self.session.heating.get_target_temperature(self.node_id)
@property
def min_temp(self):
"""Return minimum temperature."""
if self.device_type == "Heating":
return self.session.heating.min_temperature(self.node_id)
@property
def max_temp(self):
"""Return the maximum temperature."""
if self.device_type == "Heating":
return self.session.heating.max_temperature(self.node_id)
@property
def operation_list(self):
"""List of the operation modes."""
return self.modes
@property
def current_operation(self):
"""Return current mode."""
if self.device_type == "Heating":
currentmode = self.session.heating.get_mode(self.node_id)
elif self.device_type == "HotWater":
currentmode = self.session.hotwater.get_mode(self.node_id)
return HIVE_TO_HASS_STATE.get(currentmode)
def set_operation_mode(self, operation_mode):
"""Set new Heating mode."""
new_mode = HASS_TO_HIVE_STATE.get(operation_mode)
if self.device_type == "Heating":
self.session.heating.set_mode(self.node_id, new_mode)
elif self.device_type == "HotWater":
self.session.hotwater.set_mode(self.node_id, new_mode)
for entity in self.session.entities:
entity.handle_update(self.data_updatesource)
def set_temperature(self, **kwargs):
"""Set new target temperature."""
new_temperature = kwargs.get(ATTR_TEMPERATURE)
if new_temperature is not None:
if self.device_type == "Heating":
self.session.heating.set_target_temperature(self.node_id,
new_temperature)
for entity in self.session.entities:
entity.handle_update(self.data_updatesource)
@property
def is_aux_heat_on(self):
"""Return true if auxiliary heater is on."""
boost_status = None
if self.device_type == "Heating":
boost_status = self.session.heating.get_boost(self.node_id)
elif self.device_type == "HotWater":
boost_status = self.session.hotwater.get_boost(self.node_id)
return boost_status == "ON"
def turn_aux_heat_on(self):
"""Turn auxiliary heater on."""
target_boost_time = 30
if self.device_type == "Heating":
curtemp = self.session.heating.current_temperature(self.node_id)
curtemp = round(curtemp * 2) / 2
target_boost_temperature = curtemp + 0.5
self.session.heating.turn_boost_on(self.node_id,
target_boost_time,
target_boost_temperature)
elif self.device_type == "HotWater":
self.session.hotwater.turn_boost_on(self.node_id,
target_boost_time)
for entity in self.session.entities:
entity.handle_update(self.data_updatesource)
def turn_aux_heat_off(self):
"""Turn auxiliary heater off."""
if self.device_type == "Heating":
self.session.heating.turn_boost_off(self.node_id)
elif self.device_type == "HotWater":
self.session.hotwater.turn_boost_off(self.node_id)
for entity in self.session.entities:
entity.handle_update(self.data_updatesource)
def update(self):
"""Update all Node data from Hive."""
self.session.core.update_data(self.node_id)
+19 -28
View File
@@ -5,13 +5,14 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.knx/
"""
import asyncio
import voluptuous as vol
from homeassistant.components.knx import DATA_KNX, ATTR_DISCOVER_DEVICES
from homeassistant.components.climate import (
PLATFORM_SCHEMA, ClimateDevice, SUPPORT_TARGET_TEMPERATURE,
SUPPORT_OPERATION_MODE)
from homeassistant.const import CONF_NAME, TEMP_CELSIUS, ATTR_TEMPERATURE
PLATFORM_SCHEMA, SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE,
ClimateDevice)
from homeassistant.components.knx import ATTR_DISCOVER_DEVICES, DATA_KNX
from homeassistant.const import ATTR_TEMPERATURE, CONF_NAME, TEMP_CELSIUS
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
@@ -61,24 +62,20 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices,
discovery_info=None):
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up climate(s) for KNX platform."""
if DATA_KNX not in hass.data \
or not hass.data[DATA_KNX].initialized:
return False
if DATA_KNX not in hass.data or not hass.data[DATA_KNX].initialized:
return
if discovery_info is not None:
async_add_devices_discovery(hass, discovery_info, async_add_devices)
else:
async_add_devices_config(hass, config, async_add_devices)
return True
@callback
def async_add_devices_discovery(hass, discovery_info, async_add_devices):
"""Set up climates for KNX platform configured within plattform."""
"""Set up climates for KNX platform configured within platform."""
entities = []
for device_name in discovery_info[ATTR_DISCOVER_DEVICES]:
device = hass.data[DATA_KNX].xknx.devices[device_name]
@@ -88,28 +85,22 @@ def async_add_devices_discovery(hass, discovery_info, async_add_devices):
@callback
def async_add_devices_config(hass, config, async_add_devices):
"""Set up climate for KNX platform configured within plattform."""
"""Set up climate for KNX platform configured within platform."""
import xknx
climate = xknx.devices.Climate(
hass.data[DATA_KNX].xknx,
name=config.get(CONF_NAME),
group_address_temperature=config.get(
CONF_TEMPERATURE_ADDRESS),
group_address_temperature=config.get(CONF_TEMPERATURE_ADDRESS),
group_address_target_temperature=config.get(
CONF_TARGET_TEMPERATURE_ADDRESS),
group_address_setpoint_shift=config.get(
CONF_SETPOINT_SHIFT_ADDRESS),
group_address_setpoint_shift=config.get(CONF_SETPOINT_SHIFT_ADDRESS),
group_address_setpoint_shift_state=config.get(
CONF_SETPOINT_SHIFT_STATE_ADDRESS),
setpoint_shift_step=config.get(
CONF_SETPOINT_SHIFT_STEP),
setpoint_shift_max=config.get(
CONF_SETPOINT_SHIFT_MAX),
setpoint_shift_min=config.get(
CONF_SETPOINT_SHIFT_MIN),
group_address_operation_mode=config.get(
CONF_OPERATION_MODE_ADDRESS),
setpoint_shift_step=config.get(CONF_SETPOINT_SHIFT_STEP),
setpoint_shift_max=config.get(CONF_SETPOINT_SHIFT_MAX),
setpoint_shift_min=config.get(CONF_SETPOINT_SHIFT_MIN),
group_address_operation_mode=config.get(CONF_OPERATION_MODE_ADDRESS),
group_address_operation_mode_state=config.get(
CONF_OPERATION_MODE_STATE_ADDRESS),
group_address_controller_status=config.get(
@@ -127,10 +118,10 @@ def async_add_devices_config(hass, config, async_add_devices):
class KNXClimate(ClimateDevice):
"""Representation of a KNX climate."""
"""Representation of a KNX climate device."""
def __init__(self, hass, device):
"""Initialization of KNXClimate."""
"""Initialize of a KNX climate device."""
self.device = device
self.hass = hass
self.async_register_callbacks()
@@ -149,7 +140,7 @@ class KNXClimate(ClimateDevice):
"""Register callbacks to update hass after device was changed."""
@asyncio.coroutine
def after_update_callback(device):
"""Callback after device was updated."""
"""Call after device was updated."""
# pylint: disable=unused-argument
yield from self.async_update_ha_state()
self.device.register_device_updated_cb(after_update_callback)

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