Compare commits

...

178 Commits

Author SHA1 Message Date
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
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
443 changed files with 10347 additions and 5907 deletions
+7 -1
View File
@@ -145,6 +145,9 @@ 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
@@ -377,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
@@ -435,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
@@ -449,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
@@ -506,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
@@ -592,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
+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/*
+2 -1
View File
@@ -41,7 +41,7 @@ homeassistant/components/*/zwave.py @home-assistant/z-wave
homeassistant/components/hassio.py @home-assistant/hassio
# Indiviudal components
# Individual components
homeassistant/components/alarm_control_panel/egardia.py @jeroenterheerdt
homeassistant/components/camera/yi.py @bachya
homeassistant/components/climate/ephember.py @ttroy50
@@ -61,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)
+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()
+382 -129
View File
@@ -17,7 +17,7 @@ from homeassistant.const import (
SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_STOP,
SERVICE_SET_COVER_POSITION, SERVICE_TURN_OFF, SERVICE_TURN_ON,
SERVICE_UNLOCK, SERVICE_VOLUME_SET, TEMP_FAHRENHEIT, TEMP_CELSIUS,
CONF_UNIT_OF_MEASUREMENT)
CONF_UNIT_OF_MEASUREMENT, STATE_LOCKED, STATE_UNLOCKED, STATE_ON)
from .const import CONF_FILTER, CONF_ENTITY_CONFIG
_LOGGER = logging.getLogger(__name__)
@@ -40,6 +40,7 @@ CONF_DESCRIPTION = 'description'
CONF_DISPLAY_CATEGORIES = 'display_categories'
HANDLERS = Registry()
ENTITY_ADAPTERS = Registry()
class _DisplayCategory(object):
@@ -50,8 +51,8 @@ class _DisplayCategory(object):
# 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. Applies to Scenes
# 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.
@@ -133,10 +134,36 @@ def _capability(interface,
return result
class _EntityCapabilities(object):
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."""
@@ -154,17 +181,217 @@ class _EntityCapabilities(object):
"""
raise NotImplementedError
def capabilities(self):
"""Return a list of supported capabilities.
def get_interface(self, capability):
"""Return the given _AlexaInterface.
If the returned list is empty, the entity will not be discovered.
Raises _UnsupportedInterface.
"""
pass
You might find _capability() useful.
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 _GenericCapabilities(_EntityCapabilities):
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(),
},
}
# 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)
return round(self.entity.attributes['brightness'] / 255.0 * 100)
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],
}
@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.
@@ -173,78 +400,87 @@ class _GenericCapabilities(_EntityCapabilities):
def default_display_categories(self):
return [_DisplayCategory.OTHER]
def capabilities(self):
return [_capability('Alexa.PowerController')]
def interfaces(self):
return [_AlexaPowerController(self.entity)]
class _SwitchCapabilities(_EntityCapabilities):
@ENTITY_ADAPTERS.register(switch.DOMAIN)
class _SwitchCapabilities(_AlexaEntity):
def default_display_categories(self):
return [_DisplayCategory.SWITCH]
def capabilities(self):
return [_capability('Alexa.PowerController')]
def interfaces(self):
return [_AlexaPowerController(self.entity)]
class _CoverCapabilities(_EntityCapabilities):
@ENTITY_ADAPTERS.register(cover.DOMAIN)
class _CoverCapabilities(_AlexaEntity):
def default_display_categories(self):
return [_DisplayCategory.DOOR]
def capabilities(self):
capabilities = [_capability('Alexa.PowerController')]
def interfaces(self):
yield _AlexaPowerController(self.entity)
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
if supported & cover.SUPPORT_SET_POSITION:
capabilities.append(_capability('Alexa.PercentageController'))
return capabilities
yield _AlexaPercentageController(self.entity)
class _LightCapabilities(_EntityCapabilities):
@ENTITY_ADAPTERS.register(light.DOMAIN)
class _LightCapabilities(_AlexaEntity):
def default_display_categories(self):
return [_DisplayCategory.LIGHT]
def capabilities(self):
capabilities = [_capability('Alexa.PowerController')]
def interfaces(self):
yield _AlexaPowerController(self.entity)
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
if supported & light.SUPPORT_BRIGHTNESS:
capabilities.append(_capability('Alexa.BrightnessController'))
yield _AlexaBrightnessController(self.entity)
if supported & light.SUPPORT_RGB_COLOR:
capabilities.append(_capability('Alexa.ColorController'))
yield _AlexaColorController(self.entity)
if supported & light.SUPPORT_XY_COLOR:
capabilities.append(_capability('Alexa.ColorController'))
yield _AlexaColorController(self.entity)
if supported & light.SUPPORT_COLOR_TEMP:
capabilities.append(
_capability('Alexa.ColorTemperatureController'))
return capabilities
yield _AlexaColorTemperatureController(self.entity)
class _FanCapabilities(_EntityCapabilities):
@ENTITY_ADAPTERS.register(fan.DOMAIN)
class _FanCapabilities(_AlexaEntity):
def default_display_categories(self):
return [_DisplayCategory.OTHER]
def capabilities(self):
capabilities = [_capability('Alexa.PowerController')]
def interfaces(self):
yield _AlexaPowerController(self.entity)
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
if supported & fan.SUPPORT_SET_SPEED:
capabilities.append(_capability('Alexa.PercentageController'))
return capabilities
yield _AlexaPercentageController(self.entity)
class _LockCapabilities(_EntityCapabilities):
@ENTITY_ADAPTERS.register(lock.DOMAIN)
class _LockCapabilities(_AlexaEntity):
def default_display_categories(self):
return [_DisplayCategory.SMARTLOCK]
def capabilities(self):
return [_capability('Alexa.LockController')]
def interfaces(self):
return [_AlexaLockController(self.entity)]
class _MediaPlayerCapabilities(_EntityCapabilities):
@ENTITY_ADAPTERS.register(media_player.DOMAIN)
class _MediaPlayerCapabilities(_AlexaEntity):
def default_display_categories(self):
return [_DisplayCategory.TV]
def capabilities(self):
capabilities = [_capability('Alexa.PowerController')]
def interfaces(self):
yield _AlexaPowerController(self.entity)
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
if supported & media_player.SUPPORT_VOLUME_SET:
capabilities.append(_capability('Alexa.Speaker'))
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 |
@@ -252,89 +488,62 @@ class _MediaPlayerCapabilities(_EntityCapabilities):
media_player.SUPPORT_NEXT_TRACK |
media_player.SUPPORT_PREVIOUS_TRACK)
if supported & playback_features:
capabilities.append(_capability('Alexa.PlaybackController'))
yield _AlexaPlaybackController(self.entity)
return capabilities
if supported & media_player.SUPPORT_SELECT_SOURCE:
yield _AlexaInputController(self.entity)
class _SceneCapabilities(_EntityCapabilities):
@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 capabilities(self):
return [_capability('Alexa.SceneController')]
def interfaces(self):
return [_AlexaSceneController(self.entity,
supports_deactivation=False)]
class _ScriptCapabilities(_EntityCapabilities):
@ENTITY_ADAPTERS.register(script.DOMAIN)
class _ScriptCapabilities(_AlexaEntity):
def default_display_categories(self):
return [_DisplayCategory.ACTIVITY_TRIGGER]
def capabilities(self):
def interfaces(self):
can_cancel = bool(self.entity.attributes.get('can_cancel'))
return [_capability('Alexa.SceneController',
supports_deactivation=can_cancel)]
return [_AlexaSceneController(self.entity,
supports_deactivation=can_cancel)]
class _GroupCapabilities(_EntityCapabilities):
@ENTITY_ADAPTERS.register(group.DOMAIN)
class _GroupCapabilities(_AlexaEntity):
def default_display_categories(self):
return [_DisplayCategory.SCENE_TRIGGER]
def capabilities(self):
return [_capability('Alexa.SceneController',
supports_deactivation=True)]
def interfaces(self):
return [_AlexaSceneController(self.entity,
supports_deactivation=True)]
class _SensorCapabilities(_EntityCapabilities):
@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 capabilities(self):
capabilities = []
def interfaces(self):
attrs = self.entity.attributes
if attrs.get(CONF_UNIT_OF_MEASUREMENT) in (
TEMP_FAHRENHEIT,
TEMP_CELSIUS,
):
capabilities.append(_capability(
'Alexa.TemperatureSensor',
retrievable=True,
properties_supported=[{'name': 'temperature'}]))
return capabilities
class _UnknownEntityDomainError(Exception):
pass
def _capabilities_for_entity(config, entity):
"""Return an _EntityCapabilities appropriate for given entity.
raises _UnknownEntityDomainError if the given domain is unsupported.
"""
if entity.domain not in _CAPABILITIES_FOR_DOMAIN:
raise _UnknownEntityDomainError()
return _CAPABILITIES_FOR_DOMAIN[entity.domain](config, entity)
_CAPABILITIES_FOR_DOMAIN = {
alert.DOMAIN: _GenericCapabilities,
automation.DOMAIN: _GenericCapabilities,
cover.DOMAIN: _CoverCapabilities,
fan.DOMAIN: _FanCapabilities,
group.DOMAIN: _GroupCapabilities,
input_boolean.DOMAIN: _GenericCapabilities,
light.DOMAIN: _LightCapabilities,
lock.DOMAIN: _LockCapabilities,
media_player.DOMAIN: _MediaPlayerCapabilities,
scene.DOMAIN: _SceneCapabilities,
script.DOMAIN: _ScriptCapabilities,
switch.DOMAIN: _SwitchCapabilities,
sensor.DOMAIN: _SensorCapabilities,
}
yield _AlexaTemperatureSensor(self.entity)
class _Cause(object):
@@ -468,7 +677,7 @@ def api_message(request,
}
}
# 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
@@ -511,36 +720,26 @@ def async_api_discovery(hass, config, request):
entity.entity_id)
continue
try:
entity_capabilities = _capabilities_for_entity(config, entity)
except _UnknownEntityDomainError:
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)
alexa_entity = ENTITY_ADAPTERS[entity.domain](config, entity)
endpoint = {
'displayCategories': entity_capabilities.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',
}
alexa_capabilities = entity_capabilities.capabilities()
if not alexa_capabilities:
endpoint['capabilities'] = [
i.serialize_discovery() for i in alexa_entity.interfaces()]
if not endpoint['capabilities']:
_LOGGER.debug("Not exposing %s because it has no capabilities",
entity.entity_id)
continue
endpoint['capabilities'] = alexa_capabilities
discovery_endpoints.append(endpoint)
return api_message(
@@ -624,7 +823,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
@@ -812,7 +1011,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}
@@ -873,7 +1072,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)
@@ -900,11 +1099,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)
@@ -929,6 +1163,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]['volume'] / 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
@@ -1033,18 +1291,13 @@ def async_api_previous(hass, config, request, entity):
@asyncio.coroutine
def async_api_reportstate(hass, config, request, entity):
"""Process a ReportState request."""
unit = entity.attributes[CONF_UNIT_OF_MEASUREMENT]
temp_property = {
'namespace': 'Alexa.TemperatureSensor',
'name': 'temperature',
'value': {
'value': float(entity.state),
'scale': API_TEMP_UNITS[unit],
},
}
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': [temp_property]}
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
+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.'
@@ -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."""
@@ -65,6 +65,11 @@ 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):
"""Return the class of the sensor."""
@@ -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)):
@@ -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):
@@ -59,5 +59,5 @@ class HiveBinarySensorEntity(BinarySensorDevice):
self.node_device_type)
def update(self):
"""Update all Node data frome Hive."""
"""Update all Node data from Hive."""
self.session.core.update_data(self.node_id)
@@ -69,7 +69,8 @@ class IHCBinarySensor(IHCDevice, BinarySensorDevice):
"""
def __init__(self, ihc_controller, name, ihc_id: int, info: bool,
sensor_type: str, inverting: bool, product: Element=None):
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
@@ -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)
@@ -0,0 +1,94 @@
"""
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, 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()):
devices.append(MercedesMEBinarySensor(
data, key, value[0], car["vin"], None))
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
@@ -131,10 +131,8 @@ class NetatmoBinarySensor(BinarySensorDevice):
self._name += ' / ' + module_name
self._sensor_name = sensor
self._name += ' ' + sensor
camera_id = data.camera_data.cameraByName(
self._unique_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
@@ -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))
@@ -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."""
@@ -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
@@ -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
@@ -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
+1 -1
View File
@@ -174,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
+1 -1
View File
@@ -411,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
+2 -2
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,
+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
+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)
+3 -5
View File
@@ -64,13 +64,11 @@ 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
)
self._unique_id = data.camera_data.cameraByName(
camera=camera_name, home=home)['id']
self._cameratype = camera_type
def camera_image(self):
@@ -117,5 +115,5 @@ class NetatmoCamera(Camera):
@property
def unique_id(self):
"""Return the unique ID for this sensor."""
"""Return the unique ID for this camera."""
return self._unique_id
@@ -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:
+17 -9
View File
@@ -14,7 +14,7 @@ from homeassistant.const import (
CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNAME)
from homeassistant.helpers import config_validation as cv
REQUIREMENTS = ['pyxeoma==1.2']
REQUIREMENTS = ['pyxeoma==1.3']
_LOGGER = logging.getLogger(__name__)
@@ -22,6 +22,8 @@ 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,
@@ -48,9 +50,8 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
host = config[CONF_HOST]
login = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
new_version = config[CONF_NEW_VERSION]
xeoma = Xeoma(host, new_version, login, password)
xeoma = Xeoma(host, login, password)
try:
yield from xeoma.async_test_connection()
@@ -59,9 +60,12 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
{
CONF_IMAGE_NAME: image_name,
CONF_HIDE: False,
CONF_NAME: image_name
CONF_NAME: image_name,
CONF_VIEWER_USERNAME: username,
CONF_VIEWER_PASSWORD: pw
}
for image_name in discovered_image_names
for image_name, username, pw in discovered_image_names
]
for cam in config[CONF_CAMERAS]:
@@ -77,8 +81,9 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
cameras = list(filter(lambda c: not c[CONF_HIDE], discovered_cameras))
async_add_devices(
[XeomaCamera(xeoma, camera[CONF_IMAGE_NAME], camera[CONF_NAME])
for camera in cameras])
[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
@@ -87,12 +92,14 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
class XeomaCamera(Camera):
"""Implementation of a Xeoma camera."""
def __init__(self, xeoma, image, name):
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
@@ -100,7 +107,8 @@ class XeomaCamera(Camera):
"""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)
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)
+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)
+6 -5
View File
@@ -98,10 +98,16 @@ class DaikinClimate(ClimateDevice):
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."""
@@ -177,11 +183,6 @@ 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."""
+38 -14
View File
@@ -14,23 +14,19 @@ from homeassistant.components.climate import (
SUPPORT_ON_OFF)
from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT, ATTR_TEMPERATURE
SUPPORT_FLAGS = (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)
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)
])
@@ -40,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
@@ -59,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):
@@ -207,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):
+1 -1
View File
@@ -18,7 +18,7 @@ from homeassistant.const import (
TEMP_FAHRENHEIT)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pyeconet==0.0.4']
REQUIREMENTS = ['pyeconet==0.0.5']
_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."""
@@ -156,7 +156,7 @@ 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:
@@ -166,15 +166,15 @@ class GenericThermostat(ClimateDevice):
else:
self._target_temp = float(
old_state.attributes[ATTR_TEMPERATURE])
if old_state.attributes[ATTR_AWAY_MODE] is not None:
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]
if self._current_operation != STATE_OFF:
self._enabled = True
self._enabled = self._current_operation != STATE_OFF
else:
# No previous state, try and restore defaults
if self._target_temp is None:
@@ -249,7 +249,7 @@ class GenericThermostat(ClimateDevice):
else:
_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
@@ -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)
+1 -1
View File
@@ -174,5 +174,5 @@ class HiveClimateEntity(ClimateDevice):
entity.handle_update(self.data_updatesource)
def update(self):
"""Update all Node data frome Hive."""
"""Update all Node data from Hive."""
self.session.core.update_data(self.node_id)
+259
View File
@@ -0,0 +1,259 @@
"""
Support for Melissa Climate A/C.
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/climate.melissa/
"""
import logging
from homeassistant.components.climate import (
ClimateDevice, SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE,
SUPPORT_ON_OFF, STATE_AUTO, STATE_HEAT, STATE_COOL, STATE_DRY,
STATE_FAN_ONLY, SUPPORT_FAN_MODE
)
from homeassistant.components.fan import SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH
from homeassistant.components.melissa import DATA_MELISSA
from homeassistant.const import (
TEMP_CELSIUS, STATE_ON, STATE_OFF, STATE_IDLE, ATTR_TEMPERATURE,
PRECISION_WHOLE
)
DEPENDENCIES = ['melissa']
_LOGGER = logging.getLogger(__name__)
SUPPORT_FLAGS = (SUPPORT_FAN_MODE | SUPPORT_OPERATION_MODE |
SUPPORT_ON_OFF | SUPPORT_TARGET_TEMPERATURE)
OP_MODES = [
STATE_AUTO, STATE_COOL, STATE_DRY, STATE_FAN_ONLY, STATE_HEAT
]
FAN_MODES = [
STATE_AUTO, SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM
]
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Iterate through and add all Melissa devices."""
api = hass.data[DATA_MELISSA]
devices = api.fetch_devices().values()
all_devices = []
for device in devices:
all_devices.append(MelissaClimate(
api, device['serial_number'], device))
add_devices(all_devices)
class MelissaClimate(ClimateDevice):
"""Representation of a Melissa Climate device."""
def __init__(self, api, serial_number, init_data):
"""Initialize the climate device."""
self._name = init_data['name']
self._api = api
self._serial_number = serial_number
self._data = init_data['controller_log']
self._state = None
self._cur_settings = None
@property
def name(self):
"""Return the name of the thermostat, if any."""
return self._name
@property
def is_on(self):
"""Return current state."""
if self._cur_settings is not None:
return self._cur_settings[self._api.STATE] in (
self._api.STATE_ON, self._api.STATE_IDLE)
return None
@property
def current_fan_mode(self):
"""Return the current fan mode."""
if self._cur_settings is not None:
return self.melissa_fan_to_hass(
self._cur_settings[self._api.FAN])
@property
def current_temperature(self):
"""Return the current temperature."""
if self._data:
return self._data[self._api.TEMP]
@property
def target_temperature_step(self):
"""Return the supported step of target temperature."""
return PRECISION_WHOLE
@property
def current_operation(self):
"""Return the current operation mode."""
if self._cur_settings is not None:
return self.melissa_op_to_hass(
self._cur_settings[self._api.MODE])
@property
def operation_list(self):
"""Return the list of available operation modes."""
return OP_MODES
@property
def fan_list(self):
"""List of available fan modes."""
return FAN_MODES
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
if self._cur_settings is not None:
return self._cur_settings[self._api.TEMP]
@property
def state(self):
"""Return current state."""
if self._cur_settings is not None:
return self.melissa_state_to_hass(
self._cur_settings[self._api.STATE])
@property
def temperature_unit(self):
"""Return the unit of measurement which this thermostat uses."""
return TEMP_CELSIUS
@property
def min_temp(self):
"""Return the minimum supported temperature for the thermostat."""
return 16
@property
def max_temp(self):
"""Return the maximum supported temperature for the thermostat."""
return 30
@property
def supported_features(self):
"""Return the list of supported features."""
return SUPPORT_FLAGS
def set_temperature(self, **kwargs):
"""Set new target temperature."""
temp = kwargs.get(ATTR_TEMPERATURE)
self.send({self._api.TEMP: temp})
def set_fan_mode(self, fan):
"""Set fan mode."""
fan_mode = self.hass_fan_to_melissa(fan)
self.send({self._api.FAN: fan_mode})
def set_operation_mode(self, operation_mode):
"""Set operation mode."""
mode = self.hass_mode_to_melissa(operation_mode)
self.send({self._api.MODE: mode})
def turn_on(self):
"""Turn on device."""
self.send({self._api.STATE: self._api.STATE_ON})
def turn_off(self):
"""Turn off device."""
self.send({self._api.STATE: self._api.STATE_OFF})
def send(self, value):
"""Sending action to service."""
try:
old_value = self._cur_settings.copy()
self._cur_settings.update(value)
except AttributeError:
old_value = None
if not self._api.send(self._serial_number, self._cur_settings):
self._cur_settings = old_value
return False
else:
return True
def update(self):
"""Get latest data from Melissa."""
try:
self._data = self._api.status(cached=True)[self._serial_number]
self._cur_settings = self._api.cur_settings(
self._serial_number
)['controller']['_relation']['command_log']
except KeyError:
_LOGGER.warning(
'Unable to update entity %s', self.entity_id)
def melissa_state_to_hass(self, state):
"""Translate Melissa states to hass states."""
if state == self._api.STATE_ON:
return STATE_ON
elif state == self._api.STATE_OFF:
return STATE_OFF
elif state == self._api.STATE_IDLE:
return STATE_IDLE
else:
return None
def melissa_op_to_hass(self, mode):
"""Translate Melissa modes to hass states."""
if mode == self._api.MODE_AUTO:
return STATE_AUTO
elif mode == self._api.MODE_HEAT:
return STATE_HEAT
elif mode == self._api.MODE_COOL:
return STATE_COOL
elif mode == self._api.MODE_DRY:
return STATE_DRY
elif mode == self._api.MODE_FAN:
return STATE_FAN_ONLY
else:
_LOGGER.warning(
"Operation mode %s could not be mapped to hass", mode)
return None
def melissa_fan_to_hass(self, fan):
"""Translate Melissa fan modes to hass modes."""
if fan == self._api.FAN_AUTO:
return STATE_AUTO
elif fan == self._api.FAN_LOW:
return SPEED_LOW
elif fan == self._api.FAN_MEDIUM:
return SPEED_MEDIUM
elif fan == self._api.FAN_HIGH:
return SPEED_HIGH
else:
_LOGGER.warning("Fan mode %s could not be mapped to hass", fan)
return None
def hass_mode_to_melissa(self, mode):
"""Translate hass states to melissa modes."""
if mode == STATE_AUTO:
return self._api.MODE_AUTO
elif mode == STATE_HEAT:
return self._api.MODE_HEAT
elif mode == STATE_COOL:
return self._api.MODE_COOL
elif mode == STATE_DRY:
return self._api.MODE_DRY
elif mode == STATE_FAN_ONLY:
return self._api.MODE_FAN
else:
_LOGGER.warning("Melissa have no setting for %s mode", mode)
def hass_fan_to_melissa(self, fan):
"""Translate hass fan modes to melissa modes."""
if fan == STATE_AUTO:
return self._api.FAN_AUTO
elif fan == SPEED_LOW:
return self._api.FAN_LOW
elif fan == SPEED_MEDIUM:
return self._api.FAN_MEDIUM
elif fan == SPEED_HIGH:
return self._api.FAN_HIGH
else:
_LOGGER.warning("Melissa have no setting for %s fan mode", fan)
+2 -2
View File
@@ -565,7 +565,7 @@ class MqttClimate(MqttAvailability, ClimateDevice):
@asyncio.coroutine
def async_turn_aux_heat_on(self):
"""Turn auxillary heater on."""
"""Turn auxiliary heater on."""
if self._topic[CONF_AUX_COMMAND_TOPIC] is not None:
mqtt.async_publish(self.hass, self._topic[CONF_AUX_COMMAND_TOPIC],
self._payload_on, self._qos, self._retain)
@@ -576,7 +576,7 @@ class MqttClimate(MqttAvailability, ClimateDevice):
@asyncio.coroutine
def async_turn_aux_heat_off(self):
"""Turn auxillary heater off."""
"""Turn auxiliary heater off."""
if self._topic[CONF_AUX_COMMAND_TOPIC] is not None:
mqtt.async_publish(self.hass, self._topic[CONF_AUX_COMMAND_TOPIC],
self._payload_off, self._qos, self._retain)
@@ -139,8 +139,7 @@ class MySensorsHVAC(mysensors.MySensorsEntity, ClimateDevice):
self.gateway.set_child_value(
self.node_id, self.child_id, value_type, value)
if self.gateway.optimistic:
# O
# ptimistically assume that device has changed state
# Optimistically assume that device has changed state
self._values[value_type] = value
self.schedule_update_ha_state()
+5
View File
@@ -97,6 +97,11 @@ class NestThermostat(ClimateDevice):
"""Return the list of supported features."""
return SUPPORT_FLAGS
@property
def unique_id(self):
"""Unique ID for this device."""
return self.device.serial
@property
def name(self):
"""Return the name of the nest, if any."""
+1 -1
View File
@@ -24,7 +24,7 @@ CONF_RELAY = 'relay'
CONF_THERMOSTAT = 'thermostat'
DEFAULT_AWAY_TEMPERATURE = 14
# # The default offeset is 2 hours (when you use the thermostat itself)
# # The default offset is 2 hours (when you use the thermostat itself)
DEFAULT_TIME_OFFSET = 7200
# # Return cached results if last scan was less then this time ago
# # NetAtmo Data is uploaded to server every hour
+1 -1
View File
@@ -59,7 +59,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class ThermostatDevice(ClimateDevice):
"""Interface class for the oemthermostat modul."""
"""Interface class for the oemthermostat module."""
def __init__(self, hass, thermostat, name, away_temp):
"""Initialize the device."""
+3 -3
View File
@@ -249,7 +249,7 @@ class TadoClimate(ClimateDevice):
data = self._store.get_data(self._data_id)
if data is None:
_LOGGER.debug("Recieved no data for zone %s", self.zone_name)
_LOGGER.debug("Received no data for zone %s", self.zone_name)
return
if 'sensorDataPoints' in data:
@@ -294,7 +294,7 @@ class TadoClimate(ClimateDevice):
overlay = False
overlay_data = None
termination = self._current_operation
termination = CONST_MODE_SMART_SCHEDULE
cooling = False
fan_speed = CONST_MODE_OFF
@@ -317,7 +317,7 @@ class TadoClimate(ClimateDevice):
fan_speed = setting_data['fanSpeed']
if self._device_is_active:
# If you set mode manualy to off, there will be an overlay
# If you set mode manually to off, there will be an overlay
# and a termination, but we want to see the mode "OFF"
self._overlay_mode = termination
self._current_operation = termination
@@ -13,7 +13,7 @@ from homeassistant.components.climate import (
from homeassistant.const import CONF_HOST, TEMP_CELSIUS, ATTR_TEMPERATURE
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pytouchline==0.6']
REQUIREMENTS = ['pytouchline==0.7']
_LOGGER = logging.getLogger(__name__)
+1 -1
View File
@@ -20,7 +20,7 @@ from homeassistant.const import (
TEMP_FAHRENHEIT)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['venstarcolortouch==0.5']
REQUIREMENTS = ['venstarcolortouch==0.6']
_LOGGER = logging.getLogger(__name__)
+26 -30
View File
@@ -16,8 +16,7 @@ import voluptuous as vol
from homeassistant.const import (
EVENT_HOMEASSISTANT_START, CONF_REGION, CONF_MODE, CONF_NAME, CONF_TYPE)
from homeassistant.helpers import entityfilter
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers import entityfilter, config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.util import dt as dt_util
from homeassistant.components.alexa import smart_home as alexa_sh
@@ -105,12 +104,7 @@ def async_setup(hass, config):
)
cloud = hass.data[DOMAIN] = Cloud(hass, **kwargs)
success = yield from cloud.initialize()
if not success:
return False
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, cloud.async_start)
yield from http_api.async_setup(hass)
return True
@@ -192,19 +186,6 @@ class Cloud:
return self._gactions_config
@asyncio.coroutine
def initialize(self):
"""Initialize and load cloud info."""
jwt_success = yield from self._fetch_jwt_keyset()
if not jwt_success:
return False
self.hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_START, self._start_cloud)
return True
def path(self, *parts):
"""Get config path inside cloud dir.
@@ -234,19 +215,34 @@ class Cloud:
'refresh_token': self.refresh_token,
}, indent=4))
def _start_cloud(self, event):
@asyncio.coroutine
def async_start(self, _):
"""Start the cloud component."""
# Ensure config dir exists
path = self.hass.config.path(CONFIG_DIR)
if not os.path.isdir(path):
os.mkdir(path)
success = yield from self._fetch_jwt_keyset()
user_info = self.user_info_path
if not os.path.isfile(user_info):
# Fetching keyset can fail if internet is not up yet.
if not success:
self.hass.helpers.async_call_later(5, self.async_start)
return
with open(user_info, 'rt') as file:
info = json.loads(file.read())
def load_config():
"""Load config."""
# Ensure config dir exists
path = self.hass.config.path(CONFIG_DIR)
if not os.path.isdir(path):
os.mkdir(path)
user_info = self.user_info_path
if not os.path.isfile(user_info):
return None
with open(user_info, 'rt') as file:
return json.loads(file.read())
info = yield from self.hass.async_add_job(load_config)
if info is None:
return
# Validate tokens
try:
+8 -8
View File
@@ -5,16 +5,17 @@ For more details about this component, please refer to the documentation at
https://home-assistant.io/components/coinbase/
"""
from datetime import timedelta
import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.const import CONF_API_KEY
from homeassistant.util import Throttle
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.discovery import load_platform
from homeassistant.util import Throttle
REQUIREMENTS = ['coinbase==2.0.7']
REQUIREMENTS = ['coinbase==2.0.6']
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'coinbase'
@@ -46,14 +47,13 @@ def setup(hass, config):
api_secret = config[DOMAIN].get(CONF_API_SECRET)
exchange_currencies = config[DOMAIN].get(CONF_EXCHANGE_CURRENCIES)
hass.data[DATA_COINBASE] = coinbase_data = CoinbaseData(api_key,
api_secret)
hass.data[DATA_COINBASE] = coinbase_data = CoinbaseData(
api_key, api_secret)
if not hasattr(coinbase_data, 'accounts'):
return False
for account in coinbase_data.accounts.data:
load_platform(hass, 'sensor', DOMAIN,
{'account': account}, config)
load_platform(hass, 'sensor', DOMAIN, {'account': account}, config)
for currency in exchange_currencies:
if currency not in coinbase_data.exchange_rates.rates:
_LOGGER.warning("Currency %s not found", currency)
+1 -1
View File
@@ -68,7 +68,7 @@ class HMCover(HMDevice, CoverDevice):
self._hmdevice.stop(self._channel)
def _init_data_struct(self):
"""Generate a data dictoinary (self._data) from metadata."""
"""Generate a data dictionary (self._data) from metadata."""
self._state = "LEVEL"
self._data.update({self._state: STATE_UNKNOWN})
if "LEVEL_2" in self._hmdevice.WRITENODE:
+1 -1
View File
@@ -42,7 +42,7 @@ def setup_platform(hass, config: ConfigType,
class ISYCoverDevice(ISYDevice, CoverDevice):
"""Representation of an ISY994 cover device."""
def __init__(self, node: object):
def __init__(self, node: object) -> None:
"""Initialize the ISY994 cover device."""
super().__init__(node)
+1 -1
View File
@@ -74,7 +74,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 cover for KNX platform configured within plattform."""
"""Set up cover for KNX platform configured within platform."""
import xknx
cover = xknx.devices.Cover(
hass.data[DATA_KNX].xknx,
+1 -1
View File
@@ -63,7 +63,7 @@ class LutronCover(LutronDevice, CoverDevice):
def update(self):
"""Call when forcing a refresh of the device."""
# Reading the property (rather than last_level()) fetchs value
# Reading the property (rather than last_level()) fetches value
level = self._lutron_device.level
_LOGGER.debug("Lutron ID: %d updated to %f",
self._lutron_device.id, level)
-15
View File
@@ -214,16 +214,6 @@ class MqttCover(MqttAvailability, CoverDevice):
self.async_schedule_update_ha_state()
@callback
def availability_message_received(topic, payload, qos):
"""Handle new MQTT availability messages."""
if payload == self._payload_available:
self._available = True
elif payload == self._payload_not_available:
self._available = False
self.async_schedule_update_ha_state()
if self._state_topic is None:
# Force into optimistic mode.
self._optimistic = True
@@ -232,11 +222,6 @@ class MqttCover(MqttAvailability, CoverDevice):
self.hass, self._state_topic,
state_message_received, self._qos)
if self._availability_topic is not None:
yield from mqtt.async_subscribe(
self.hass, self._availability_topic,
availability_message_received, self._qos)
if self._tilt_status_topic is None:
self._tilt_optimistic = True
else:
@@ -89,11 +89,6 @@ class RPiGPIOCover(CoverDevice):
rpi_gpio.setup_input(self._state_pin, self._state_pull_mode)
rpi_gpio.write_output(self._relay_pin, not self._invert_relay)
@property
def unique_id(self):
"""Return the ID of this cover."""
return '{}.{}'.format(self.__class__, self._name)
@property
def name(self):
"""Return the name of the cover if any."""
+2 -2
View File
@@ -51,8 +51,8 @@ set_cover_tilt_position:
entity_id:
description: Name(s) of cover(s) to set cover tilt position.
example: 'cover.living_room'
position:
description: Position of the cover (0 to 100).
tilt_position:
description: Tilt position of the cover (0 to 100).
example: 30
stop_cover_tilt:
-3
View File
@@ -5,7 +5,6 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/cover.tahoma/
"""
import logging
from datetime import timedelta
from homeassistant.components.cover import CoverDevice
from homeassistant.components.tahoma import (
@@ -15,8 +14,6 @@ DEPENDENCIES = ['tahoma']
_LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(seconds=60)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up Tahoma covers."""
@@ -67,11 +67,6 @@ COVER_SCHEMA = vol.Schema({
vol.Optional(CONF_ENTITY_ID): cv.entity_ids
})
COVER_SCHEMA = vol.All(
cv.deprecated(CONF_ENTITY_ID),
COVER_SCHEMA,
)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_COVERS): vol.Schema({cv.slug: COVER_SCHEMA}),
})
+1 -1
View File
@@ -158,7 +158,7 @@ class ZwaveGarageDoorBarrier(ZwaveGarageDoorBase):
@property
def is_closing(self):
"""Return true if cover is in an closing state."""
"""Return true if cover is in a closing state."""
return self._state == "Closing"
@property
+2 -2
View File
@@ -17,7 +17,7 @@ from homeassistant.helpers import discovery
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.util.json import load_json, save_json
REQUIREMENTS = ['pydeconz==25']
REQUIREMENTS = ['pydeconz==27']
_LOGGER = logging.getLogger(__name__)
@@ -38,7 +38,7 @@ SERVICE_DATA = 'data'
SERVICE_SCHEMA = vol.Schema({
vol.Required(SERVICE_FIELD): cv.string,
vol.Required(SERVICE_DATA): cv.string,
vol.Required(SERVICE_DATA): dict,
})
CONFIG_INSTRUCTIONS = """
@@ -42,7 +42,7 @@ Device = namedtuple('Device', ['mac', 'ip', 'last_update'])
class ActiontecDeviceScanner(DeviceScanner):
"""This class queries a an actiontec router for connected devices."""
"""This class queries an actiontec router for connected devices."""
def __init__(self, config):
"""Initialize the scanner."""
@@ -214,7 +214,8 @@ class AsusWrtDeviceScanner(DeviceScanner):
for device in result:
if device['mac'] is not None:
mac = device['mac'].upper()
old_ip = cur_devices.get(mac, {}).ip or None
old_device = cur_devices.get(mac)
old_ip = old_device.ip if old_device else None
devices[mac] = Device(mac, device.get('ip', old_ip), None)
return devices
@@ -241,7 +242,7 @@ class _Connection:
return self._connected
def connect(self):
"""Mark currenct connection state as connected."""
"""Mark current connection state as connected."""
self._connected = True
def disconnect(self):
@@ -2,7 +2,7 @@
Support for HUAWEI routers.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.huawei/
https://home-assistant.io/components/device_tracker.huawei_router/
"""
import base64
import logging
@@ -119,7 +119,7 @@ class HuaweiDeviceScanner(DeviceScanner):
cnt = requests.post('http://{}/asp/GetRandCount.asp'.format(self.host))
cnt_str = str(cnt.content, cnt.apparent_encoding, errors='replace')
_LOGGER.debug("Loggin in")
_LOGGER.debug("Logging in")
cookie = requests.post('http://{}/login.cgi'.format(self.host),
data=[('UserName', self.username),
('PassWord', self.password),
@@ -0,0 +1,71 @@
"""
Support for Mercedes cars with Mercedes ME.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/device_tracker.mercedesme/
"""
import logging
from datetime import timedelta
from homeassistant.components.mercedesme import DATA_MME
from homeassistant.helpers.event import track_time_interval
from homeassistant.util import Throttle
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['mercedesme']
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=30)
def setup_scanner(hass, config, see, discovery_info=None):
"""Set up the Mercedes ME tracker."""
if discovery_info is None:
return False
data = hass.data[DATA_MME].data
if not data.cars:
return False
MercedesMEDeviceTracker(hass, config, see, data)
return True
class MercedesMEDeviceTracker(object):
"""A class representing a Mercedes ME device tracker."""
def __init__(self, hass, config, see, data):
"""Initialize the Mercedes ME device tracker."""
self.see = see
self.data = data
self.update_info()
track_time_interval(
hass, self.update_info, MIN_TIME_BETWEEN_SCANS)
@Throttle(MIN_TIME_BETWEEN_SCANS)
def update_info(self, now=None):
"""Update the device info."""
for device in self.data.cars:
_LOGGER.debug("Updating %s", device["vin"])
location = self.data.get_location(device["vin"])
if location is None:
return False
dev_id = device["vin"]
name = device["license"]
lat = location['positionLat']['value']
lon = location['positionLong']['value']
attrs = {
'trackr_id': dev_id,
'id': dev_id,
'name': name
}
self.see(
dev_id=dev_id, host_name=name,
gps=(lat, lon), attributes=attrs
)
return True
@@ -14,7 +14,7 @@ from homeassistant.components.device_tracker import (
from homeassistant.const import (
CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_PORT)
REQUIREMENTS = ['librouteros==1.0.4']
REQUIREMENTS = ['librouteros==1.0.5']
MTK_DEFAULT_API_PORT = '8728'
@@ -31,17 +31,14 @@ def async_setup_scanner(hass, config, async_see, discovery_info=None):
devices = config[CONF_DEVICES]
qos = config[CONF_QOS]
dev_id_lookup = {}
@callback
def async_tracker_message_received(topic, payload, qos):
"""Handle received MQTT message."""
hass.async_add_job(
async_see(dev_id=dev_id_lookup[topic], location_name=payload))
for dev_id, topic in devices.items():
dev_id_lookup[topic] = dev_id
@callback
def async_message_received(topic, payload, qos, dev_id=dev_id):
"""Handle received MQTT message."""
hass.async_add_job(
async_see(dev_id=dev_id, location_name=payload))
yield from mqtt.async_subscribe(
hass, topic, async_tracker_message_received, qos)
hass, topic, async_message_received, qos)
return True
@@ -41,32 +41,26 @@ def async_setup_scanner(hass, config, async_see, discovery_info=None):
devices = config[CONF_DEVICES]
qos = config[CONF_QOS]
dev_id_lookup = {}
@callback
def async_tracker_message_received(topic, payload, qos):
"""Handle received MQTT message."""
dev_id = dev_id_lookup[topic]
try:
data = GPS_JSON_PAYLOAD_SCHEMA(json.loads(payload))
except vol.MultipleInvalid:
_LOGGER.error("Skipping update for following data "
"because of missing or malformatted data: %s",
payload)
return
except ValueError:
_LOGGER.error("Error parsing JSON payload: %s", payload)
return
kwargs = _parse_see_args(dev_id, data)
hass.async_add_job(
async_see(**kwargs))
for dev_id, topic in devices.items():
dev_id_lookup[topic] = dev_id
@callback
def async_message_received(topic, payload, qos, dev_id=dev_id):
"""Handle received MQTT message."""
try:
data = GPS_JSON_PAYLOAD_SCHEMA(json.loads(payload))
except vol.MultipleInvalid:
_LOGGER.error("Skipping update for following data "
"because of missing or malformatted data: %s",
payload)
return
except ValueError:
_LOGGER.error("Error parsing JSON payload: %s", payload)
return
kwargs = _parse_see_args(dev_id, data)
hass.async_add_job(async_see(**kwargs))
yield from mqtt.async_subscribe(
hass, topic, async_tracker_message_received, qos)
hass, topic, async_message_received, qos)
return True
@@ -143,6 +143,8 @@ def _parse_see_args(message, subscribe_topic):
kwargs['attributes']['tid'] = message['tid']
if 'addr' in message:
kwargs['attributes']['address'] = message['addr']
if 'cog' in message:
kwargs['attributes']['course'] = message['cog']
if 't' in message:
if message['t'] == 'c':
kwargs['attributes'][ATTR_SOURCE_TYPE] = SOURCE_TYPE_GPS
@@ -46,8 +46,8 @@ def get_scanner(hass, config):
return scanner if scanner.success_init else None
def _refresh_on_acccess_denied(func):
"""If remove rebooted, it lost our session so rebuld one and try again."""
def _refresh_on_access_denied(func):
"""If remove rebooted, it lost our session so rebuild one and try again."""
def decorator(self, *args, **kwargs):
"""Wrap the function to refresh session_id on PermissionError."""
try:
@@ -95,16 +95,15 @@ class UbusDeviceScanner(DeviceScanner):
"""Must be implemented depending on the software."""
raise NotImplementedError
@_refresh_on_acccess_denied
@_refresh_on_access_denied
def get_device_name(self, mac):
"""Return the name of the given device or None if we don't know."""
if self.mac2name is None:
self._generate_mac2name()
name = self.mac2name.get(mac.upper(), None)
self.mac2name = None
return name
@_refresh_on_acccess_denied
@_refresh_on_access_denied
def _update_info(self):
"""Ensure the information from the router is up to date.
@@ -122,13 +121,18 @@ class UbusDeviceScanner(DeviceScanner):
self.last_results = []
results = 0
# for each access point
for hostapd in self.hostapd:
result = _req_json_rpc(
self.url, self.session_id, 'call', hostapd, 'get_clients')
if result:
results = results + 1
self.last_results.extend(result['clients'].keys())
# Check for each device is authorized (valid wpa key)
for key in result['clients'].keys():
device = result['clients'][key]
if device['authorized']:
self.last_results.append(key)
return bool(results)
@@ -75,7 +75,7 @@ def get_scanner(hass, config):
class UnifiScanner(DeviceScanner):
"""Provide device_tracker support from Unifi WAP client data."""
def __init__(self, controller, detection_time: timedelta):
def __init__(self, controller, detection_time: timedelta) -> None:
"""Initialize the scanner."""
self._detection_time = detection_time
self._controller = controller
+1 -1
View File
@@ -1,7 +1,7 @@
"""
Support for Dominos Pizza ordering.
The Dominos Pizza component ceates a service which can be invoked to order
The Dominos Pizza component creates a service which can be invoked to order
from their menu
For more details about this platform, please refer to the documentation at
+1
View File
@@ -73,6 +73,7 @@ def setup(hass, config):
class DoorbirdRequestView(HomeAssistantView):
"""Provide a page for the device to call."""
requires_auth = False
url = API_URL
name = API_URL[1:].replace('/', ':')
extra_urls = [API_URL + '/{sensor}']
+1 -1
View File
@@ -79,7 +79,7 @@ def setup(hass, config):
if req.status_code != 200:
_LOGGER.warning(
"downloading '%s' failed, stauts_code=%d",
"downloading '%s' failed, status_code=%d",
url,
req.status_code)
+2 -2
View File
@@ -193,7 +193,7 @@ class EightSleepUserEntity(Entity):
"""The Eight Sleep device entity."""
def __init__(self, eight):
"""Initialize the data oject."""
"""Initialize the data object."""
self._eight = eight
@asyncio.coroutine
@@ -217,7 +217,7 @@ class EightSleepHeatEntity(Entity):
"""The Eight Sleep device entity."""
def __init__(self, eight):
"""Initialize the data oject."""
"""Initialize the data object."""
self._eight = eight
@asyncio.coroutine
+1 -1
View File
@@ -59,7 +59,7 @@ def setup(hass, config):
payload, fullurl, req.status_code)
def update_emoncms(time):
"""Send whitelisted entities states reguarly to Emoncms."""
"""Send whitelisted entities states regularly to Emoncms."""
payload_dict = {}
for entity_id in whitelist:
@@ -39,6 +39,9 @@ CONF_OFF_MAPS_TO_ON_DOMAINS = 'off_maps_to_on_domains'
CONF_EXPOSE_BY_DEFAULT = 'expose_by_default'
CONF_EXPOSED_DOMAINS = 'exposed_domains'
CONF_TYPE = 'type'
CONF_ENTITIES = 'entities'
CONF_ENTITY_NAME = 'name'
CONF_ENTITY_HIDDEN = 'hidden'
TYPE_ALEXA = 'alexa'
TYPE_GOOGLE = 'google_home'
@@ -52,6 +55,11 @@ DEFAULT_EXPOSED_DOMAINS = [
]
DEFAULT_TYPE = TYPE_GOOGLE
CONFIG_ENTITY_SCHEMA = vol.Schema({
vol.Optional(CONF_ENTITY_NAME): cv.string,
vol.Optional(CONF_ENTITY_HIDDEN): cv.boolean
})
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Optional(CONF_HOST_IP): cv.string,
@@ -63,11 +71,14 @@ CONFIG_SCHEMA = vol.Schema({
vol.Optional(CONF_EXPOSE_BY_DEFAULT): cv.boolean,
vol.Optional(CONF_EXPOSED_DOMAINS): cv.ensure_list,
vol.Optional(CONF_TYPE, default=DEFAULT_TYPE):
vol.Any(TYPE_ALEXA, TYPE_GOOGLE)
vol.Any(TYPE_ALEXA, TYPE_GOOGLE),
vol.Optional(CONF_ENTITIES):
vol.Schema({cv.entity_id: CONFIG_ENTITY_SCHEMA})
})
}, extra=vol.ALLOW_EXTRA)
ATTR_EMULATED_HUE = 'emulated_hue'
ATTR_EMULATED_HUE_NAME = 'emulated_hue_name'
ATTR_EMULATED_HUE_HIDDEN = 'emulated_hue_hidden'
@@ -183,6 +194,8 @@ class Config(object):
self.advertise_port = conf.get(
CONF_ADVERTISE_PORT) or self.listen_port
self.entities = conf.get(CONF_ENTITIES, {})
def entity_id_to_number(self, entity_id):
"""Get a unique number for the entity id."""
if self.type == TYPE_ALEXA:
@@ -215,6 +228,14 @@ class Config(object):
assert isinstance(number, str)
return self.numbers.get(number)
def get_entity_name(self, entity):
"""Get the name of an entity."""
if entity.entity_id in self.entities and \
CONF_ENTITY_NAME in self.entities[entity.entity_id]:
return self.entities[entity.entity_id][CONF_ENTITY_NAME]
return entity.attributes.get(ATTR_EMULATED_HUE_NAME, entity.name)
def is_entity_exposed(self, entity):
"""Determine if an entity should be exposed on the emulated bridge.
@@ -227,6 +248,12 @@ class Config(object):
domain = entity.domain.lower()
explicit_expose = entity.attributes.get(ATTR_EMULATED_HUE, None)
explicit_hidden = entity.attributes.get(ATTR_EMULATED_HUE_HIDDEN, None)
if entity.entity_id in self.entities and \
CONF_ENTITY_HIDDEN in self.entities[entity.entity_id]:
explicit_hidden = \
self.entities[entity.entity_id][CONF_ENTITY_HIDDEN]
if explicit_expose is True or explicit_hidden is False:
expose = True
elif explicit_expose is False or explicit_hidden is True:
@@ -24,9 +24,6 @@ from homeassistant.components.http import HomeAssistantView
_LOGGER = logging.getLogger(__name__)
ATTR_EMULATED_HUE = 'emulated_hue'
ATTR_EMULATED_HUE_NAME = 'emulated_hue_name'
HUE_API_STATE_ON = 'on'
HUE_API_STATE_BRI = 'bri'
@@ -77,7 +74,7 @@ class HueAllLightsStateView(HomeAssistantView):
number = self.config.entity_id_to_number(entity.entity_id)
json_response[number] = entity_to_json(
entity, state, brightness)
self.config, entity, state, brightness)
return self.json(json_response)
@@ -110,7 +107,7 @@ class HueOneLightStateView(HomeAssistantView):
state, brightness = get_entity_state(self.config, entity)
json_response = entity_to_json(entity, state, brightness)
json_response = entity_to_json(self.config, entity, state, brightness)
return self.json(json_response)
@@ -344,10 +341,8 @@ def get_entity_state(config, entity):
return (final_state, final_brightness)
def entity_to_json(entity, is_on=None, brightness=None):
def entity_to_json(config, entity, is_on=None, brightness=None):
"""Convert an entity to its Hue bridge JSON representation."""
name = entity.attributes.get(ATTR_EMULATED_HUE_NAME, entity.name)
return {
'state':
{
@@ -356,7 +351,7 @@ def entity_to_json(entity, is_on=None, brightness=None):
'reachable': True
},
'type': 'Dimmable light',
'name': name,
'name': config.get_entity_name(entity),
'modelid': 'HASS123',
'uniqueid': entity.entity_id,
'swversion': '123'
+1 -1
View File
@@ -205,7 +205,7 @@ def async_setup(hass, config: dict):
@asyncio.coroutine
def async_handle_fan_service(service):
"""Hande service call for fans."""
"""Handle service call for fans."""
method = SERVICE_TO_METHOD.get(service.service)
params = service.data.copy()
+2 -2
View File
@@ -37,7 +37,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class ComfoConnectFan(FanEntity):
"""Representation of the ComfoConnect fan platform."""
def __init__(self, hass, name, ccb: ComfoConnectBridge):
def __init__(self, hass, name, ccb: ComfoConnectBridge) -> None:
"""Initialize the ComfoConnect fan."""
from pycomfoconnect import SENSOR_FAN_SPEED_MODE
@@ -93,7 +93,7 @@ class ComfoConnectFan(FanEntity):
speed = SPEED_LOW
self.set_speed(speed)
def turn_off(self) -> None:
def turn_off(self, **kwargs) -> None:
"""Turn off the fan (to away)."""
self.set_speed(SPEED_OFF)
@@ -60,7 +60,7 @@ class InsteonLocalFanDevice(FanEntity):
@property
def unique_id(self):
"""Return the ID of this Insteon node."""
return 'insteon_local_{}_fan'.format(self.node.device_id)
return self.node.device_id
@property
def speed(self) -> str:
+2 -2
View File
@@ -29,7 +29,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
})
REQUIREMENTS = ['python-miio==0.3.4']
REQUIREMENTS = ['python-miio==0.3.5']
ATTR_TEMPERATURE = 'temperature'
ATTR_HUMIDITY = 'humidity'
@@ -200,7 +200,7 @@ class XiaomiAirPurifier(FanEntity):
@asyncio.coroutine
def _try_command(self, mask_error, func, *args, **kwargs):
"""Call a air purifier command handling error messages."""
"""Call an air purifier command handling error messages."""
from miio import DeviceException
try:
result = yield from self.hass.async_add_job(
+2 -4
View File
@@ -153,8 +153,7 @@ class StoredData(object):
with self._lock, open(self._data_file, 'rb') as myfile:
self._data = pickle.load(myfile) or {}
self._cache_outdated = False
# pylint: disable=bare-except
except:
except: # noqa: E722 # pylint: disable=bare-except
_LOGGER.error("Error loading data from pickled file %s",
self._data_file)
@@ -172,8 +171,7 @@ class StoredData(object):
url, self._data_file)
try:
pickle.dump(self._data, myfile)
# pylint: disable=bare-except
except:
except: # noqa: E722 # pylint: disable=bare-except
_LOGGER.error(
"Error saving pickled data to %s", self._data_file)
self._cache_outdated = True
+11 -8
View File
@@ -23,7 +23,7 @@ from homeassistant.const import CONF_NAME, EVENT_THEMES_UPDATED
from homeassistant.core import callback
from homeassistant.loader import bind_hass
REQUIREMENTS = ['home-assistant-frontend==20180126.0', 'user-agents==1.1.0']
REQUIREMENTS = ['home-assistant-frontend==20180209.0', 'user-agents==1.1.0']
DOMAIN = 'frontend'
DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log']
@@ -300,7 +300,8 @@ def async_setup(hass, config):
if is_dev:
for subpath in ["src", "build-translations", "build-temp", "build",
"hass_frontend", "bower_components", "panels"]:
"hass_frontend", "bower_components", "panels",
"hassio"]:
hass.http.register_static_path(
"/home-assistant-polymer/{}".format(subpath),
os.path.join(repo_path, subpath),
@@ -407,7 +408,7 @@ def async_setup_themes(hass, themes):
@callback
def set_theme(call):
"""Set backend-prefered theme."""
"""Set backend-preferred theme."""
data = call.data
name = data[CONF_NAME]
if name == DEFAULT_THEME or name in hass.data[DATA_THEMES]:
@@ -584,11 +585,13 @@ def _is_latest(js_option, request):
return useragent.os.version[0] >= 12
family_min_version = {
'Chrome': 50, # Probably can reduce this
'Firefox': 43, # Array.protopype.includes added in 43
'Opera': 40, # Probably can reduce this
'Edge': 14, # Array.protopype.includes added in 14
'Safari': 10, # many features not supported by 9
'Chrome': 54, # Object.values
'Chrome Mobile': 54,
'Firefox': 47, # Object.values
'Firefox Mobile': 47,
'Opera': 41, # Object.values
'Edge': 14, # Array.prototype.includes added in 14
'Safari': 10, # Many features not supported by 9
}
version = family_min_version.get(useragent.browser.family)
return version and useragent.browser.version[0] >= version
+62
View File
@@ -0,0 +1,62 @@
"""
Component for the Goalfeed service.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/goalfeed/
"""
import json
import requests
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
REQUIREMENTS = ['pysher==0.2.0']
DOMAIN = 'goalfeed'
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
})
}, extra=vol.ALLOW_EXTRA)
GOALFEED_HOST = 'feed.goalfeed.ca'
GOALFEED_AUTH_ENDPOINT = 'https://goalfeed.ca/feed/auth'
GOALFEED_APP_ID = 'bfd4ed98c1ff22c04074'
def setup(hass, config):
"""Set up the Goalfeed component."""
import pysher
conf = config[DOMAIN]
username = conf.get(CONF_USERNAME)
password = conf.get(CONF_PASSWORD)
def goal_handler(data):
"""Handle goal events."""
goal = json.loads(json.loads(data))
hass.bus.fire('goal', event_data=goal)
def connect_handler(data):
"""Handle connection."""
post_data = {
'username': username,
'password': password,
'connection_info': data}
resp = requests.post(GOALFEED_AUTH_ENDPOINT, post_data,
timeout=30).json()
channel = pusher.subscribe('private-goals', resp['auth'])
channel.bind('goal', goal_handler)
pusher = pysher.Pusher(GOALFEED_APP_ID, secure=False, port=8080,
custom_host=GOALFEED_HOST)
pusher.connection.bind('pusher:connection_established', connect_handler)
pusher.connect()
return True
+1 -1
View File
@@ -128,7 +128,7 @@ def do_authentication(hass, config):
"""Keep trying to validate the user_code until it expires."""
if now >= dt.as_local(dev_flow.user_code_expiry):
hass.components.persistent_notification.create(
'Authenication code expired, please restart '
'Authentication code expired, please restart '
'Home-Assistant and try again',
title=NOTIFICATION_TITLE,
notification_id=NOTIFICATION_ID)
@@ -37,6 +37,7 @@ from .const import (
)
HANDLERS = Registry()
QUERY_HANDLERS = Registry()
_LOGGER = logging.getLogger(__name__)
# Mapping is [actions schema, primary trait, optional features]
@@ -79,7 +80,7 @@ class SmartHomeError(Exception):
"""Log error code."""
super(SmartHomeError, self).__init__(msg)
_LOGGER.error(
"An error has ocurred in Google SmartHome: %s."
"An error has occurred in Google SmartHome: %s."
"Error code: %s", msg, code
)
self.code = code
@@ -96,7 +97,7 @@ class Config:
def entity_to_device(entity: Entity, config: Config, units: UnitSystem):
"""Convert a hass entity into an google actions device."""
"""Convert a hass entity into a google actions device."""
entity_config = config.entity_config.get(entity.entity_id, {})
google_domain = entity_config.get(CONF_TYPE)
class_data = MAPPING_COMPONENT.get(
@@ -177,120 +178,145 @@ def entity_to_device(entity: Entity, config: Config, units: UnitSystem):
return device
def query_device(entity: Entity, config: Config, units: UnitSystem) -> dict:
"""Take an entity and return a properly formatted device object."""
def celsius(deg: Optional[float]) -> Optional[float]:
"""Convert a float to Celsius and rounds to one decimal place."""
if deg is None:
return None
return round(METRIC_SYSTEM.temperature(deg, units.temperature_unit), 1)
def celsius(deg: Optional[float], units: UnitSystem) -> Optional[float]:
"""Convert a float to Celsius and rounds to one decimal place."""
if deg is None:
return None
return round(METRIC_SYSTEM.temperature(deg, units.temperature_unit), 1)
if entity.domain == sensor.DOMAIN:
entity_config = config.entity_config.get(entity.entity_id, {})
google_domain = entity_config.get(CONF_TYPE)
if google_domain == climate.DOMAIN:
# check if we have a string value to convert it to number
value = entity.state
if isinstance(entity.state, str):
try:
value = float(value)
except ValueError:
value = None
if value is None:
raise SmartHomeError(
ERROR_NOT_SUPPORTED,
"Invalid value {} for the climate sensor"
.format(entity.state)
)
# detect if we report temperature or humidity
unit_of_measurement = entity.attributes.get(
ATTR_UNIT_OF_MEASUREMENT,
units.temperature_unit
)
if unit_of_measurement in [TEMP_FAHRENHEIT, TEMP_CELSIUS]:
value = celsius(value)
attr = 'thermostatTemperatureAmbient'
elif unit_of_measurement == '%':
attr = 'thermostatHumidityAmbient'
else:
raise SmartHomeError(
ERROR_NOT_SUPPORTED,
"Unit {} is not supported by the climate sensor"
.format(unit_of_measurement)
)
return {attr: value}
@QUERY_HANDLERS.register(sensor.DOMAIN)
def query_response_sensor(
entity: Entity, config: Config, units: UnitSystem) -> dict:
"""Convert a sensor entity to a QUERY response."""
entity_config = config.entity_config.get(entity.entity_id, {})
google_domain = entity_config.get(CONF_TYPE)
if google_domain != climate.DOMAIN:
raise SmartHomeError(
ERROR_NOT_SUPPORTED,
"Sensor type {} is not supported".format(google_domain)
)
if entity.domain == climate.DOMAIN:
mode = entity.attributes.get(climate.ATTR_OPERATION_MODE).lower()
if mode not in CLIMATE_SUPPORTED_MODES:
mode = 'heat'
response = {
'thermostatMode': mode,
'thermostatTemperatureSetpoint':
celsius(entity.attributes.get(climate.ATTR_TEMPERATURE)),
'thermostatTemperatureAmbient':
celsius(entity.attributes.get(climate.ATTR_CURRENT_TEMPERATURE)),
'thermostatTemperatureSetpointHigh':
celsius(entity.attributes.get(climate.ATTR_TARGET_TEMP_HIGH)),
'thermostatTemperatureSetpointLow':
celsius(entity.attributes.get(climate.ATTR_TARGET_TEMP_LOW)),
'thermostatHumidityAmbient':
entity.attributes.get(climate.ATTR_CURRENT_HUMIDITY),
}
return {k: v for k, v in response.items() if v is not None}
# check if we have a string value to convert it to number
value = entity.state
if isinstance(entity.state, str):
try:
value = float(value)
except ValueError:
value = None
final_state = entity.state != STATE_OFF
final_brightness = entity.attributes.get(light.ATTR_BRIGHTNESS, 255
if final_state else 0)
if value is None:
raise SmartHomeError(
ERROR_NOT_SUPPORTED,
"Invalid value {} for the climate sensor"
.format(entity.state)
)
if entity.domain == media_player.DOMAIN:
level = entity.attributes.get(media_player.ATTR_MEDIA_VOLUME_LEVEL, 1.0
if final_state else 0.0)
# Convert 0.0-1.0 to 0-255
final_brightness = round(min(1.0, level) * 255)
# detect if we report temperature or humidity
unit_of_measurement = entity.attributes.get(
ATTR_UNIT_OF_MEASUREMENT,
units.temperature_unit
)
if unit_of_measurement in [TEMP_FAHRENHEIT, TEMP_CELSIUS]:
value = celsius(value, units)
attr = 'thermostatTemperatureAmbient'
elif unit_of_measurement == '%':
attr = 'thermostatHumidityAmbient'
else:
raise SmartHomeError(
ERROR_NOT_SUPPORTED,
"Unit {} is not supported by the climate sensor"
.format(unit_of_measurement)
)
if final_brightness is None:
final_brightness = 255 if final_state else 0
return {attr: value}
final_brightness = 100 * (final_brightness / 255)
query_response = {
"on": final_state,
"online": True,
"brightness": int(final_brightness)
@QUERY_HANDLERS.register(climate.DOMAIN)
def query_response_climate(
entity: Entity, config: Config, units: UnitSystem) -> dict:
"""Convert a climate entity to a QUERY response."""
mode = entity.attributes.get(climate.ATTR_OPERATION_MODE).lower()
if mode not in CLIMATE_SUPPORTED_MODES:
mode = 'heat'
attrs = entity.attributes
response = {
'thermostatMode': mode,
'thermostatTemperatureSetpoint':
celsius(attrs.get(climate.ATTR_TEMPERATURE), units),
'thermostatTemperatureAmbient':
celsius(attrs.get(climate.ATTR_CURRENT_TEMPERATURE), units),
'thermostatTemperatureSetpointHigh':
celsius(attrs.get(climate.ATTR_TARGET_TEMP_HIGH), units),
'thermostatTemperatureSetpointLow':
celsius(attrs.get(climate.ATTR_TARGET_TEMP_LOW), units),
'thermostatHumidityAmbient':
attrs.get(climate.ATTR_CURRENT_HUMIDITY),
}
return {k: v for k, v in response.items() if v is not None}
@QUERY_HANDLERS.register(media_player.DOMAIN)
def query_response_media_player(
entity: Entity, config: Config, units: UnitSystem) -> dict:
"""Convert a media_player entity to a QUERY response."""
level = entity.attributes.get(
media_player.ATTR_MEDIA_VOLUME_LEVEL,
1.0 if entity.state != STATE_OFF else 0.0)
# Convert 0.0-1.0 to 0-255
brightness = int(level * 100)
return {'brightness': brightness}
@QUERY_HANDLERS.register(light.DOMAIN)
def query_response_light(
entity: Entity, config: Config, units: UnitSystem) -> dict:
"""Convert a light entity to a QUERY response."""
response = {} # type: Dict[str, Any]
brightness = entity.attributes.get(light.ATTR_BRIGHTNESS)
if brightness is not None:
response['brightness'] = int(100 * (brightness / 255))
supported_features = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
if supported_features & \
(light.SUPPORT_COLOR_TEMP | light.SUPPORT_RGB_COLOR):
query_response["color"] = {}
response['color'] = {}
if entity.attributes.get(light.ATTR_COLOR_TEMP) is not None:
query_response["color"]["temperature"] = \
response['color']['temperature'] = \
int(round(color.color_temperature_mired_to_kelvin(
entity.attributes.get(light.ATTR_COLOR_TEMP))))
if entity.attributes.get(light.ATTR_COLOR_NAME) is not None:
query_response["color"]["name"] = \
response['color']['name'] = \
entity.attributes.get(light.ATTR_COLOR_NAME)
if entity.attributes.get(light.ATTR_RGB_COLOR) is not None:
color_rgb = entity.attributes.get(light.ATTR_RGB_COLOR)
if color_rgb is not None:
query_response["color"]["spectrumRGB"] = \
response['color']['spectrumRGB'] = \
int(color.color_rgb_to_hex(
color_rgb[0], color_rgb[1], color_rgb[2]), 16)
return query_response
return response
def query_device(entity: Entity, config: Config, units: UnitSystem) -> dict:
"""Take an entity and return a properly formatted device object."""
state = entity.state != STATE_OFF
defaults = {
'on': state,
'online': True
}
handler = QUERY_HANDLERS.get(entity.domain)
if callable(handler):
defaults.update(handler(entity, config, units))
return defaults
# erroneous bug on old pythons and pylint
@@ -429,7 +455,7 @@ def async_devices_query(hass, config, payload):
devices = {}
for device in payload.get('devices', []):
devid = device.get('id')
# In theory this should never happpen
# In theory this should never happen
if not devid:
_LOGGER.error('Device missing ID: %s', device)
continue
@@ -438,11 +464,11 @@ def async_devices_query(hass, config, payload):
if not state:
# If we can't find a state, the device is offline
devices[devid] = {'online': False}
try:
devices[devid] = query_device(state, config, hass.config.units)
except SmartHomeError as error:
devices[devid] = {'errorCode': error.code}
else:
try:
devices[devid] = query_device(state, config, hass.config.units)
except SmartHomeError as error:
devices[devid] = {'errorCode': error.code}
return {'devices': devices}
+2 -7
View File
@@ -247,7 +247,7 @@ def get_entity_ids(hass, entity_id, domain_filter=None):
@asyncio.coroutine
def async_setup(hass, config):
"""Set up all groups found definded in the configuration."""
"""Set up all groups found defined in the configuration."""
component = hass.data.get(DOMAIN)
if component is None:
@@ -371,7 +371,6 @@ def async_setup(hass, config):
@asyncio.coroutine
def _async_process_config(hass, config, component):
"""Process group configuration."""
groups = []
for object_id, conf in config.get(DOMAIN, {}).items():
name = conf.get(CONF_NAME, object_id)
entity_ids = conf.get(CONF_ENTITIES) or []
@@ -381,13 +380,9 @@ def _async_process_config(hass, config, component):
# Don't create tasks and await them all. The order is important as
# groups get a number based on creation order.
group = yield from Group.async_create_group(
yield from Group.async_create_group(
hass, name, entity_ids, icon=icon, view=view,
control=control, object_id=object_id)
groups.append(group)
if groups:
yield from component.async_add_entities(groups)
class Group(Entity):
+1 -1
View File
@@ -320,7 +320,7 @@ def setup(hass: HomeAssistant, base_config):
class CecDevice(Entity):
"""Representation of a HDMI CEC device entity."""
def __init__(self, hass: HomeAssistant, device, logical):
def __init__(self, hass: HomeAssistant, device, logical) -> None:
"""Initialize the device."""
self._device = device
self.hass = hass
@@ -20,7 +20,7 @@ from homeassistant.helpers.entity import Entity
import homeassistant.helpers.config_validation as cv
from homeassistant.loader import bind_hass
REQUIREMENTS = ['pyhomematic==0.1.38']
REQUIREMENTS = ['pyhomematic==0.1.39']
DOMAIN = 'homematic'
_LOGGER = logging.getLogger(__name__)
@@ -218,7 +218,7 @@ SCHEMA_SERVICE_SET_INSTALL_MODE = vol.Schema({
@bind_hass
def virtualkey(hass, address, channel, param, interface=None):
"""Send virtual keypress to homematic controlller."""
"""Send virtual keypress to homematic controller."""
data = {
ATTR_ADDRESS: address,
ATTR_CHANNEL: channel,
@@ -256,7 +256,7 @@ def set_device_value(hass, address, channel, param, value, interface=None):
@bind_hass
def set_install_mode(hass, interface, mode=None, time=None, address=None):
"""Call setInstallMode XML-RPC method of supplied inteface."""
"""Call setInstallMode XML-RPC method of supplied interface."""
data = {
key: value for key, value in (
(ATTR_INTERFACE, interface),
@@ -466,7 +466,7 @@ def _system_callback_handler(hass, config, src, *args):
hass, discovery_type, addresses, interface)
# When devices of this type are found
# they are setup in HASS and an discovery event is fired
# they are setup in HASS and a discovery event is fired
if found_devices:
discovery.load_platform(hass, component_name, DOMAIN, {
ATTR_DISCOVER_DEVICES: found_devices
@@ -665,7 +665,7 @@ class HMHub(Entity):
self.schedule_update_ha_state()
def _update_variables(self, now):
"""Retrive all variable data and update hmvariable states."""
"""Retrieve all variable data and update hmvariable states."""
variables = self._homematic.getAllSystemVariables(self._name)
if variables is None:
return
@@ -13,7 +13,7 @@ virtualkey:
description: Event to send i.e. PRESS_LONG, PRESS_SHORT.
example: PRESS_LONG
interface:
description: (Optional) for set a interface value.
description: (Optional) for set an interface value.
example: Interfaces name from config
set_variable_value:
@@ -42,7 +42,7 @@ set_device_value:
description: Event to send i.e. PRESS_LONG, PRESS_SHORT
example: PRESS_LONG
interface:
description: (Optional) for set a interface value
description: (Optional) for set an interface value
example: Interfaces name from config
value:
description: New value
+2 -2
View File
@@ -23,7 +23,7 @@ def auth_middleware(request, handler):
# If no password set, just always set authenticated=True
if request.app['hass'].http.api_password is None:
request[KEY_AUTHENTICATED] = True
return handler(request)
return (yield from handler(request))
# Check authentication
authenticated = False
@@ -46,7 +46,7 @@ def auth_middleware(request, handler):
authenticated = True
request[KEY_AUTHENTICATED] = authenticated
return handler(request)
return (yield from handler(request))
def is_trusted_ip(request):

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