Compare commits

...

201 Commits

Author SHA1 Message Date
Paulus Schoutsen c21172dd36 Merge branch 'dev' 2017-01-15 08:39:56 -08:00
Paulus Schoutsen 24bc035e22 ps - version bump to 0.36 2017-01-15 08:38:41 -08:00
Paulus Schoutsen 36da5d9adb Merge pull request #5245 from home-assistant/dev
0.36
2017-01-15 08:36:53 -08:00
Pascal Vizeli d7d428119b Bugfix stack trace from api (#5332) 2017-01-15 08:36:24 -08:00
Pascal Vizeli c458ee29f2 Rename log message / handle cancellederror on image proxy (#5331) 2017-01-15 08:35:58 -08:00
Lupin Demid 85a84549eb Yandex tts component (#5342)
* Added Yandex SpeechKit TTS

* Added test stub

* Added two test and added property for yandex tts

* Copy all test from voice rss

* Added test vith different speaker and  code style changes

* Added new line to end of file

* Url format replaced with url_params
2017-01-15 15:43:10 +01:00
Fabian Affolter 2465aea63b Fix link (#5343) 2017-01-15 14:53:07 +01:00
Zac Hatfield Dodds e00e6f9db6 Bom weather platform (#5153)
* Fix typo

* Auto-config for `sensor.bom`

Deprecate (but still support) the old two-part station ID, and move to a
single `station` identifier.  Any combination of these, including none,
is valid; most results in downloading and caching the station map to
work out any missing info.

* Add `weather.bom` platform

Very similar to `sensor.bom`, but supporting the lovely new `weather`
component interface.  Easier to configure, and does not support the
deprecated config options.

* Review improvements to BOM weather

Largely around better input validation.
2017-01-15 12:12:50 +01:00
Daniel Høyer Iversen 9fff634b9d Merge pull request #5334 from MrMep/patch-1
Update vlc.py
2017-01-15 10:42:13 +01:00
Adam Mills e7c157e766 Tests for async volume up/down (#5265) 2017-01-15 08:27:56 +01:00
Martin Hjelmare 9db1aa7629 Add discovery notify support and mysensors notify (#5219)
* Add mysensors notify platform

* Make add_devices optional in platform callback function.
* Use new argument structure for all existing mysensors platforms.
* Add notify platform.
* Update mysensors gateway.

* Refactor notify setup

* Enable discovery of notify platforms.
* Update and add tests for notify component and some platforms.
* Continue setup of notify platforms if a platform fails setup.
* Remove notify tests that check platform config. These tests are not
  needed when config validation is used.
* Add config validation to APNS notify platform.
* Use discovery to set up mysensors notify platform.

* Add discovery_info to get_service and update tests

* Add discovery_info as keyword argument to the get_service function
  signature and update all notify platforms.
* Update existing notify tests to check config validation using test
  helper.
* Add removed tests back in that checked config in apns, command_line
  and file platforms, but use config validation test helper to verify
  config.
* Add a test for notify file to increase coverage.
* Fix some PEP issues.

* Fix comments and use more constants

* Move apns notify service under notify domain
2017-01-15 03:53:14 +01:00
Gianluca Barbaro d998cba6a2 Update vlc.py
Added default value for "arguments"
2017-01-15 01:35:46 +01:00
Gianluca Barbaro 8013963784 Update vlc.py 2017-01-14 23:31:17 +01:00
Gianluca Barbaro 7436a96978 Update vlc.py 2017-01-14 22:32:50 +01:00
Gianluca Barbaro 3d9b2b5ed0 Update vlc.py
Added support for additional optional configuration

arguments:

to send to vlc.
It is useful for special configurations of VLC.
For example, I have two sound cards on my server, so I defined two vlc media players:

media_player:
- platform: vlc
  name: speaker_1
  arguments: '--alsa-audio-device=hw:1,0'
- platform: vlc
  name: speaker_2
  arguments: '--alsa-audio-device=hw:0,0'

This way, by specifying the corresponding entity_id, I can send the output to the desired speaker.
It is also useful for TTS.
2017-01-14 22:30:24 +01:00
Thibault Cohen 3b9fb6ccf5 Improve InfluxDB (#5238)
* Revert #4791 and fixes #4696

* Update influxDB based on PR comments

* Add migration script

* Update influxdb_migrator based on PR comments

* Add override_measurement option to influxdb_migrator

* Rename value field to state when data is string type

* Fix influxdb cloning query
2017-01-14 09:52:47 -08:00
Colin O'Dell e3418f633c Add ffmpeg to Docker from jessie-backports (#5322) 2017-01-14 09:43:40 -08:00
Fabian Affolter bf3e5b460e Clean-up (#5327) 2017-01-14 09:42:45 -08:00
Colin O'Dell 03a6aa48e0 Copy openzwave config to ensure it exists (fixes #5328) (#5329) 2017-01-14 09:41:38 -08:00
Fabian Affolter 2aa996b558 Update name (#5324) 2017-01-14 17:08:48 +01:00
Fabian Affolter ef4a9bf354 Add timeout to requests, remove pylint disable, and docsstrings (#5326) 2017-01-14 17:08:21 +01:00
Michaël Arnauts d4eabaf844 Remove build dirs from docker image to keep the layers small (#5243)
* Remove build dirs from docker image to keep the layers small

* Create setup_docker_prereqs script to prepare docker env

* Add documentation for required packages, drop colorlog and cython in first step of Dockerfile since it will be installed later on anyway. Drop libglib2.0-dev and libbluetooth-dev

* Also remove early install of colorlog and cython in Dockerfile.dev

* Re-add libglib2.0-dev and libbluetooth-dev for Bluetooth LE
2017-01-14 07:41:41 -08:00
Pascal Vizeli 7ed83306ea Add warning to openalpr (#5315)
* Add warning to openalpr

* fix lint

* update comment
2017-01-14 15:36:20 +01:00
Fabian Affolter 2e7ae1d5fe Update the link to the docs (#5321) 2017-01-14 15:16:42 +01:00
joopert f2a42d767e fix hass.loop.run_in_executor in mediaplayer init (#5320) 2017-01-14 14:54:00 +01:00
Michaël Arnauts c3783bf49b Bugfix for ping component now DEFAULT_SCAN_INTERVAL is a timedelta (#5318) 2017-01-14 11:55:29 +01:00
Pascal Vizeli b817c7d0c2 Bugfix camera fake image (#5314)
* Bugfix camera fake image

* add logger
2017-01-14 11:53:00 +01:00
Pascal Vizeli c2492d1493 Component "Image processing" (#5166)
* Init new component for image processing.

* Add demo platform

* address comments

* add unittest v1 for demo

* Add unittest for alpr

* Add openalpr local test

* Add openalpr cloud platform

* Add unittest openalpr cloud platform

* Update stale docstring

* Address paulus comments

* Update stale docstring

* Add coro to function

* Add coro to cloud
2017-01-14 08:18:03 +01:00
Teemu R 5bba9a63a5 switch.tplink: bump to the newest release of pyhs100 (#5308) 2017-01-13 22:20:47 -08:00
Matthew Garrett d6747d6aaf Add support for Zengge Bluetooth bulbs (#5196)
* Add support for Zengge Bluetooth bulbs

Adds support for the Zengge Bluetooth RGBW bulbs. These are sold under a
number of brands, including Flux. The bulbs do not support full RGBW
control - they turn off the RGB LEDs when white is enabled, and vice versa.

* Update zengge.py
2017-01-13 22:15:43 -08:00
William Scanlon 0da8418f3f Wink fan support (#5174)
* Initial commit for Wink fan support

* Added fan to discovery list

* Raise NotImplementedError and fixed is_on

* Added speed property

* Update __init__.py
2017-01-13 22:08:13 -08:00
Johann Kellerman 9f765836f8 [core] Add 'packages' to the config (#5140)
* Initial

* Merge dicts and lists

* feedback

* Move to homeassistant

* feedback

* increase_coverage

* kick_the_hound
2017-01-13 22:01:47 -08:00
Teemu R d58b901a78 eq3btsmart: support modes and clean up the code to use climatedevice's features (#4959)
* eq3btsmart: support modes and clean up the code to use climatedevice's features

* eq3btsmart: re-add device state attributes

adds reporting for is_locked, valve, window_open and low_battery,
exposing everything the device reports currently.

* eq3btsmart: bump version req

* eq3btsmart: fix a typo in mode name, report unknown when not initialized

* eq3btsmart: depend on newly forked python-eq3bt lib, pythonify states
2017-01-13 21:34:35 -08:00
Touliloup 394b52b9e8 Xiaomi device tracker (#5283)
* [Device Tracker] Xiaomi Mi Router integration as device tracker

    This device tracker allow to track device connected to Xiaomi Router.
    Parameter: host, username (default admin) and password.

* [Device Tracker] Addition of Xiaomi device tracker file in coverage
2017-01-13 21:24:58 -08:00
Johann Kellerman b67cce7215 Add correct line numbers for yaml include directives (#5303) 2017-01-13 21:13:17 -08:00
Pascal Vizeli 0cf3c22da0 Bugfix media_player volume_ up and down (#5282)
* Bugfix media_player volume_ up and down

* fix lint

* make a coro
2017-01-13 21:09:02 -08:00
Martin Rowan a7cb9bdfff Fixed bootstrap to upgrade pip if mininum version not present. As parameter --only-binary in requirements.txt doesn't work on pip < 7.0.0 so install fails. This is to simplify the setup of the development environment when using pyvenv. (#5301) 2017-01-13 21:03:50 -08:00
Dan Cinnamon 5f7d53c06b Bump pyenvisalink to version 2.0 (#5311) 2017-01-13 21:02:33 -08:00
Pascal Vizeli 4b43537801 Bugfix camera streams (#5306)
* fix mjpeg streams

* fix trow error on close by frontend

* fix ffmpeg
2017-01-13 15:57:38 -08:00
Adam Mills 6abad6b76e Version bump for kodi dependency (#5307)
Catches timeout exceptions
2017-01-14 00:16:38 +01:00
Robbie Trencheny 6000c59bb5 Remove GTFS default name & string change 2017-01-13 14:02:00 -08:00
Pascal Vizeli 2c3f55acc4 Add HMWIOSwitch to sensor, binary (#5304) 2017-01-13 22:39:42 +01:00
Pascal Vizeli a30711f1a0 Update pyhomematic 1.19 & small cleanups (#5299) 2017-01-13 21:22:09 +01:00
Thom Troy 1219ca3c3b [sensor] Add Dublin bus RTPI sensor (#5257) 2017-01-13 19:15:46 +02:00
Pascal Vizeli baa8e53e66 Bugfix group reload (#5292)
* Bugfix group / hit update

* try to fix round 2

* Convert it to coro

* Don't check statemachine, check unsub listener.
2017-01-13 03:29:20 -08:00
William Scanlon f7a1d63d52 Support for TrackR device trackers (#5010)
* Support for TrackR device trackers

* Change small style for hass
2017-01-13 00:16:05 +01:00
Pascal Bach d12decc471 Upgrade to voluptuous to 0.9.3 (#5288) 2017-01-12 23:56:37 +01:00
Pascal Bach 64800fd48c Upgrade distro to 1.0.2 (#5291) 2017-01-12 23:16:32 +01:00
Greg Dowling 9a3c0c8cd3 Don't build Adafruit_BBIO - doesn't work on all platforms. (#5281)
* Don't build Adafruit_BBIO - doesn't work on all platforms.

* Disable pylint import warning on BBIO.
2017-01-12 08:31:30 -08:00
Greg Dowling d4a54acda0 Merge pull request #5280 from home-assistant/bump-pywemo-version
Bump pywemo version.
2017-01-11 23:26:06 +00:00
pavoni dc937cc8cf Bump pywemo version. 2017-01-11 23:10:24 +00:00
Adam Mills eb9b95c292 Convert flic to synchronous platform. (#5276)
* Convert flic to synchronous platform.

pyflic is a synchronous library

* Move pyflic event loop to dedicated thread.
2017-01-11 22:59:39 +01:00
Paulus Schoutsen e68e29e03e Upgrade to aiohttp 1.2 (#4964)
* Upgrade to aiohttp 1.2

* Clean up emulated_hue tests
2017-01-11 21:25:02 +01:00
Andrew Williams 1cf9ae5a01 Fix TCP sensor to correctly use value_template (#5211)
* Fix TCP sensor to correctly use value_template

* Fix TCP component tests

* Update tcp.py
2017-01-11 17:26:29 +01:00
Pascal Vizeli 3f3a3bcc8a Cleanup language support on TTS (#5255)
* Cleanup language support on TTS

* change to default_language & address comments

* Cleanup not needed code / comment from paulus
2017-01-11 16:31:16 +01:00
Daniel Høyer Iversen 467cb18625 Add last triggered to script (#5261)
* Add last triggered to script

* Add tests for script last_triggered
2017-01-11 16:23:05 +01:00
Robbie Trencheny 82d037a828 Merge pull request #5117 from gopalkildoliya/dev
Notify component for Facebook Messenger
2017-01-10 23:16:43 -08:00
Marcelo Moreira de Mello 34a9fb01ac Bump flux_led version and make use of PyPi package (#5267)
* Bump flux_led version and make use of PyPi package

* Makes script/gen_requirements_all.py happy
2017-01-11 06:45:46 +01:00
joopert 3a4b4380a1 Add support for NAD receivers (#5191)
* Add support for NAD receivers

* remove self.update() in various methods

* remove setting attributes in various methods

* Change import to hass style
2017-01-10 22:32:43 +01:00
Pascal Vizeli 6b00f7ff28 Bugfix async device_tracker see callback (#5259) 2017-01-10 17:19:51 +01:00
Valentin Alexeev f75e13f55e Use SHA hash to make token harder to guess (#5258)
* Use SHA hash to make token harder to guess

Use hashlib SHA256 to encode object id instead of using it directly.

* Cache access token

Instead of generating a token on the fly cache it in the constructor.

* Fix lint
2017-01-10 16:01:04 +01:00
sander76 6845a0974d adding a default icon "blind" to a PowerView blinds scene. (#5210)
* adding a default icon "blind" to a PowerView blinds scene.

* Adding icon property to define blind icon. Removed it from the state attributes dict.

* fixing lint error
2017-01-10 13:21:15 +01:00
Anton Lundin 7e1629a962 Fix async_volume_up / async_volume_down (#5249)
async_volume_up / async_volume_down should be async versions of
volume_up / volume_down, not a async version of the default variants of
volume_up / volume_down.

The previous code always called into the mediaplayers set_volume_level,
and never into volume_up / volume_down.

Signed-off-by: Anton Lundin <glance@acc.umu.se>
2017-01-10 10:58:39 +01:00
andrey-git 0b685a5b1e Expose supported_features of mqtt_json (#5250)
* Expose supported_features of mqtt_json

* Remove whitespace
2017-01-10 00:40:52 +01:00
Johann Kellerman 1f31dfe5d3 [recorder] Include & Exclude domain fix & unit tests (#5213)
* Tests & domain fix

* incl/excl combined
2017-01-09 21:53:30 +01:00
Fabian Affolter 6be19e8997 Update pytz to 2016.10 (#5247) 2017-01-09 21:50:38 +01:00
Johann Kellerman e6a9b6404f [sensor/sma] SMA Solar Inverter sensor (#5118)
* Initial

* Rebase ensure_list

* timedelta & remove prints
2017-01-09 21:35:47 +01:00
Fabian Affolter dd7cafd5e3 Upgrade TwitterAPI to 2.4.3 (#5244) 2017-01-09 21:34:18 +01:00
Adam Mills c7249a3e3a Build libcec for Docker image (#5230)
* Build libcec for Docker image

* Update development dockerfile as well

* Dynamically load python paths for current version
2017-01-09 17:49:11 +01:00
Pascal Vizeli bb02fc707c [device_traker/upc] New UPC connect box platform (#5100) 2017-01-09 18:08:37 +02:00
Michaël Arnauts 3ed7c1c6ad Add Lannouncer tts notify component (#5187)
* Add Lannouncer notify component

* Send message by opening a raw TCP socket instead of using requests. Cleanup of method validation.

* Use 'return' instead of 'return None'
2017-01-09 14:10:46 +01:00
Terry Carlin ee055651cd Update insteon_local.py (#5236)
Correct typo
2017-01-09 12:11:15 +01:00
St. John Johnson 9fdefa5a1d Fix #5188 by Closing the stream instead of Releasing it (#5235)
* Fix #5188 by Closing the stream instead of Releasing it

Closing just terminates the connection, release attempts to download all the contents before closing.  Since the MJPEG stream is infinite, it loads and loads content until the python script runs out of memory.

Source: https://github.com/KeepSafe/aiohttp/blob/50b1d30f4128abd895532022c726e97993987c7c/aiohttp/client_reqrep.py#L668-L672

* Update mjpeg.py
2017-01-09 10:35:38 +01:00
Paulus Schoutsen f87016afe6 Update MDI 2017-01-09 01:38:27 +01:00
Paulus Schoutsen b685e6e2b5 Update frontend 2017-01-09 01:38:04 +01:00
Paulus Schoutsen 5983dc232f Update frontend 2017-01-09 01:14:13 +01:00
Adam Mills 469472914b Add SUPPORT_PLAY flag (#5181)
* Add SUPPORT_PLAY flag

* Add SUPPPORT_PLAY to existing media players

* Leave usage of new flag to device devs
2017-01-09 01:09:30 +01:00
Craig J. Ward a3971d7ad1 Insteon local (#5088)
* platform set-up begin components

* lights seem to be getting set up properly, not sure why they aren't being added...

* typo

* Dependencies line

* toggle working

* toggle working

* added the switch to insteon_local

First commit hope to test tonight or in the morning

* Update insteon_local.py

* Update insteon_local.py

* Update insteon_local.py

* Update insteon_local.py

* Update insteon_local.py

* Update insteon_local.py

* Update insteon_local.py

* Update insteon_local.py

* Update insteon_local.py

* Update insteon_local.py

* Update insteon_local.py

* move dependency declaration before import?

* Move dependencies in Switch

* Update insteon_local.py

* wait for response

* switched the while to an if

switched the while 'cmd2' not in resp: to an if 'cmd2' not in resp: this seems to have the updater working

* Switched the while sleep loop to an if

switched the wile cmd2 not ins resp to be if cmd2 not in resp seems to be working.

* Update insteon_local.py

* import statement

Updated the import statement to import the instance of the insteon_local component not the hub Instance.

* updated import and the device assignment

update the import to import the instance of the insteon_local component not the hub.

* more changes to support the import change

* more changes to support the import change

* change to hass.data and add loop logic

* &&

* Update insteon_local.py

* Update insteon_local.py

* logic fixes and throttle

* reduce polling time

* brightness support

* import util

* hound fixes

* requirements file

* more hound fixes

* newline

* newline weirdness

* lint fixes

* more lint fixes

* switch state

* Update insteon_local.py

* log cmd2 for debugging

* assume success

* remove check for none

* fix comments

* fix comments again

* fix comments, add fixed version of lib, add support for timeout, add support for port, handle invalid login and connection problems

* fix logging exception

* fix hounceci-bot errors

* fix hounceci-bot errors

* requirements fix

* unique-id changes

* make dimmer off use saved ramp rate

* configurator working for lights

* configurator working for switches?

* configurator working for switches?

* include model names and fix lint errors

* lint fix

* fix exception order

* lint fixes

* fix lint errors

* update to use insteon local 0.38

* fix device id

* move status check to library

* move status check to library

* add SKU to setup

* lint fixes

* requirements

* linting
2017-01-09 00:33:35 +01:00
markferry 2b14d407c0 onkyo: fix selecting sources with only one name (#5221) 2017-01-08 23:59:26 +01:00
happyleavesaoc 81f988cf9e date fix (#5227) 2017-01-08 23:50:42 +01:00
happyleavesaoc f643149d24 usps sensor: better delivery handling (#5202)
* better delivery handling

* bump dep version
2017-01-08 14:35:14 +01:00
dasos fd50201407 Squeezebox JSON-RPC (#5084)
* Refactor of Squeezebox connection code

* Refactor of Squeezebox connection code

* Typos

* Make Python 3.4 friendly

* Addressing comments

* Improving docstring

* Using discovered port

* Style better

* Accept new disco object

* Revert "Accept new disco object"

* Make it obvious that port isn't discovered yet

* Flake8. ;)
2017-01-08 14:32:15 +01:00
Jan Losinski 469aad5fc8 Add teardown method to pilight tests (#5195)
* Add teardown method to pilight tests

This is necessary to stop the HomeAssistant instance that was started
in the setUp method. Without this there happen random test failures.

This is necessary to stabilize the tests for PR #5045.

Signed-off-by: Jan Losinski <losinski@wh2.tu-dresden.de>

* Update test_pilight.py
2017-01-08 14:23:01 +01:00
Pascal Vizeli a65388e778 Bugfix segfault with new helper track_time_interval (#5222)
* Bugfix sigfault with new helper track_time_interval

* Add none init also to sunrise/sunset for consistance
2017-01-08 14:06:15 +01:00
Lewis Juggins 41ef6228be [device_tracker] Use home zone GPS coordinates for router based devices. (#4852) 2017-01-07 23:09:07 +02:00
Pascal Vizeli ca4a857532 Improve aiohttp default clientsession close with connector (#5208) 2017-01-07 21:11:19 +01:00
Lewis Juggins 81922b88a2 [calendar] Fix scan interval (#5205) 2017-01-07 11:24:25 +01:00
Paulus Schoutsen aa1e4c564c Fix tests closing properly (#5203) 2017-01-07 01:47:25 +01:00
Pascal Vizeli 2b991e2f32 [new] component rest_command (#5055)
* New component rest_command

* add unittests

* change handling like other command

* change unittest

* address @balloob comments
2017-01-06 23:42:53 +01:00
Pascal Vizeli 1719d88602 Bugfix default values to timedelta (#5193)
* Bugfix default values to timedelta

* fix unittests
2017-01-06 00:16:12 +01:00
Jared J c959637ebe Add Yeelight auto discovery, color temp (#5145)
* Add Yeelight auto discovery, color temp

* Fixing linting issues
2017-01-05 23:39:28 +01:00
Ryan Kraus db623040a4 Re-enabled Weather Sensors for the ISY component. (#5148)
* Re-enabled Weather Sensors for the ISY component.

As the ISY component had been updated to support newer components and
have better UOM support, the support for ISY’s weather module was
dropped. This adds that support back into Home Assistant.

* Cleanup of the ISY Weather support.

Cleaned up the for loops used to generate nodes representing weather
data from the ISY. Moved the weather_node named tuple to the top of the
file so that it can be more easily identified.

* Update isy994.py
2017-01-05 23:33:52 +01:00
Michaël Arnauts ba29ba0fc3 Universal media_player returns ATTR_MEDIA_POSITION and ATTR_MEDIA_POSITION_UPDATED_AT from it's active child now. (#5184) 2017-01-05 23:15:26 +01:00
Adam Mills 93d462b010 Fix for async device_tracker (#5192) 2017-01-05 23:10:43 +01:00
Pascal Vizeli 50a8ec7335 Bugfix aiohttp connector pool close on shutdown (#5190)
* Bugfix aiohttp connector pool close on shutdown

* fix circular import

* remove lint disable
2017-01-05 23:09:04 +01:00
Nick Touran a36ca62445 Support longer-than-60-second scan_interval and interval_seconds (#5147)
* Update scan_interval and interval_seconds max to 1 day vs. 60 seconds

* Format fixes

* Add docstring on unittest.

* Added and implemented new async_track_time_interval helper.

* Format fixes, removed unused import.

* Undid whoops on unsub_polling.

* Updated unit tests for scan_interval.

* Added unit test for track_time_interval.

* Allow other forms of time interval input for scan_interval and interval_seconds
2017-01-05 23:05:16 +01:00
Paulus Schoutsen f88b5a9c5e Update frontend 2017-01-05 22:28:48 +01:00
webworxshop 51446e0772 Add support for customised Kankun SP3 Wifi switches. (#5089)
* Add support for customised Kankun SP3 Wifi switches.

* Add config validation for Kankun SP3 wifi switch.

* Update kankun.py
2017-01-05 22:16:00 +01:00
MrMep 95ddef31fe Update keyboard_remote.py (#5112)
* Update keyboard_remote.py

I changed os.path.isFile() to os.path.exists: as far as I know isFile doesn't work with device files. At least on my Ubuntu it wasn't working.

Then I added some error control in case the keyboard disconnects: with bluetooth keyboards this happen often due to battery saving. Now it reconnects automatically when the keyboard wakes up.

We could fire an event to hass when the keyboard connects-disconnects, maybe I'll do this later.

We should also manage errors due to permissions problems on the device file, or at least give some info in the docs about how to allow HA to grab control over an system input file.

I'm sorry if my coding isn't up to some standard practice I'm not aware of: I'm new to HA and to python itself, I'm just trying to be of help.
Gianluca

* Update keyboard_remote.py

I changed some other few things.

Not the component gets loaded even if the keyboard is disconnected. When it connects, it starts to fire events when keys are pressed.

I also added a sleep(0.1) that reduces a lot the load on the CPU, but without many consequences on key pressed detection.
2017-01-05 22:03:05 +01:00
Adam Mills 276a29c8f4 Convert Kodi platform to async (#5167)
* Convert Kodi platform to async

* Remove unnecessary async_update_ha_state
2017-01-05 21:01:13 +01:00
Martin Vacula 6b682d0d81 Beaglebone black GPIO control by switch v2 (#4908)
* Added support for BBB GPIO

* Requirements updated

* unnecessary pylint statement removed

* Changed according arduino switch

* typo corrected

* Hound errors solved

* lint error

* Update bbb_gpio.py
2017-01-05 20:53:48 +01:00
Johann Kellerman cbda516af9 ensure_list validator - Allow None to return an empty list (#5133) 2017-01-05 21:33:22 +02:00
Michaël Arnauts cb85128304 Speeds up lint and test in docker by keeping the cache between invocations. (#5177)
* Add a volume to store the tox cache on the host. This gives quite some speed boost when running lint_docker and test_docker.

* Only map .tox directory for cache.
2017-01-05 09:45:14 +01:00
happyleavesaoc 74aa8194d7 USPS sensor (#5180)
* usps sensor

* Update usps.py
2017-01-05 09:44:15 +01:00
Sander de Leeuw 497a1c84b5 Fixed invalid response when sending a test-request from Locative iOS app (#5179) 2017-01-05 10:05:39 +02:00
Daniel Høyer Iversen 2f907696f3 [WIP] Spread the traffic to yr.no servers and remove yr.no from the default… (#5171)
* Spread the traffic to yr.no servers and remove yr.no from the default config

* use unique address for yr.no

* update yr tests

* Wait 10 min extra before requesting new data

* Update config.py

* Update yr.py
2017-01-04 23:20:30 +01:00
Brent Hughes 4ef7e08553 Rewrite influxdb metrics to be more consistent (#4791)
* Updated to make all metrics consistent

* Updated existing test for new format

* Updated checks on lists and dictionarys
2017-01-04 22:36:54 +01:00
Matthew Garrett ff0788324c Add support for Tikteck Bluetooth bulbs (#4843)
* Add support for Tikteck Bluetooth bulbs

Adds support for the Tikteck RGBW BLE bulbs. These don't provide "true" RGBW
support - at a certain point in RGB space, the white LEDs turn on. Each bulb
has a specific key that needs to be extracted from the Android app.

* Update tikteck.py
2017-01-04 22:31:31 +01:00
doudz 9f65b8fef5 add offline tts using pico (#5005)
* add offline tts using pico

* Update picotts.py

* Update picotts.py

* Update picotts.py

* Update picotts.py

* Update picotts.py

* Update picotts.py

* Update picotts.py

* Update .coveragerc
2017-01-04 22:05:41 +01:00
Daniel Høyer Iversen 67ab1f69d8 user agent header (#5172)
* user agent in header

* update user agent info

* Use user-agent from lib
2017-01-04 21:15:50 +01:00
Michaël Arnauts 5e8e2a8312 Ping device tracker (#5176)
* Ping device tracker

* Style fixes
2017-01-04 21:06:09 +01:00
Dan Smith 6ed3c69604 Bump uvcclient to 0.10.0 (#5175)
This brings fixes for newer versions of unifi-video and a few
fixes.
2017-01-04 19:55:16 +01:00
Gopal d09dcc4b03 Timeout and Constant added 2017-01-04 18:23:29 +05:30
Florian Holzapfel 3f7a629079 fix #5157 (#5173) 2017-01-04 13:16:52 +01:00
Oliver 8e61fab579 Pushed to another version of denonavr library with fixes for AVR-nonX devices (#5156) 2017-01-04 00:02:44 +01:00
Magnus Ihse Bursie d9614cff46 Improve async/multithreaded behavior of tellstick code (#4989)
* Refactor tellstick code for increased readability. Especially highlight if "device" is a telldus core device or a HA entity.

* Refactor Tellstick object model for increased clarity.

* Update comments. Unify better with sensors. Fix typo bug. Add debug logging.

* Refactor tellstick code for increased readability. Especially highlight if "device" is a telldus core device or a HA entity.

* Refactor Tellstick object model for increased clarity.

* Update comments. Unify better with sensors. Fix typo bug. Add debug logging.

* Fix lint issues.

* Remove global variable according to hint from balloob.

* Better async/threading behavior for tellstick.

* Fix lint/style checks.

* Use hass.async_add_job
2017-01-03 23:54:11 +01:00
Adam Mills 2a7a419ff3 Async support in media player component (#4995)
* Async support in media player component

* Removed redundant new tests

* Update to new 'reduce coroutine' standards

* Remove extra create_task
2017-01-03 23:51:23 +01:00
Nathan Henrie b78cf4772d Add ability to set rpi_rf tx_repeats attribute (#5070)
* Add ability to set rpi_rf `tx_repeats` attribute

Uses the current `rpi_rf` default of 10
Closes home-assistant/home-assistant#5069

* Use `signal_repetitions` instead of `repeats` for consistency
2017-01-03 23:46:30 +01:00
Daniel Høyer Iversen ebfb2c9b26 Broadlink fix (#5065)
* Broadlink fix

* style fix

* style fix

* typo

* restructure

* Update broadlink.py

* Update broadlink.py

* Add support for more devices

* fix library version

* fix library version

* fix library version

* fix library version

* fix library version

* Update broadlink.py

* lib version

* remove lower

* remove lower

* refactor

* refactor

* refactor

* authorization

* authorization

* refactor

* lib version

* lib version
2017-01-03 23:45:11 +01:00
Thibault Cohen e17ce4f374 Improve Sharp Aquos TV component - Fixes #4973 (#5108) 2017-01-03 23:38:21 +01:00
Giannie 67b74abf8d Allow selection of bluetooth device to use (#5104)
Adding the device_id key to the bevice tracker component allows selecting the bluetooth device to use in case the default device does not have BLE Capabilities
2017-01-03 23:34:51 +01:00
Brandon Weeks c14a5fa7c1 Upgrade samsungctl to 0.6.0 (#5126) 2017-01-03 23:28:23 +01:00
Matthew Garrett 2970196f61 Add support for limiting which entities are recorded (#4733) 2017-01-04 00:19:28 +02:00
Johann Kellerman 4692ea85b7 [mqtt] Embedded MQTT server fix (#5132)
* Embedded MQTT server fix and schema

* feedback
2017-01-03 23:17:56 +01:00
Adam Mills 018d786f36 Kodi cleanup and config update (#5098) 2017-01-03 21:41:42 +01:00
Mike Hennessy 254eb4e88a Change zeroconf to use local ip not base_url (#5151)
Rather than rely on base_url being unconfigured and therefore be
set as the host ip in the api component, get the local ip directly.

Use server port from the http component instead of the api component
where it will be None if base_url is set.

Change the exception catch meant to check for ipv4 vs ipv6 to wrap the
address encoding only instead of the zeroconf service info declaration.
2017-01-03 21:39:42 +01:00
Paulus Schoutsen 9eed03108f Run tests on Python 3.6 (#5162)
* Run tests on Python 3.6

* Fix dsmr test

* Fix async util tests

* Fix rest sensor test
2017-01-03 21:33:48 +01:00
eieste fdd3fa7d80 Apple TouchBar icon Support (mask-icon) (#5159)
* Update index.html

Add Apple TouchBar icon Color Support

* Add svg icon

* Change path to SVG icon

* Rename mask-icon.svg to home-assistant-icon.svg

* Remove useless whitespace

* Update index.html
2017-01-03 21:32:40 +01:00
Daniel Høyer Iversen 6ec500f2e7 issue #5101 (#5161) 2017-01-03 21:13:02 +01:00
andrey-git a0a9f26312 Keep previous brightness of dimming zwave light upon turning it on/off (#5160)
* Keep previous brightness of dimming zwave light upon turning it on/off

* Fix initialization

* Use value 255 to set dimmer to previous value
2017-01-03 20:31:44 +01:00
Adam Mills 21f59a0732 Merge pull request #5150 from Zac-HD/fix-contrib-link
Fix link to pull request advice for contributors
2017-01-03 14:24:53 -05:00
Brandon Weeks 52f6fe3e06 Add version test for monkey_patch_asyncio() (#5127)
* Add version test for monkey_patch_asyncio()

* Update __main__.py
2017-01-03 17:47:33 +01:00
Dan Smith f71027a9c7 Nx584 maint (#5149)
* Update nx584 requirement to 0.4

There have been a few bug fixes to nx584 since 0.2, so this just updates
our requirement to pull in the newer version.

* Fix nx584 if no partitions are found

If we succeed in our connection to the panel but find no
configured partitions, then we will fall through to the bypass
probe and fail because 'zones' is never initialized. This fixes
both exception paths and adds a test that would poke it.
2017-01-03 08:19:33 +01:00
Zac-HD 328ff6027b Fix link to pull request advice for contributors 2017-01-03 14:26:24 +11:00
Michaël Arnauts c864ea60c9 Improve development workflow in docker (#5079)
* Allow bower install of frontend components as root. Needed for frontend development in docker since everything runs as root in the docker image.

* Improve development workflow in docker

* Use LANG=C.UTF-8 in tox. Fixes installation of libraries with UTF-8 in it's readme.

* Install mysqlclient psycopg2 uvloop after requirements_all.txt again, but with a --no-cache-dir this time. Allows bootstrap_frontend to be executed in a different path like the other scripts.
2017-01-02 22:04:09 +01:00
Pascal Vizeli b2371c6614 Update device_traker for async platforms (#5102)
Async DeviceScanner object, migrate to async, cleanups
2017-01-02 21:50:42 +02:00
John Arild Berentsen 9c6a985c56 [zwave]Use schedule_ha_state and add debug message (#5143)
* Use schedule_ha_state and add debug message

* Logger not defined
2017-01-02 18:55:56 +01:00
John Arild Berentsen 5c006cd2d2 Prevent Homeassistant to create a segfault with OZW (#5085) 2017-01-02 18:53:46 +01:00
andrey-git a7cc7ce476 Clean up DEFAULT_DEBUG constant in zwave (#5138)
Nice 👍
2017-01-02 18:45:44 +01:00
John Arild Berentsen 23f16bb68f Catch RuntimeError (#5134) 2017-01-02 18:45:10 +01:00
Greg Dowling 8a463ef7a4 Merge pull request #5142 from home-assistant/bump_loop_energy
Bump pyloopenergy to catch SSL exception.
2017-01-02 15:02:08 +00:00
pavoni 9af1e0ccf3 Bump pyloopenergy to catch SSL exception. 2017-01-02 14:15:38 +00:00
jtscott a8a98f2585 [climate] Fix typo in services.yaml (#5136) 2017-01-02 10:17:52 +00:00
andrey-git 7fbf68df35 Add print_config_parameter service to Z-Wave (#5121)
* Add print_config_param service to z-wave

* Add print_config_parameter service to z-wave

* Add print_config_parameter service to z-wave

* Fix typos

* Fix typos

* Fix typo
2017-01-01 21:10:45 +01:00
Mark 01be70cda9 Philio 3-in-1 Gen 4 zwave sensor needs the no-off-event workaround. (#5120) 2017-01-01 20:40:34 +01:00
Gopal e89aa6b2d6 File added to coveragerc 2016-12-31 10:11:30 +05:30
Gopal 227fb29cab Facebook notify updated 2016-12-31 09:59:44 +05:30
Felix d04ee66669 Fixes moldindicator sensor after switch to asyncio (#5038) 2016-12-30 11:16:34 +01:00
Thibault Cohen 9e66755baf Fix proximity issue (#5109) 2016-12-30 08:51:37 +01:00
Pascal Vizeli 0ecd185f0d Bugfix close async log handler (#5086) 2016-12-29 17:27:58 +01:00
Jesse Newland a2f17cccbb Replace dots in Alexa built-in intent slots w/ underscores (#5092)
* Replace dots in built-in intent slots w/ underscores

* Add a built-in intent test
2016-12-29 17:26:23 +01:00
Gopal 6892048de0 Facebook Notify Started 2016-12-29 18:19:11 +05:30
Johann Kellerman aaff8d8602 Qwikswitch library PyPi update (#5099) 2016-12-29 11:08:11 +02:00
Brent Hughes cf714f42df Added Ledenet protocol support to flux_led (#5097)
* Added Ledenet protocol support to flux_led

* Made the protocol config lowercase
2016-12-28 23:21:29 -06:00
Johann Kellerman f0b1874d2d Fix up docstring for tests (#5090) 2016-12-28 20:04:59 +02:00
Magnus Ihse Bursie 98efbbc129 Fix typo. (#5087) 2016-12-28 08:12:10 +01:00
Magnus Ihse Bursie d8ff22870a Include flake8-docstrings to test requirements to better mimic tox -e lint (#4926) 2016-12-28 07:26:27 +02:00
John Arild Berentsen fee47f35b9 Improvements to zwave lock platform (#5066) 2016-12-27 22:08:35 +01:00
Brian Torres-Gil 7b6503c017 Ecobee service fix and new 'resume program' service (#4510)
* ecobee_set_fan_min_on_time: fix issue using 'entity_id' field and add service field help text

* climate.ecobee: add 'resume_program' service

* Add default value for resume_all and correct entity_id field name reference
2016-12-27 21:56:26 +01:00
Michaël Arnauts c77b4a4806 Update icons from materialdesignicons.com (#5081) 2016-12-27 21:36:07 +01:00
andrey-git 4728fa8da6 Allow to specify TTS language in the service call. (#5047)
* Allow to specify TTS language in the service call.

* Allow to specify TTS language in the service call.

* Respect 79 char limit

* Fix "Too many blank lines"

* Fix "Too many blank lines"

* Fix "Too many blank lines"

* Change language to be optional parameter of *get_tts_audio

* Change language to be optional parameter of *get_tts_audio

* Respect 79 char limit

* Don't pass "None

* Use default of "None" for TTS language

* Use default of "None" for TTS language

* Don't pass "None"

* Change TTS cache key to be hash_lang_engine

* Change language from demo to en

* Fix wrong replace
2016-12-27 17:01:22 +01:00
Daniel Høyer Iversen 68865ec27b upgrade miflora (#5075)
Add an optional extended description…
2016-12-27 09:24:05 +01:00
Fabian Affolter c5f70e8be3 Upgrade speedtest-cli to 1.0.1 (#5073) 2016-12-26 16:41:18 +01:00
Fabian Affolter ec89accd29 Upgrade psutil to 5.0.1 (#5072) 2016-12-26 16:31:44 +01:00
Fabian Affolter ac1063266c Upgrade pyowm to 2.6.0 (#5071) 2016-12-26 16:31:26 +01:00
Hydreliox 5b619a94ad Add sensor for International Space Station (#4968)
* Add sensor for International Space Station

* Change two sensors to one with attributes.

* Fix due to comments in HA PR. Thanks !

* Update Requirement
2016-12-26 16:02:11 +01:00
Pascal Vizeli 244cdf43d0 Async reduce coro (#5001)
* Asyncio coro reduce

* Replace comments
2016-12-26 14:10:23 +01:00
Pascal Vizeli 22d1bf0acd Async migrate climate (#5026)
* Async migrate climate

* Change update handling
2016-12-26 14:09:15 +01:00
Johan Bloemberg 43e5d28643 Fix and test for prefixed MAC addresses. (#5052)
* Fix and test for prefixed MAC addresses.

* Fix style.

* Don't commit old code.

* Fix style.
2016-12-26 14:02:12 +01:00
abmantis e5dfcf7310 Emulated hue: add support for cover open/close (#5057)
* add support for cover open/close

* fix action compare; reduce line width
2016-12-26 14:00:43 +01:00
abmantis 1c1b04718f emulated_hue: fix alexa "device not responding" (#5058)
* emulated_hue: fix alexa "device not responding"

we need to set the brightness to 100 for devices that only turn on

* emulated_hue: dont override brightness for scenes/scripts

* emulated_hue: python and semi-colons

* emulated_hue: fix output brightness level
2016-12-26 13:58:32 +01:00
Paulus Schoutsen ce24ef0c20 Merge branch 'master' into dev 2016-12-23 07:08:33 +01:00
Josh Nichols 6c50f53696 Nest fixes (#5011)
* Updated Nest API to have logical names

* Fix NoneType not having replace method in NestSensor constructor

* Move name setting to constructor, in case zone.name causes IO.

* normalize is_online to online

* Updated python-nest API

* push is_* helpers down to python-nest, and use inheritence to implement rather than checking class name

* Update python-nest
2016-12-22 20:22:07 +01:00
John Mihalic 5e1e5992af Update pyHik requirement version (#5040) 2016-12-22 18:45:05 +01:00
Pascal Vizeli 334b3b8636 Bugfix async log handle re-close bug (#5034)
* Bugfix async log handle re-close bug

* Check on running thread on async_close

* Fix now on right place
2016-12-22 16:08:01 +01:00
Pascal Vizeli 9a16054867 Bugfix create a task from a task in component update (#5033) 2016-12-21 15:11:14 +01:00
Pascal Vizeli 4c9347eb2a Fix spell media_player service (#5030)
Add an optional extended description…
2016-12-21 11:39:59 +01:00
Pascal Vizeli 25469dd8ee Bugfix voicerss post api (#5021)
* Bugfix voicerss post api

* fix unittest

* Add cache to service description
2016-12-21 10:22:12 +01:00
Johann Kellerman b170f4c399 Spread seconds (#5025) 2016-12-21 08:42:23 +01:00
Johann Kellerman a8b3900913 device_tracker (#5023)
2
2016-12-21 08:40:44 +01:00
Daniel Høyer Iversen 39bdd5310b Merge pull request #5022 from home-assistant/broadlink_bug_fix
Solve some bugs in the broadlink switch
2016-12-21 05:24:16 +01:00
Daniel Høyer Iversen 133c03ee57 style fix 2016-12-20 21:16:18 +01:00
Daniel Høyer Iversen f224ee7229 Solve some bugs in the bradlink switch 2016-12-20 21:05:54 +01:00
Petr Vraník 1aea3e0d51 script/lint only on python files (#5018) 2016-12-20 13:06:53 +02:00
Daniel Perna 877efac630 Add missing support for HMIP-PSM (#5013) 2016-12-20 11:39:29 +01:00
Hugo Dupras ee6fb93018 Hotfix for Netatmo Camera (#4998)
Signed-off-by: Hugo D. (jabesq) <jabesq@gmail.com>
2016-12-19 07:47:38 -08:00
Daniel Høyer Iversen 08591aacc9 Merge pull request #5000 from home-assistant/rfxtrx_lib_0.14
rfxtrx lib upgrade
2016-12-19 11:54:58 +01:00
Daniel Hoyer Iversen 2cb67eca46 rfxtrx lib upgrade 2016-12-19 11:35:00 +01:00
Daniel Høyer Iversen 00b80d4fe1 Support for broadlink sp (#4961)
* initial support for broadlink sp

* style fix

* style

* bug fix
2016-12-18 17:59:08 -08:00
Paulus Schoutsen 53dde0e4e1 Unbreak dev 2016-12-18 17:35:42 -08:00
Paulus Schoutsen 4778ec4f94 Merge branch 'master' into dev 2016-12-18 15:02:02 -08:00
Paulus Schoutsen ed0d14c902 Base url: Fix external port different from internal port (#4990)
* Base url: Fix external port different from internal port

* Add base_url example to new config
2016-12-18 14:59:45 -08:00
andrey-git 744d00a36e Fix non-radio Sonos album-art by using track_info['album_art'] before checking other options. (#4958)
* Fix Sonos album art for non-radio streams

* Revert "Fix Sonos album art for non-radio streams"

This reverts commit d71502d18f.

* Fix Sonos album art for non-radio streams

* Move art existance check into _format_media_image_url
2016-12-18 12:58:59 -08:00
Pascal Vizeli a6d995e394 Bugfix wait in automation (#4984) 2016-12-18 12:57:31 -08:00
Paulus Schoutsen f8af6e7863 Allow setting base url (#4985) 2016-12-18 12:56:07 -08:00
Pascal Vizeli fec33347fb Bugfix TTS clear cache (#4974)
* Bugfix TTS base url with certificate

* fix lint

* remove base_url stuff fix only clear_cache stuff

* cleanup
2016-12-18 11:26:40 -08:00
Nick Sabinske 44eaca5985 Add support for the Sonarr URL Base setting (#4975)
* Add support for the Sonarr URL Base setting

For those of us who have sonarr hidden behind reverse proxy under a path, we need to be able to pass the path

Adds urlbase: XXX to the sonarr yaml... Match it to what you have set in Sonarr (Settings>General>URL Base)

* Fix line lengths

* Fix trailing white space caused by last change

* Removing use of len()
2016-12-18 10:05:05 -08:00
Thibault Cohen 18cf6f6f99 Add HydroQuebec support (#4840) 2016-12-18 12:23:10 +01:00
Fabian Affolter 9f298a92f4 Remove and update docstrings (#4969) 2016-12-18 11:39:33 +01:00
Paulus Schoutsen 01e6bd2c92 Gracefully exit with async logger (#4965)
* Gracefully exit with async logger

* Lint
2016-12-18 00:14:59 -08:00
Paulus Schoutsen a76684f203 Version bump to 0.36.0.dev0 2016-12-17 14:07:44 -08:00
382 changed files with 13644 additions and 2229 deletions
+22
View File
@@ -13,6 +13,9 @@ omit =
homeassistant/components/arduino.py
homeassistant/components/*/arduino.py
homeassistant/components/bbb_gpio.py
homeassistant/components/*/bbb_gpio.py
homeassistant/components/bloomsky.py
homeassistant/components/*/bloomsky.py
@@ -34,6 +37,9 @@ omit =
homeassistant/components/insteon_hub.py
homeassistant/components/*/insteon_hub.py
homeassistant/components/insteon_local.py
homeassistant/components/*/insteon_local.py
homeassistant/components/ios.py
homeassistant/components/*/ios.py
@@ -157,13 +163,16 @@ omit =
homeassistant/components/device_tracker/luci.py
homeassistant/components/device_tracker/netgear.py
homeassistant/components/device_tracker/nmap_tracker.py
homeassistant/components/device_tracker/ping.py
homeassistant/components/device_tracker/snmp.py
homeassistant/components/device_tracker/swisscom.py
homeassistant/components/device_tracker/thomson.py
homeassistant/components/device_tracker/tomato.py
homeassistant/components/device_tracker/tplink.py
homeassistant/components/device_tracker/trackr.py
homeassistant/components/device_tracker/ubus.py
homeassistant/components/device_tracker/volvooncall.py
homeassistant/components/device_tracker/xiaomi.py
homeassistant/components/discovery.py
homeassistant/components/downloader.py
homeassistant/components/emoncms_history.py
@@ -183,8 +192,10 @@ omit =
homeassistant/components/light/lifx.py
homeassistant/components/light/limitlessled.py
homeassistant/components/light/osramlightify.py
homeassistant/components/light/tikteck.py
homeassistant/components/light/x10.py
homeassistant/components/light/yeelight.py
homeassistant/components/light/zengge.py
homeassistant/components/lirc.py
homeassistant/components/media_player/aquostv.py
homeassistant/components/media_player/braviatv.py
@@ -202,6 +213,7 @@ omit =
homeassistant/components/media_player/lg_netcast.py
homeassistant/components/media_player/mpchc.py
homeassistant/components/media_player/mpd.py
homeassistant/components/media_player/nad.py
homeassistant/components/media_player/onkyo.py
homeassistant/components/media_player/panasonic_viera.py
homeassistant/components/media_player/pandora.py
@@ -219,12 +231,14 @@ omit =
homeassistant/components/notify/aws_lambda.py
homeassistant/components/notify/aws_sns.py
homeassistant/components/notify/aws_sqs.py
homeassistant/components/notify/facebook.py
homeassistant/components/notify/free_mobile.py
homeassistant/components/notify/gntp.py
homeassistant/components/notify/group.py
homeassistant/components/notify/instapush.py
homeassistant/components/notify/joaoapps_join.py
homeassistant/components/notify/kodi.py
homeassistant/components/notify/lannouncer.py
homeassistant/components/notify/llamalab_automate.py
homeassistant/components/notify/matrix.py
homeassistant/components/notify/message_bird.py
@@ -254,6 +268,7 @@ omit =
homeassistant/components/sensor/bitcoin.py
homeassistant/components/sensor/bom.py
homeassistant/components/sensor/broadlink.py
homeassistant/components/sensor/dublin_bus_transport.py
homeassistant/components/sensor/coinmarketcap.py
homeassistant/components/sensor/cpuspeed.py
homeassistant/components/sensor/cups.py
@@ -277,6 +292,8 @@ omit =
homeassistant/components/sensor/haveibeenpwned.py
homeassistant/components/sensor/hddtemp.py
homeassistant/components/sensor/hp_ilo.py
homeassistant/components/sensor/hydroquebec.py
homeassistant/components/sensor/iss.py
homeassistant/components/sensor/imap.py
homeassistant/components/sensor/imap_email_content.py
homeassistant/components/sensor/influxdb.py
@@ -301,6 +318,7 @@ omit =
homeassistant/components/sensor/scrape.py
homeassistant/components/sensor/sensehat.py
homeassistant/components/sensor/serial_pm.py
homeassistant/components/sensor/sma.py
homeassistant/components/sensor/snmp.py
homeassistant/components/sensor/sonarr.py
homeassistant/components/sensor/speedtest.py
@@ -317,6 +335,7 @@ omit =
homeassistant/components/sensor/transmission.py
homeassistant/components/sensor/twitch.py
homeassistant/components/sensor/uber.py
homeassistant/components/sensor/usps.py
homeassistant/components/sensor/vasttrafik.py
homeassistant/components/sensor/waqi.py
homeassistant/components/sensor/xbox_live.py
@@ -331,6 +350,7 @@ omit =
homeassistant/components/switch/edimax.py
homeassistant/components/switch/hikvisioncam.py
homeassistant/components/switch/hook.py
homeassistant/components/switch/kankun.py
homeassistant/components/switch/mystrom.py
homeassistant/components/switch/netio.py
homeassistant/components/switch/orvibo.py
@@ -342,7 +362,9 @@ omit =
homeassistant/components/switch/transmission.py
homeassistant/components/switch/wake_on_lan.py
homeassistant/components/thingspeak.py
homeassistant/components/tts/picotts.py
homeassistant/components/upnp.py
homeassistant/components/weather/bom.py
homeassistant/components/weather/openweathermap.py
homeassistant/components/zeroconf.py
+2
View File
@@ -12,6 +12,8 @@ matrix:
env: TOXENV=typing
- python: "3.5"
env: TOXENV=py35
- python: "3.6"
env: TOXENV=py36
allow_failures:
- python: "3.5"
env: TOXENV=typing
+1 -1
View File
@@ -4,7 +4,7 @@ Everybody is invited and welcome to contribute to Home Assistant. There is a lot
The process is straight-forward.
- Read [How to get faster PR reviews](https://github.com/kubernetes/kubernetes/blob/master/docs/devel/faster_reviews.md) by Kubernetes (but skip step 0)
- Read [How to get faster PR reviews](https://github.com/kubernetes/community/blob/master/contributors/devel/faster_reviews.md) by Kubernetes (but skip step 0)
- Fork the Home Assistant [git repository](https://github.com/home-assistant/home-assistant).
- Write the code for your device, notification service, sensor, or IoT thing.
- Ensure tests work.
+5 -15
View File
@@ -6,24 +6,14 @@ VOLUME /config
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
RUN pip3 install --no-cache-dir colorlog cython
# For the nmap tracker, bluetooth tracker, Z-Wave, tellstick
RUN echo "deb http://download.telldus.com/debian/ stable main" >> /etc/apt/sources.list.d/telldus.list && \
wget -qO - http://download.telldus.se/debian/telldus-public.key | apt-key add - && \
apt-get update && \
apt-get install -y --no-install-recommends nmap net-tools cython3 libudev-dev sudo libglib2.0-dev bluetooth libbluetooth-dev \
libtelldus-core2 && \
apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
COPY script/build_python_openzwave script/build_python_openzwave
RUN script/build_python_openzwave && \
mkdir -p /usr/local/share/python-openzwave && \
ln -sf /usr/src/app/build/python-openzwave/openzwave/config /usr/local/share/python-openzwave/config
# Copy build scripts
COPY script/setup_docker_prereqs script/build_python_openzwave script/build_libcec script/
RUN script/setup_docker_prereqs
# Install hass component dependencies
COPY requirements_all.txt requirements_all.txt
RUN pip3 install --no-cache-dir -r requirements_all.txt && \
pip3 install mysqlclient psycopg2 uvloop
pip3 install --no-cache-dir mysqlclient psycopg2 uvloop
# Copy source
COPY . .
+2 -1
View File
@@ -26,7 +26,8 @@ Examples of devices Home Assistant can interface with:
`Netgear <http://netgear.com>`__,
`DD-WRT <http://www.dd-wrt.com/site/index>`__,
`TPLink <http://www.tp-link.us/>`__,
`ASUSWRT <http://event.asus.com/2013/nw/ASUSWRT/>`__ and any SNMP
`ASUSWRT <http://event.asus.com/2013/nw/ASUSWRT/>`__,
`Xiaomi <http://miwifi.com/>`__ and any SNMP
capable Linksys WAP/WRT
- `Philips Hue <http://meethue.com>`__ lights,
`WeMo <http://www.belkin.com/us/Products/home-automation/c/wemo-home-automation/>`__
+2 -1
View File
@@ -356,7 +356,8 @@ def try_to_restart() -> None:
def main() -> int:
"""Start Home Assistant."""
monkey_patch_asyncio()
if sys.version_info[:3] < (3, 5, 3):
monkey_patch_asyncio()
validate_python()
+5 -1
View File
@@ -395,6 +395,10 @@ def async_from_config_dict(config: Dict[str, Any],
if not loader.PREPARED:
yield from hass.loop.run_in_executor(None, loader.prepare, hass)
# Merge packages
conf_util.merge_packages_config(
config, core_config.get(conf_util.CONF_PACKAGES, {}))
# Make a copy because we are mutating it.
# Use OrderedDict in case original one was one.
# Convert values to dictionaries if they are None
@@ -606,7 +610,7 @@ def async_log_exception(ex, domain, config, hass):
message += '{}.'.format(humanize_error(config, ex))
domain_config = config.get(domain, config)
message += " (See {}:{}). ".format(
message += " (See {}, line {}). ".format(
getattr(domain_config, '__config_file__', '?'),
getattr(domain_config, '__line__', '?'))
@@ -5,6 +5,7 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel/
"""
import asyncio
from datetime import timedelta
import logging
import os
@@ -20,7 +21,7 @@ from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
DOMAIN = 'alarm_control_panel'
SCAN_INTERVAL = 30
SCAN_INTERVAL = timedelta(seconds=30)
ATTR_CHANGED_BY = 'changed_by'
ENTITY_ID_FORMAT = DOMAIN + '.{}'
@@ -152,40 +153,48 @@ class AlarmControlPanel(Entity):
"""Send disarm command."""
raise NotImplementedError()
@asyncio.coroutine
def async_alarm_disarm(self, code=None):
"""Send disarm command."""
yield from self.hass.loop.run_in_executor(
"""Send disarm command.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.loop.run_in_executor(
None, self.alarm_disarm, code)
def alarm_arm_home(self, code=None):
"""Send arm home command."""
raise NotImplementedError()
@asyncio.coroutine
def async_alarm_arm_home(self, code=None):
"""Send arm home command."""
yield from self.hass.loop.run_in_executor(
"""Send arm home command.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.loop.run_in_executor(
None, self.alarm_arm_home, code)
def alarm_arm_away(self, code=None):
"""Send arm away command."""
raise NotImplementedError()
@asyncio.coroutine
def async_alarm_arm_away(self, code=None):
"""Send arm away command."""
yield from self.hass.loop.run_in_executor(
"""Send arm away command.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.loop.run_in_executor(
None, self.alarm_arm_away, code)
def alarm_trigger(self, code=None):
"""Send alarm trigger command."""
raise NotImplementedError()
@asyncio.coroutine
def async_alarm_trigger(self, code=None):
"""Send alarm trigger command."""
yield from self.hass.loop.run_in_executor(
"""Send alarm trigger command.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.loop.run_in_executor(
None, self.alarm_trigger, code)
@property
@@ -5,6 +5,7 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.concord232/
"""
import datetime
from datetime import timedelta
import logging
import requests
@@ -25,7 +26,7 @@ DEFAULT_HOST = 'localhost'
DEFAULT_NAME = 'CONCORD232'
DEFAULT_PORT = 5007
SCAN_INTERVAL = 1
SCAN_INTERVAL = timedelta(seconds=1)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
@@ -16,7 +16,7 @@ from homeassistant.const import (
STATE_UNKNOWN, CONF_NAME, CONF_HOST, CONF_PORT)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pynx584==0.2']
REQUIREMENTS = ['pynx584==0.4']
_LOGGER = logging.getLogger(__name__)
@@ -86,9 +86,11 @@ class NX584Alarm(alarm.AlarmControlPanel):
_LOGGER.error('Unable to connect to %(host)s: %(reason)s',
dict(host=self._url, reason=ex))
self._state = STATE_UNKNOWN
zones = []
except IndexError:
_LOGGER.error('nx584 reports no partitions')
self._state = STATE_UNKNOWN
zones = []
bypassed = False
for zone in zones:
+5 -4
View File
@@ -203,11 +203,12 @@ class AlexaResponse(object):
self.reprompt = None
self.session_attributes = {}
self.should_end_session = True
self.variables = {}
if intent is not None and 'slots' in intent:
self.variables = {key: value['value'] for key, value
in intent['slots'].items() if 'value' in value}
else:
self.variables = {}
for key, value in intent['slots'].items():
if 'value' in value:
underscored_key = key.replace('.', '_')
self.variables[underscored_key] = value['value']
def add_card(self, card_type, title, content):
"""Add a card to the response."""
+3
View File
@@ -133,6 +133,9 @@ class APIEventStream(HomeAssistantView):
except asyncio.TimeoutError:
yield from to_write.put(STREAM_PING_PAYLOAD)
except asyncio.CancelledError:
_LOGGER.debug('STREAM %s ABORT', id(stop_obj))
finally:
_LOGGER.debug('STREAM %s RESPONSE CLOSED', id(stop_obj))
unsub_stream()
+74
View File
@@ -0,0 +1,74 @@
"""
Support for controlling GPIO pins of a Beaglebone Black.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/bbb_gpio/
"""
import logging
from homeassistant.const import (
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP)
REQUIREMENTS = ['Adafruit_BBIO==1.0.0']
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'bbb_gpio'
# pylint: disable=no-member
def setup(hass, config):
"""Set up the BeagleBone Black GPIO component."""
# pylint: disable=import-error
import Adafruit_BBIO.GPIO as GPIO
def cleanup_gpio(event):
"""Stuff to do before stopping."""
GPIO.cleanup()
def prepare_gpio(event):
"""Stuff to do when home assistant starts."""
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, cleanup_gpio)
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, prepare_gpio)
return True
# noqa: F821
def setup_output(pin):
"""Setup a GPIO as output."""
# pylint: disable=import-error,undefined-variable
import Adafruit_BBIO.GPIO as GPIO
GPIO.setup(pin, GPIO.OUT)
def setup_input(pin, pull_mode):
"""Setup a GPIO as input."""
# pylint: disable=import-error,undefined-variable
import Adafruit_BBIO.GPIO as GPIO
GPIO.setup(pin, GPIO.IN, # noqa: F821
GPIO.PUD_DOWN if pull_mode == 'DOWN' # noqa: F821
else GPIO.PUD_UP) # noqa: F821
def write_output(pin, value):
"""Write a value to a GPIO."""
# pylint: disable=import-error,undefined-variable
import Adafruit_BBIO.GPIO as GPIO
GPIO.output(pin, value)
def read_input(pin):
"""Read a value from a GPIO."""
# pylint: disable=import-error,undefined-variable
import Adafruit_BBIO.GPIO as GPIO
return GPIO.input(pin)
def edge_detect(pin, event_callback, bounce):
"""Add detection for RISING and FALLING events."""
# pylint: disable=import-error,undefined-variable
import Adafruit_BBIO.GPIO as GPIO
GPIO.add_event_detect(
pin, GPIO.BOTH, callback=event_callback, bouncetime=bounce)
@@ -5,6 +5,7 @@ For more details about this component, please refer to the documentation at
https://home-assistant.io/components/binary_sensor/
"""
import asyncio
from datetime import timedelta
import logging
import voluptuous as vol
@@ -15,7 +16,7 @@ from homeassistant.const import (STATE_ON, STATE_OFF)
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
DOMAIN = 'binary_sensor'
SCAN_INTERVAL = 30
SCAN_INTERVAL = timedelta(seconds=30)
ENTITY_ID_FORMAT = DOMAIN + '.{}'
SENSOR_CLASSES = [
@@ -4,6 +4,7 @@ Support for custom shell commands to retrieve values.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.command_line/
"""
from datetime import timedelta
import logging
import voluptuous as vol
@@ -22,7 +23,7 @@ DEFAULT_NAME = 'Binary Command Sensor'
DEFAULT_PAYLOAD_ON = 'ON'
DEFAULT_PAYLOAD_OFF = 'OFF'
SCAN_INTERVAL = 60
SCAN_INTERVAL = timedelta(seconds=60)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_COMMAND): cv.string,
@@ -27,7 +27,7 @@ DEFAULT_NAME = 'Alarm'
DEFAULT_PORT = '5007'
DEFAULT_SSL = False
SCAN_INTERVAL = 1
SCAN_INTERVAL = datetime.timedelta(seconds=1)
ZONE_TYPES_SCHEMA = vol.Schema({
cv.positive_int: vol.In(SENSOR_CLASSES),
+20 -36
View File
@@ -1,6 +1,6 @@
"""Contains functionality to use flic buttons as a binary sensor."""
import asyncio
import logging
import threading
import voluptuous as vol
@@ -10,7 +10,6 @@ from homeassistant.const import (
EVENT_HOMEASSISTANT_STOP)
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA)
from homeassistant.util.async import run_callback_threadsafe
REQUIREMENTS = ['https://github.com/soldag/pyflic/archive/0.4.zip#pyflic==0.4']
@@ -43,9 +42,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Setup the flic platform."""
import pyflic
@@ -63,26 +60,29 @@ def async_setup_platform(hass, config, async_add_entities,
def new_button_callback(address):
"""Setup newly verified button as device in home assistant."""
hass.add_job(async_setup_button(hass, config, async_add_entities,
client, address))
setup_button(hass, config, add_entities, client, address)
client.on_new_verified_button = new_button_callback
if discovery:
start_scanning(hass, config, async_add_entities, client)
start_scanning(config, add_entities, client)
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP,
lambda event: client.close())
hass.loop.run_in_executor(None, client.handle_events)
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP,
lambda event: client.close())
# Start the pyflic event handling thread
threading.Thread(target=client.handle_events).start()
def get_info_callback(items):
"""Add entities for already verified buttons."""
addresses = items["bd_addr_of_verified_buttons"] or []
for address in addresses:
setup_button(hass, config, add_entities, client, address)
# Get addresses of already verified buttons
addresses = yield from async_get_verified_addresses(client)
if addresses:
for address in addresses:
yield from async_setup_button(hass, config, async_add_entities,
client, address)
client.get_info(get_info_callback)
def start_scanning(hass, config, async_add_entities, client):
def start_scanning(config, add_entities, client):
"""Start a new flic client for scanning & connceting to new buttons."""
import pyflic
@@ -97,36 +97,20 @@ def start_scanning(hass, config, async_add_entities, client):
address, result)
# Restart scan wizard
start_scanning(hass, config, async_add_entities, client)
start_scanning(config, add_entities, client)
scan_wizard.on_completed = scan_completed_callback
client.add_scan_wizard(scan_wizard)
@asyncio.coroutine
def async_setup_button(hass, config, async_add_entities, client, address):
def setup_button(hass, config, add_entities, client, address):
"""Setup single button device."""
timeout = config.get(CONF_TIMEOUT)
ignored_click_types = config.get(CONF_IGNORED_CLICK_TYPES)
button = FlicButton(hass, client, address, timeout, ignored_click_types)
_LOGGER.info("Connected to button (%s)", address)
yield from async_add_entities([button])
@asyncio.coroutine
def async_get_verified_addresses(client):
"""Retrieve addresses of verified buttons."""
future = asyncio.Future()
loop = asyncio.get_event_loop()
def get_info_callback(items):
"""Set the addressed of connected buttons as result of the future."""
addresses = items["bd_addr_of_verified_buttons"]
run_callback_threadsafe(loop, future.set_result, addresses)
client.get_info(get_info_callback)
return future
add_entities([button])
class FlicButton(BinarySensorDevice):
@@ -17,7 +17,7 @@ from homeassistant.const import (
CONF_HOST, CONF_PORT, CONF_NAME, CONF_USERNAME, CONF_PASSWORD,
CONF_SSL, EVENT_HOMEASSISTANT_STOP, ATTR_LAST_TRIP_TIME, CONF_CUSTOMIZE)
REQUIREMENTS = ['pyhik==0.0.6', 'pydispatcher==2.0.5']
REQUIREMENTS = ['pyhik==0.0.7', 'pydispatcher==2.0.5']
_LOGGER = logging.getLogger(__name__)
CONF_IGNORED = 'ignored'
@@ -47,7 +47,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
devices = {}
gateway.platform_callbacks.append(mysensors.pf_callback_factory(
map_sv_types, devices, add_devices, MySensorsBinarySensor))
map_sv_types, devices, MySensorsBinarySensor, add_devices))
class MySensorsBinarySensor(
@@ -16,7 +16,7 @@ from homeassistant.components.binary_sensor import (
from homeassistant.const import (CONF_HOST, CONF_PORT)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pynx584==0.2']
REQUIREMENTS = ['pynx584==0.4']
_LOGGER = logging.getLogger(__name__)
@@ -20,6 +20,8 @@ DEPENDENCIES = []
PHILIO = 0x013c
PHILIO_SLIM_SENSOR = 0x0002
PHILIO_SLIM_SENSOR_MOTION = (PHILIO, PHILIO_SLIM_SENSOR, 0)
PHILIO_3_IN_1_SENSOR_GEN_4 = 0x000d
PHILIO_3_IN_1_SENSOR_GEN_4_MOTION = (PHILIO, PHILIO_3_IN_1_SENSOR_GEN_4, 0)
WENZHOU = 0x0118
WENZHOU_SLIM_SENSOR_MOTION = (WENZHOU, PHILIO_SLIM_SENSOR, 0)
@@ -27,6 +29,7 @@ WORKAROUND_NO_OFF_EVENT = 'trigger_no_off_event'
DEVICE_MAPPINGS = {
PHILIO_SLIM_SENSOR_MOTION: WORKAROUND_NO_OFF_EVENT,
PHILIO_3_IN_1_SENSOR_GEN_4_MOTION: WORKAROUND_NO_OFF_EVENT,
WENZHOU_SLIM_SENSOR_MOTION: WORKAROUND_NO_OFF_EVENT,
}
@@ -96,6 +99,7 @@ class ZWaveBinarySensor(BinarySensorDevice, zwave.ZWaveDeviceEntity, Entity):
"""Called when a value has changed on the network."""
if self._value.value_id == value.value_id or \
self._value.node == value.node:
_LOGGER.debug('Value changed for label %s', self._value.label)
self.schedule_update_ha_state()
@@ -6,6 +6,8 @@ https://home-assistant.io/components/calendar/
"""
import logging
from datetime import timedelta
import re
from homeassistant.components.google import (CONF_OFFSET,
@@ -20,6 +22,7 @@ from homeassistant.util import dt
_LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(seconds=60)
DOMAIN = 'calendar'
ENTITY_ID_FORMAT = DOMAIN + '.{}'
@@ -27,7 +30,7 @@ ENTITY_ID_FORMAT = DOMAIN + '.{}'
def setup(hass, config):
"""Track states and offer events for calendars."""
component = EntityComponent(
logging.getLogger(__name__), DOMAIN, hass, 60, DOMAIN)
logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL, DOMAIN)
component.setup(config)
@@ -144,10 +147,10 @@ class CalendarEventDevice(Entity):
def _get_date(date):
"""Get the dateTime from date or dateTime as a local."""
if 'date' in date:
return dt.as_utc(dt.dt.datetime.combine(
dt.parse_date(date['date']), dt.dt.time()))
return dt.start_of_local_day(dt.dt.datetime.combine(
dt.parse_date(date['date']), dt.dt.time.min))
else:
return dt.parse_datetime(date['dateTime'])
return dt.as_local(dt.parse_datetime(date['dateTime']))
start = _get_date(self.data.event['start'])
end = _get_date(self.data.event['end'])
+1 -1
View File
@@ -66,7 +66,7 @@ class GoogleCalendarData(object):
"""Get the latest data."""
service = self.calendar_service.get()
params = dict(DEFAULT_GOOGLE_SEARCH_PARAMS)
params['timeMin'] = dt.utcnow().isoformat('T')
params['timeMin'] = dt.start_of_local_day().isoformat('T')
params['calendarId'] = self.calendar_id
if self.search:
params['q'] = self.search
+66 -14
View File
@@ -6,18 +6,27 @@ For more details about this component, please refer to the documentation at
https://home-assistant.io/components/camera/
"""
import asyncio
from datetime import timedelta
import logging
import hashlib
import aiohttp
from aiohttp import web
import async_timeout
from homeassistant.const import ATTR_ENTITY_PICTURE
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
from homeassistant.components.http import HomeAssistantView, KEY_AUTHENTICATED
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'camera'
DEPENDENCIES = ['http']
SCAN_INTERVAL = 30
SCAN_INTERVAL = timedelta(seconds=30)
ENTITY_ID_FORMAT = DOMAIN + '.{}'
STATE_RECORDING = 'recording'
@@ -27,11 +36,45 @@ STATE_IDLE = 'idle'
ENTITY_IMAGE_URL = '/api/camera_proxy/{0}?token={1}'
@asyncio.coroutine
def async_get_image(hass, entity_id, timeout=10):
"""Fetch a 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))
url = "{0}{1}".format(
hass.config.api.base_url,
state.attributes.get(ATTR_ENTITY_PICTURE)
)
response = None
try:
with async_timeout.timeout(timeout, loop=hass.loop):
response = yield from websession.get(url)
if response.status != 200:
raise HomeAssistantError("Error {0} on {1}".format(
response.status, url))
image = yield from response.read()
return image
except (asyncio.TimeoutError, aiohttp.errors.ClientError):
raise HomeAssistantError("Can't connect to {0}".format(url))
finally:
if response is not None:
yield from response.release()
@asyncio.coroutine
def async_setup(hass, config):
"""Setup the camera component."""
component = EntityComponent(
logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL)
component = EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL)
hass.http.register_view(CameraImageView(component.entities))
hass.http.register_view(CameraMjpegStream(component.entities))
@@ -46,11 +89,13 @@ class Camera(Entity):
def __init__(self):
"""Initialize a camera."""
self.is_streaming = False
self._access_token = hashlib.sha256(
str.encode(str(id(self)))).hexdigest()
@property
def access_token(self):
"""Access token for this camera."""
return str(id(self))
return self._access_token
@property
def should_poll(self):
@@ -81,15 +126,12 @@ class Camera(Entity):
"""Return bytes of camera image."""
raise NotImplementedError()
@asyncio.coroutine
def async_camera_image(self):
"""Return bytes of camera image.
This method must be run in the event loop.
This method must be run in the event loop and returns a coroutine.
"""
image = yield from self.hass.loop.run_in_executor(
None, self.camera_image)
return image
return self.hass.loop.run_in_executor(None, self.camera_image)
@asyncio.coroutine
def handle_async_mjpeg_stream(self, request):
@@ -131,8 +173,14 @@ class Camera(Entity):
yield from response.drain()
yield from asyncio.sleep(.5)
except asyncio.CancelledError:
_LOGGER.debug("Close stream by frontend.")
response = None
finally:
yield from response.write_eof()
if response is not None:
yield from response.write_eof()
@property
def state(self):
@@ -201,12 +249,16 @@ class CameraImageView(CameraView):
@asyncio.coroutine
def handle(self, request, camera):
"""Serve camera image."""
image = yield from camera.async_camera_image()
try:
image = yield from camera.async_camera_image()
if image is None:
return web.Response(status=500)
if image is None:
return web.Response(status=500)
return web.Response(body=image)
return web.Response(body=image)
except asyncio.CancelledError:
_LOGGER.debug("Close stream by frontend.")
class CameraMjpegStream(CameraView):
+8 -2
View File
@@ -84,9 +84,15 @@ class FFmpegCamera(Camera):
if not data:
break
response.write(data)
except asyncio.CancelledError:
_LOGGER.debug("Close stream by frontend.")
response = None
finally:
self.hass.async_add_job(stream.close())
yield from response.write_eof()
yield from stream.close()
if response is not None:
yield from response.write_eof()
@property
def name(self):
+5 -1
View File
@@ -124,9 +124,13 @@ class MjpegCamera(Camera):
except asyncio.TimeoutError:
raise HTTPGatewayTimeout()
except asyncio.CancelledError:
_LOGGER.debug("Close stream by frontend.")
response = None
finally:
if stream is not None:
self.hass.async_add_job(stream.release())
stream.close()
if response is not None:
yield from response.write_eof()
+5 -1
View File
@@ -276,9 +276,13 @@ class SynologyCamera(Camera):
_LOGGER.exception("Error on %s", streaming_url)
raise HTTPGatewayTimeout()
except asyncio.CancelledError:
_LOGGER.debug("Close stream by frontend.")
response = None
finally:
if stream is not None:
self.hass.async_add_job(stream.release())
stream.close()
if response is not None:
yield from response.write_eof()
+1 -1
View File
@@ -14,7 +14,7 @@ from homeassistant.const import CONF_PORT
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['uvcclient==0.9.0']
REQUIREMENTS = ['uvcclient==0.10.0']
_LOGGER = logging.getLogger(__name__)
+154 -57
View File
@@ -4,15 +4,18 @@ Provides functionality to interact with climate devices.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/climate/
"""
import asyncio
from datetime import timedelta
import logging
import os
import functools as ft
from numbers import Number
import voluptuous as vol
from homeassistant.helpers.entity_component import EntityComponent
import voluptuous as vol
from homeassistant.config import load_yaml_config_file
from homeassistant.util.temperature import convert as convert_temperature
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
import homeassistant.helpers.config_validation as cv
@@ -23,7 +26,7 @@ from homeassistant.const import (
DOMAIN = "climate"
ENTITY_ID_FORMAT = DOMAIN + ".{}"
SCAN_INTERVAL = 60
SCAN_INTERVAL = timedelta(seconds=60)
SERVICE_SET_AWAY_MODE = "set_away_mode"
SERVICE_SET_AUX_HEAT = "set_aux_heat"
@@ -185,17 +188,38 @@ def set_swing_mode(hass, swing_mode, entity_id=None):
hass.services.call(DOMAIN, SERVICE_SET_SWING_MODE, data)
def setup(hass, config):
@asyncio.coroutine
def async_setup(hass, config):
"""Setup climate devices."""
component = EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL)
component.setup(config)
yield from component.async_setup(config)
descriptions = load_yaml_config_file(
descriptions = yield from hass.loop.run_in_executor(
None, load_yaml_config_file,
os.path.join(os.path.dirname(__file__), 'services.yaml'))
def away_mode_set_service(service):
@asyncio.coroutine
def _async_update_climate(target_climate):
"""Update climate entity after service stuff."""
update_tasks = []
for climate in target_climate:
if not climate.should_poll:
continue
update_coro = hass.loop.create_task(
climate.async_update_ha_state(True))
if hasattr(climate, 'async_update'):
update_tasks.append(update_coro)
else:
yield from update_coro
if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)
@asyncio.coroutine
def async_away_mode_set_service(service):
"""Set away mode on target climate devices."""
target_climate = component.extract_from_service(service)
target_climate = component.async_extract_from_service(service)
away_mode = service.data.get(ATTR_AWAY_MODE)
@@ -207,21 +231,21 @@ def setup(hass, config):
for climate in target_climate:
if away_mode:
climate.turn_away_mode_on()
yield from climate.async_turn_away_mode_on()
else:
climate.turn_away_mode_off()
yield from climate.async_turn_away_mode_off()
if climate.should_poll:
climate.update_ha_state(True)
yield from _async_update_climate(target_climate)
hass.services.register(
DOMAIN, SERVICE_SET_AWAY_MODE, away_mode_set_service,
hass.services.async_register(
DOMAIN, SERVICE_SET_AWAY_MODE, async_away_mode_set_service,
descriptions.get(SERVICE_SET_AWAY_MODE),
schema=SET_AWAY_MODE_SCHEMA)
def aux_heat_set_service(service):
@asyncio.coroutine
def async_aux_heat_set_service(service):
"""Set auxillary heater on target climate devices."""
target_climate = component.extract_from_service(service)
target_climate = component.async_extract_from_service(service)
aux_heat = service.data.get(ATTR_AUX_HEAT)
@@ -233,21 +257,21 @@ def setup(hass, config):
for climate in target_climate:
if aux_heat:
climate.turn_aux_heat_on()
yield from climate.async_turn_aux_heat_on()
else:
climate.turn_aux_heat_off()
yield from climate.async_turn_aux_heat_off()
if climate.should_poll:
climate.update_ha_state(True)
yield from _async_update_climate(target_climate)
hass.services.register(
DOMAIN, SERVICE_SET_AUX_HEAT, aux_heat_set_service,
hass.services.async_register(
DOMAIN, SERVICE_SET_AUX_HEAT, async_aux_heat_set_service,
descriptions.get(SERVICE_SET_AUX_HEAT),
schema=SET_AUX_HEAT_SCHEMA)
def temperature_set_service(service):
@asyncio.coroutine
def async_temperature_set_service(service):
"""Set temperature on the target climate devices."""
target_climate = component.extract_from_service(service)
target_climate = component.async_extract_from_service(service)
for climate in target_climate:
kwargs = {}
@@ -261,18 +285,19 @@ def setup(hass, config):
else:
kwargs[value] = temp
climate.set_temperature(**kwargs)
if climate.should_poll:
climate.update_ha_state(True)
yield from climate.async_set_temperature(**kwargs)
hass.services.register(
DOMAIN, SERVICE_SET_TEMPERATURE, temperature_set_service,
yield from _async_update_climate(target_climate)
hass.services.async_register(
DOMAIN, SERVICE_SET_TEMPERATURE, async_temperature_set_service,
descriptions.get(SERVICE_SET_TEMPERATURE),
schema=SET_TEMPERATURE_SCHEMA)
def humidity_set_service(service):
@asyncio.coroutine
def async_humidity_set_service(service):
"""Set humidity on the target climate devices."""
target_climate = component.extract_from_service(service)
target_climate = component.async_extract_from_service(service)
humidity = service.data.get(ATTR_HUMIDITY)
@@ -283,19 +308,19 @@ def setup(hass, config):
return
for climate in target_climate:
climate.set_humidity(humidity)
yield from climate.async_set_humidity(humidity)
if climate.should_poll:
climate.update_ha_state(True)
yield from _async_update_climate(target_climate)
hass.services.register(
DOMAIN, SERVICE_SET_HUMIDITY, humidity_set_service,
hass.services.async_register(
DOMAIN, SERVICE_SET_HUMIDITY, async_humidity_set_service,
descriptions.get(SERVICE_SET_HUMIDITY),
schema=SET_HUMIDITY_SCHEMA)
def fan_mode_set_service(service):
@asyncio.coroutine
def async_fan_mode_set_service(service):
"""Set fan mode on target climate devices."""
target_climate = component.extract_from_service(service)
target_climate = component.async_extract_from_service(service)
fan = service.data.get(ATTR_FAN_MODE)
@@ -306,19 +331,19 @@ def setup(hass, config):
return
for climate in target_climate:
climate.set_fan_mode(fan)
yield from climate.async_set_fan_mode(fan)
if climate.should_poll:
climate.update_ha_state(True)
yield from _async_update_climate(target_climate)
hass.services.register(
DOMAIN, SERVICE_SET_FAN_MODE, fan_mode_set_service,
hass.services.async_register(
DOMAIN, SERVICE_SET_FAN_MODE, async_fan_mode_set_service,
descriptions.get(SERVICE_SET_FAN_MODE),
schema=SET_FAN_MODE_SCHEMA)
def operation_set_service(service):
@asyncio.coroutine
def async_operation_set_service(service):
"""Set operating mode on the target climate devices."""
target_climate = component.extract_from_service(service)
target_climate = component.async_extract_from_service(service)
operation_mode = service.data.get(ATTR_OPERATION_MODE)
@@ -329,19 +354,19 @@ def setup(hass, config):
return
for climate in target_climate:
climate.set_operation_mode(operation_mode)
yield from climate.async_set_operation_mode(operation_mode)
if climate.should_poll:
climate.update_ha_state(True)
yield from _async_update_climate(target_climate)
hass.services.register(
DOMAIN, SERVICE_SET_OPERATION_MODE, operation_set_service,
hass.services.async_register(
DOMAIN, SERVICE_SET_OPERATION_MODE, async_operation_set_service,
descriptions.get(SERVICE_SET_OPERATION_MODE),
schema=SET_OPERATION_MODE_SCHEMA)
def swing_set_service(service):
@asyncio.coroutine
def async_swing_set_service(service):
"""Set swing mode on the target climate devices."""
target_climate = component.extract_from_service(service)
target_climate = component.async_extract_from_service(service)
swing_mode = service.data.get(ATTR_SWING_MODE)
@@ -352,15 +377,15 @@ def setup(hass, config):
return
for climate in target_climate:
climate.set_swing_mode(swing_mode)
yield from climate.async_set_swing_mode(swing_mode)
if climate.should_poll:
climate.update_ha_state(True)
yield from _async_update_climate(target_climate)
hass.services.register(
DOMAIN, SERVICE_SET_SWING_MODE, swing_set_service,
hass.services.async_register(
DOMAIN, SERVICE_SET_SWING_MODE, async_swing_set_service,
descriptions.get(SERVICE_SET_SWING_MODE),
schema=SET_SWING_MODE_SCHEMA)
return True
@@ -521,38 +546,110 @@ class ClimateDevice(Entity):
"""Set new target temperature."""
raise NotImplementedError()
def async_set_temperature(self, **kwargs):
"""Set new target temperature.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.loop.run_in_executor(
None, ft.partial(self.set_temperature, **kwargs))
def set_humidity(self, humidity):
"""Set new target humidity."""
raise NotImplementedError()
def async_set_humidity(self, humidity):
"""Set new target humidity.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.loop.run_in_executor(
None, self.set_humidity, humidity)
def set_fan_mode(self, fan):
"""Set new target fan mode."""
raise NotImplementedError()
def async_set_fan_mode(self, fan):
"""Set new target fan mode.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.loop.run_in_executor(
None, self.set_fan_mode, fan)
def set_operation_mode(self, operation_mode):
"""Set new target operation mode."""
raise NotImplementedError()
def async_set_operation_mode(self, operation_mode):
"""Set new target operation mode.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.loop.run_in_executor(
None, self.set_operation_mode, operation_mode)
def set_swing_mode(self, swing_mode):
"""Set new target swing operation."""
raise NotImplementedError()
def async_set_swing_mode(self, swing_mode):
"""Set new target swing operation.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.loop.run_in_executor(
None, self.set_swing_mode, swing_mode)
def turn_away_mode_on(self):
"""Turn away mode on."""
raise NotImplementedError()
def async_turn_away_mode_on(self):
"""Turn away mode on.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.loop.run_in_executor(
None, self.turn_away_mode_on)
def turn_away_mode_off(self):
"""Turn away mode off."""
raise NotImplementedError()
def async_turn_away_mode_off(self):
"""Turn away mode off.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.loop.run_in_executor(
None, self.turn_away_mode_off)
def turn_aux_heat_on(self):
"""Turn auxillary heater on."""
raise NotImplementedError()
def async_turn_aux_heat_on(self):
"""Turn auxillary heater on.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.loop.run_in_executor(
None, self.turn_aux_heat_on)
def turn_aux_heat_off(self):
"""Turn auxillary heater off."""
raise NotImplementedError()
def async_turn_aux_heat_off(self):
"""Turn auxillary heater off.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.loop.run_in_executor(
None, self.turn_aux_heat_off)
@property
def min_temp(self):
"""Return the minimum temperature."""
+39 -4
View File
@@ -22,16 +22,25 @@ _CONFIGURING = {}
_LOGGER = logging.getLogger(__name__)
ATTR_FAN_MIN_ON_TIME = 'fan_min_on_time'
ATTR_RESUME_ALL = 'resume_all'
DEFAULT_RESUME_ALL = False
DEPENDENCIES = ['ecobee']
SERVICE_SET_FAN_MIN_ON_TIME = 'ecobee_set_fan_min_on_time'
SERVICE_RESUME_PROGRAM = 'ecobee_resume_program'
SET_FAN_MIN_ON_TIME_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Required(ATTR_FAN_MIN_ON_TIME): vol.Coerce(int),
})
RESUME_PROGRAM_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional(ATTR_RESUME_ALL, default=DEFAULT_RESUME_ALL): cv.boolean,
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Ecobee Thermostat Platform."""
@@ -48,21 +57,36 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
def fan_min_on_time_set_service(service):
"""Set the minimum fan on time on the target thermostats."""
entity_id = service.data.get('entity_id')
entity_id = service.data.get(ATTR_ENTITY_ID)
fan_min_on_time = service.data[ATTR_FAN_MIN_ON_TIME]
if entity_id:
target_thermostats = [device for device in devices
if device.entity_id == entity_id]
if device.entity_id in entity_id]
else:
target_thermostats = devices
fan_min_on_time = service.data[ATTR_FAN_MIN_ON_TIME]
for thermostat in target_thermostats:
thermostat.set_fan_min_on_time(str(fan_min_on_time))
thermostat.update_ha_state(True)
def resume_program_set_service(service):
"""Resume the program on the target thermostats."""
entity_id = service.data.get(ATTR_ENTITY_ID)
resume_all = service.data.get(ATTR_RESUME_ALL)
if entity_id:
target_thermostats = [device for device in devices
if device.entity_id in entity_id]
else:
target_thermostats = devices
for thermostat in target_thermostats:
thermostat.resume_program(resume_all)
thermostat.update_ha_state(True)
descriptions = load_yaml_config_file(
path.join(path.dirname(__file__), 'services.yaml'))
@@ -71,6 +95,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
descriptions.get(SERVICE_SET_FAN_MIN_ON_TIME),
schema=SET_FAN_MIN_ON_TIME_SCHEMA)
hass.services.register(
DOMAIN, SERVICE_RESUME_PROGRAM, resume_program_set_service,
descriptions.get(SERVICE_RESUME_PROGRAM),
schema=RESUME_PROGRAM_SCHEMA)
class Thermostat(ClimateDevice):
"""A thermostat class for Ecobee."""
@@ -249,6 +278,12 @@ class Thermostat(ClimateDevice):
fan_min_on_time)
self.update_without_throttle = True
def resume_program(self, resume_all):
"""Resume the thermostat schedule program."""
self.data.ecobee.resume_program(self.thermostat_index,
str(resume_all).lower())
self.update_without_throttle = True
# Home and Sleep mode aren't used in UI yet:
# def turn_home_mode_on(self):
+73 -17
View File
@@ -8,18 +8,27 @@ import logging
import voluptuous as vol
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
from homeassistant.components.climate import (
ClimateDevice, PLATFORM_SCHEMA, PRECISION_HALVES,
STATE_UNKNOWN, STATE_AUTO, STATE_ON, STATE_OFF,
)
from homeassistant.const import (
CONF_MAC, TEMP_CELSIUS, CONF_DEVICES, ATTR_TEMPERATURE)
from homeassistant.util.temperature import convert
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['bluepy_devices==0.2.0']
REQUIREMENTS = ['python-eq3bt==0.1.2']
_LOGGER = logging.getLogger(__name__)
ATTR_MODE = 'mode'
ATTR_MODE_READABLE = 'mode_readable'
STATE_BOOST = "boost"
STATE_AWAY = "away"
STATE_MANUAL = "manual"
ATTR_STATE_WINDOW_OPEN = "window_open"
ATTR_STATE_VALVE = "valve"
ATTR_STATE_LOCKED = "is_locked"
ATTR_STATE_LOW_BAT = "low_battery"
DEVICE_SCHEMA = vol.Schema({
vol.Required(CONF_MAC): cv.string,
@@ -48,10 +57,23 @@ class EQ3BTSmartThermostat(ClimateDevice):
def __init__(self, _mac, _name):
"""Initialize the thermostat."""
from bluepy_devices.devices import eq3btsmart
# we want to avoid name clash with this module..
import eq3bt as eq3
self.modes = {None: STATE_UNKNOWN, # When not yet connected.
eq3.Mode.Unknown: STATE_UNKNOWN,
eq3.Mode.Auto: STATE_AUTO,
# away handled separately, here just for reverse mapping
eq3.Mode.Away: STATE_AWAY,
eq3.Mode.Closed: STATE_OFF,
eq3.Mode.Open: STATE_ON,
eq3.Mode.Manual: STATE_MANUAL,
eq3.Mode.Boost: STATE_BOOST}
self.reverse_modes = {v: k for k, v in self.modes.items()}
self._name = _name
self._thermostat = eq3btsmart.EQ3BTSmartThermostat(_mac)
self._thermostat = eq3.Thermostat(_mac)
@property
def name(self):
@@ -63,6 +85,11 @@ class EQ3BTSmartThermostat(ClimateDevice):
"""Return the unit of measurement that is used."""
return TEMP_CELSIUS
@property
def precision(self):
"""Return eq3bt's precision 0.5."""
return PRECISION_HALVES
@property
def current_temperature(self):
"""Can not report temperature, so return target_temperature."""
@@ -81,24 +108,53 @@ class EQ3BTSmartThermostat(ClimateDevice):
self._thermostat.target_temperature = temperature
@property
def device_state_attributes(self):
"""Return the device specific state attributes."""
return {
ATTR_MODE: self._thermostat.mode,
ATTR_MODE_READABLE: self._thermostat.mode_readable,
}
def current_operation(self):
"""Current mode."""
return self.modes[self._thermostat.mode]
@property
def operation_list(self):
"""List of available operation modes."""
return [x for x in self.modes.values()]
def set_operation_mode(self, operation_mode):
"""Set operation mode."""
self._thermostat.mode = self.reverse_modes[operation_mode]
def turn_away_mode_off(self):
"""Away mode off turns to AUTO mode."""
self.set_operation_mode(STATE_AUTO)
def turn_away_mode_on(self):
"""Set away mode on."""
self.set_operation_mode(STATE_AWAY)
@property
def is_away_mode_on(self):
"""Return if we are away."""
return self.current_operation == STATE_AWAY
@property
def min_temp(self):
"""Return the minimum temperature."""
return convert(self._thermostat.min_temp, TEMP_CELSIUS,
self.unit_of_measurement)
return self._thermostat.min_temp
@property
def max_temp(self):
"""Return the maximum temperature."""
return convert(self._thermostat.max_temp, TEMP_CELSIUS,
self.unit_of_measurement)
return self._thermostat.max_temp
@property
def device_state_attributes(self):
"""Return the device specific state attributes."""
dev_specific = {
ATTR_STATE_LOCKED: self._thermostat.locked,
ATTR_STATE_LOW_BAT: self._thermostat.low_battery,
ATTR_STATE_VALVE: self._thermostat.valve_state,
ATTR_STATE_WINDOW_OPEN: self._thermostat.window_open,
}
return dev_specific
def update(self):
"""Update the data from the thermostat."""
@@ -39,7 +39,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
}
devices = {}
gateway.platform_callbacks.append(mysensors.pf_callback_factory(
map_sv_types, devices, add_devices, MySensorsHVAC))
map_sv_types, devices, MySensorsHVAC, add_devices))
class MySensorsHVAC(mysensors.MySensorsDeviceEntity, ClimateDevice):
+25 -1
View File
@@ -76,7 +76,7 @@ set_operation_mode:
fields:
entity_id:
description: Name(s) of entities to change
example: 'climet.nest'
example: 'climate.nest'
operation_mode:
description: New value of operation mode
@@ -94,3 +94,27 @@ set_swing_mode:
swing_mode:
description: New value of swing mode
example: 1
ecobee_set_fan_min_on_time:
description: Set the minimum fan on time
fields:
entity_id:
description: Name(s) of entities to change
example: 'climate.kitchen'
fan_min_on_time:
description: New value of fan min on time
example: 5
ecobee_resume_program:
description: Resume the programmed schedule
fields:
entity_id:
description: Name(s) of entities to change
example: 'climate.kitchen'
resume_all:
description: Resume all events and return to the scheduled program. This default to false which removes only the top event.
example: true
+1 -1
View File
@@ -89,9 +89,9 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
"""Called when a value has changed on the network."""
if self._value.value_id == value.value_id or \
self._value.node == value.node:
_LOGGER.debug('Value changed for label %s', self._value.label)
self.update_properties()
self.schedule_update_ha_state()
_LOGGER.debug("Value changed on network %s", value)
def update_properties(self):
"""Callback on data change for the registered node/value pair."""
+2 -1
View File
@@ -5,6 +5,7 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/cover/
"""
import os
from datetime import timedelta
import logging
import voluptuous as vol
@@ -23,7 +24,7 @@ from homeassistant.const import (
DOMAIN = 'cover'
SCAN_INTERVAL = 15
SCAN_INTERVAL = timedelta(seconds=15)
GROUP_NAME_ALL_COVERS = 'all covers'
ENTITY_ID_ALL_COVERS = group.ENTITY_ID_FORMAT.format('all_covers')
+1 -1
View File
@@ -35,7 +35,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
})
devices = {}
gateway.platform_callbacks.append(mysensors.pf_callback_factory(
map_sv_types, devices, add_devices, MySensorsCover))
map_sv_types, devices, MySensorsCover, add_devices))
class MySensorsCover(mysensors.MySensorsDeviceEntity, CoverDevice):
+4 -4
View File
@@ -78,9 +78,9 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice):
"""Called when a value has changed on the network."""
if self._value.value_id == value.value_id or \
self._value.node == value.node:
_LOGGER.debug('Value changed for label %s', self._value.label)
self.update_properties()
self.update_ha_state()
_LOGGER.debug("Value changed on network %s", value)
self.schedule_update_ha_state()
def update_properties(self):
"""Callback on data change for the registered node/value pair."""
@@ -170,9 +170,9 @@ class ZwaveGarageDoor(zwave.ZWaveDeviceEntity, CoverDevice):
def value_changed(self, value):
"""Called when a value has changed on the network."""
if self._value.value_id == value.value_id:
_LOGGER.debug('Value changed for label %s', self._value.label)
self._state = value.data
self.update_ha_state()
_LOGGER.debug("Value changed on network %s", value)
self.schedule_update_ha_state()
@property
def is_closed(self):
+2
View File
@@ -23,12 +23,14 @@ COMPONENTS_WITH_DEMO_PLATFORM = [
'cover',
'device_tracker',
'fan',
'image_processing',
'light',
'lock',
'media_player',
'notify',
'sensor',
'switch',
'tts',
]
@@ -8,7 +8,7 @@ import asyncio
from datetime import timedelta
import logging
import os
from typing import Any, Sequence, Callable
from typing import Any, List, Sequence, Callable
import aiohttp
import async_timeout
@@ -24,6 +24,7 @@ from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers import config_per_platform, discovery
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.typing import GPSType, ConfigType, HomeAssistantType
import homeassistant.helpers.config_validation as cv
import homeassistant.util as util
@@ -50,10 +51,10 @@ CONF_TRACK_NEW = 'track_new_devices'
DEFAULT_TRACK_NEW = True
CONF_CONSIDER_HOME = 'consider_home'
DEFAULT_CONSIDER_HOME = 180
DEFAULT_CONSIDER_HOME = timedelta(seconds=180)
CONF_SCAN_INTERVAL = 'interval_seconds'
DEFAULT_SCAN_INTERVAL = 12
DEFAULT_SCAN_INTERVAL = timedelta(seconds=12)
CONF_AWAY_HIDE = 'hide_if_away'
DEFAULT_AWAY_HIDE = False
@@ -69,12 +70,16 @@ ATTR_LOCATION_NAME = 'location_name'
ATTR_GPS = 'gps'
ATTR_BATTERY = 'battery'
ATTR_ATTRIBUTES = 'attributes'
ATTR_SOURCE_TYPE = 'source_type'
SOURCE_TYPE_GPS = 'gps'
SOURCE_TYPE_ROUTER = 'router'
PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({
vol.Optional(CONF_SCAN_INTERVAL): cv.positive_int, # seconds
vol.Optional(CONF_SCAN_INTERVAL): cv.time_period,
vol.Optional(CONF_TRACK_NEW, default=DEFAULT_TRACK_NEW): cv.boolean,
vol.Optional(CONF_CONSIDER_HOME,
default=timedelta(seconds=DEFAULT_CONSIDER_HOME)): vol.All(
default=DEFAULT_CONSIDER_HOME): vol.All(
cv.time_period, cv.positive_timedelta)
})
@@ -121,8 +126,7 @@ def async_setup(hass: HomeAssistantType, config: ConfigType):
return False
else:
conf = conf[0] if len(conf) > 0 else {}
consider_home = conf.get(CONF_CONSIDER_HOME,
timedelta(seconds=DEFAULT_CONSIDER_HOME))
consider_home = conf.get(CONF_CONSIDER_HOME, DEFAULT_CONSIDER_HOME)
track_new = conf.get(CONF_TRACK_NEW, DEFAULT_TRACK_NEW)
devices = yield from async_load_config(yaml_path, hass, consider_home)
@@ -142,23 +146,34 @@ def async_setup(hass: HomeAssistantType, config: ConfigType):
if platform is None:
return
_LOGGER.info("Setting up %s.%s", DOMAIN, p_type)
try:
if hasattr(platform, 'get_scanner'):
scanner = None
setup = None
if hasattr(platform, 'async_get_scanner'):
scanner = yield from platform.async_get_scanner(
hass, {DOMAIN: p_config})
elif hasattr(platform, 'get_scanner'):
scanner = yield from hass.loop.run_in_executor(
None, platform.get_scanner, hass, {DOMAIN: p_config})
elif hasattr(platform, 'async_setup_scanner'):
setup = yield from platform.async_setup_scanner(
hass, p_config, tracker.async_see)
elif hasattr(platform, 'setup_scanner'):
setup = yield from hass.loop.run_in_executor(
None, platform.setup_scanner, hass, p_config, tracker.see)
else:
raise HomeAssistantError("Invalid device_tracker platform.")
if scanner is None:
_LOGGER.error('Error setting up platform %s', p_type)
return
if scanner:
yield from async_setup_scanner_platform(
hass, p_config, scanner, tracker.async_see)
return
ret = yield from hass.loop.run_in_executor(
None, platform.setup_scanner, hass, p_config, tracker.see)
if not ret:
if not setup:
_LOGGER.error('Error setting up platform %s', p_type)
return
except Exception: # pylint: disable=broad-except
_LOGGER.exception('Error setting up platform %s', p_type)
@@ -223,17 +238,19 @@ class DeviceTracker(object):
def see(self, mac: str=None, dev_id: str=None, host_name: str=None,
location_name: str=None, gps: GPSType=None, gps_accuracy=None,
battery: str=None, attributes: dict=None):
battery: str=None, attributes: dict=None,
source_type: str=SOURCE_TYPE_GPS):
"""Notify the device tracker that you see a device."""
self.hass.add_job(
self.async_see(mac, dev_id, host_name, location_name, gps,
gps_accuracy, battery, attributes)
gps_accuracy, battery, attributes, source_type)
)
@asyncio.coroutine
def async_see(self, mac: str=None, dev_id: str=None, host_name: str=None,
location_name: str=None, gps: GPSType=None,
gps_accuracy=None, battery: str=None, attributes: dict=None):
gps_accuracy=None, battery: str=None, attributes: dict=None,
source_type: str=SOURCE_TYPE_GPS):
"""Notify the device tracker that you see a device.
This method is a coroutine.
@@ -251,7 +268,8 @@ class DeviceTracker(object):
if device:
yield from device.async_seen(host_name, location_name, gps,
gps_accuracy, battery, attributes)
gps_accuracy, battery, attributes,
source_type)
if device.track:
yield from device.async_update_ha_state()
return
@@ -266,7 +284,8 @@ class DeviceTracker(object):
self.mac_to_dev[mac] = device
yield from device.async_seen(host_name, location_name, gps,
gps_accuracy, battery, attributes)
gps_accuracy, battery, attributes,
source_type)
if device.track:
yield from device.async_update_ha_state()
@@ -370,6 +389,9 @@ class Device(Entity):
self.away_hide = hide_if_away
self.vendor = vendor
self.source_type = None
self._attributes = {}
@property
@@ -390,7 +412,9 @@ class Device(Entity):
@property
def state_attributes(self):
"""Return the device state attributes."""
attr = {}
attr = {
ATTR_SOURCE_TYPE: self.source_type
}
if self.gps:
attr[ATTR_LATITUDE] = self.gps[0]
@@ -415,12 +439,13 @@ class Device(Entity):
@asyncio.coroutine
def async_seen(self, host_name: str=None, location_name: str=None,
gps: GPSType=None, gps_accuracy=0, battery: str=None,
attributes: dict=None):
attributes: dict=None, source_type: str=SOURCE_TYPE_GPS):
"""Mark the device as seen."""
self.source_type = source_type
self.last_seen = dt_util.utcnow()
self.host_name = host_name
self.location_name = location_name
self.gps_accuracy = gps_accuracy or 0
if battery:
self.battery = battery
if attributes:
@@ -431,7 +456,10 @@ class Device(Entity):
if gps is not None:
try:
self.gps = float(gps[0]), float(gps[1])
self.gps_accuracy = gps_accuracy or 0
except (ValueError, TypeError, IndexError):
self.gps = None
self.gps_accuracy = 0
_LOGGER.warning('Could not parse gps value for %s: %s',
self.dev_id, gps)
@@ -456,7 +484,7 @@ class Device(Entity):
return
elif self.location_name:
self._state = self.location_name
elif self.gps is not None:
elif self.gps is not None and self.source_type == SOURCE_TYPE_GPS:
zone_state = zone.async_active_zone(
self.hass, self.gps[0], self.gps[1], self.gps_accuracy)
if zone_state is None:
@@ -465,9 +493,9 @@ class Device(Entity):
self._state = STATE_HOME
else:
self._state = zone_state.name
elif self.stale():
self._state = STATE_NOT_HOME
self.gps = None
self.last_update_home = False
else:
self._state = STATE_HOME
@@ -485,13 +513,18 @@ class Device(Entity):
if not self.mac:
return None
if '_' in self.mac:
_, mac = self.mac.split('_', 1)
else:
mac = self.mac
# prevent lookup of invalid macs
if not len(self.mac.split(':')) == 6:
if not len(mac.split(':')) == 6:
return 'unknown'
# we only need the first 3 bytes of the mac for a lookup
# this improves somewhat on privacy
oui_bytes = self.mac.split(':')[0:3]
oui_bytes = mac.split(':')[0:3]
# bytes like 00 get truncates to 0, API needs full bytes
oui = '{:02x}:{:02x}:{:02x}'.format(*[int(b, 16) for b in oui_bytes])
url = 'http://api.macvendors.com/' + oui
@@ -521,6 +554,34 @@ class Device(Entity):
yield from resp.release()
class DeviceScanner(object):
"""Device scanner object."""
hass = None # type: HomeAssistantType
def scan_devices(self) -> List[str]:
"""Scan for devices."""
raise NotImplementedError()
def async_scan_devices(self) -> Any:
"""Scan for devices.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.loop.run_in_executor(None, self.scan_devices)
def get_device_name(self, mac: str) -> str:
"""Get device name from mac."""
raise NotImplementedError()
def async_get_device_name(self, mac: str) -> Any:
"""Get device name from mac.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.loop.run_in_executor(None, self.get_device_name, mac)
def load_config(path: str, hass: HomeAssistantType, consider_home: timedelta):
"""Load devices from YAML configuration file."""
return run_coroutine_threadsafe(
@@ -577,26 +638,39 @@ def async_setup_scanner_platform(hass: HomeAssistantType, config: ConfigType,
This method is a coroutine.
"""
interval = config.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)
scanner.hass = hass
# Initial scan of each mac we also tell about host name for config
seen = set() # type: Any
def device_tracker_scan(now: dt_util.dt.datetime):
@asyncio.coroutine
def async_device_tracker_scan(now: dt_util.dt.datetime):
"""Called when interval matches."""
found_devices = scanner.scan_devices()
found_devices = yield from scanner.async_scan_devices()
for mac in found_devices:
if mac in seen:
host_name = None
else:
host_name = scanner.get_device_name(mac)
host_name = yield from scanner.async_get_device_name(mac)
seen.add(mac)
hass.add_job(async_see_device(mac=mac, host_name=host_name))
async_track_utc_time_change(
hass, device_tracker_scan, second=range(0, 60, interval))
kwargs = {
'mac': mac,
'host_name': host_name,
'source_type': SOURCE_TYPE_ROUTER
}
hass.async_add_job(device_tracker_scan, None)
zone_home = hass.states.get(zone.ENTITY_ID_HOME)
if zone_home:
kwargs['gps'] = [zone_home.attributes[ATTR_LATITUDE],
zone_home.attributes[ATTR_LONGITUDE]]
kwargs['gps_accuracy'] = 0
hass.async_add_job(async_see_device(**kwargs))
async_track_time_interval(hass, async_device_tracker_scan, interval)
hass.async_add_job(async_device_tracker_scan, None)
def update_config(path: str, dev_id: str, device: Device):
@@ -14,7 +14,8 @@ import voluptuous as vol
import homeassistant.helpers.config_validation as cv
import homeassistant.util.dt as dt_util
from homeassistant.components.device_tracker import (DOMAIN, PLATFORM_SCHEMA)
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.util import Throttle
@@ -46,7 +47,7 @@ def get_scanner(hass, config):
Device = namedtuple("Device", ["mac", "ip", "last_update"])
class ActiontecDeviceScanner(object):
class ActiontecDeviceScanner(DeviceScanner):
"""This class queries a an actiontec router for connected devices."""
def __init__(self, config):
@@ -12,7 +12,8 @@ from datetime import timedelta
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import DOMAIN, PLATFORM_SCHEMA
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.util import Throttle
@@ -42,7 +43,7 @@ def get_scanner(hass, config):
return scanner if scanner.success_init else None
class ArubaDeviceScanner(object):
class ArubaDeviceScanner(DeviceScanner):
"""This class queries a Aruba Access Point for connected devices."""
def __init__(self, config):
@@ -14,7 +14,8 @@ from datetime import timedelta
import voluptuous as vol
from homeassistant.components.device_tracker import DOMAIN, PLATFORM_SCHEMA
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.util import Throttle
import homeassistant.helpers.config_validation as cv
@@ -97,7 +98,7 @@ def get_scanner(hass, config):
AsusWrtResult = namedtuple('AsusWrtResult', 'neighbors leases arp nvram')
class AsusWrtDeviceScanner(object):
class AsusWrtDeviceScanner(DeviceScanner):
"""This class queries a router running ASUSWRT firmware."""
# Eighth attribute needed for mode (AP mode vs router mode)
@@ -11,8 +11,8 @@ import requests
import voluptuous as vol
from homeassistant.components.device_tracker import (PLATFORM_SCHEMA,
ATTR_ATTRIBUTES)
from homeassistant.components.device_tracker import (
PLATFORM_SCHEMA, ATTR_ATTRIBUTES)
from homeassistant.const import CONF_USERNAME, CONF_PASSWORD
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import track_utc_time_change
@@ -9,7 +9,7 @@ import logging
from datetime import timedelta
import homeassistant.util.dt as dt_util
from homeassistant.components.device_tracker import DOMAIN
from homeassistant.components.device_tracker import DOMAIN, DeviceScanner
from homeassistant.util import Throttle
REQUIREMENTS = ['pybbox==0.0.5-alpha']
@@ -29,7 +29,7 @@ def get_scanner(hass, config):
Device = namedtuple('Device', ['mac', 'name', 'ip', 'last_update'])
class BboxDeviceScanner(object):
class BboxDeviceScanner(DeviceScanner):
"""This class scans for devices connected to the bbox."""
def __init__(self, config):
@@ -5,13 +5,8 @@ from datetime import timedelta
import voluptuous as vol
from homeassistant.helpers.event import track_point_in_utc_time
from homeassistant.components.device_tracker import (
YAML_DEVICES,
CONF_TRACK_NEW,
CONF_SCAN_INTERVAL,
DEFAULT_SCAN_INTERVAL,
PLATFORM_SCHEMA,
load_config,
DEFAULT_TRACK_NEW
YAML_DEVICES, CONF_TRACK_NEW, CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL,
PLATFORM_SCHEMA, load_config, DEFAULT_TRACK_NEW
)
import homeassistant.util as util
import homeassistant.util.dt as dt_util
@@ -24,9 +19,11 @@ REQUIREMENTS = ['gattlib==0.20150805']
BLE_PREFIX = 'BLE_'
MIN_SEEN_NEW = 5
CONF_SCAN_DURATION = "scan_duration"
CONF_BLUETOOTH_DEVICE = "device_id"
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_SCAN_DURATION, default=10): cv.positive_int
vol.Optional(CONF_SCAN_DURATION, default=10): cv.positive_int,
vol.Optional(CONF_BLUETOOTH_DEVICE, default="hci0"): cv.string
})
@@ -60,7 +57,7 @@ def setup_scanner(hass, config, see):
"""Discover Bluetooth LE devices."""
_LOGGER.debug("Discovering Bluetooth LE devices")
try:
service = DiscoveryService()
service = DiscoveryService(ble_dev_id)
devices = service.discover(duration)
_LOGGER.debug("Bluetooth LE devices discovered = %s", devices)
except RuntimeError as error:
@@ -70,6 +67,7 @@ def setup_scanner(hass, config, see):
yaml_path = hass.config.path(YAML_DEVICES)
duration = config.get(CONF_SCAN_DURATION)
ble_dev_id = config.get(CONF_BLUETOOTH_DEVICE)
devs_to_track = []
devs_donot_track = []
@@ -16,7 +16,8 @@ import requests
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import DOMAIN, PLATFORM_SCHEMA
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST
from homeassistant.util import Throttle
@@ -40,7 +41,7 @@ def get_scanner(hass, config):
return scanner if scanner.success_init else None
class BTHomeHub5DeviceScanner(object):
class BTHomeHub5DeviceScanner(DeviceScanner):
"""This class queries a BT Home Hub 5."""
def __init__(self, config):
@@ -10,7 +10,8 @@ from datetime import timedelta
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import DOMAIN, PLATFORM_SCHEMA
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME, \
CONF_PORT
from homeassistant.util import Throttle
@@ -39,7 +40,7 @@ def get_scanner(hass, config):
return scanner if scanner.success_init else None
class CiscoDeviceScanner(object):
class CiscoDeviceScanner(DeviceScanner):
"""This class queries a wireless router running Cisco IOS firmware."""
def __init__(self, config):
@@ -13,7 +13,8 @@ import requests
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import DOMAIN, PLATFORM_SCHEMA
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.util import Throttle
@@ -41,7 +42,7 @@ def get_scanner(hass, config):
return None
class DdWrtDeviceScanner(object):
class DdWrtDeviceScanner(DeviceScanner):
"""This class queries a wireless router running DD-WRT firmware."""
def __init__(self, config):
@@ -10,7 +10,8 @@ from datetime import timedelta
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import DOMAIN, PLATFORM_SCHEMA
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.util import Throttle
@@ -38,7 +39,7 @@ def get_scanner(hass, config):
return scanner if scanner.success_init else None
class FritzBoxScanner(object):
class FritzBoxScanner(DeviceScanner):
"""This class queries a FRITZ!Box router."""
def __init__(self, config):
@@ -12,7 +12,7 @@ import voluptuous as vol
from homeassistant.const import CONF_USERNAME, CONF_PASSWORD
from homeassistant.components.device_tracker import (
PLATFORM_SCHEMA, DOMAIN, ATTR_ATTRIBUTES, ENTITY_ID_FORMAT)
PLATFORM_SCHEMA, DOMAIN, ATTR_ATTRIBUTES, ENTITY_ID_FORMAT, DeviceScanner)
from homeassistant.components.zone import active_zone
from homeassistant.helpers.event import track_utc_time_change
import homeassistant.helpers.config_validation as cv
@@ -131,7 +131,7 @@ def setup_scanner(hass, config: dict, see):
return True
class Icloud(object):
class Icloud(DeviceScanner):
"""Represent an icloud account in Home Assistant."""
def __init__(self, hass, username, password, name, see):
@@ -8,9 +8,8 @@ import asyncio
from functools import partial
import logging
from homeassistant.const import (ATTR_LATITUDE, ATTR_LONGITUDE,
STATE_NOT_HOME,
HTTP_UNPROCESSABLE_ENTITY)
from homeassistant.const import (
ATTR_LATITUDE, ATTR_LONGITUDE, STATE_NOT_HOME, HTTP_UNPROCESSABLE_ENTITY)
from homeassistant.components.http import HomeAssistantView
# pylint: disable=unused-import
from homeassistant.components.device_tracker import ( # NOQA
@@ -64,18 +63,18 @@ class LocativeView(HomeAssistantView):
return ('Device id not specified.',
HTTP_UNPROCESSABLE_ENTITY)
if 'id' not in data:
_LOGGER.error('Location id not specified.')
return ('Location id not specified.',
HTTP_UNPROCESSABLE_ENTITY)
if 'trigger' not in data:
_LOGGER.error('Trigger is not specified.')
return ('Trigger is not specified.',
HTTP_UNPROCESSABLE_ENTITY)
if 'id' not in data and data['trigger'] != 'test':
_LOGGER.error('Location id not specified.')
return ('Location id not specified.',
HTTP_UNPROCESSABLE_ENTITY)
device = data['device'].replace('-', '')
location_name = data['id'].lower()
location_name = data.get('id', data['trigger']).lower()
direction = data['trigger']
gps_location = (data[ATTR_LATITUDE], data[ATTR_LONGITUDE])
@@ -14,7 +14,8 @@ import requests
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import DOMAIN, PLATFORM_SCHEMA
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.util import Throttle
@@ -37,7 +38,7 @@ def get_scanner(hass, config):
return scanner if scanner.success_init else None
class LuciDeviceScanner(object):
class LuciDeviceScanner(DeviceScanner):
"""This class queries a wireless router running OpenWrt firmware.
Adapted from Tomato scanner.
@@ -11,7 +11,8 @@ from datetime import timedelta
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import DOMAIN, PLATFORM_SCHEMA
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import (
CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_PORT)
from homeassistant.util import Throttle
@@ -47,7 +48,7 @@ def get_scanner(hass, config):
return scanner if scanner.success_init else None
class NetgearDeviceScanner(object):
class NetgearDeviceScanner(DeviceScanner):
"""Queries a Netgear wireless router using the SOAP-API."""
def __init__(self, host, username, password, port):
@@ -14,7 +14,8 @@ import voluptuous as vol
import homeassistant.helpers.config_validation as cv
import homeassistant.util.dt as dt_util
from homeassistant.components.device_tracker import DOMAIN, PLATFORM_SCHEMA
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOSTS
from homeassistant.util import Throttle
@@ -63,7 +64,7 @@ def _arp(ip_address):
return None
class NmapDeviceScanner(object):
class NmapDeviceScanner(DeviceScanner):
"""This class scans for devices using nmap."""
exclude = []
@@ -0,0 +1,92 @@
"""
Tracks devices by sending a ICMP ping.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.ping/
device_tracker:
- platform: ping
count: 2
hosts:
host_one: pc.local
host_two: 192.168.2.25
"""
import logging
import subprocess
import sys
from datetime import timedelta
import voluptuous as vol
from homeassistant.components.device_tracker import (
PLATFORM_SCHEMA, DEFAULT_SCAN_INTERVAL)
from homeassistant.helpers.event import track_point_in_utc_time
from homeassistant import util
from homeassistant import const
import homeassistant.helpers.config_validation as cv
DEPENDENCIES = []
_LOGGER = logging.getLogger(__name__)
CONF_PING_COUNT = 'count'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(const.CONF_HOSTS): {cv.string: cv.string},
vol.Optional(CONF_PING_COUNT, default=1): cv.positive_int,
})
class Host:
"""Host object with ping detection."""
def __init__(self, ip_address, dev_id, hass, config):
"""Initialize the Host pinger."""
self.hass = hass
self.ip_address = ip_address
self.dev_id = dev_id
self._count = config[CONF_PING_COUNT]
if sys.platform == "win32":
self._ping_cmd = ['ping', '-n 1', '-w 1000', self.ip_address]
else:
self._ping_cmd = ['ping', '-n', '-q', '-c1', '-W1',
self.ip_address]
def ping(self):
"""Send ICMP ping and return True if success."""
pinger = subprocess.Popen(self._ping_cmd, stdout=subprocess.PIPE)
try:
pinger.communicate()
return pinger.returncode == 0
except subprocess.CalledProcessError:
return False
def update(self, see):
"""Update device state by sending one or more ping messages."""
failed = 0
while failed < self._count: # check more times if host in unreachable
if self.ping():
see(dev_id=self.dev_id)
return True
failed += 1
_LOGGER.debug("ping KO on ip=%s failed=%d", self.ip_address, failed)
def setup_scanner(hass, config, see):
"""Setup the Host objects and return the update function."""
hosts = [Host(ip, dev_id, hass, config) for (dev_id, ip) in
config[const.CONF_HOSTS].items()]
interval = timedelta(seconds=len(hosts) * config[CONF_PING_COUNT]) + \
DEFAULT_SCAN_INTERVAL
_LOGGER.info("Started ping tracker with interval=%s on hosts: %s",
interval, ",".join([host.ip_address for host in hosts]))
def update(now):
"""Update all the hosts on every interval time."""
for host in hosts:
host.update(see)
track_point_in_utc_time(hass, update, now + interval)
return True
return update(util.dt.utcnow())
@@ -12,7 +12,8 @@ from datetime import timedelta
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import DOMAIN, PLATFORM_SCHEMA
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST
from homeassistant.util import Throttle
@@ -46,7 +47,7 @@ def get_scanner(hass, config):
return scanner if scanner.success_init else None
class SnmpScanner(object):
class SnmpScanner(DeviceScanner):
"""Queries any SNMP capable Access Point for connected devices."""
def __init__(self, config):
@@ -12,7 +12,8 @@ import requests
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import DOMAIN, PLATFORM_SCHEMA
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST
from homeassistant.util import Throttle
@@ -35,7 +36,7 @@ def get_scanner(hass, config):
return scanner if scanner.success_init else None
class SwisscomDeviceScanner(object):
class SwisscomDeviceScanner(DeviceScanner):
"""This class queries a router running Swisscom Internet-Box firmware."""
def __init__(self, config):
@@ -13,7 +13,8 @@ from datetime import timedelta
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import DOMAIN, PLATFORM_SCHEMA
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.util import Throttle
@@ -46,7 +47,7 @@ def get_scanner(hass, config):
return scanner if scanner.success_init else None
class ThomsonDeviceScanner(object):
class ThomsonDeviceScanner(DeviceScanner):
"""This class queries a router running THOMSON firmware."""
def __init__(self, config):
@@ -14,7 +14,8 @@ import requests
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import DOMAIN, PLATFORM_SCHEMA
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.util import Throttle
@@ -38,7 +39,7 @@ def get_scanner(hass, config):
return TomatoDeviceScanner(config[DOMAIN])
class TomatoDeviceScanner(object):
class TomatoDeviceScanner(DeviceScanner):
"""This class queries a wireless router running Tomato firmware."""
def __init__(self, config):
@@ -15,7 +15,8 @@ import requests
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import DOMAIN, PLATFORM_SCHEMA
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.util import Throttle
@@ -42,7 +43,7 @@ def get_scanner(hass, config):
return None
class TplinkDeviceScanner(object):
class TplinkDeviceScanner(DeviceScanner):
"""This class queries a wireless router running TP-Link firmware."""
def __init__(self, config):
@@ -0,0 +1,79 @@
"""
Support for the TrackR platform.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.trackr/
"""
import logging
import voluptuous as vol
from homeassistant.components.device_tracker import PLATFORM_SCHEMA
from homeassistant.const import CONF_USERNAME, CONF_PASSWORD
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import track_utc_time_change
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['pytrackr==0.0.5']
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string
})
def setup_scanner(hass, config: dict, see):
"""Validate the configuration and return a TrackR scanner."""
TrackRDeviceScanner(hass, config, see)
return True
class TrackRDeviceScanner(object):
"""A class representing a TrackR device."""
def __init__(self, hass, config: dict, see) -> None:
"""Initialize the TrackR device scanner."""
from pytrackr.api import trackrApiInterface
self.hass = hass
self.api = trackrApiInterface(config.get(CONF_USERNAME),
config.get(CONF_PASSWORD))
self.see = see
self.devices = self.api.get_trackrs()
self._update_info()
track_utc_time_change(self.hass, self._update_info,
second=range(0, 60, 30))
def _update_info(self, now=None) -> None:
"""Update the device info."""
_LOGGER.debug('Updating devices %s', now)
# Update self.devices to collect new devices added
# to the users account.
self.devices = self.api.get_trackrs()
for trackr in self.devices:
trackr.update_state()
trackr_id = trackr.tracker_id()
trackr_device_id = trackr.id()
lost = trackr.lost()
dev_id = trackr.name().replace(" ", "_")
if dev_id is None:
dev_id = trackr_id
location = trackr.last_known_location()
lat = location['latitude']
lon = location['longitude']
attrs = {
'last_updated': trackr.last_updated(),
'last_seen': trackr.last_time_seen(),
'trackr_id': trackr_id,
'id': trackr_device_id,
'lost': lost,
'battery_level': trackr.battery_level()
}
self.see(
dev_id=dev_id, gps=(lat, lon), attributes=attrs
)
@@ -14,7 +14,8 @@ import requests
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import DOMAIN, PLATFORM_SCHEMA
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.util import Throttle
@@ -37,7 +38,7 @@ def get_scanner(hass, config):
return scanner if scanner.success_init else None
class UbusDeviceScanner(object):
class UbusDeviceScanner(DeviceScanner):
"""
This class queries a wireless router running OpenWrt firmware.
@@ -10,7 +10,8 @@ import voluptuous as vol
import homeassistant.helpers.config_validation as cv
import homeassistant.loader as loader
from homeassistant.components.device_tracker import DOMAIN, PLATFORM_SCHEMA
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD
# Unifi package doesn't list urllib3 as a requirement
@@ -59,7 +60,7 @@ def get_scanner(hass, config):
return UnifiScanner(ctrl)
class UnifiScanner(object):
class UnifiScanner(DeviceScanner):
"""Provide device_tracker support from Unifi WAP client data."""
def __init__(self, controller):
@@ -0,0 +1,164 @@
"""
Support for UPC ConnectBox router.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.upc_connect/
"""
import asyncio
import logging
import xml.etree.ElementTree as ET
import aiohttp
import async_timeout
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST, CONF_PASSWORD
from homeassistant.helpers.aiohttp_client import async_create_clientsession
_LOGGER = logging.getLogger(__name__)
DEFAULT_IP = '192.168.0.1'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_HOST, default=DEFAULT_IP): cv.string,
})
CMD_LOGIN = 15
CMD_DEVICES = 123
@asyncio.coroutine
def async_get_scanner(hass, config):
"""Return the UPC device scanner."""
scanner = UPCDeviceScanner(hass, config[DOMAIN])
success_init = yield from scanner.async_login()
return scanner if success_init else None
class UPCDeviceScanner(DeviceScanner):
"""This class queries a router running UPC ConnectBox firmware."""
def __init__(self, hass, config):
"""Initialize the scanner."""
self.hass = hass
self.host = config[CONF_HOST]
self.password = config[CONF_PASSWORD]
self.data = {}
self.token = None
self.headers = {
'X-Requested-With': 'XMLHttpRequest',
'Referer': "http://{}/index.html".format(self.host),
'User-Agent': ("Mozilla/5.0 (Windows NT 10.0; WOW64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/47.0.2526.106 Safari/537.36")
}
self.websession = async_create_clientsession(
hass, cookie_jar=aiohttp.CookieJar(unsafe=True, loop=hass.loop))
@asyncio.coroutine
def async_scan_devices(self):
"""Scan for new devices and return a list with found device IDs."""
if self.token is None:
reconnect = yield from self.async_login()
if not reconnect:
_LOGGER.error("Not connected to %s", self.host)
return []
raw = yield from self._async_ws_function(CMD_DEVICES)
xml_root = ET.fromstring(raw)
return [mac.text for mac in xml_root.iter('MACAddr')]
@asyncio.coroutine
def async_get_device_name(self, device):
"""The firmware doesn't save the name of the wireless device."""
return None
@asyncio.coroutine
def async_login(self):
"""Login into firmware and get first token."""
response = None
try:
# get first token
with async_timeout.timeout(10, loop=self.hass.loop):
response = yield from self.websession.get(
"http://{}/common_page/login.html".format(self.host)
)
self.token = self._async_get_token()
# login
data = yield from self._async_ws_function(CMD_LOGIN, {
'Username': 'NULL',
'Password': self.password,
})
# successfull?
if data.find("successful") != -1:
return True
return False
except (asyncio.TimeoutError, aiohttp.errors.ClientError):
_LOGGER.error("Can not load login page from %s", self.host)
return False
finally:
if response is not None:
yield from response.release()
@asyncio.coroutine
def _async_ws_function(self, function, additional_form=None):
"""Execute a command on UPC firmware webservice."""
form_data = {
'token': self.token,
'fun': function
}
if additional_form:
form_data.update(additional_form)
response = None
try:
with async_timeout.timeout(10, loop=self.hass.loop):
response = yield from self.websession.post(
"http://{}/xml/getter.xml".format(self.host),
data=form_data,
headers=self.headers
)
# error on UPC webservice
if response.status != 200:
_LOGGER.warning(
"Error %d on %s.", response.status, function)
self.token = None
return
# load data, store token for next request
raw = yield from response.text()
self.token = self._async_get_token()
return raw
except (asyncio.TimeoutError, aiohttp.errors.ClientError):
_LOGGER.error("Error on %s", function)
self.token = None
finally:
if response is not None:
yield from response.release()
def _async_get_token(self):
"""Extract token from cookies."""
cookie_manager = self.websession.cookie_jar.filter_cookies(
"http://{}".format(self.host))
return cookie_manager.get('sessionToken')
@@ -14,12 +14,9 @@ from homeassistant.helpers.event import track_point_in_utc_time
from homeassistant.util.dt import utcnow
from homeassistant.util import slugify
from homeassistant.const import (
CONF_PASSWORD,
CONF_SCAN_INTERVAL,
CONF_USERNAME)
CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_USERNAME)
from homeassistant.components.device_tracker import (
DEFAULT_SCAN_INTERVAL,
PLATFORM_SCHEMA)
DEFAULT_SCAN_INTERVAL, PLATFORM_SCHEMA)
MIN_TIME_BETWEEN_SCANS = timedelta(minutes=1)
@@ -0,0 +1,145 @@
"""
Support for Xiaomi Mi routers.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.xiaomi/
"""
import logging
import threading
from datetime import timedelta
import requests
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.util import Throttle
# Return cached results if last scan was less then this time ago.
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_USERNAME, default='admin'): cv.string,
vol.Required(CONF_PASSWORD): cv.string
})
def get_scanner(hass, config):
"""Validate the configuration and return a Xiaomi Device Scanner."""
scanner = XioamiDeviceScanner(config[DOMAIN])
return scanner if scanner.success_init else None
class XioamiDeviceScanner(DeviceScanner):
"""This class queries a Xiaomi Mi router.
Adapted from Luci scanner.
"""
def __init__(self, config):
"""Initialize the scanner."""
host = config[CONF_HOST]
username, password = config[CONF_USERNAME], config[CONF_PASSWORD]
self.lock = threading.Lock()
self.last_results = {}
self.token = _get_token(host, username, password)
self.host = host
self.mac2name = None
self.success_init = self.token is not None
def scan_devices(self):
"""Scan for new devices and return a list with found device IDs."""
self._update_info()
return self.last_results
def get_device_name(self, device):
"""Return the name of the given device or None if we don't know."""
with self.lock:
if self.mac2name is None:
url = "http://{}/cgi-bin/luci/;stok={}/api/misystem/devicelist"
url = url.format(self.host, self.token)
result = _get_device_list(url)
if result:
hosts = [x for x in result
if 'mac' in x and 'name' in x]
mac2name_list = [
(x['mac'].upper(), x['name']) for x in hosts]
self.mac2name = dict(mac2name_list)
else:
# Error, handled in the _req_json_rpc
return
return self.mac2name.get(device.upper(), None)
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
"""Ensure the informations from the router are up to date.
Returns true if scanning successful.
"""
if not self.success_init:
return False
with self.lock:
_LOGGER.info('Refreshing device list')
url = "http://{}/cgi-bin/luci/;stok={}/api/misystem/devicelist"
url = url.format(self.host, self.token)
result = _get_device_list(url)
if result:
self.last_results = []
for device_entry in result:
# Check if the device is marked as connected
if int(device_entry['online']) == 1:
self.last_results.append(device_entry['mac'])
return True
return False
def _get_device_list(url, **kwargs):
try:
res = requests.get(url, timeout=5, **kwargs)
except requests.exceptions.Timeout:
_LOGGER.exception('Connection to the router timed out')
return
return _extract_result(res, 'list')
def _get_token(host, username, password):
"""Get authentication token for the given host+username+password."""
url = 'http://{}/cgi-bin/luci/api/xqsystem/login'.format(host)
data = {'username': username, 'password': password}
try:
res = requests.post(url, data=data, timeout=5)
except requests.exceptions.Timeout:
_LOGGER.exception('Connection to the router timed out')
return
return _extract_result(res, 'token')
def _extract_result(res, key_name):
if res.status_code == 200:
try:
result = res.json()
except ValueError:
# If json decoder could not parse the response
_LOGGER.exception('Failed to parse response from mi router')
return
try:
return result[key_name]
except KeyError:
_LOGGER.exception('No %s in response from mi router. %s',
key_name, result)
return
else:
_LOGGER.error('Invalid response from mi router: %s', res)
+1
View File
@@ -38,6 +38,7 @@ SERVICE_HANDLERS = {
'directv': ('media_player', 'directv'),
'denonavr': ('media_player', 'denonavr'),
'samsung_tv': ('media_player', 'samsungtv'),
'yeelight': ('light', 'yeelight'),
}
CONFIG_SCHEMA = vol.Schema({
@@ -7,7 +7,8 @@ from aiohttp import web
from homeassistant import core
from homeassistant.const import (
ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON, SERVICE_VOLUME_SET,
STATE_ON, STATE_OFF, HTTP_BAD_REQUEST, HTTP_NOT_FOUND,
SERVICE_OPEN_COVER, SERVICE_CLOSE_COVER, STATE_ON, STATE_OFF,
HTTP_BAD_REQUEST, HTTP_NOT_FOUND,
)
from homeassistant.components.light import (
ATTR_BRIGHTNESS, ATTR_SUPPORTED_FEATURES, SUPPORT_BRIGHTNESS
@@ -90,7 +91,7 @@ class HueOneLightStateView(HomeAssistantView):
self.config = config
@core.callback
def get(self, request, username, entity_id=None):
def get(self, request, username, entity_id):
"""Process a request to get the state of an individual light."""
hass = request.app['hass']
entity_id = self.config.number_to_entity_id(entity_id)
@@ -161,6 +162,9 @@ class HueOneLightChangeView(HomeAssistantView):
# Choose general HA domain
domain = core.DOMAIN
# Entity needs separate call to turn on
turn_on_needed = False
# Convert the resulting "on" status into the service we need to call
service = SERVICE_TURN_ON if result else SERVICE_TURN_OFF
@@ -189,11 +193,20 @@ class HueOneLightChangeView(HomeAssistantView):
ATTR_SUPPORTED_MEDIA_COMMANDS, 0)
if media_commands & SUPPORT_VOLUME_SET == SUPPORT_VOLUME_SET:
if brightness is not None:
turn_on_needed = True
domain = entity.domain
service = SERVICE_VOLUME_SET
# Convert 0-100 to 0.0-1.0
data[ATTR_MEDIA_VOLUME_LEVEL] = brightness / 100.0
# If the requested entity is a cover, convert to open_cover/close_cover
elif entity.domain == "cover":
domain = entity.domain
if service == SERVICE_TURN_ON:
service = SERVICE_OPEN_COVER
else:
service = SERVICE_CLOSE_COVER
if entity.domain in config.off_maps_to_on_domains:
# Map the off command to on
service = SERVICE_TURN_ON
@@ -206,7 +219,7 @@ class HueOneLightChangeView(HomeAssistantView):
config.cached_states[entity_id] = (result, brightness)
# Separate call to turn on needed
if domain != core.DOMAIN:
if turn_on_needed:
hass.async_add_job(hass.services.async_call(
core.DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: entity_id},
blocking=True))
@@ -288,6 +301,9 @@ def get_entity_state(config, entity):
final_brightness = round(min(1.0, level) * 255)
else:
final_state, final_brightness = cached_state
# Make sure brightness is valid
if final_brightness is None:
final_brightness = 255 if final_state else 0
return (final_state, final_brightness)
+1 -1
View File
@@ -12,7 +12,7 @@ from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.helpers.entity import Entity
from homeassistant.components.discovery import load_platform
REQUIREMENTS = ['pyenvisalink==1.9', 'pydispatcher==2.0.5']
REQUIREMENTS = ['pyenvisalink==2.0', 'pydispatcher==2.0.5']
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'envisalink'
+48 -5
View File
@@ -4,6 +4,7 @@ Provides functionality to interact with fans.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/fan/
"""
from datetime import timedelta
import logging
import os
@@ -21,7 +22,7 @@ import homeassistant.helpers.config_validation as cv
DOMAIN = 'fan'
SCAN_INTERVAL = 30
SCAN_INTERVAL = timedelta(seconds=30)
GROUP_NAME_ALL_FANS = 'all fans'
ENTITY_ID_ALL_FANS = group.ENTITY_ID_FORMAT.format(GROUP_NAME_ALL_FANS)
@@ -32,9 +33,11 @@ ENTITY_ID_FORMAT = DOMAIN + '.{}'
ATTR_SUPPORTED_FEATURES = 'supported_features'
SUPPORT_SET_SPEED = 1
SUPPORT_OSCILLATE = 2
SUPPORT_DIRECTION = 4
SERVICE_SET_SPEED = 'set_speed'
SERVICE_OSCILLATE = 'oscillate'
SERVICE_SET_DIRECTION = 'set_direction'
SPEED_OFF = 'off'
SPEED_LOW = 'low'
@@ -42,15 +45,20 @@ SPEED_MED = 'med'
SPEED_MEDIUM = 'medium'
SPEED_HIGH = 'high'
DIRECTION_FORWARD = 'forward'
DIRECTION_REVERSE = 'reverse'
ATTR_SPEED = 'speed'
ATTR_SPEED_LIST = 'speed_list'
ATTR_OSCILLATING = 'oscillating'
ATTR_DIRECTION = 'direction'
PROP_TO_ATTR = {
'speed': ATTR_SPEED,
'speed_list': ATTR_SPEED_LIST,
'oscillating': ATTR_OSCILLATING,
'supported_features': ATTR_SUPPORTED_FEATURES,
'direction': ATTR_DIRECTION,
} # type: dict
FAN_SET_SPEED_SCHEMA = vol.Schema({
@@ -76,6 +84,11 @@ FAN_TOGGLE_SCHEMA = vol.Schema({
vol.Required(ATTR_ENTITY_ID): cv.entity_ids
})
FAN_SET_DIRECTION_SCHEMA = vol.Schema({
vol.Required(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional(ATTR_DIRECTION): cv.string
}) # type: dict
_LOGGER = logging.getLogger(__name__)
@@ -140,6 +153,18 @@ def set_speed(hass, entity_id: str=None, speed: str=None) -> None:
hass.services.call(DOMAIN, SERVICE_SET_SPEED, data)
def set_direction(hass, entity_id: str=None, direction: str=None) -> None:
"""Set direction for all or specified fan."""
data = {
key: value for key, value in [
(ATTR_ENTITY_ID, entity_id),
(ATTR_DIRECTION, direction),
] if value is not None
}
hass.services.call(DOMAIN, SERVICE_SET_DIRECTION, data)
def setup(hass, config: dict) -> None:
"""Expose fan control via statemachine and services."""
component = EntityComponent(
@@ -157,7 +182,8 @@ def setup(hass, config: dict) -> None:
service_fun = None
for service_def in [SERVICE_TURN_ON, SERVICE_TURN_OFF,
SERVICE_SET_SPEED, SERVICE_OSCILLATE]:
SERVICE_SET_SPEED, SERVICE_OSCILLATE,
SERVICE_SET_DIRECTION]:
if service_def == service.service:
service_fun = service_def
break
@@ -190,6 +216,10 @@ def setup(hass, config: dict) -> None:
descriptions.get(SERVICE_OSCILLATE),
schema=FAN_OSCILLATE_SCHEMA)
hass.services.register(DOMAIN, SERVICE_SET_DIRECTION, handle_fan_service,
descriptions.get(SERVICE_SET_DIRECTION),
schema=FAN_SET_DIRECTION_SCHEMA)
return True
@@ -200,7 +230,11 @@ class FanEntity(ToggleEntity):
def set_speed(self: ToggleEntity, speed: str) -> None:
"""Set the speed of the fan."""
pass
raise NotImplementedError()
def set_direction(self: ToggleEntity, direction: str) -> None:
"""Set the direction of the fan."""
raise NotImplementedError()
def turn_on(self: ToggleEntity, speed: str=None, **kwargs) -> None:
"""Turn on the fan."""
@@ -217,14 +251,23 @@ class FanEntity(ToggleEntity):
@property
def is_on(self):
"""Return true if the entity is on."""
return self.state_attributes.get(ATTR_SPEED, STATE_UNKNOWN) \
not in [SPEED_OFF, STATE_UNKNOWN]
return self.speed not in [SPEED_OFF, STATE_UNKNOWN]
@property
def speed(self) -> str:
"""Return the current speed."""
return None
@property
def speed_list(self: ToggleEntity) -> list:
"""Get the list of available speeds."""
return []
@property
def current_direction(self) -> str:
"""Return the current direction of the fan."""
return None
@property
def state_attributes(self: ToggleEntity) -> dict:
"""Return optional state attributes."""
+20 -4
View File
@@ -7,14 +7,14 @@ https://home-assistant.io/components/demo/
from homeassistant.components.fan import (SPEED_LOW, SPEED_MED, SPEED_HIGH,
FanEntity, SUPPORT_SET_SPEED,
SUPPORT_OSCILLATE)
SUPPORT_OSCILLATE, SUPPORT_DIRECTION)
from homeassistant.const import STATE_OFF
FAN_NAME = 'Living Room Fan'
FAN_ENTITY_ID = 'fan.living_room_fan'
DEMO_SUPPORT = SUPPORT_SET_SPEED | SUPPORT_OSCILLATE
DEMO_SUPPORT = SUPPORT_SET_SPEED | SUPPORT_OSCILLATE | SUPPORT_DIRECTION
# pylint: disable=unused-argument
@@ -31,8 +31,9 @@ class DemoFan(FanEntity):
def __init__(self, hass, name: str, initial_state: str) -> None:
"""Initialize the entity."""
self.hass = hass
self.speed = initial_state
self._speed = initial_state
self.oscillating = False
self.direction = "forward"
self._name = name
@property
@@ -45,6 +46,11 @@ class DemoFan(FanEntity):
"""No polling needed for a demo fan."""
return False
@property
def speed(self) -> str:
"""Return the current speed."""
return self._speed
@property
def speed_list(self) -> list:
"""Get the list of available speeds."""
@@ -61,7 +67,12 @@ class DemoFan(FanEntity):
def set_speed(self, speed: str) -> None:
"""Set the speed of the fan."""
self.speed = speed
self._speed = speed
self.update_ha_state()
def set_direction(self, direction: str) -> None:
"""Set the direction of the fan."""
self.direction = direction
self.update_ha_state()
def oscillate(self, oscillating: bool) -> None:
@@ -69,6 +80,11 @@ class DemoFan(FanEntity):
self.oscillating = oscillating
self.update_ha_state()
@property
def current_direction(self) -> str:
"""Fan direction."""
return self.direction
@property
def supported_features(self) -> int:
"""Flag supported features."""
+5 -1
View File
@@ -64,7 +64,11 @@ class ISYFanDevice(isy.ISYDevice, FanEntity):
def __init__(self, node) -> None:
"""Initialize the ISY994 fan device."""
isy.ISYDevice.__init__(self, node)
self.speed = self.state
@property
def speed(self) -> str:
"""Return the current speed."""
return self.state
@property
def state(self) -> str:
+92
View File
@@ -0,0 +1,92 @@
"""
Support for Wink fans.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/fan.wink/
"""
import logging
from homeassistant.components.fan import (FanEntity, SPEED_HIGH,
SPEED_LOW, SPEED_MEDIUM,
STATE_UNKNOWN)
from homeassistant.helpers.entity import ToggleEntity
from homeassistant.components.wink import WinkDevice
_LOGGER = logging.getLogger(__name__)
SPEED_LOWEST = "lowest"
SPEED_AUTO = "auto"
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Wink platform."""
import pywink
add_devices(WinkFanDevice(fan, hass) for fan in pywink.get_fans())
class WinkFanDevice(WinkDevice, FanEntity):
"""Representation of a Wink fan."""
def __init__(self, wink, hass):
"""Initialize the fan."""
WinkDevice.__init__(self, wink, hass)
def set_drection(self: ToggleEntity, direction: str) -> None:
"""Set the direction of the fan."""
self.wink.set_fan_direction(direction)
def set_speed(self: ToggleEntity, speed: str) -> None:
"""Set the speed of the fan."""
self.wink.set_fan_speed(speed)
def turn_on(self: ToggleEntity, speed: str=None, **kwargs) -> None:
"""Turn on the fan."""
self.wink.set_state(True)
def turn_off(self: ToggleEntity, **kwargs) -> None:
"""Turn off the fan."""
self.wink.set_state(False)
@property
def is_on(self):
"""Return true if the entity is on."""
return self.wink.state()
@property
def speed(self) -> str:
"""Return the current speed."""
current_wink_speed = self.wink.current_fan_speed()
if SPEED_AUTO == current_wink_speed:
return SPEED_AUTO
if SPEED_LOWEST == current_wink_speed:
return SPEED_LOWEST
if SPEED_LOW == current_wink_speed:
return SPEED_LOW
if SPEED_MEDIUM == current_wink_speed:
return SPEED_MEDIUM
if SPEED_HIGH == current_wink_speed:
return SPEED_HIGH
return STATE_UNKNOWN
@property
def current_direction(self):
"""Return direction of the fan [forward, reverse]."""
return self.wink.current_fan_direction()
@property
def speed_list(self: ToggleEntity) -> list:
"""Get the list of available speeds."""
wink_supported_speeds = self.wink.fan_speeds()
supported_speeds = []
if SPEED_AUTO in wink_supported_speeds:
supported_speeds.append(SPEED_AUTO)
if SPEED_LOWEST in wink_supported_speeds:
supported_speeds.append(SPEED_LOWEST)
if SPEED_LOW in wink_supported_speeds:
supported_speeds.append(SPEED_LOW)
if SPEED_MEDIUM in wink_supported_speeds:
supported_speeds.append(SPEED_MEDIUM)
if SPEED_HIGH in wink_supported_speeds:
supported_speeds.append(SPEED_HIGH)
return supported_speeds
@@ -8,6 +8,7 @@
<link rel='icon' href='/static/icons/favicon.ico'>
<link rel='apple-touch-icon' sizes='180x180'
href='/static/icons/favicon-apple-180x180.png'>
<link rel="mask-icon" href="/static/icons/home-assistant-icon.svg" color="#3fbbf4">
{% for panel in panels.values() -%}
<link rel='prefetch' href='{{ panel.url }}'>
{% endfor -%}
+10 -10
View File
@@ -1,18 +1,18 @@
"""DO NOT MODIFY. Auto-generated by script/fingerprint_frontend."""
FINGERPRINTS = {
"core.js": "ad1ebcd0614c98a390d982087a7ca75c",
"frontend.html": "826ee6a4b39c939e31aa468b1ef618f9",
"mdi.html": "46a76f877ac9848899b8ed382427c16f",
"core.js": "22d39af274e1d824ca1302e10971f2d8",
"frontend.html": "61e57194179b27563a05282b58dd4f47",
"mdi.html": "5bb2f1717206bad0d187c2633062c575",
"micromarkdown-js.html": "93b5ec4016f0bba585521cf4d18dec1a",
"panels/ha-panel-dev-event.html": "c2d5ec676be98d4474d19f94d0262c1e",
"panels/ha-panel-dev-info.html": "a9c07bf281fe9791fb15827ec1286825",
"panels/ha-panel-dev-service.html": "ac74f7ce66fd7136d25c914ea12f4351",
"panels/ha-panel-dev-state.html": "65e5f791cc467561719bf591f1386054",
"panels/ha-panel-dev-template.html": "7d744ab7f7c08b6d6ad42069989de400",
"panels/ha-panel-history.html": "efe1bcdd7733b09e55f4f965d171c295",
"panels/ha-panel-dev-event.html": "f19840b9a6a46f57cb064b384e1353f5",
"panels/ha-panel-dev-info.html": "3765a371478cc66d677cf6dcc35267c6",
"panels/ha-panel-dev-service.html": "e32bcd3afdf485417a3e20b4fc760776",
"panels/ha-panel-dev-state.html": "8257d99a38358a150eafdb23fa6727e0",
"panels/ha-panel-dev-template.html": "cbb251acabd5e7431058ed507b70522b",
"panels/ha-panel-history.html": "7baeb4bd7d9ce0def4f95eab6f10812e",
"panels/ha-panel-iframe.html": "d920f0aa3c903680f2f8795e2255daab",
"panels/ha-panel-logbook.html": "4bc5c8370a85a4215413fbae8f85addb",
"panels/ha-panel-logbook.html": "93de4cee3a2352a6813b5c218421d534",
"panels/ha-panel-map.html": "3b0ca63286cbe80f27bd36dbc2434e89",
"websocket_test.html": "575de64b431fe11c3785bf96d7813450"
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 211 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,2 +1,2 @@
<html><head><meta charset="UTF-8"></head><body><dom-module id="ha-panel-dev-info"><template><style include="iron-positioning ha-style">:host{background-color:#fff;-ms-user-select:initial;-webkit-user-select:initial;-moz-user-select:initial}.content{padding:24px}.about{text-align:center;line-height:2em}.version{@apply(--paper-font-headline)}.develop{@apply(--paper-font-subhead)}.about a{color:var(--dark-primary-color)}.error-log-intro{margin-top:16px;border-top:1px solid var(--light-primary-color);padding-top:16px}paper-icon-button{float:right}.error-log{@apply(--paper-font-code1)
<html><head><meta charset="UTF-8"></head><body><dom-module id="ha-panel-dev-info"><template><style include="iron-positioning ha-style">:host{background-color:#fff;-ms-user-select:initial;-webkit-user-select:initial;-moz-user-select:initial}.content{padding:16px}.about{text-align:center;line-height:2em}.version{@apply(--paper-font-headline)}.develop{@apply(--paper-font-subhead)}.about a{color:var(--dark-primary-color)}.error-log-intro{margin-top:16px;border-top:1px solid var(--light-primary-color);padding-top:16px}paper-icon-button{float:right}.error-log{@apply(--paper-font-code1)
clear: both;white-space:pre-wrap}</style><app-header-layout has-scrolling-region=""><app-header fixed=""><app-toolbar><ha-menu-button narrow="[[narrow]]" show-menu="[[showMenu]]"></ha-menu-button><div main-title="">About</div></app-toolbar></app-header><div class="content fit"><div class="about"><p class="version"><a href="https://home-assistant.io"><img src="/static/icons/favicon-192x192.png" height="192"></a><br>Home Assistant<br>[[hassVersion]]</p><p>Path to configuration.yaml: [[hassConfigDir]]</p><p class="develop"><a href="https://home-assistant.io/developers/credits/" target="_blank">Developed by a bunch of awesome people.</a></p><p>Published under the MIT license<br>Source: <a href="https://github.com/home-assistant/home-assistant" target="_blank">server</a><a href="https://github.com/home-assistant/home-assistant-polymer" target="_blank">frontend-ui</a><a href="https://github.com/home-assistant/home-assistant-js" target="_blank">frontend-core</a></p><p>Built using <a href="https://www.python.org">Python 3</a>, <a href="https://www.polymer-project.org" target="_blank">Polymer [[polymerVersion]]</a>, <a href="https://optimizely.github.io/nuclear-js/" target="_blank">NuclearJS [[nuclearVersion]]</a><br>Icons by <a href="https://www.google.com/design/icons/" target="_blank">Google</a> and <a href="https://MaterialDesignIcons.com" target="_blank">MaterialDesignIcons.com</a>.</p></div><p class="error-log-intro">The following errors have been logged this session:<paper-icon-button icon="mdi:refresh" on-tap="refreshErrorLog"></paper-icon-button></p><div class="error-log">[[errorLog]]</div></div></app-header-layout></template></dom-module><script>Polymer({is:"ha-panel-dev-info",behaviors:[window.hassBehavior],properties:{hass:{type:Object},narrow:{type:Boolean,value:!1},showMenu:{type:Boolean,value:!1},hassVersion:{type:String,bindNuclear:function(r){return r.configGetters.serverVersion}},hassConfigDir:{type:String,bindNuclear:function(r){return r.configGetters.configDir}},polymerVersion:{type:String,value:Polymer.version},nuclearVersion:{type:String,value:"1.4.0"},errorLog:{type:String,value:""}},attached:function(){this.refreshErrorLog()},refreshErrorLog:function(r){r&&r.preventDefault(),this.errorLog="Loading error log…",this.hass.errorLogActions.fetchErrorLog().then(function(r){this.errorLog=r||"No errors have been reported."}.bind(this))}})</script></body></html>
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long

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