Compare commits

..

689 Commits

Author SHA1 Message Date
Paulus Schoutsen 3ea984ca25 Version bump to 0.35.2 2016-12-18 15:00:37 -08:00
Paulus Schoutsen 1258c4c680 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 15:00:16 -08:00
Paulus Schoutsen 75dd391118 Merge pull request #4988 from home-assistant/release-0-35-1
0.35.1
2016-12-18 14:11:10 -08:00
Paulus Schoutsen 76a9eba744 Version bump to 0.35.1 2016-12-18 13:00:35 -08:00
Paulus Schoutsen 31fe1d28e8 Gracefully exit with async logger (#4965)
* Gracefully exit with async logger

* Lint
2016-12-18 13:00:21 -08:00
Pascal Vizeli a4a38c8a00 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 13:00:21 -08:00
Paulus Schoutsen 3b74cc606e Allow setting base url (#4985) 2016-12-18 13:00:21 -08:00
Pascal Vizeli b750319de4 Bugfix wait in automation (#4984) 2016-12-18 13:00:21 -08:00
Paulus Schoutsen 9bc16157af Merge pull request #4875 from home-assistant/dev
0.35
2016-12-17 14:07:28 -08:00
Paulus Schoutsen 35d7f2b8bb Version bump to 0.35.0 2016-12-17 14:07:13 -08:00
Georgi Kirichkov 7390f82e1f Updates TP-Link dependency (#4914)
* Updates TP-Link switches dependent module

Refactors code to use the new module API

* Set TP-Link Switch name from the device settings

If no name has been set in the configuration file the name set on the device will be used

* Removes default name for TP-Link switch

Fallback to device alias now works properly

* Removes logging

* Updates comment to denote support for HS200 switch
2016-12-17 13:49:43 -08:00
Paulus Schoutsen cc9e5de503 Only report slowness warning once per entity (#4962) 2016-12-17 13:00:08 -08:00
Pascal Vizeli 50c8224365 Bugfix async log handler (#4954)
* Bugfix async log handler

* fix boostrap test

* Use hass.data for store handler and cleanup on async_stop

* Update bootstrap.py
2016-12-17 12:21:52 -08:00
Erik Eriksson b08b376aa7 eliqonline lib upgrade (#4948) 2016-12-17 12:14:04 -08:00
Fabian Affolter 60ef0153a2 Upgrade Sphinx to 1.5.1 (#4957) 2016-12-17 19:30:54 +01:00
Fabian Affolter 44c4b25f2b Upgrade astral to 1.3.3 (#4956) 2016-12-17 19:29:36 +01:00
Fabian Affolter 4abcaea4b7 Upgrade python-telegram-bot to 5.3.0 (#4955) 2016-12-17 19:29:24 +01:00
Albert Lee 831cad4220 Use Wake-on-LAN to turn on LG webOS TV (#4808) 2016-12-16 23:24:35 -08:00
Daniel Perna 6c524594c1 Fixing issue #4899 (#4947) 2016-12-16 22:34:13 -08:00
Paulus Schoutsen 78f6cfd1eb Update coverage 2016-12-16 22:03:45 -08:00
Pascal Vizeli 6d6abab358 Async logging file handler (#4901)
* Async logging file handler

* add time rotation handle

* new layout

* address paulus comments

* fix lint
2016-12-16 15:51:06 -08:00
Ashura 326cc83a17 [media_player.braviatv] Add turn on capabilities. (#4938) 2016-12-16 17:41:31 +00:00
Pascal Vizeli 8358ab56ea Bugfix asyncio wait (#4946) 2016-12-16 08:36:50 -08:00
Albert Lee 32dc518971 Use Wake-on-LAN to turn on Panasonic Viera TV (#4809) 2016-12-16 08:16:46 -08:00
Paulus Schoutsen b318a033bb Cast fix (#4939)
* Update frontend

* Fix exception on cast startup
2016-12-16 00:10:56 -08:00
Pascal Vizeli a0b2105ea0 Add voicerss for TTS (#4916)
* Add voicerss for TTS

* add unittests

* fix tests

* fix status bug in google/voicerss

* remove ssl
2016-12-16 00:10:48 -08:00
Nolan Gilley 9f9b87692a add manual option to prevent scheduled tests. (#4906) 2016-12-15 22:55:51 -08:00
Roi Dayan 5c4f04e9fc Fix webostv component to accept any custom sources (#4915)
Updated the schema check to accept any string
Search custom sources in app title and app id
The makes the short list redundant and thus removed
Tested by adding livetv, netflix, youtube, makovod and others
This is also compatible with the list that was supported till now
so current users won't see any difference.

Signed-off-by: Roi Dayan <roi.dayan@gmail.com>
2016-12-15 22:51:08 -08:00
Erik Eriksson 757f6278eb initialize self._last_brightness (#4917) 2016-12-15 22:35:53 -08:00
Adam Mills b9dcc2777b Setup DarkSky platform when offline during init (#4919)
* Setup DarkSky platform when offline during init

* Fail setup_platform if fetch was unsuccessful
2016-12-15 22:27:37 -08:00
Magnus Ihse Bursie 103fffa0f4 Add support for new netdisco detection of Samsung Smart TV. (#4925) 2016-12-15 22:20:00 -08:00
Fabian Affolter 7748867732 Avoid TypeError for state (#4897) 2016-12-15 22:14:59 -08:00
Paulus Schoutsen 02517ae5ec Fix synologydsm (#4895) 2016-12-15 22:13:38 -08:00
Fabian Affolter 2a31bb48c6 Clean-up (#4894) 2016-12-15 22:12:33 -08:00
Magas 5b70ada7b4 Panasonic viera fix (#4888)
* Removed return False so the Panasonic Viera TV can be added even if it doesn't connect

* Removed return False so the Panasonic Viera TV can be added even if it doesn't connect

* Removed return False so the Panasonic Viera TV can be added even if it doesn't connect

* Remove try/except to connect to the TV

* Update panasonic_viera.py

* Update panasonic_viera.py
2016-12-15 22:11:58 -08:00
Albert Lee 7b45cf8e59 Expose media volume as emulated Hue brightness (#4869)
* Allow virtual Hue bridge to set volume level of media_player entities
* Show correct states in all lights view
2016-12-15 21:47:23 -08:00
Daniel Høyer Iversen 394d53e748 Broadlink sensor and switch (#4834)
* Broadlink sensor and switch

* broadlink logging

* Use async

* style

* style
2016-12-15 21:42:00 -08:00
Hugo Dupras c125c4af4f Fix for GTFS sensor (#4828)
* Fix for GTFS sensor

Signed-off-by: Hugo D. (jabesq) <jabesq@gmail.com>

* GTFS fix

Signed-off-by: Hugo D. (jabesq) <jabesq@gmail.com>
2016-12-15 21:40:33 -08:00
Joe Rocklin f90b89bc74 Add nest hvac state (#4810)
* Add basic property details for Nest hvac_state

* Add the hvac_state sensor

* Update requirements and remove trailing whitespace

Clean up the multiline docstring

Adding a space between summary and description

* Removing the hvac_state as a property on the nest climate

* Update nest.py
2016-12-15 21:39:59 -08:00
Daniel Høyer Iversen ceac9eab94 Bug fix for #4903 (#4927) 2016-12-15 21:35:47 -08:00
joopert 7bb0abdf09 kodi fanart fix basic auth (#4930) 2016-12-15 21:35:01 -08:00
Pascal Vizeli 1d60760e21 Protect add_job (#4932) 2016-12-15 21:30:09 -08:00
Pascal Vizeli 43d18daebd Homematic faster update with async (#4929) 2016-12-15 21:26:13 +01:00
Lewis Juggins 1a7895b1d8 [media_player.sonos] Bugfix, initalise source_name. (#4911) 2016-12-15 11:46:18 +00:00
Daniel Høyer Iversen c2f31bbb38 Merge pull request #4924 from home-assistant/flux_lib
Update flux led lib
2016-12-15 08:26:24 +01:00
Daniel Høyer Iversen a7e75dd01e Merge pull request #4907 from home-assistant/rpi_camera
Bug in rpi_camera
2016-12-15 08:09:23 +01:00
Daniel Hoyer Iversen 58ea3c25df Update flux led lib 2016-12-15 07:58:58 +01:00
Pascal Vizeli 6d2de67620 TTS add google language list for config check (#4912)
* Add config check for language

* update default

* move language from component to platform

* fix lint
2016-12-14 22:32:20 +01:00
Valentin Alexeev a359d21799 [media_player.sonos] Source selection from favorites (#4804) 2016-12-14 18:05:03 +00:00
Daniel Høyer Iversen be552a59c9 Bug in rpi_camera 2016-12-14 18:45:05 +01:00
Paulus Schoutsen 832f9737a8 Fix hue groups on older hubs (#4884) 2016-12-13 23:46:27 -08:00
Paulus Schoutsen da6bdf275e Update frontend 2016-12-13 23:30:08 -08:00
Marcelo Moreira de Mello 7ca025f653 Fixes issues #4844 to avoid traceback when self.rest.data is None (#4886)
6-12-09 18:12:30 homeassistant.core: Error doing job: Task exception was never retrieved
Traceback (most recent call last):
  File "/usr/lib/python3.4/asyncio/tasks.py", line 237, in _step
    result = next(coro)
  File "/srv/hass/hass_venv/lib/python3.4/site-packages/homeassistant/helpers/entity_component.py", line 386, in _update_entity_states
    yield from update_coro
  File "/srv/hass/hass_venv/lib/python3.4/site-packages/homeassistant/helpers/entity.py", line 240, in async_update_ha_state
    self._attr_setter('entity_picture', str, ATTR_ENTITY_PICTURE, attr)
  File "/srv/hass/hass_venv/lib/python3.4/site-packages/homeassistant/helpers/entity.py", line 307, in _attr_setter
    value = getattr(self, name)
  File "/srv/hass/hass_venv/lib/python3.4/site-packages/homeassistant/components/sensor/wunderground.py", line 176, in entity_picture
    url = self.rest.data['icon_url']
TypeError: 'NoneType' object is not subscriptable
2016-12-13 23:01:14 -08:00
Erik Eriksson 570cfc60c5 bugfix: is_on is a property (#4889) 2016-12-13 22:58:43 -08:00
Oliver dc551b825f Added a volume set option and autodiscovery functions to Denon AVR rece… (#4845)
* Added Volume Set option and autodiscovery functions to Denon AVR receivers

* Corrected issues in SSDP discovery and in case no host could be discovered

* Corrected discovery handling / added denonavr to discovery platform

* No needless discoveries anymore / add_devices() with list instead of loop
2016-12-13 20:04:40 -08:00
Erik Eriksson 6da3e23436 Update __init__.py (#4877)
Cleaner exit by not throwing exception if server was not set during initialization of component (ref https://github.com/home-assistant/home-assistant/pull/4866)
2016-12-13 08:57:33 -08:00
Pascal Vizeli e4b6395250 Migrate REST switch to async (#4517)
* Migrate REST switch to async

* Update rest.py

* Address comments from paulus
2016-12-13 08:55:13 -08:00
Audun Ytterdal 72bd9fb5c7 Remove libtelldus-core-dev from Dockerfile (#4878)
Remove unnecessary  libtelldus-core-dev from Dockerfile . Ref https://github.com/home-assistant/home-assistant/pull/4680#issuecomment-266006310
2016-12-13 08:53:42 -08:00
Pascal Vizeli 2dec38d8d4 TTS Component / Google speech platform (#4837)
* TTS Component / Google speech platform

* Change file backend handling / cache

* Use mimetype / rename Provider function / allow cache on service call

* Add a memcache for faster response

* Add demo platform

* First version of unittest

* Address comments

* improve error handling / address comments

* Add google unittest & check http response code

* Change url param handling

* add test for other language

* Change hash to sha256 for same hash on every os/hardware

* add unittest for receive demo data

* add test for error cases

* Test case load from file to mem over aiohttp server

* Use cache SpeechManager level, address other comments

* Add service for clear cache

* Update service.yaml

* add support for spliting google message
2016-12-12 23:23:08 -08:00
John Mihalic acb841a1f4 Add Hikvision binary sensor component (#4825)
* Add Hikvision binary sensor component

* Simplify customize configuration

* Add delay attribute

* Remove use of threading timer, fix delay functionality
2016-12-12 23:10:16 -08:00
Paulus Schoutsen eeb8bc3913 Fix dev tag detection in release script (#4873) 2016-12-12 22:18:20 -08:00
Erik Eriksson 12f790c7cf Display error message instead of exception (#4866)
* Display error message instead of exception

Display error message in log instead of stack trace.
(Usually happens when a server is already running at the same port.)

* Update __init__.py

Better error handling when reading SSL certificate

* Update __init__.py

* Update __init__.py
2016-12-12 22:02:24 -08:00
Erik Eriksson dbb4e4c3fa [tellduslive] Upgrade requirement (#4865) 2016-12-12 20:05:38 +02:00
Paulus Schoutsen d51e62d0a3 Merge pull request #4863 from home-assistant/master
Backmerge point releases
2016-12-11 22:51:30 -08:00
Paulus Schoutsen ab92a91ac5 Merge branch 'dev' into master 2016-12-11 22:49:06 -08:00
Paulus Schoutsen cfa36f3546 Merge pull request #4862 from home-assistant/release-0-34-5
0.34.5
2016-12-11 22:46:19 -08:00
Paulus Schoutsen 96d8fbe513 Version bump to 0.34.5 2016-12-11 22:26:48 -08:00
Paulus Schoutsen 1e9d91be0e Fix Plex from doing I/O inside event loop (#4857) 2016-12-11 22:26:30 -08:00
Paulus Schoutsen 2402897f47 Fix Nest doing I/O inside event loop (#4855) 2016-12-11 22:26:30 -08:00
Josh Nichols b857d5dad0 Bump python-nest to fix issue with Nest Cam without activity zones (#4820)
* Bump python-nest to fix issue with Nest Cam without activity zones

* bump to include fix python-nest dependency with hvac_state

* regenerate requirements_all.txt
2016-12-11 22:26:30 -08:00
R1chardTM d17753009a Fix python-nest version bump (#4799)
* Fix python-nest version bump

* Change SHA so version in HASS and dependency are the same
2016-12-11 22:26:30 -08:00
Marcelo Moreira de Mello 3467020dbf Added resolution support to Amcrest cameras (#4860)
* Added resolution support to Amcrest cameras

* Ordered alphabetically DEFAULT_ options
2016-12-11 21:46:19 -08:00
Adam Mills 4114884cdc Flic: Support use of queued events within timeout (#4822)
* Flic: Support use of queued events within timeout

* Linter fixes
2016-12-11 21:43:59 -08:00
John Mihalic d7ccf07922 Add media position support and trailer type to Emby (#4792)
* Add media position support and trailer type to Emby

* Adjustments to mitigate TypeError

* Simplify media_position property

* Update handling when data isn't available

* Update emby.py
2016-12-11 21:43:53 -08:00
Erik Eriksson 2a7fa5afc3 Telldus Live: (#4645)
- Implemented support for covers and dimmable lights.
- Removed global object, use hass.data.
- Disabled polling via update.
- Inherit from common TelldusLiveEntity device.
- Configurable polling interval
- Use https API endpoint
- Use tellduslive package
2016-12-11 21:39:37 -08:00
Anton Lundin 04aa4e898a Improve denon media_player (#4836)
* Add debug level logging of messages in denon

* Added media stop for Denon AVR Media Player

* Sort source list

* Rework input selection for Denon AVR

This reworks the input selection, adding more modes and making it so
that the media controls are only announced in modes where they actually
makes sense.

* Added real media info for Denon AVR Media modes

* Read more configuration from denon devices

This reads network name, and overrides the local name with that.

This also reads the source names and reconfigures the input list to
those names, and also reads the source deleted list and removes the
inputs that are set to deleted in the device.

* Discover and handle max volume in Denon media player

* Rework source discovery in Denon media player

This uses SSFUN as authorative source for which sources that we should
present.
2016-12-11 21:04:36 -08:00
Michaël Arnauts b156ae7812 Add support for Hue LightGroups (#4744)
* Add support for Hue LightGroup entity

* Don't filter on LightGroup and add properties for a group

* Reuse code from HueLight in HueLightGroup

* Remove HueLightGroup and add is_group variable to HueLight

* Make linter happy

* Update light or lightgroup state when a new state is available

* Use schedule_update_ha_state() to schedule the state update. Drop new_lightgroups and use new_lights instead.

* code style fix
2016-12-11 17:59:30 -08:00
David-Leon Pohl 48928d1f9e Fix config validation (#4853) (#4854) 2016-12-11 17:38:33 -08:00
Paulus Schoutsen df98d5b3c1 Fix Nest doing I/O inside event loop (#4855) 2016-12-11 17:34:26 -08:00
Paulus Schoutsen f4b5c439a1 Fix Plex from doing I/O inside event loop (#4857) 2016-12-11 17:34:13 -08:00
Jeff Wilson ecc514b7e4 Use current mode to determine which temperature attributes to use (#4858) 2016-12-11 15:48:47 -08:00
IoTGuy 6edb54052f adding sensehat plugin (#4775)
* adding sensehat plugin

* added

* fix PR

* requirement updated

* Update sensehat.py
2016-12-11 15:46:55 -08:00
Marcelo Moreira de Mello 4d2480bbd1 Added support to language codes on Weather Underground (#4815)
* Added supported to language codes to Weather Underground

* Removed unecessary None assigments
2016-12-11 15:43:42 -08:00
Daniel Høyer Iversen 2708e193ec vlc media player (#4800)
* vlc media player

* Update vlc.py
2016-12-11 14:59:12 -08:00
Jean-Philippe Bouillot c3923b2768 Netatmo improving Battery info (#4724)
* Improving Battery info

Improving battery status info (to reflect NetAtmo API documentation and change deprecated DeviceList for WeatherStationData

* Fixes from previous update

Fix the hound issue.
add battery_lvl, WindAngle_value, GustAngle_value, rf_status_lvl, wifi_status_lvl
2016-12-11 14:47:27 -08:00
devdelay 080c4efb00 Ecobee detect Smart Away (#4769)
* Ecobee autoAway Event

* Update ecobee.py

Checking if event['running'] true is pointless because if false event['type'] will equal template and when true type will only be 'hold' or 'autoAway' so I've removed this check from the statement
2016-12-11 14:46:10 -08:00
Pascal Vizeli 99f1ea9b59 Migrate alarm control panel to async (#4807)
* Merge alarm control panel to async

* fix lint
2016-12-11 14:39:20 -08:00
Johann Kellerman 46cad514d4 Revert "[device_tracker] Don't clear GPS coordinates when using two device trackers." (#4851) 2016-12-11 19:18:11 +02:00
Lewis Juggins e0552ad899 [device_tracker] Don't clear GPS coordinates if no GPS seen (#4848) 2016-12-11 15:13:43 +02:00
Daniel Høyer Iversen 5c99dd0e3d Merge pull request #4846 from lwis/gpslogger
[device_tracker.gpslogger] Add additional activity attribute.
2016-12-11 11:07:55 +01:00
Lewis Juggins cdf9464698 [device_tracker.gpslogger] Add additional activity attribute. 2016-12-11 09:06:29 +00:00
Stefan Jonasson 7ba25f3526 Fixed crash during light objects initizilation (#4835)
* Fixed crash when lights objects was inited
2016-12-11 09:54:50 +01:00
Daniel Høyer Iversen ee5b9e7291 Configurable scan options for nmap (#4838) 2016-12-10 19:53:25 +02:00
r-jordan 167260bcc6 [climate.generic_thermostat] Make tolerance work both ways (#4830) 2016-12-10 10:36:35 +00:00
Josh Nichols 64de1c9777 Bump python-nest to fix issue with Nest Cam without activity zones (#4820)
* Bump python-nest to fix issue with Nest Cam without activity zones

* bump to include fix python-nest dependency with hvac_state

* regenerate requirements_all.txt
2016-12-09 11:04:40 -08:00
Sören Oldag 1547045f2c Flic: Support ignoring individual click types. (#4827) 2016-12-09 08:52:14 -08:00
Keaton Taylor d02899216d Prevent emulated hue discovery by hue component (#4819)
* Prevent emulated hue discovery

Test for “HASS Bridge” in discovery info, pass if found, else try and
setup the bridge.

* Solved coding error

Duplicate commands and return false added for component.
2016-12-09 08:45:14 -08:00
Albert Lee 0aac4d64e1 Add away mode for Radio Thermostat/3M Filtrete (#4793) 2016-12-08 23:26:02 -08:00
Pascal Vizeli 0bf9e6d4bb Bugfix error on automation reload (#4823) 2016-12-08 23:24:03 -08:00
Jan Losinski f78246e686 Pilight receive match fix for bug 4637 (#4639)
* Pilight: dont protocol as list in COMMAND_SCHEMA

As described in bug #4637 the protocol should not be wrapped in a list
in the spec of COMMAND_SCHEMA because this causes the component to
never successfully match any received rf code.

As pointed ot in PR #4639 the easiest way to do this, is to not derive
COMMAND_SCHEMA from RF_CODE_SCHEMA and specify protocol as simple
string there.

This fixes bug #4637.

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

* Pilight: Add "unitcode" to command schema.

This adds "unitcode" to the COMMAND_SCHEMA. It is used for example in
the brennenstuhl protocol of pilight.

Signed-off-by: Jan Losinski <losinski@wh2.tu-dresden.de>
2016-12-08 23:19:14 -08:00
Paulus Schoutsen c90a1b9760 Update frontend 2016-12-08 22:31:32 -08:00
Lewis Juggins 14446c5731 [media_player.sonos] Add stop support. (#4788) 2016-12-08 08:36:37 +00:00
Albert Lee 2e2b764dbe Add exception handling when turning on Onkyo receivers (#4813) 2016-12-07 21:46:42 -08:00
R1chardTM 695f062e29 Fix python-nest version bump (#4799)
* Fix python-nest version bump

* Change SHA so version in HASS and dependency are the same
2016-12-07 21:45:43 -08:00
Keaton Taylor 194b268ae3 Get entity name from entity.name (#4798)
Grabbing the ATTR_FRIENDLY_NAME directly produces an error. Instead
grab from entity.name.
2016-12-07 21:45:18 -08:00
Pascal Vizeli 8295fc8b4c Pararell execute state restore by domain (#4801)
* Pararell execute state restore per domain

* fix spell
2016-12-07 08:37:35 -08:00
Jan Losinski d0dcd1bb73 Scene: add support for input_select (#4674)
This adds support for the scene component to handle input_select
devices and set their options.

This fixes bug #4673

Signed-off-by: Jan Losinski <losinski@wh2.tu-dresden.de>
2016-12-07 05:33:41 -08:00
Nolan Gilley 82ad8b0a8f round $ to 2 decimals (#4786) 2016-12-07 09:15:48 +01:00
Paulus Schoutsen 91a9da8f0c Merge pull request #4796 from home-assistant/release-0-34-4
0.34.4
2016-12-06 22:44:57 -08:00
Paulus Schoutsen e3415c4e22 Version bump to 0.34.4 2016-12-06 22:35:43 -08:00
Paulus Schoutsen 9bca3f3103 Update after service calls (#4795)
* Update after service calls

* Service update: wrap async_update in create_task
2016-12-06 22:35:24 -08:00
Pascal Vizeli 7c3ae884df Migrate remote to async (#4678)
* Migrate remote to async

* add coro

* remove sync from init since only used in harmony

* import ATTR from remote

* remove unused sync stuff from tests
2016-12-06 22:35:24 -08:00
Adam Mills 8a4aace789 Fix incorrect caching of /api/error_log (#4789) 2016-12-06 22:35:24 -08:00
Josh Nichols 0e74cd833d Updated python-nest dependency (#4785)
* Updated python-nest dependency

This sha fixes two issues:

- min and max temperatures not being set when temperature isn't locked
- fixes error when setting farenheit with a .5

* gen requirements all

* Add fix for https://github.com/home-assistant/home-assistant/issues/4731
2016-12-06 22:35:24 -08:00
Paulus Schoutsen 5e2911f071 Fix Kodi auth (#4770) 2016-12-06 22:35:24 -08:00
Paulus Schoutsen 7dacc4a7bb Fix default auth influxdb (#4771) 2016-12-06 22:35:24 -08:00
Paulus Schoutsen 98fe50d5ad Update after service calls (#4795)
* Update after service calls

* Service update: wrap async_update in create_task
2016-12-06 22:30:47 -08:00
Paulus Schoutsen 37e3c2a133 Contributing: add skip step 0 2016-12-06 21:48:44 -08:00
Paulus Schoutsen 76b79019ce Add faster reviews link to contributing 2016-12-06 21:48:02 -08:00
Adam Mills c40ddf18c7 Fix incorrect caching of /api/error_log (#4789) 2016-12-06 21:03:49 -08:00
Josh Nichols 9a3fe691b1 Updated python-nest dependency (#4785)
* Updated python-nest dependency

This sha fixes two issues:

- min and max temperatures not being set when temperature isn't locked
- fixes error when setting farenheit with a .5

* gen requirements all

* Add fix for https://github.com/home-assistant/home-assistant/issues/4731
2016-12-06 20:31:07 -08:00
Audun Ytterdal 8826e6a8d0 Add support for telldus in the Docker image. (#4680)
* Add support for telldus in the Docker image. Start with -v /tmp/TelldusClient:/tmp/TelldusClient -v /tmp/TelldusEvents:/tmp/TelldusEvents

* Merged telldus install with the others

* Clean up indenting

* Stream apt-key
2016-12-06 09:01:47 -08:00
Paulus Schoutsen 860a12cffb Fix Kodi auth (#4770) 2016-12-06 07:43:11 -08:00
Fabian Affolter 76ff934bd3 Move details to docs, update doc strings, and use consts (#4777) 2016-12-06 15:49:59 +01:00
Lewis Juggins d968e1d011 Add test to ensure device_tracker records state correctly. (#4776) 2016-12-06 15:01:24 +02:00
Paulus Schoutsen fa0dbaf065 Fix default auth influxdb (#4771) 2016-12-05 23:39:22 -08:00
Paulus Schoutsen 4d0f19496a Merge remote-tracking branch 'origin/master' into dev 2016-12-05 23:35:36 -08:00
Paulus Schoutsen 0cc9555d14 Merge pull request #4774 from home-assistant/release-0-34-3
0.34.3
2016-12-05 23:35:09 -08:00
Paulus Schoutsen d712a3dc38 Version bump to 0.34.3 2016-12-05 22:42:55 -08:00
rubund 84446bed14 Fix broken EnOcean support (#4710)
* ensure_list

* CONF_ID is not required configuration for enocean lights

* Use vol.All(cv.ensure_list, [vol.Coerce(int)]) as suggested in pull request review

* Fix line too long
2016-12-05 22:42:32 -08:00
Jeff Wilson e92b15f966 Set hue-bridgeid in UPNP response (#4740) 2016-12-05 22:42:23 -08:00
Paulus Schoutsen a458ce8069 Fix websocket async (#4752)
* Ensure we write to websocket from inside event loop

* Inline service call helper
2016-12-05 22:42:13 -08:00
dasos 5e492db9a3 Fix connection check (#4732)
* Fix connection check

* Release instead

* Remove if

* Update hook.py
2016-12-05 22:42:02 -08:00
Albert Lee bc646070c8 Match uppercase MAC addresses in asuswrt 'arp -n' output (#4742) (#4764) 2016-12-05 22:20:21 -08:00
Paulus Schoutsen 64290d74f0 Update frontend 2016-12-05 21:40:45 -08:00
dasos a11b68c560 Fix connection check (#4732)
* Fix connection check

* Release instead

* Remove if

* Update hook.py
2016-12-05 21:37:05 -08:00
Caleb 8ca2345fd4 Pyunifi dep (#4754)
* change unifi dependency to pyunifi

* Change dependency to fix #4336

* Run gen_requirements_all.py script

* Changed import statement to reflect new package

* Updated test_unifiy.py with different module

* Update requirements_all.txt
2016-12-05 21:35:54 -08:00
Hugo Dupras 8c628071f3 Add support for Netatmo tags (#4761)
* Add support for Netatmo Welcome Tags

Signed-off-by: Hugo D. (jabesq) <jabesq@gmail.com>

* Add size parameter for WelcomeData

* minor fixes

* Add Throttling mechanism for update event

This will prevent to reach the API limit

Signed-off-by: Hugo D. (jabesq) <jabesq@gmail.com>

* Change scan interval for Netatmo Binary sensors

Signed-off-by: Hugo D. (jabesq) <jabesq@gmail.com>

* Minor fixes

Signed-off-by: Hugo D. (jabesq) <jabesq@gmail.com>

* Update netatmo.py
2016-12-05 21:35:33 -08:00
Sören Oldag 81d38c3463 Add flic smart button component (#4681)
* Added component for flic smart buttons.

* Apply home-assistant coding styles.

* Fixed flic configuration.

* Made logging for scanning for new buttons less verbose.

* Fixed flic event data.

* Follow async conventions.

* Added new requirements to requirements_all.txt.

* Added flic component to .coveragerc

* Updated flic threshold configuration key names.

* Flic devices are now removed when they disconnect.

* Include review feedback.

* Fixed stopping of clients in flic component when home assistant is stopped.

* Updated flic component by integrating input of #4738.

Use library method to determine click type. Merge three click events into single one with click_type parameter.

* Use a single client for both handling click events and scanning for new buttons.

* Renamed flic ‘auto_scan’ configuration variable to ‘discovery’ using HA constants.
2016-12-05 21:12:24 -08:00
Adam Mills 776455030f Fix media_image_urls for universal media player (#4765)
* Fix media_image_urls for universal media player

* Linter fixes
2016-12-05 18:07:04 -08:00
Daniel Høyer Iversen 8afd30b7d4 fix setting battery in device_tracker (#4756) 2016-12-05 18:04:04 -08:00
Paulus Schoutsen b60f5714fc Fix websocket async (#4752)
* Ensure we write to websocket from inside event loop

* Inline service call helper
2016-12-05 18:03:06 -08:00
John Arild Berentsen fa8bc0a36c Add Verisure smartcam capture service (#4559)
* Add verisure capture as service

* docstyle
2016-12-05 17:51:58 -08:00
Martin J. Laubach 1ae8256ffd Add sensor for reading Austrian ZAMG weather conditions (#4347)
* Add sensor for reading ZAMG weather conditions

* Add to coveragerc; Correct some doc style problems

* More doc fixes

* More doc fixes

* Lose license and whatever.

* Don't return UNKNOWN for unknown variables

* Verify that the configured station id is actually one in the data set.

Don't warn about unknown stations, this cannot happen any more as the configuration parser now checks that.
This could still happen if the data set is incomplete though ...

* Clean up imports

* Clarify comment on throttling interval

* Base zamg sensor on Entity, not WeatherEntity, and delete unused code

* Fix formatting nits from flake8

* Use ATTR_FRIENDLY_NAME, clean up imports, remove unnecessary indirection.

* Use {}.format() instead of "" %

* Re-add unit of measurement that got lost somehow

* Use guard clauses instead of if-matroshka.
Wrap requests.get() in try/except for RequestException.

* Huh, how did this happen? White space corrections...

* Add sensor for reading ZAMG weather conditions

* Add to coveragerc; Correct some doc style problems

* More doc fixes

* More doc fixes

* Verify that the configured station id is actually one in the data set.

Don't warn about unknown stations, this cannot happen any more as the configuration parser now checks that.
This could still happen if the data set is incomplete though ...

* Lose license and whatever.

* Don't return UNKNOWN for unknown variables

* Clean up imports

* Clarify comment on throttling interval

* Base zamg sensor on Entity, not WeatherEntity, and delete unused code

* Fix formatting nits from flake8

* Use ATTR_FRIENDLY_NAME, clean up imports, remove unnecessary indirection.

* Use {}.format() instead of "" %

* Re-add unit of measurement that got lost somehow

* Use guard clauses instead of if-matroshka.
Wrap requests.get() in try/except for RequestException.

* Huh, how did this happen? White space corrections...

* Precipitation actually is a float, good it rained today

* Logger needs no module visibility

* Do not name sensors with _ to be in line with the other weather sensor platforms.

* Remove manually set friendly_name

* comment format police

* Less comments

* Update zamg.py
2016-12-05 17:50:50 -08:00
Jeff Wilson b3253403aa Set hue-bridgeid in UPNP response (#4740) 2016-12-05 17:39:40 -08:00
Daniel Høyer Iversen 308744d8a0 Add additional attributes to GPSLogger (#4755) 2016-12-05 19:33:51 +00:00
Paulus Schoutsen 13006cee68 Device tracker attributes (#4753) 2016-12-05 19:32:17 +00:00
rubund e21382cd3e Fix broken EnOcean support (#4710)
* ensure_list

* CONF_ID is not required configuration for enocean lights

* Use vol.All(cv.ensure_list, [vol.Coerce(int)]) as suggested in pull request review

* Fix line too long
2016-12-05 08:15:36 -08:00
Daniel Høyer Iversen 71fc446425 Merge pull request #4719 from home-assistant/device_tracker
device tracker
2016-12-05 16:32:29 +01:00
Fabian Affolter 03d19ec2f1 Netdata sensor (#4743)
* Added netdata sensor

* Typo

* Add netdata sensor

* Improvement of the work done by @ezar
2016-12-05 11:19:20 +01:00
Daniel Hoyer Iversen 5a7e446646 device tracker 2016-12-05 09:12:13 +01:00
Paulus Schoutsen 2b3caa716a Cast progress (#4735)
* add progress to google cast

* Add progress to media player demo
2016-12-04 15:30:55 -08:00
Paulus Schoutsen 6574dd8439 Merge branch 'release-0-34-2' into dev 2016-12-04 15:20:48 -08:00
Paulus Schoutsen 6d56519297 Merge pull request #4736 from home-assistant/release-0-34-2
0.34.2
2016-12-04 15:19:56 -08:00
Paulus Schoutsen 60bcb12a48 Version bump to 0.34.2 2016-12-04 15:08:38 -08:00
Lukas 58509f8bba [0.34] bugfix influxdb node_id (#4712)
* Bugfix for #4709 - do not convert node_id to float

* Update influxdb.py
2016-12-04 15:08:25 -08:00
Lukas 2099d023ef [0.34] bugfix influxdb node_id (#4712)
* Bugfix for #4709 - do not convert node_id to float

* Update influxdb.py
2016-12-04 15:08:14 -08:00
Paulus Schoutsen db6a6fa4cb Lint 2016-12-04 14:45:25 -08:00
Josh Nichols d89bfcdaa5 Make sure all nest platforms require discovery info (#4734) 2016-12-04 14:34:01 -08:00
Josh Nichols 64b1179c13 Make sure all nest platforms require discovery info (#4734) 2016-12-04 14:33:50 -08:00
Paulus Schoutsen 840e27adec Fix Nest interpreting Celsius temperature as Fahrenheit (#4729) 2016-12-04 14:32:59 -08:00
Paulus Schoutsen 31a8537ab5 Update frontend 2016-12-04 14:32:43 -08:00
Paulus Schoutsen 87dab37b8a Fix Nest interpreting Celsius temperature as Fahrenheit (#4729) 2016-12-04 13:49:46 -08:00
Paulus Schoutsen 3a2cdd3de0 Merge pull request #4728 from home-assistant/release-0-34-1
0.34.1
2016-12-04 11:18:46 -08:00
Paulus Schoutsen 2009e98497 Version bump to 0.34.1 2016-12-04 11:03:16 -08:00
Paulus Schoutsen b354a18bf3 Fix CORS when static resources registered (#4727) 2016-12-04 11:02:33 -08:00
hexa- 1cd3cd8d77 Revert "Update reference to correct tplink switch" (#4722) 2016-12-04 11:02:33 -08:00
Pascal Vizeli d9556392bc Protect hm thread for hangs on events (#4717) 2016-12-04 11:02:33 -08:00
Paulus Schoutsen 695fb412cd Re-org emulated_hue and fix google home (#4708) 2016-12-04 11:02:33 -08:00
Josh Nichols 93322b0251 Updated python-nest to fix a camera bug when loading images (#4701) 2016-12-04 11:02:33 -08:00
Paulus Schoutsen 9b9b625ac4 Fix synology dsm doing I/O inside loop (#4699) 2016-12-04 11:02:33 -08:00
Pascal Vizeli 0ae6585a90 Bugfix sonos hosts (#4698) 2016-12-04 11:02:33 -08:00
Paulus Schoutsen cffc7ac4d8 Update netdisco to 0.8 (#4723) 2016-12-04 10:59:18 -08:00
Paulus Schoutsen a9be6c36f1 Re-org emulated_hue and fix google home (#4708) 2016-12-04 10:57:48 -08:00
Paulus Schoutsen 1b35f0878e Fix CORS when static resources registered (#4727) 2016-12-04 10:57:24 -08:00
Paulus Schoutsen 93872590b6 Fix synology dsm doing I/O inside loop (#4699) 2016-12-04 09:54:49 -08:00
Paulus Schoutsen b2a15e17d3 MQTT Automation: parse payload as JSON (#4703) 2016-12-04 09:53:05 -08:00
Johan Bloemberg 9bf13231f7 Actually test calling async macvendor lookup and fix it. (#4718) 2016-12-04 09:51:40 -08:00
hexa- c8c6bee539 Revert "Update reference to correct tplink switch" (#4722) 2016-12-04 09:50:43 -08:00
Pascal Vizeli b5c2be8ffa Protect hm thread for hangs on events (#4717) 2016-12-04 15:31:24 +01:00
DaveSergeant 4d35f2805f New support Digital Loggers relays (#4684)
* initial commit

Previous work included with no history.  Sorry, I was figuring out how to use git, branches and deal with open source projects.  At this point this is a working switch but with the shortcomings of each of the 8 ports causes a network query.  This needs to be rewritten so that the SwitchDevice is part of a larger device group that is only queried once, saving traffic and preventing the small device from timing out.

* Device polls independent of switches now

Used anel_pwrctrl.py as a basis to extract the per-switch polling out to per-device so it can be trottled properly.  Likewise, no longer touching the device independently for relay status AND relay name.  Getting them both from the same statuslist() return.

* Final comments and tweaks

Lowered cycle and update time since the device update is working so well now.  Effectively no timeouts anymore.

* Added dlipower to requirements

homeassistant.components.switch.digitalloggers

* Tox fixes

pydocstyle updates

* More tox errors

* Yet more tox

Removed useful future TODO and helpful details on the structure of the statuslocal list.
Good catch on not initializing .update(), though it worked.

* Blank line fix

* Added file to .coveragerc
2016-12-03 23:40:22 -08:00
Marcelo Moreira de Mello 53c1b93b61 Added persistent_notification in case of error during Unifi device_tracker setup (#4682) 2016-12-03 22:11:52 -08:00
Thibault Cohen c25aa56751 Add Sharp AquosTV component (#4679) 2016-12-03 22:09:49 -08:00
Pascal Vizeli e8c9dcf0fe Migrate remote to async (#4678)
* Migrate remote to async

* add coro

* remove sync from init since only used in harmony

* import ATTR from remote

* remove unused sync stuff from tests
2016-12-03 22:08:24 -08:00
William Scanlon ca63e44227 Wink hub sensor (#4704) 2016-12-03 21:39:48 -08:00
Johan Bloemberg 776e53a7f0 Dsmr hourly gas usage. (#4609)
* Hourly rate of Gas consumption. Use proper unknown state.

* Import unknown state constant.

* doh

* Cleanup device add.

* Fix lint.

* Add test for derivative calculation.

* Remove conflict.

* Document and move calculation into update call.
2016-12-03 20:45:42 -08:00
Dan a099430834 Universal source list (#4086)
* Add source_list to universal media player

* Expanded attirubte and command support for UMP

Added support to the universal media player
for the following:
    Volume Set
    Current Source
    Set Source
    Current Volume

The goal is to facilitate a single-card media player
that includes source selection and setting the volume
of the receiver.

Example setup:
```
media_player:
  - platform: universal
    name: Media Center
    children:
      - media_player.kodi
      - media_player.cast
    commands:
      select_source:
        service: media_player.select_source
        data:
          entity_id: media_player.receiver
      volume_set:
        service: media_player.volume_set
        data:
          entity_id: media_player.receiver
      volume_mute:
        service: media_player.volume_mute
        data:
          entity_id: media_player.receiver
      turn_on:
        service: homeassistant.turn_on
        data:
          entity_id: media_player.receiver
      turn_off:
        service: homeassistant.turn_off
        data:
          entity_id: media_player.receiver
    attributes:
      state: media_player.receiver
      is_volume_muted: media_player.receiver|is_volume_muted
      volume_level: media_player.receiver|volume_level
      source: media_player.receiver|source
      source_list: media_player.receiver|source_list
```

* Remove print statements

* Change service call back to use call_from_config

* Modified service calls to use template data

* linting fixes

* Add tests

* linting fices

* More pylinting
2016-12-03 20:09:28 -08:00
Pascal Vizeli 7746ecd98e Migrate weather to async (#4677) 2016-12-03 17:58:43 -08:00
Josh Nichols 10d1496f5a Updated python-nest to fix a camera bug when loading images (#4701) 2016-12-03 17:55:14 -08:00
Sebastian von Minckwitz cf0ff54d14 Add option to hide the group card switch (#4631)
* Add option to hide the group card switch

* Disallow control of hidden group switches

* Revert "Disallow control of hidden group switches"

This reverts commit 75e5ddfe30.

* Changed hide_switch to control
2016-12-03 17:50:11 -08:00
Magnus Ihse Bursie 97cc76b43e Remove global variable from tellstick code (#4700)
* 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.
2016-12-03 17:27:55 -08:00
Jacob Minnis c89e6ec915 Updated email message headers to have 'Date' and 'Message-Id' fields (#4693) (#4695) 2016-12-03 16:56:42 -08:00
Pascal Vizeli efdf51b542 Bugfix sonos hosts (#4698) 2016-12-04 00:31:27 +01:00
Paulus Schoutsen bbb251c0cf Version bump to 0.35.0dev0 2016-12-03 12:17:16 -08:00
Paulus Schoutsen 94b719e150 Merge pull request #4626 from home-assistant/dev
0.34
2016-12-03 12:17:02 -08:00
Paulus Schoutsen 69d3a3dd32 Version bump to 0.34 2016-12-03 12:16:18 -08:00
Paulus Schoutsen 4904653b70 Yarl has been fixed (#4694) 2016-12-03 11:59:05 -08:00
Fabian Affolter dddf4d1460 Style 0.34 (#4689)
* Minor style updates

* Minor style updates

* Update validation and logger messages

* Update ordering

* Fix lint issue

* Fix line too long

* Update ordering

* update logger messages
2016-12-03 20:46:04 +01:00
GadgetReactor 9a6c9cff30 Update reference to correct tplink switch (#4670) 2016-12-03 11:38:14 -08:00
Paulus Schoutsen d3b62e1fe1 Requirements use zip instead of git (#4692) 2016-12-03 10:18:00 -08:00
Paulus Schoutsen f63a79ee8f Remove not dev related scripts (#4690) 2016-12-03 09:59:20 -08:00
Paulus Schoutsen 898ba56d9f Fix aiohttp build (#4691) 2016-12-03 09:49:10 -08:00
Josh Nichols 64a5bff5b2 Nest further improvements (#4655)
* Further improvements on nest platform

- fix binary sensor
- add deprecations for monitored_conditions
- better names for sensors (includes device type)

* lint

* Remove unused weather sensor

* Fix to python-nest to a specific commit

* lint

* lint

* lint

* lint
2016-12-03 09:26:47 -08:00
Paulus Schoutsen af7de8d5ae Merge remote-tracking branch 'origin/master' into dev 2016-12-03 09:11:47 -08:00
Pascal Vizeli 754d98bcd5 Cleanups on homematic climate (#4685) 2016-12-03 14:06:08 +01:00
Paulus Schoutsen 4874030b70 Have api_streams sensor also monitor websocket connections (#4668) 2016-12-02 18:17:46 -08:00
Paulus Schoutsen 84c89686a9 Update __init__.py 2016-12-02 09:13:39 -08:00
Brent Hughes 48fd8f1f63 InfluxDB: Fixed attributes that are lists causing invalid syntax (#4642)
* Fixed attributes that are lists cuasing invalid influx syntax

* Added bool and fixed mixed data type issue

* Fixed changing nearly all data types to float causing some worse influxdb errors. whoops

* Added line to end of file
2016-12-01 23:02:58 -08:00
Lewis Juggins 83a108b20a Sonos specify IP for event subscription (#4177) 2016-12-01 22:22:03 -08:00
Alberto Arias Maestro b0a800cc6d Update commands to match the strings in pynx584 (#4623)
The command string don't match the ones pynx584. See source code:

https://github.com/kk7ds/pynx584/blob/master/nx584/api.py#L68
2016-12-01 22:20:44 -08:00
Matt N 1f5f4e7a89 zoneminder: Support excluding archived events (#4445) 2016-12-01 22:17:38 -08:00
Fabian Affolter b1fbada02d Update throttle and add more attributes (#4644) 2016-12-01 22:15:48 -08:00
Lewis Juggins 08909ed420 (InfluxDB) Configuration for a default measurement value for events without a unit. (#4632) 2016-12-01 22:13:55 -08:00
Nick Touran ec8969351d Prevent Pandora component from crashing or hanging during shutdown. (#4255)
* Prevent Pandora component from crashing or hanging during shutdown.

* Update pandora.py

* Update pandora.py
2016-12-01 22:06:23 -08:00
Javier González Calleja 801a69be3a Extending efergy component for get the amount of energy consumed (#4202)
* Extending efergy component for get the amount of energy consumed

* Changing units from kW to kWh

* Chaning units for Instant Consumption from kWh to kW

* Adding timeout for get and removing pylint config

* Update efergy.py
2016-12-01 22:00:17 -08:00
Fabian Affolter 51e20c92f9 WIP Fix pylint and PEP257 issues (tests) (#4120)
* Fix pylint and PEP257 issues

* More PEP257 fixes
2016-12-01 21:45:19 -08:00
Russell Cloran 443553ff16 Handle IPv6 in zeroconf (#4052) 2016-12-01 21:43:33 -08:00
Pascal Vizeli 2e6a48ff5f WIP: Migrate scene to async + homeassistant scene async (#4665)
* Migrate scene to async + homeassistant scene async

* fix lint

* Update state.py

* Fix tests
2016-12-01 21:38:12 -08:00
Paulus Schoutsen 49cfe38cca Demo platform to group climate instead of thermostat 2016-12-01 21:11:13 -08:00
Pascal Vizeli 8a042586f1 Migrate sensor to async (#4663) 2016-12-01 18:31:55 -08:00
Johan Bloemberg 08f8e540e3 Macvendor (#4468)
* Add MAC vendor lookup for device_tracker.

* Test vendor mac lookup and fix device attribute.

* Generate requirements.

* Style.

* Use hyphen instead of underscore to satisfy 'idna'.

https://github.com/kjd/idna/issues/17

* Resort imports.

* Refactor macvendor to use macvendors.com API instead of netaddr library.

* Test vendor lookup using macvendors.com api.

* Remove debugging.

* Correct description.

* No longer needed.

* Device tracker is now an async component. Fix ddwrt tests.

* Fix linting.

* Add test case for error conditions.

* There is no reason to retry failes vendor loopups as they won't be saved to the file anyways at that point.

* Sorry, bad assumption, this only made things worse.

* Wait for async parts during setup component to complete before asserting results.

* Fix linting.

* Is generated when running 'coverage html'.

* Undo isort.

* Make aioclient_mock exception more generic.

* Only lookup mac vendor string with adding new device to known_devices.yaml.

* Undo isort.

* Revert unneeded change.

* Adjust to use new websession pattern.

* Always make sure to cleanup response.

* Use correct function to release response.

* Fix tests.
2016-12-01 18:30:41 -08:00
Brandon Weeks f09b888a8a Fixes #3511 - handle multiple return values (#4659) 2016-12-01 18:28:52 -08:00
lichtteil 279f82acc4 Mutate values for light color temperature and white value (#4660)
* Mutate values for light color temperature and white value

* Fix lenght of line

* Fix under-indented line

* Fix cgl
2016-12-01 18:26:53 -08:00
iandday de6c5a503b Remote Component and Harmony Platform (#4254)
* Initial Harmony device support, working current activity sensor and switch for each activity
TODO: add new device per hub to send device specific activity

 Changes to be committed:
	new file:   homeassistant/components/harmony.py
	new file:   homeassistant/components/sensor/harmony.py
	new file:   homeassistant/components/switch/harmony.py

* ready for beta, I think

 Changes to be committed:
	modified:   homeassistant/components/harmony.py
	modified:   homeassistant/components/sensor/harmony.py
	modified:   homeassistant/components/switch/harmony.py

*  Changes to be committed:
	modified:   homeassistant/components/harmony.py
	new file:   homeassistant/components/remote/__init__.py
	new file:   homeassistant/components/remote/harmony.py
	new file:   homeassistant/components/remote/services.yaml
	modified:   homeassistant/components/sensor/harmony.py
	modified:   homeassistant/components/switch/harmony.py
Implemented remote component and harmony platform

* streamlined harmony support

* typo

* Initial Harmony device support, working current activity sensor and switch for each activity
TODO: add new device per hub to send device specific activity

 Changes to be committed:
	new file:   homeassistant/components/harmony.py
	new file:   homeassistant/components/sensor/harmony.py
	new file:   homeassistant/components/switch/harmony.py

* ready for beta, I think

 Changes to be committed:
	modified:   homeassistant/components/harmony.py
	modified:   homeassistant/components/sensor/harmony.py
	modified:   homeassistant/components/switch/harmony.py

*  Changes to be committed:
	modified:   homeassistant/components/harmony.py
	new file:   homeassistant/components/remote/__init__.py
	new file:   homeassistant/components/remote/harmony.py
	new file:   homeassistant/components/remote/services.yaml
	modified:   homeassistant/components/sensor/harmony.py
	modified:   homeassistant/components/switch/harmony.py
Implemented remote component and harmony platform

* streamlined harmony support

* typo

* reworked token generation

* delete

* Initial Harmony device support, working current activity sensor and switch for each activity
TODO: add new device per hub to send device specific activity

 Changes to be committed:
	new file:   homeassistant/components/harmony.py
	new file:   homeassistant/components/sensor/harmony.py
	new file:   homeassistant/components/switch/harmony.py

* Initial Harmony device support, working current activity sensor and switch for each activity
TODO: add new device per hub to send device specific activity

 Changes to be committed:
	new file:   homeassistant/components/harmony.py
	new file:   homeassistant/components/sensor/harmony.py
	new file:   homeassistant/components/switch/harmony.py

* ready for beta, I think

 Changes to be committed:
	modified:   homeassistant/components/harmony.py
	modified:   homeassistant/components/sensor/harmony.py
	modified:   homeassistant/components/switch/harmony.py

* ready for beta, I think

 Changes to be committed:
	modified:   homeassistant/components/harmony.py
	modified:   homeassistant/components/sensor/harmony.py
	modified:   homeassistant/components/switch/harmony.py

*  Changes to be committed:
	modified:   homeassistant/components/harmony.py
	new file:   homeassistant/components/remote/__init__.py
	new file:   homeassistant/components/remote/harmony.py
	new file:   homeassistant/components/remote/services.yaml
	modified:   homeassistant/components/sensor/harmony.py
	modified:   homeassistant/components/switch/harmony.py
Implemented remote component and harmony platform

* streamlined harmony support

* typo

* reworked token generation

* delete

* readded after rebase

* cleaning up style errors

* modified .coveragerc

* moved import statements

* added more debug logging

* Added URL encoding of token received from Logitech

* Corrected import for python 3

* new pyharmony version

* new pyharmony version

* remote tests

* only write config file if not present or sync service is called

* more tests

* more tests

* bumped pyharmony version to work with new auth

* bumped pyharmony version to work with new auth

* style corrections

* harmony local auth and remote demo platform

* style fix

* PR refinements and permission issues

* forgot a blank line

* removed sync test from test_init

* removed sync test from test_init

* visual indent

* send_command test in demo platform
2016-12-01 12:48:08 -08:00
Jesse Newland 898f89ffc7 Make trusted_networks iterable (#4649) 2016-12-01 12:28:59 -08:00
Jan Losinski 5c807c6bd9 MPD: Reconnect mpd client afetr OSError (#4651)
If the mpd client ran into an socket timeout, the socket will raise an
OSError on every further request. This adds OSError to the list of
excptions, that causes a client reconnect.

This fixes #4650

Signed-off-by: Jan Losinski <losinski@wh2.tu-dresden.de>
2016-12-01 12:28:31 -08:00
Jan Losinski dd84b4e237 Mpd: Use "file" instead "id" for media_content_id (#4653)
In media_content_id() the "id" of the current song was returned. as
stated in bug #4652 the id is only the Tracklist-Id in the current
tracklist and is omitted if the track is not part of a tracklist (what
caused the bug in the first place).

To match the semantics described in the dockstring, to return a "Content
ID", this chooses the filename of the current song as id and returns
it.

It also uses get() instead of [] to prevent KeyError.

This fixes bug #4652

Signed-off-by: Jan Losinski <losinski@wh2.tu-dresden.de>
2016-12-01 12:20:42 -08:00
John Mihalic 6dfae7a259 Add support for NUT (Network UPS Tools) sensor. (#4551)
* Add support for NUT (Network UPS Tools) sensor.

* Address comments

* Fix issues

* Fix issues 2

* Fix unhandled exception
2016-12-01 08:58:16 +01:00
Johann Kellerman c6c8cd4f51 Yr.no: New aiohttp client needs params to form websession URL (#4634)
* Yr.no: New aiohttp client needs params to form websession URL
* Support params in aiohttp mocking
2016-12-01 08:20:21 +02:00
Pascal Vizeli bde7176b3c Migrate light component to async (#4635) 2016-11-30 13:33:38 -08:00
William Scanlon 4c03d670c1 Wink PubNub v4 (#4561)
* PubNub v4

* Updated to pubnubsub-handler 0.0.5

* Updated requirements_all.txt
2016-11-30 13:12:26 -08:00
Johan Bloemberg 406afbb369 Philips controls (#4441)
* Add channel switching for philips tvs.

* Disable track buttons when not watching tv.

* Undo isort config.

* Yes it does.

* Just testing some assumption on hound's flake8 behaviour.

* Revert "Just testing some assumption on hound's flake8 behaviour."

This reverts commit ff9940b39e.

* poke
2016-11-30 13:07:57 -08:00
Marcelo Moreira de Mello 9c6609cb79 Added support to Amcrest camera (#4573)
* Introduced support to Amcrest IP Cameras

* Fixed lint issues

* Fixed requirements test

* * Implemented test to verify crendentials during camera setup

* Added persistent_notification in case of error when during Amcrest setup
2016-11-30 13:07:17 -08:00
Paulus Schoutsen e5504b39ec Close aiohttp responses (#4624)
* Close aiohttp responses

* Update generic.py
2016-11-30 13:05:58 -08:00
Paulus Schoutsen b1ef5042f9 Make updater more robust (#4625) 2016-11-30 13:03:09 -08:00
Paulus Schoutsen b35fa4f1c1 Finish all tasks before setup phase is done (#4606) 2016-11-30 13:02:45 -08:00
Martin Hjelmare 71da9d2f50 Fix mysensors ir switch overwriting devices (#4612) 2016-11-30 13:02:18 -08:00
Fabian Affolter 86388f5af2 Upgrade Sphinx to 1.4.9 (#4641) 2016-11-30 14:21:00 +01:00
Pascal Vizeli 17f0fb69bd Homematic update with HomematicIP/HomematicWired support and multible… (#4568)
* Homematic update with HomematicIP/HomematicWired support and multible connections

* fix bug in virtualkey service

* create new service & cleanups

* fix lint

* Pump pyhomematic 0.1.18
2016-11-29 20:53:02 +01:00
DaveSergeant 2d02baf3d0 Default dimmable brightness to 255 from 100 (#4621)
* Default dimmable brightness to 255 from 100

Full brightness for ISY dimmers is 255. The current 100 value turns dimmer switches on to just under half brightness.  Probably just an oversight from the Sept implementation.

* Brightness change for turn_on, ramp for turn_off.

Per discussion with Teagan42 and jbcodemonkey, the brightness should rightfully be None and not an explicit value.  There is a continuing issue that the ISY modules don't respect HA's brightness customization values.  A new issue will be opened for this.
Additionally, turn_off was using ISY's fastoff() which didn't respect the ramping time.  The default behavior should just be off().
2016-11-29 09:50:12 -07:00
Lewis Juggins 66473120ab Add test for delay on automations (#4630) 2016-11-29 08:45:04 -08:00
Christian Brædstrup 6ddbb4d568 Improved exception handling for D-Link switch (#4633) 2016-11-29 08:40:51 -08:00
Valentin Alexeev 154c69a454 Bump version of pwaqi module to 1.3. Fixes #4595. (#4610) 2016-11-28 23:11:21 -08:00
Charles Spirakis ad4ec49f9c Update color names to follow w3.org list. (#4374)
The color names -> rgb dictionary now follows the
color names listed in the w3.org site for css3,
section 4.3. Extended color keywords:

https://www.w3.org/TR/2010/PR-css3-color-20101028/#svg-color
2016-11-28 22:59:46 -08:00
Fabian Affolter e8367f245a Update ordering and sync logger messages (#4615) 2016-11-28 20:50:42 +01:00
Fabian Affolter 4bc37bd661 Add timeout to request, update ordering, make dev info message shorter, and (#4613)
update the other logger messages
2016-11-28 20:49:01 +01:00
Pascal Vizeli b4841a17a6 Hotfix device_tracker yaml config (#4611) 2016-11-28 18:43:47 +01:00
Valentin Alexeev 3b9d5cdf73 DuneHD media player (#4588)
* Implement WAQI sensor

* Corrections based on CI check.

* Updated requirements_all.txt for pwaqi==1.2

* Require latest version of pwaqi

* Initial implementation of DuneHD media player component based on pdunehd.

* Major: avoid update() in property fetch,
Major: implement source support,
Major: single device per media player instance,
Major: support for volume / mute controls

* Pythonify pdunehd.
Support media_title.

* Fix pylint.

* Further pylint.

* docstring

* Formatting and indentation.

* Change indentation to spaces.

* Update coverage and recorded requirements before PR.

* Further pylint / fake8 / pydocstyle fixes.

* Implement next / prev track,
Properly decode blu-ray playback,
Attempt to decode media title

* Fix play / pause
Linting

* Update requirements.
Fix lint.

* Fix lint and syntax error

* Yet more linting.

* Yet more linting.

* Fix lint: line too long.

* Force update of HA state.
2016-11-27 23:42:57 -08:00
Daniel Perna 77d568dc47 Fixed incorrect event-order (#4605) 2016-11-27 22:29:21 -08:00
Paulus Schoutsen 9db1ff8cd4 Update frontend 2016-11-27 22:27:02 -08:00
Oliver 248a90b71d Added denon media player controls via denonavr library (#4580)
* Added denonavr module again

* Edited requirements_all.txt

* Edited .coveragerc

* Fixed error with AUX1 input source in library

* Adding device should not fail on connection timeout

* Changed method to select source

* Update requirements_all.txt
2016-11-27 22:13:22 -08:00
Mark King d8c4af9c81 TEMPer component: reset devices on address change (#4596)
Fixes https://github.com/home-assistant/home-assistant/issues/4389

The USB address of these devices periodically changes, causing
home-assistant to fail to read the temperature data. This PR fixes this
by re-reading the available devices on failure. I've been running this
for several days and for the first time have consistent temperature
data without having to restart home-assistant.
2016-11-27 22:01:13 -08:00
Fabian Affolter 1e6c660f59 Threshold sensor (#4216)
* Add threshold sensor

* New config requirement, update async, other changes, and update tests

* Update threshold.py
2016-11-27 21:55:26 -08:00
Harris Borawski 44a508e86c Add exception handling to Sonarr (#4569)
* Add exception handling to request call to prevent
failure in setup_platform if host is down

* update for comments

* update test for state being none

* remove unused import
2016-11-27 21:11:49 -08:00
Bjarni Ivarsson 92c6cee2a1 Support for media_position property on media_player (#4172)
* Added support for media_position property to media_player + implementation for sonos.

* Pla yback progress now updates without needed state transitions in HA.

* Linting fixes

* media_position_update_at property is now a datetime.

* Minor fix.

* Linting fixes.
2016-11-27 17:45:49 -08:00
Paulus Schoutsen d4bc8e23af Update frontend 2016-11-27 17:21:11 -08:00
Antoine Bertin f0db698f75 Light effects (#4538)
* Add support for light effects

* Move PLATFORM_SCHEMA changes in light to mqtt_template

* Add effect validation

* Add unittests

* Add light effect to demo and unittests

* Use cv.string for config validation

* Use cv.ensure_list for config validation

* Fix typo

* Remove unused exception management for effect
2016-11-27 17:15:28 -08:00
Pascal Vizeli cf57db919e Refactory aiohttp clientsession handling in HA (#4602)
* Refactory aiohttp clientsession handling in HA

* remove from core / update platforms / rename file
2016-11-27 16:26:46 -08:00
Josh Nichols 84b12ab007 Nest Cam support (#4292)
* start nestcam support

* start nestcam support

* introduce a access_token_cache_file

* Bare minimum to get nest thermostat loading

* occaisonally the image works

* switch to nest-aware interval for testing

* Add Nest Aware awareness

* remove duplicate error logging line

* Fix nest protect support

* address baloobot

* fix copy pasta

* fix more baloobot

* last baloobot thing for now?

* Use streaming status to determine online or not. online from nest means its on the network

* Fix temperature scale for climate

* Add support for eco mode

* Fix auto mode for nest climate

* update update current_operation and set_operation mode to use constant when possible. try to get setting something working

* remove stale comment

* unused-argument already disabled globally

* Add eco to the end, instead of after off

* Simplify conditional when the hass mode is the same as the nest one

* away_temperature became eco_temperature, and works with eco mode

* Update min/max temp based on locked temperature

* Forgot to set locked stuff during construction

* Cache image instead of throttling (which returns none), respect NestAware subscription

* Fix _time_between_snapshots before the first update

* WIP pin authorization

* Add some more logging

* Working configurator, woo. Fix some hound errors

* Updated pin workflow

* Deprecate more sensors

* Don't update during access of name

* Don't update during access of name

* Add camera brand

* Fix up some syntastic errors

* Fix ups ome hound errors

* Maybe fix some more?

* Move snapshot simulator url checking down into python-nest

* Rename _ready_to_update_camera_image to _ready_for_snapshot

* More fixes

* Set the next time a snapshot can be taken when one is taken to simplify logic

* Add a FIXME about update not getting called

* Call update during constructor, so values get set at least once

* Fix up names

* Remove todo about eco, since that's pretty nest

* thanks hound

* Fix temperature being off for farenheight.

* Fix some lint errors, which includes using a git version of python-nest with updated code

* generate requirements_all.py

* fix pylint

* Update nestcam before adding

* Fix polling of NestCamera

* Lint
2016-11-27 16:18:47 -08:00
Johan Bloemberg 601193b1d2 Expose isort preferences for tools. (#4481)
* Expose isort preferences for tools.

* Adhere to pylints sorted imports requirement.

* More documentation, set typing in between stdlib and 3rd party.
2016-11-27 14:33:30 -08:00
Sean Dague 038b1c1fc6 precision properties for climate components (#4562)
This lets components declare their precision for temperatures. If
nothing is declared, we assume 0.1 C and whole integer precision in
F. Currently this supports only WHOLE, HALVES, and TENTHS for
precision, but adding other precision levels is pretty straight
forward.

This also uses proliphix as an example of changing the precision for a
platform.

Closes bug #4350
2016-11-27 14:19:12 -08:00
Paulus Schoutsen 0d734303a4 HTTP: Fix registering views after start (#4604) 2016-11-27 14:01:12 -08:00
Lewis Juggins ff4cb23f2a Update nginx docs (#4603) 2016-11-27 13:49:21 -08:00
Paulus Schoutsen e94b4ec006 Tweak services return result (#4600)
* Tweak services return result

* Lint
2016-11-27 12:33:02 -08:00
Paulus Schoutsen be91207830 Upgrade HBMQTT (#4599) 2016-11-27 12:21:20 -08:00
Ron Klinkien ecf285105c Fixed unit_of_measurement functionality for knx sensor (#4594) 2016-11-27 12:21:05 -08:00
Paulus Schoutsen 767f3d58ff Add websocket_api as frontend dependency 2016-11-27 12:13:01 -08:00
Lewis Juggins 34097cda24 Allow generic thermostat tolerance to be customisable to determine the temperature difference required to turn switch on. (#4585) 2016-11-27 09:31:00 +00:00
Michaël Arnauts 0ce3703e30 Remove fixed throttle for binary_sensor.command_line and sensor.command_line since the scan_interval is configured trough YAML since #1059 (#4586)
* Remove fixed throttle for binary_sensor.command_line and sensor.command_line since the scan_interval is configured trough YAML since #1059

* Clean up imports

* Add SCAN_INTERVAL=60 to put default scan_inteval back to 60
2016-11-27 00:29:49 -08:00
Paulus Schoutsen 464e843186 Update frontend 2016-11-26 23:44:20 -08:00
Paulus Schoutsen 5d2b7a6e0b Add ping to websockets API (#4592) 2016-11-26 23:22:34 -08:00
Paulus Schoutsen 914a868fbd Add websocket API (#4582)
* Add websocket API

* Add identifiers to interactions

* Allow unsubscribing event listeners

* Add support for fetching data

* Clean up handling code websockets api

* Lint

* Add Home Assistant version to auth messages

* Py.test be less verbose in tox
2016-11-26 18:23:28 -08:00
Paulus Schoutsen 03e0c7c71c Prevent edimax from doing I/O in event loop (#4584) 2016-11-26 10:10:29 -08:00
Paulus Schoutsen 32ffd006fa Reorganize HTTP component (#4575)
* Move HTTP to own folder

* Break HTTP into middlewares

* Lint

* Split tests per middleware

* Clean up HTTP tests

* Make HomeAssistantViews more stateless

* Lint

* Make HTTP setup async
2016-11-25 13:04:06 -08:00
Fabian Affolter 58b85b2e0e Upgrade speedtest-cli to 1.0.0 (#4578) 2016-11-25 12:30:53 -08:00
Marcelo Moreira de Mello 61653a517d #4421 - Forced icons to be displayed via SSL to avoid Mixed Content warnings (#4544)
* #4421 - Forced icons to be displayed via SSL to avoid Mixed Content warnings

* Fixed houndci-bot whitespace

* Using regex to replace http:// for https://

* Created assert test to verify https translation
2016-11-25 20:03:12 +00:00
Vlad Korniev 2a7bc0e55c Advanced Ip filtering (#4424)
* Added IP Bans configuration

* Fixing warnings

* Added ban enabled option and unit tests

* Fixed py34 tox

* http: requested changes fix

* Requested changes fix
2016-11-24 21:52:10 -08:00
Lewis Juggins 95b439fbd5 Upgrade aiohttp to 1.1.5 (#4213) 2016-11-24 21:37:56 -08:00
Paulus Schoutsen 1872481f47 Merge pull request #4572 from home-assistant/release-0-33-4
0.33.4
2016-11-24 15:37:38 -08:00
Paulus Schoutsen 44b6d23e0f Version bump to 0.33.4 2016-11-24 14:57:12 -08:00
Paulus Schoutsen 58eb0ec52a Set executor pool size to 10 (#4571) 2016-11-24 14:56:59 -08:00
Paulus Schoutsen febe16d700 Set executor pool size to 10 (#4571) 2016-11-24 14:56:33 -08:00
Pascal Vizeli 8c56091af7 Hotfix executor pool size (#4552) 2016-11-24 14:53:46 -08:00
Paulus Schoutsen eacdce9ed9 Track tasks only during shutdown and tests (#4428)
* Track tasks only when needed

* Tweak async_block_till_done
2016-11-24 14:49:29 -08:00
Paulus Schoutsen 42c99b0ccb Pass hass object to ServiceRegistry constructor (#4570) 2016-11-24 14:02:39 -08:00
Jon Caruana 2a6c0cfc17 LiteJet: Unit tests and new trigger options held_more_than and held_less_than. (#4473)
* LiteJet: Unit tests and new trigger options held_more_than and held_less_than.
* Unit tests for the LiteJet component and associated platforms. Coverage is almost 100% -- just misses one line.
* The automation LiteJet trigger returns an empty "removal" function to ensure the automation base is happy with it. The pylitejet library doesn't actually support a real removal.
* The automation LiteJet trigger can detect hold time and act appropriately to support things like short tap or long hold.

* LiteJet: Fix indent in unit test source code.

* LiteJet: Fix test_include_switches_* unit tests on Python 3.5

* LiteJet: Remove wait for state existence from unit tests. Recent fixes to discovery make this no longer necessary.
2016-11-24 09:52:15 -08:00
Fabian Affolter 84040892df Remove globally disable pylint issue (#4565) 2016-11-24 12:25:01 +01:00
Fabian Affolter 345008c673 Fix docstring (#4564) 2016-11-24 10:15:00 +01:00
Matt N 14d1494cd2 systemmonitor: Support monitoring removable network interfaces (#4462) 2016-11-24 10:14:38 +01:00
Marcel030nl f1d11e77ed Update pvoutput.py (#4557)
This addition could be usefull when working with the template sensor using the data of this sensor.
2016-11-24 09:58:38 +01:00
Fabian Affolter b1b8715f7d Minor comment updates and ordering (#4554) 2016-11-24 00:27:31 +01:00
Fabian Affolter b6d559da1f Add timeout to requests, use consts, and add link to docs (#4555) 2016-11-24 00:26:59 +01:00
Fabian Affolter 475c412ae4 Minor changes (switch.hook) (#4553)
* Use string formatting, add link to docs, and pylint

* Extent platform for validation
2016-11-24 00:21:48 +01:00
Pascal Vizeli c04a002c55 Hotfix executor pool size (#4552) 2016-11-23 09:52:03 -08:00
dasos 5013a82655 Hook Smart Home support (#4392)
* Support for Hook (hooksmarthome.com)

* Linting

* Add asyncio

* Move to aiohttp

* Yield more
2016-11-23 14:52:14 +00:00
Johan Bloemberg 05181bf232 0.4 release upstream. (#4545) 2016-11-23 10:44:37 +00:00
Marcelo Moreira de Mello c22a73e1d0 Removed raise statement to don't pollute the user log. (#4536)
* Removed raise statement to don't polute the user log.
Only the error message should be displayed.

Nov 22 11:28:32 tchellopi hass[20138]: 16-11-22 11:28:32 ERROR (MainThread) [homeassistant.core] Error doing job: Task exception was never retrieved
Nov 22 11:28:32 tchellopi hass[20138]: Traceback (most recent call last):
Nov 22 11:28:32 tchellopi hass[20138]: File "/usr/local/lib/python3.5/asyncio/tasks.py", line 241, in _step
Nov 22 11:28:32 tchellopi hass[20138]: result = coro.throw(exc)
Nov 22 11:28:32 tchellopi hass[20138]: File "/home/hass/.virtualenvs/home_assistant/lib/python3.5/site-packages/homeassistant/helpers/entity_component.py", line 386, in _update_entity_states
Nov 22 11:28:32 tchellopi hass[20138]: yield from update_coro
Nov 22 11:28:32 tchellopi hass[20138]: File "/home/hass/.virtualenvs/home_assistant/lib/python3.5/site-packages/homeassistant/helpers/entity.py", line 213, in async_update_ha_state
Nov 22 11:28:32 tchellopi hass[20138]: yield from self.hass.loop.run_in_executor(None, self.update)
Nov 22 11:28:32 tchellopi hass[20138]: File "/usr/local/lib/python3.5/asyncio/futures.py", line 361, in __iter__
Nov 22 11:28:32 tchellopi hass[20138]: yield self  # This tells Task to wait for completion.
Nov 22 11:28:32 tchellopi hass[20138]: File "/usr/local/lib/python3.5/asyncio/tasks.py", line 296, in _wakeup
Nov 22 11:28:32 tchellopi hass[20138]: future.result()
Nov 22 11:28:32 tchellopi hass[20138]: File "/usr/local/lib/python3.5/asyncio/futures.py", line 274, in result
Nov 22 11:28:32 tchellopi hass[20138]: raise self._exception
Nov 22 11:28:32 tchellopi hass[20138]: File "/usr/local/lib/python3.5/concurrent/futures/thread.py", line 55, in run
Nov 22 11:28:32 tchellopi hass[20138]: result = self.fn(*self.args, **self.kwargs)
Nov 22 11:28:32 tchellopi hass[20138]: File "/home/hass/.homeassistant/custom_components/sensor/wunderground.py", line 187, in update
Nov 22 11:28:32 tchellopi hass[20138]: self.rest.update()
Nov 22 11:28:32 tchellopi hass[20138]: File "/home/hass/.virtualenvs/home_assistant/lib/python3.5/site-packages/homeassistant/util/__init__.py", line 296, in wrapper
Nov 22 11:28:32 tchellopi hass[20138]: result = method(*args, **kwargs)
Nov 22 11:28:32 tchellopi hass[20138]: File "/home/hass/.homeassistant/custom_components/sensor/wunderground.py", line 222, in update
Nov 22 11:28:32 tchellopi hass[20138]: ["description"])
Nov 22 11:28:32 tchellopi hass[20138]: ValueError: you must supply a key

* Updated unittest since we are just printing the error instead raising
2016-11-22 23:41:51 -08:00
Charles Blonde c9b353f7a7 Add Bose SoundTouch device support - v2 (#4523)
* Add Bose SoundTouch device support

* Update soundtouch.py
2016-11-22 23:22:52 -08:00
Johan Bloemberg 64cfc4ff02 DSMR sensor (#4309)
* Initial implemenation of DSMR component.

* Fix linting

* Remove protocol V2.2 support until merged upstream.

* Generate requirements using script.

* Use updated dsmr-parser with protocol 2.2 support.

* Add tests.

* Isort and input validation.

* Add entities for gas and actual meter reading. Error handling. Use Throttle.

* Implement non-blocking serial reader.

* Improve logging.

* Merge entities into one, add icons, fix tests for asyncio.

* Add error logging for serial reader.

* Refactoring and documentation.

- refactor asyncio reader task to make sure it stops with HA
- document general principle of this component
- refactor entity reading to be more clear
- remove cruft from split entity implementation

* Use `port` configuration key.

* DSMR V2.2 seems to conflict in explaining which tariff is high and low.

http://www.netbeheernederland.nl/themas/hotspot/hotspot-documenten/?dossierid=11010056&title=Slimme%20meter&onderdeel=Documenten
> DSMR v2.2 Final P1
>> 6.1: table vs table note

    Meter Reading electricity delivered to client normal tariff) in 0,01 kWh - 1-0:1.8.1.255
    Meter Reading electricity delivered to client (low tariff) in 0,01 kWh - 1-0:1.8.2.255

    Note: Tariff code 1 is used for low tariff and tariff code 2 is used for normal tariff.

* Refactor to use asyncio.Protocol instead of loop+queue.

* Fix requirements

* Close transport when HA stops.

* Cleanup.

* Include as dependency for testing (until merged upstream.)

* Fix style.

* Update setup.cfg
2016-11-22 23:03:39 -08:00
Valentin Alexeev bb46009efa World Air Quality Index sensor (#4434)
* Implement WAQI sensor

* Corrections based on CI check.

* Updated requirements_all.txt for pwaqi==1.2

* Require latest version of pwaqi

* Fix lint: single argument for .exception and no more pass statement.

* Further lint fixes.

* pydocstyle fix

* Implement rate throttle.
Data on WAQI is usually updated once an hour - make it refresh every thirty minutes.

* Implement schema validation with voluptuous.
Change exception handling scope.
Move messages to debug().

* Fix lint (empty indented line).

* Sort lines correctly.

* Fix last lint issue.

* Provide additional sensor data as received from WAQI.
Easier-to-read throttle timing.

* Additional object attributes to be unrolled later.
2016-11-22 22:59:27 -08:00
Paulus Schoutsen 3f9250415f Skip broken tests (#4543) 2016-11-22 22:58:14 -08:00
Pascal Vizeli c294a534d0 Migrate binary_sensor to async (#4516) 2016-11-22 22:47:43 -08:00
Harris Borawski 85d6970df8 Add Sensor for Sonarr (#4496)
* Add sonarr sensor and tests for sensor

* Fixed some linting errors and removed unused import

* Add SSL option for those who use SSL from within Sonarr

* Add requirements to all requirements, and sensor to coveragerc

* remove unused variable

* move methods to functions, and other lint fixes

* linting fixes

* linting is clean now

* Remove double requirement

* fix linting for docstrings, this should probably be a part of the script/lint and not just travis
2016-11-22 22:32:45 -08:00
dainok 260a619a40 Added GPSLogger API (#4089)
* Added GPSLogger API, check https://goo.gl/eJnKw5 for details.

* Switched to debug severity and added to coveragerc

* Switched to debug severity for logs

* Updated .coveragerc

* Update .coveragerc

* Merged from sfiorini

* Merged from sfiorini

* Update .coveragerc
2016-11-22 22:19:57 -08:00
Michaël Arnauts 0c6ef3b7f9 Try to register a Chromecast anyway, even if it could not be detected by get_chromecasts(), since it might be on a other network. Fixes #4469. (#4470) 2016-11-22 22:16:01 -08:00
Thomas Friedel 0c47434aad Change Osram to use Github lightify dep (#4256)
* used MindrustUK's version ( https://github.com/MindrustUK/python-lightify/commits/master/osramlightify.py ) from Oct 2, 2016 and changed the REQUIRMENTS line to use the fixed lightify component with thread safety fixes

* reformatted long lines

* updated osramlightify requirements in requirements_all.txt

* ran script gen_requirements_all.py

* rerun requirements gen script on linux

* fixed some inspection warnings

* zip file points to a specific commit

* no requests to lights in properties, instead instance variables are update in update method

* regenerated requirements_all.txt

* removed call to update from is_on() property
2016-11-22 22:10:45 -08:00
Magnus Ihse Bursie 1d8a1df2c4 Refactor tellstick code (#4460)
* 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.
2016-11-22 21:48:22 -08:00
Aaron Morris 65b85ec6c0 Fix missing space in error message between "accuracy" and "is" (#4542) 2016-11-22 20:45:06 -08:00
Paulus Schoutsen b6b9da7e6e Merge pull request #4541 from home-assistant/release-0-33-3
0.33.3
2016-11-22 20:35:55 -08:00
Paulus Schoutsen d18f2684fb Version bump to 0.33.3 2016-11-22 19:39:15 -08:00
Paulus Schoutsen e93b079ef4 Fix platform discovery when platform discovered during discovery of a (#4529)
component
2016-11-22 19:38:34 -08:00
Paulus Schoutsen 356ad6e468 Bump netdisco (#4539) 2016-11-22 19:36:39 -08:00
Johann Kellerman 8f35212dd6 Yr.no update entities every hour (#4521) 2016-11-22 19:35:49 -08:00
Johann Kellerman 0827a26642 Yr.no update entities every hour (#4521) 2016-11-22 19:28:31 -08:00
Paulus Schoutsen b4756e6dda Bump netdisco (#4539) 2016-11-22 18:36:10 -08:00
Paulus Schoutsen 4cc192e445 Disable broken google offset test (#4540) 2016-11-22 18:34:48 -08:00
mnestor 962e5315ab Mock call to google servers (#4532)
* Fix for #4520

* mock call to do_auth to prevent call to google servers
2016-11-22 18:19:32 -08:00
Paulus Schoutsen 2c7e895105 Entity and climate: do not convert temperature unnecessary (#4522)
* Climate: more consistent units

* Prevent unnecessary conversion in entity component

* int -> round

* Disable Google tests because they connect to the internet

* Remove default conversion rounding F->C

* Add rounding of temp to weather comp

* Fix equality

* Maintain precision when converting temp in entity

* Revert "Disable Google tests because they connect to the internet"

This reverts commit b60485dc19.
2016-11-22 17:38:04 -08:00
Paulus Schoutsen 00019b9ff0 Fix warning in test 2016-11-22 12:48:35 -08:00
Pascal Vizeli 8e776b4dc0 Fix wrong name handling in rfxtrx sensor (#4531) 2016-11-22 12:47:37 -08:00
mnestor ce13b0989d Fix for #4520 (#4526)
* Fix for #4520

* fix lint
2016-11-22 10:15:39 -08:00
Paulus Schoutsen c81735cc84 Fix platform discovery when platform discovered during discovery of a (#4529)
component
2016-11-22 08:21:08 -08:00
Fabian Affolter 5d18759146 Upgrade miflora to 0.1.13 (fixes #4479) (#4524) 2016-11-22 15:41:37 +01:00
Malte Franken 9cdcfae8f3 New config parameter for min_max sensor to specify number of digits for rounding mean value (#4237)
* new config parameter to specify number of digits for rounding average value

* fixed two `line too long` errors

* added three new tests for the mean sensor including test for precision of mean value
2016-11-22 15:36:29 +01:00
Gilles Margerie 547d93f631 Added source selection for Denon AVR Media Player (#4304)
* Added source selection for Denon AVR Media Player

* Update denon.py

* Update denon.py

* Update denon.py

* Update denon.py

* Update denon.py

slight format update (space issue and new line)

* Further update regarding formatting

* Updated the source name with lowercase

* Update denon.py
2016-11-21 23:45:17 -08:00
Paulus Schoutsen d841ddc50b Merge pull request #4519 from home-assistant/release-0-33-2
0.33.2
2016-11-21 20:42:21 -08:00
Paulus Schoutsen 40b5824230 Skip google calendar offset test (#4520) 2016-11-21 20:16:50 -08:00
Paulus Schoutsen 86f3e2455d Skip google calendar offset test (#4520) 2016-11-21 20:16:34 -08:00
Paulus Schoutsen 9a065cc536 Version bump to 0.33.2 2016-11-21 19:40:19 -08:00
Richard Cox 8e4dbcaf21 Fixing 'Unknown' status for Nest Protect devices (#4475)
* Fixing 'Unknown' status for Nest Protect devices

* Fixing bad formatting
2016-11-21 19:39:38 -08:00
Richard Cox 6863d2e0af Fixing 'Unknown' status for Nest Protect devices (#4475)
* Fixing 'Unknown' status for Nest Protect devices

* Fixing bad formatting
2016-11-21 19:39:23 -08:00
John Arild Berentsen c23809488b Neato Fixes (#4490)
* Fix, switch state. Move constants to hub

* Responsiveness

* Whitespace

* Delay was not needed as commands does not return until done.
2016-11-21 19:36:54 -08:00
John Arild Berentsen 248f5c0209 Neato Fixes (#4490)
* Fix, switch state. Move constants to hub

* Responsiveness

* Whitespace

* Delay was not needed as commands does not return until done.
2016-11-21 19:36:44 -08:00
Jack Chapple e5aa40fa5d Fixes #4500 (#4502) 2016-11-21 19:35:49 -08:00
Jack Chapple 1f573b46a4 Fixes #4500 (#4502) 2016-11-21 19:35:36 -08:00
hexa- 0647bb7f6b switch.tplink: expect daily stats to be empty (#4504)
Signed-off-by: Martin Weinelt <hexa@darmstadt.ccc.de>
2016-11-21 19:34:58 -08:00
hexa- a73fbbaf7a switch.tplink: expect daily stats to be empty (#4504)
Signed-off-by: Martin Weinelt <hexa@darmstadt.ccc.de>
2016-11-21 19:34:48 -08:00
Pascal Vizeli 755f5b61b7 Bugfix discovery use wrong time async (#4515)
* Bugfix discovery use wrong time async

* fix lint
2016-11-21 19:33:32 -08:00
Pascal Vizeli 6869c7401e Bugfix device_tracker init tracker scan (#4514) 2016-11-21 19:33:32 -08:00
Pascal Vizeli 835577b2bc Bugfix discovery use wrong time async (#4515)
* Bugfix discovery use wrong time async

* fix lint
2016-11-21 19:33:08 -08:00
Pascal Vizeli 859d0d5ad6 Bugfix device_tracker init tracker scan (#4514) 2016-11-21 19:32:21 -08:00
Fabian Affolter aed797f438 Upgrade freesms to 0.1.1 (#4491) 2016-11-21 17:32:05 +01:00
Fabian Affolter eb8093934f Upgrade python-hpilo to 3.9 (#4482) 2016-11-21 17:31:14 +01:00
Fabian Affolter 608b482906 Upgrade sqlalchemy to 1.1.4 (#4486) 2016-11-21 17:29:06 +01:00
Fabian Affolter 7207c2cca1 Upgrade sendgrid to 3.6.3 (#4485) 2016-11-21 17:28:31 +01:00
Fabian Affolter ed1d0b4197 Upgrade astral to 1.3.2 (#4505) 2016-11-21 17:27:48 +01:00
Fabian Affolter 63461e9007 Upgrade slacker to 0.9.30 (#4484) 2016-11-21 17:27:15 +01:00
Fabian Affolter 40a2145558 Upgrade yahoo-finance to 1.4.0 (#4483) 2016-11-21 17:25:43 +01:00
Sean Dague d883b18751 Merge pull request #4503 from sdague/pyvera_bump
Bump pyvera to 0.2.21
2016-11-21 06:09:54 -05:00
Sean Dague b8e462cf5b Bump pyvera to 0.2.21
pyvera 0.2.21 fixes the fact that use of requests.get was not using a
timeout. Some times (after a few days of use) the pyvera poll loop
would hang indefinitely on a requests.get of the event interface. This
would cause the pyvera thread to hang completely. It would also
prevent graceful shutdown, as pyvera does a thread join.

The new version uses a timeout, so that we won't lock up any more.
2016-11-21 06:06:17 -05:00
Paulus Schoutsen 11df7becd3 Merge pull request #4492 from home-assistant/release-0-33-1
0.33.1
2016-11-20 13:58:01 -08:00
Paulus Schoutsen 99f5db8c02 Version bump to 0.33.1 2016-11-20 12:11:07 -08:00
John Arild Berentsen 19b08a975a ZWave lights: Not use super() (#4476)
* Not use super

* Review changes
2016-11-20 12:10:50 -08:00
John Arild Berentsen 123f4acfc1 ZWave lights: Not use super() (#4476)
* Not use super

* Review changes
2016-11-20 11:49:54 -08:00
Paulus Schoutsen 0f90426023 Version bump to 0.34.0.dev0 2016-11-19 16:06:42 -08:00
Paulus Schoutsen 8b6a94b0f5 Merge pull request #4446 from home-assistant/dev
0.33
2016-11-19 16:06:26 -08:00
Paulus Schoutsen 0a333230c1 Version bump to 0.33 2016-11-19 16:05:56 -08:00
Paulus Schoutsen 455e1df7cb Fix typo 2016-11-19 16:05:33 -08:00
Matt N f71396c293 Fix nmap_tracker documentation link (#4471) 2016-11-19 15:31:45 -08:00
Paulus Schoutsen d930c399fe Better locking while setting up components + discovery (#4463) 2016-11-19 08:18:33 -08:00
Bjarni Ivarsson f3748ce535 Sonos line-in and tv source fixes + Sonos discovery fix. (#4440)
* Fixes line-in and tv sources on Sonos + Sonos discovery fixes.

* Style fix.
2016-11-19 15:29:00 +00:00
John Arild Berentsen 8beefcfc69 Switch did not update (#4466) 2016-11-19 15:52:42 +01:00
John Arild Berentsen 93747f2766 switch base cover did not appear (#4454)
Thanks for testing the PR @emilhetty 👍
2016-11-19 12:33:08 +01:00
John Arild Berentsen 7af438fa2f Hound for zwave climate (#4465) 2016-11-19 10:19:22 +01:00
Fabian Affolter 2b5fcd737b PVOutput sensor (#4203)
* Add PVOutput sensor

* Remove attributes

* Revert `verify_ssl` back to true
2016-11-19 10:04:03 +01:00
John Arild Berentsen 2b320f23fc Hound comments (#4464) 2016-11-19 09:46:02 +01:00
John Arild Berentsen 679d500e61 Neato refactor and support for sensors (#4319)
* Imporvements to neato

* Review changes
2016-11-19 00:14:40 -08:00
mnestor 613615433a Google Calendar round 2 (#4161)
* Google Calendar round 2

* Add google back to .coveragerc

* Update __init__.py
2016-11-18 22:29:20 -08:00
Fabian Affolter f70ff66d11 Upgrade batinfo to 0.4.2 (#4452) 2016-11-18 22:04:15 -08:00
Paulus Schoutsen d2bbc6ef70 Upgrade linter (#4461) 2016-11-18 21:47:59 -08:00
Paulus Schoutsen 37e28428c1 Merge remote-tracking branch 'origin/master' into dev 2016-11-18 18:39:11 -08:00
Pascal Vizeli c56f99baaf Async migration device_tracker (#4406)
* Async migration device_tracker

* change location stuff to async

* address paulus comments

* fix lint & add async discovery listener

* address paulus comments v2

* fix tests

* fix test_mqtt

* fix test_init

* fix gps_acc

* fix lint

* change async_update_stale to callback
2016-11-18 23:35:08 +01:00
Erik Eriksson 265232af98 only check heater status if present (#4459) 2016-11-18 14:12:51 -08:00
Fabian Affolter e6c4113c5b Fix lint issues for 0.33 (#4451)
* Fix PEP257 issues

* Fix ident

* Fix lint issues

* Update docstrings

* Fix indent

* Fix indent

* Fix lint issues

* Fix lint issue

* Again lint
2016-11-18 23:05:03 +01:00
Igor Shults c86e1b31b3 Fix typo in OWM (#4458) 2016-11-18 22:54:46 +01:00
Lewis Juggins 5912316496 pywebpush update to 0.6.1 (#4449) 2016-11-18 13:03:44 -08:00
John Arild Berentsen 58f0655298 ZWave Light: Use Configurable refresh (#4437)
* Use Configurable refresh

* Use super instead of object
2016-11-18 21:59:01 +01:00
John Arild Berentsen 43a93fb345 ZWave: Fix missing battery_level, node_id and location (#4422)
* Fix missing battery_level, node_id and location

* use super instead of object
2016-11-18 21:42:30 +01:00
Daniel Høyer Iversen 36b338051b Merge pull request #4450 from home-assistant/flux_led_lib_09
Upgrade flux led lib
2016-11-18 14:18:12 +01:00
Daniel Hoyer Iversen fc566309c1 Upgrade flux led lib 2016-11-18 13:20:51 +01:00
Sean Dague 23ce9949b1 Merge pull request #4447 from sdague/proliphix
bump proliphix library version
2016-11-18 06:56:04 -05:00
Sean Dague 275c80183c bump proliphix library version
This fixes an upstream bug with daylight savings time handling
2016-11-18 05:05:05 -05:00
Sean Dague cd1655f43b create light.hue_activate_scene service (#4425)
* create light.hue_activate_scene service

This creates a light.hue_activate_scene service that takes group_name
and scene_name, and calls phue's bridge.run_scene with those
parameters. This allows calling hue bridge stored scene names by name
during automation.

This only currently works reliably in 1 hue hub configurations (which
is most of them). Phue will be further enhanced to display warnings
when it can't figure out what to do with the parameters passed in to HA.

* Update hue.py
2016-11-17 22:14:06 -08:00
jnimmo 1a117d0bea Add keypress & output control services to Envisalink component (#3932)
* Add keypress & output control services to Envisalink component

Add services to allow sending custom keypresses and activating
programmable outputs on an alarm control panel.
Implemented for the Envisalink alarm, and moving to new version of
pyenvisalink to support this.

Replicated the service handler mapping code from Cover component into
Alarm Control Panel to allow handling alternative schemas if required
by new services.

* Update requirements_all.txt

* Updated services.yaml

* Removed requirement to enter code in HA UI

Incorporated changes suggested by @sriram
https://github.com/srirams/home-assistant/commit/2f8deb70cb5f3621a69b6b9
acb72f8e29123650c

Including pending state for exit/entry delay

Clarified services to use the code passed to them as a first priority,
otherwise use the code from configuration

Swapped back to using NotImplementedError for the service definitions

* - Add support for alarm_keypress to manual alarm (functions like a standard alarm keypad where entering the code disarms or arms the alarm)
- Add tests for alarm_keypress to manual alarm
- Style corrections (too many returns, comment & whitespace issues)

* Removed alarm_output_control service as unable to incorporate in the demo/test in a meaningful way

* Add keypress & output control services to Envisalink component

Add services to allow sending custom keypresses and activating
programmable outputs on an alarm control panel.
Implemented for the Envisalink alarm, and moving to new version of
pyenvisalink to support this.

Replicated the service handler mapping code from Cover component into
Alarm Control Panel to allow handling alternative schemas if required
by new services.

* Update requirements_all.txt

* Updated services.yaml

* Removed requirement to enter code in HA UI

Incorporated changes suggested by @sriram
https://github.com/srirams/home-assistant/commit/2f8deb70cb5f3621a69b6b9
acb72f8e29123650c

Including pending state for exit/entry delay

Clarified services to use the code passed to them as a first priority,
otherwise use the code from configuration

Swapped back to using NotImplementedError for the service definitions

* - Add support for alarm_keypress to manual alarm (functions like a standard alarm keypad where entering the code disarms or arms the alarm)
- Add tests for alarm_keypress to manual alarm
- Style corrections (too many returns, comment & whitespace issues)

* Removed alarm_output_control service as unable to incorporate in the demo/test in a meaningful way

* Moved the Alarm_Keypress service into Envisalink component out of the generic

* Update envisalink.py

* Update services.yaml
2016-11-17 22:13:22 -08:00
Fabian Affolter 944bb8474f Change validation to optional (#4400) 2016-11-17 22:09:57 -08:00
Magnus Ihse Bursie 779f520c56 Make UI more responsive to power off for Samsung Smart TV (#4438) 2016-11-17 22:00:18 -08:00
Magnus Ihse Bursie 82ed7b6b08 Fix so shell script adheres to posix standards. (#4439) 2016-11-17 21:59:53 -08:00
Paulus Schoutsen af77341494 Add sensor to show how many clients are connected. (#4430)
* Add sensor to show how many clients are connected.

* Lint

* Fix tests
2016-11-17 21:54:47 -08:00
Paulus Schoutsen 23fb8c4cdd Convert script component to async (#4427) 2016-11-17 21:50:01 -08:00
Paulus Schoutsen 726bc5b670 Do not report on shutting down errors (#4431)
* Do not report on shutting down errors

* Lint
2016-11-17 12:02:43 -08:00
Open Home Automation b615b3349f Fix for Miflora 2.6.6 firmware (#4436) 2016-11-17 08:40:21 -08:00
Paulus Schoutsen 0f59bb208c Migrate callbacks to use schedule_update_ha_state (#4426)
* Migrate callbacks to use schedule_update_ha_state

* Migrate MQTT sensor callback to async

* Migrate wemo to not update inside schedule_update_ha_state

* Make MQTT switch async

* Fix nx584 test

* Migrate tellstick callback

* Migrate vera callback

* Alarm control panel - manual: use async callbacks

* Run the switch rest tests that work
2016-11-17 07:34:46 -08:00
Paulus Schoutsen 38d201a54a Increase logging level of errors while doing jobs (#4429) 2016-11-16 23:01:14 -08:00
Pascal Vizeli c8bc1e3c5d change add_job to use call_soon_threadsafe (#4410)
* change add_job to use call_soon_threadsafe

* address comments from paulus

* Tweak core tests

* Fix tests Python 3.4.2
2016-11-16 20:00:08 -08:00
Lewis Juggins a862bc4edc Fix DLink async I/O (#4301) 2016-11-16 18:55:58 -08:00
Magnus Ihse Bursie b0e3d5a576 Better handling of accented characters in slugify (#4399) (#4423)
* Better handling of accented characters in slugify (#4399)

* Update __init__.py
2016-11-16 15:05:10 -08:00
Paulus Schoutsen f006b00dc1 Fix spelling schedule_update_ha_state (#4415) 2016-11-16 08:26:29 -08:00
bestlibre 1fff6ce438 Deduplicate MQTT_PUBLISH_SCHEMA definition (#4411) 2016-11-16 08:19:00 -08:00
Sean Dague c06c82905a dynamically fetch yamaha media playback support (#4385)
This makes it so that media playback support for inputs is dynamically
fetched from the receiver, instead of assuming that all playback
commands work for all inputs.

Tests are added for this, using a FakeYamaha class, which has some
sample data stubbed in for key methods that need to be called. We also
include an example of the desc.xml needed to dynamically parse these
features for these tests (as this is done in platform init).
2016-11-15 21:56:40 -08:00
Paulus Schoutsen 2b86d89bb4 Fix tplink test 2016-11-15 21:26:03 -08:00
Sean Dague 7bdb79bd54 bump phue to 0.9 (#4404)
This increases the phue library to 0.9, which includes some basic
Scene support that could be consumed from home assistant.
2016-11-15 21:14:54 -08:00
Pascal Vizeli 41aaeb715a Convert switch to AsnycIO (#4382)
* Convert switch to AsnycIO

* Move update entity to service

* use time better for faster handling

* Change to suggestion from paulus

* Use new shedule_update_ha_state

* fix lint

* minimize executor calls
2016-11-15 21:06:50 -08:00
Fabian Affolter 5d8a465c18 Add timeout to requests (#4398) 2016-11-15 21:02:17 -08:00
John Arild Berentsen c6f5a5443f Make zwave climate entities contain it's respective setpoints (#4357)
* Make zwave entities contain it's respective setpoints

* Add fan state
2016-11-15 13:14:29 +01:00
Fabian Affolter d6cb102f63 Merge pull request #4144 from dasos/squeezebox_name_fix
Squeezebox name
2016-11-15 10:06:10 +01:00
Fabian Affolter edde76e544 Fix validation and use consts (mqtt) (#4396) 2016-11-15 08:33:42 +01:00
Fabian Affolter d5fff2f94a Fix validation and use consts (mqtt) 2016-11-15 08:21:44 +01:00
bestlibre 0e0ba28249 support for last will and birth message for mqtt (#4381) 2016-11-14 22:18:33 -08:00
Paulus Schoutsen 6745e83a6c Merge pull request #4394 from home-assistant/release-0-32-4
0.32.4
2016-11-14 22:04:43 -08:00
Paulus Schoutsen 44bc057fdb Version bump to 0.32.4 2016-11-14 21:34:40 -08:00
hexa- 96b8d8fcfa http: reimplement X-Forwarded-For parsing (#4355)
This feature needs to be enabled through the `http.use_x_forwarded_for` option,
satisfying security concerns of spoofed remote addresses in untrusted network
environments.

The testsuite was enhanced to explicitly test the functionality of the
header.

Fixes #4265.

Signed-off-by: Martin Weinelt <hexa@darmstadt.ccc.de>
2016-11-14 21:33:34 -08:00
Sean Dague fc2df34206 Pin versions on linters for tests
The linters really need to specify an exact version, because when
either flake8 or pylint release a new version, a whole lot of new
issues are caught, causing failures on the code unrelated to the
patches being pushed.

Pinning is a best practice for linters. This allows patches which move
forward the linter version to happen with any code fixes required for
it to pass.
2016-11-14 21:32:02 -08:00
Paulus Schoutsen 09c29737de Fix device tracker sending invalid event data 2016-11-14 21:31:17 -08:00
Paulus Schoutsen 4c01b47945 device_tracker.see should not call async methods (#4377) 2016-11-14 21:31:06 -08:00
Paulus Schoutsen 7aaf3a46db Fix device tracker sending invalid event data (#4393) 2016-11-14 21:28:57 -08:00
Paulus Schoutsen d774ba46c7 Fix device tracker sending invalid event data 2016-11-14 20:59:29 -08:00
Lewis Juggins 4c37ee8884 Handle live content better in Kodi (#4388) 2016-11-14 20:11:22 -08:00
Paulus Schoutsen 7f5f458074 Faster async entity update on component. (#4384) 2016-11-14 18:54:38 -08:00
Paulus Schoutsen 479457d6ec device_tracker.see should not call async methods (#4377) 2016-11-14 18:35:58 -08:00
Paulus Schoutsen 7e73d27dd1 Do not serve HTTP requests while stopping (#4378) 2016-11-14 18:33:53 -08:00
Martin Wood e7ffec87ac Squeezebox name fix #4019 2016-11-14 21:46:05 +00:00
Robbie Trencheny 2d47b187c5 notify.html5: decode bytes values in registration data (#4379)
Occassionally the values of `keys` and `p256h` are bytes objects instead of
strings. As JSON by default does not serialize bytes objects let's decode
bytes objects to unicode strings.

Resolves the registration issue mentioned in #4012.

Signed-off-by: Martin Weinelt <hexa@darmstadt.ccc.de>
2016-11-14 11:35:31 -08:00
Sean Dague fe2103dedb Merge pull request #4386 from sdague/lint
Pin versions on linters for tests
2016-11-14 13:58:50 -05:00
Sean Dague 7bf5d1c662 Pin versions on linters for tests
The linters really need to specify an exact version, because when
either flake8 or pylint release a new version, a whole lot of new
issues are caught, causing failures on the code unrelated to the
patches being pushed.

Pinning is a best practice for linters. This allows patches which move
forward the linter version to happen with any code fixes required for
it to pass.
2016-11-14 13:50:27 -05:00
pvizeli cb24282040 Faster async entity update on component. 2016-11-14 14:18:04 +01:00
Fabian Affolter bd9429d3af Upgrade sendgrid to 3.6.2 (#4370) 2016-11-14 09:35:08 +01:00
Martin Weinelt d7a005ad0f notify.html5: decode bytes values in registration data
Occassionally the values of `keys` and `p256h` are bytes objects instead of
strings. As JSON by default does not serialize bytes objects let's decode
bytes objects to unicode strings.

Resolves the registration issue mentioned in #4012.

Signed-off-by: Martin Weinelt <hexa@darmstadt.ccc.de>
2016-11-14 05:14:18 +01:00
Paulus Schoutsen 2e2a996a8e Do not serve HTTP requests while stopping 2016-11-13 18:12:50 -08:00
Paulus Schoutsen 0364498dee Add .hound.yml 2016-11-13 11:34:09 -08:00
Daniel Høyer Iversen c5fdd4392a Merge pull request #4373 from home-assistant/flux_led_color_bright
support color and brightness in flux_led light
2016-11-13 20:33:48 +01:00
Daniel Hoyer Iversen 895454b6c3 support color and brightness in flux_led light 2016-11-13 11:10:27 +01:00
Nathan Henrie 2109b7a1b9 Use entity_id for backend, friendly name for frontend (#4343)
* Use entity_id for backend, friendly name for frontend

Closes https://github.com/home-assistant/home-assistant/issues/3434

Command line switches had the option to set a `friendly_name` reportedly
for use in the front end. However, if set, it was also being used as the
`entity_id`.

This did not seem like obvious behavior to me. This PR changes the
behavior so the entity_id is the object_id, which must already be
unique, and is an obvious place to have a very predictable slug (even if
long or unsightly), and the friendly name (if set) is used for the
display.

Example:

```yaml
switch:
  platform: command_line
  switches:
    rf_kitchen_light_one:
      command_on: switch_command on kitchen
      command_off: switch_command off kitchen
      command_state: query_command kitchen
      value_template: '{{ value == "online" }}'
      friendly_name: "Beautiful bright kitchen light!"
```

If you were using in an automation or from dev tools, would use:
`switch.rf_kitchen_light_one`, but your front end would still show `Beautiful
bright kitchen light!`

* Add new arg to test_assumed_state_should_be_true_if_command_state_is_false

* Import ENTITY_ID _FORMAT from existing, rename device_name to object_id

* Rename `device_name` to `object_id`

* Test that `entity_id` and `name` are set as expected
2016-11-12 22:46:23 -08:00
Pascal Vizeli 71a305ea45 Hotfix deadlock on platform setup (#4354)
* Hotfix deadlock on platform setup

* fix wrong import
2016-11-12 16:19:13 -08:00
hexa- e73634e6c7 http: reimplement X-Forwarded-For parsing (#4355)
This feature needs to be enabled through the `http.use_x_forwarded_for` option,
satisfying security concerns of spoofed remote addresses in untrusted network
environments.

The testsuite was enhanced to explicitly test the functionality of the
header.

Fixes #4265.

Signed-off-by: Martin Weinelt <hexa@darmstadt.ccc.de>
2016-11-12 16:14:39 -08:00
Pascal Vizeli 3d47ad5018 Use hass aiohttp connector for ssl connection (#4344) 2016-11-12 16:00:31 -08:00
Erik Eriksson c823ea9f2a Don't fail if component name is None. Fixes (#4345)
https://github.com/home-assistant/home-assistant/issues/4326

Might fix https://github.com/home-assistant/home-assistant/issues/4326
2016-11-12 14:16:27 -08:00
Fabian Affolter 75bcb1ff0f Upgrade schiene to 0.18 (#4359) 2016-11-12 12:30:05 -08:00
Sean Dague 1663cc9084 Fix typo in generic thermostat (#4348)
It looks like a copy / paste error was made when doing the min/max
code. This fixes that.
2016-11-11 17:42:58 +01:00
Christian Brædstrup 17cfcc981d D-Link switch version bump of external library (#4351) 2016-11-11 17:38:12 +01:00
Hugo Dupras 60fabaec24 Add timeout for Netatmo binary sensor (#4280)
* Add time limit for Netatmo binary sensor

* Change limit to timeout

Signed-off-by: Hugo D. (jabesq) <jabesq@gmail.com>

* Update requirements_all.txt
2016-11-10 23:04:11 -08:00
Marcelo Moreira de Mello 5e44934e7e Added some extra options to Weather Underground component (#4306)
* Added some extra options to Weather Underground component

* Added Location and Elevation options

* Fixed if statement

* Fixed lint

* Updated tests including  elevation and location

* Update wunderground.py
2016-11-10 23:01:20 -08:00
Lewis Juggins 01a6c1c1c8 Add strptime template function (#3950) 2016-11-10 22:57:44 -08:00
Jeffrey Lin cd1b0ac67d Added NVRAM-based MAC to IP mapping as backup to ARP tables (#4189) 2016-11-10 22:46:58 -08:00
Paulus Schoutsen 2bfded7153 MQTT.Server will use HASS eventloop (#3429) 2016-11-10 22:45:38 -08:00
Javier González Calleja 20af5cb5b4 Daily consumption information for HS110 (#4206)
* Add daily consumption information for HS110

* Fixing code review

* Fixing code review

* Fixing code review
2016-11-10 22:43:16 -08:00
Paulus Schoutsen 080f56e0f5 Merge pull request #4342 from home-assistant/release-0-32-3
0.32.3
2016-11-10 21:59:39 -08:00
Paulus Schoutsen 173e15e733 Version bump to 0.32.3 2016-11-10 21:50:05 -08:00
Paulus Schoutsen 72407c2f95 Make yr compatible with 0.32 2016-11-10 21:49:56 -08:00
Paulus Schoutsen 1b79722b69 Fix KNX async I/O (#4267) 2016-11-10 21:43:50 -08:00
Pascal Vizeli cc5233103c Fix rest switch default template (#4331) 2016-11-10 21:43:50 -08:00
Daniel Høyer Iversen 2feea1d1eb Add support for rgb light in led flux, fixes issue #4303 (#4332) 2016-11-10 21:43:50 -08:00
Johann Kellerman 2c39c39d52 Improve async generic camera's error handling (#4316)
* Handle errors

* Feedback

* DisconnectedError
2016-11-10 21:43:48 -08:00
Paulus Schoutsen 6e6b1ef7ab fix panasonic viera doing I/O in event loop (#4341) 2016-11-10 21:42:41 -08:00
Pascal Vizeli 55ddaf1ee7 Synology SSL fix & Error handling (#4325)
* Synology SSL fix & Error handling

* change handling for cookies/ssl

* fix use not deprecated functions

* fix lint

* change verify

* fix connector close to coro

* fix force close

* not needed since websession close connector too

* fix params

* fix lint
2016-11-10 21:42:37 -08:00
Pascal Vizeli 6860d9b096 Update SoCo to 0.12 (#4337)
* Update SoCo to 0.12

* fix req
2016-11-10 21:41:28 -08:00
Sean Dague 3e1cc4282e Fix "argument of type 'NoneType' is not iterable" during discovery (#4279)
* Fix "argument of type 'NoneType' is not iterable" during discovery

When yamaha receivers are dynamically discovered, there config is
empty, which means that we need to set zone_ignore to [] otherwise the
iteration over receivers fails.

* Bump rxv library version to fix play_status bug

rxv version 0.3 will issue the play_status command even for sources
that don't support it, causing stack traces during updates when
receivers are on HDMI inputs.

This was fixed in rxv 0.3.1. Bump to fix bug #4226.

* Don't discovery receivers that we've already configured

The discovery component doesn't know anything about already configured
receivers. This means that specifying a receiver manually will make it
show up twice if you have the discovery component enabled.

This puts a platform specific work around here that ensures that if
the media_player is found, we ignore the discovery system.
2016-11-10 21:41:28 -08:00
Jan Losinski 200bdb30ff Change pilight systemcode validation to integer (#4286)
* Change pilight systemcode validation to integer

According to the pilight code the systemcode should be an integer and
not a string (it is an int in the pilight code). Passing this as a
string caused errors from pilight:
"ERROR: elro_800_switch: insufficient number of arguments"

This fixes #4282

* Change pilight unit-id to positive integer

According to the pilight code the unit of an entity is also evrywhere
handled as an integer. So converting and passing this as string causes
pilight not to work.

This fixes #4282

Signed-off-by: Jan Losinski <losinski@wh2.tu-dresden.de>
2016-11-10 21:41:28 -08:00
Paulus Schoutsen eb17ba970c Increase update delay (#4321) 2016-11-10 21:41:28 -08:00
Paulus Schoutsen ffe4c425af Fix Tellstick doing I/O inside event loop (#4268) 2016-11-10 21:41:28 -08:00
Jesse Newland a18fdbfbb8 Fix alarm.com I/O inside properties (#4307)
* Fix alarm.com I/O inside properties

* First line should end with a period

* Not needed

* Fetch state on init
2016-11-10 21:41:28 -08:00
Lewis Juggins 58600f25b3 Fix OWM async I/O (#4298) 2016-11-10 21:41:28 -08:00
Pascal Vizeli 749fc583ea Fix rest switch default template (#4331) 2016-11-10 21:32:08 -08:00
Daniel Høyer Iversen b07d887d77 Add support for rgb light in led flux, fixes issue #4303 (#4332) 2016-11-10 21:30:52 -08:00
Johann Kellerman 9bb94a4512 Improve async generic camera's error handling (#4316)
* Handle errors

* Feedback

* DisconnectedError
2016-11-10 21:28:22 -08:00
Paulus Schoutsen e76d553513 fix panasonic viera doing I/O in event loop (#4341) 2016-11-10 21:17:44 -08:00
Pascal Vizeli 844799a1f7 Synology SSL fix & Error handling (#4325)
* Synology SSL fix & Error handling

* change handling for cookies/ssl

* fix use not deprecated functions

* fix lint

* change verify

* fix connector close to coro

* fix force close

* not needed since websession close connector too

* fix params

* fix lint
2016-11-10 21:04:47 -08:00
Pascal Vizeli e005ebe989 Update SoCo to 0.12 (#4337)
* Update SoCo to 0.12

* fix req
2016-11-10 21:01:42 -08:00
Sean Dague e9d19c1dcc Fix "argument of type 'NoneType' is not iterable" during discovery (#4279)
* Fix "argument of type 'NoneType' is not iterable" during discovery

When yamaha receivers are dynamically discovered, there config is
empty, which means that we need to set zone_ignore to [] otherwise the
iteration over receivers fails.

* Bump rxv library version to fix play_status bug

rxv version 0.3 will issue the play_status command even for sources
that don't support it, causing stack traces during updates when
receivers are on HDMI inputs.

This was fixed in rxv 0.3.1. Bump to fix bug #4226.

* Don't discovery receivers that we've already configured

The discovery component doesn't know anything about already configured
receivers. This means that specifying a receiver manually will make it
show up twice if you have the discovery component enabled.

This puts a platform specific work around here that ensures that if
the media_player is found, we ignore the discovery system.
2016-11-10 20:44:38 -08:00
Jan Losinski 7d2ab4fce6 Change pilight systemcode validation to integer (#4286)
* Change pilight systemcode validation to integer

According to the pilight code the systemcode should be an integer and
not a string (it is an int in the pilight code). Passing this as a
string caused errors from pilight:
"ERROR: elro_800_switch: insufficient number of arguments"

This fixes #4282

* Change pilight unit-id to positive integer

According to the pilight code the unit of an entity is also evrywhere
handled as an integer. So converting and passing this as string causes
pilight not to work.

This fixes #4282

Signed-off-by: Jan Losinski <losinski@wh2.tu-dresden.de>
2016-11-10 13:14:40 -08:00
Pascal Vizeli ba2ea35089 Add logging to platform/component setup (#4300)
* Add timeout to platform/component

* Revert "Add timeout to platform/component"

This reverts commit 280a311e48.

* Add logging data

* Change log message with paulus comments
2016-11-10 18:46:31 +01:00
Erik Eriksson ade62faa38 Don't fail if component name is None. Fixes (#4334)
https://github.com/home-assistant/home-assistant/issues/4326

Might fix https://github.com/home-assistant/home-assistant/issues/4326
2016-11-10 08:46:32 -08:00
Finbarr Brady ee322dbbdc Cisco IOS device tracker support (#4193) 2016-11-09 22:36:57 +02:00
John Arild Berentsen 0d4141bf13 Add missing Index labels (#4328) 2016-11-09 19:11:24 +01:00
Christopher Viel d404ac8978 Add support for off script to WOL switch (#4258) 2016-11-09 08:44:30 -08:00
Pascal Vizeli 71da21dcc8 Change pending task sheduler to time based cleanup (#4324)
* Change pending task sheduler to time based cleanup

* update unittest
2016-11-09 08:41:17 -08:00
Paulus Schoutsen 04dbc992ec Increase update delay (#4321) 2016-11-09 07:21:58 -08:00
Paulus Schoutsen 6d0e08cf7d Fix KNX async I/O (#4267) 2016-11-08 21:00:33 -08:00
Paulus Schoutsen 1e0025acae Fix Tellstick doing I/O inside event loop (#4268) 2016-11-08 20:25:19 -08:00
Pascal Vizeli 8fc853ba11 Add more unittest for async_add_job (#4320)
* Add more unittest for async_add_job

* fix test

* lint
2016-11-08 20:01:05 -08:00
sustah 8cbb8f6527 Update dlink.py (#4317)
corrected "total consumption" units from W to kWh
2016-11-08 19:58:27 -08:00
Jesse Newland 4f86c9ecda Fix alarm.com I/O inside properties (#4307)
* Fix alarm.com I/O inside properties

* First line should end with a period

* Not needed

* Fetch state on init
2016-11-08 19:57:46 -08:00
Lewis Juggins 9561fed650 Fix Dark Sky async I/O (#4299) 2016-11-08 19:46:44 -08:00
Lewis Juggins 67b599475e Fix OWM async I/O (#4298) 2016-11-08 18:57:56 -08:00
Pascal Vizeli 114ece1848 Fix possible sigterm / unittest / Fix all lazy test (#4297)
* replace weakref with a list

* add unittest

* fix lint

* fix handling

* fix unittest

* change code style

* fix lazy tests
2016-11-08 10:24:50 +01:00
Fabian Affolter c05815cced Upgrade sqlalchemy to 1.1.3 (#4277) 2016-11-07 23:08:17 -08:00
Pascal Vizeli 2e0c185740 Async cleanup part 3 (#4302) 2016-11-07 22:31:40 -08:00
Johann Kellerman 231ef40f53 iOS links (#4295) 2016-11-08 00:07:24 +01:00
Fabian Affolter b4159c7dc9 Upgrade python-digitalocean to 1.10.1 (#4276) 2016-11-06 23:49:25 -08:00
Fabian Affolter 8cc5fc1369 Upgrade psutil to 5.0.0 (#4275) 2016-11-06 23:49:11 -08:00
Paulus Schoutsen fc3235fb6d Merge pull request #4271 from home-assistant/release-0-32-2
Release 0 32 2
2016-11-06 23:40:06 -08:00
David-Leon Pohl d129df93dd Hotfix #4272 (#4273) 2016-11-06 23:34:45 -08:00
David-Leon Pohl 67336a111b Hotfix #4272 (#4273) 2016-11-06 23:34:32 -08:00
Paulus Schoutsen 0af1a96f14 Lint 2016-11-06 23:24:25 -08:00
andyat 272899ec96 Fix setting temperature in Celsius on radiotherm CT50 (#4270) 2016-11-06 23:21:08 -08:00
andyat 7d28d9d6b4 Fix setting temperature in Celsius on radiotherm CT50 (#4270) 2016-11-06 23:18:06 -08:00
Paulus Schoutsen 6a92e27e2f Version bump to 0.32.2 2016-11-06 23:12:37 -08:00
Paulus Schoutsen faceb4c1dc Sequential updates for non-async entities 2016-11-06 23:12:20 -08:00
Paulus Schoutsen 6d5f00098a Move Honeywell I/O out of event loop (#4244) 2016-11-06 23:09:31 -08:00
Pascal Vizeli 618a86a37c Set executor to 15 and help to reduce flooting async core with updates (#4252)
* Set executor to 15 and help to reduce flooting async core with udpates

* fix typing

* if it a executor, wait

* address comments from paulus

* add space for style :)

* fix spell

* Update entity_component.py

* Update entity_component.py
2016-11-06 22:28:03 -08:00
Paulus Schoutsen 880ef8af48 Remove broken disable verify ssl synology (#4269) 2016-11-06 22:17:56 -08:00
William Scanlon 95124c7ddb Revert "Catch AttributeError on Wink PubNub update" (#4263) 2016-11-06 20:05:42 -08:00
William Scanlon 0aba227300 Catch AttributeError (#4253) 2016-11-06 16:04:57 -08:00
Martin Hjelmare 734bd75fd3 Fix mysensors overwriting gateway in GATEWAYS (#4013)
GATEWAYS was a dict, so would overwrite item if key was the same. This
would happen when using multiple MQTT gateways, since the device id is
the same (`mqtt`).

* Fix by changing GATEWAYS from dict into list.
* Use hass data to store mysensors gateways instead of having GATEWAYS
  be a global.
2016-11-06 10:49:43 -08:00
Frantz 0c5e077091 Updated netdisco to 0.7.6 (#4250) 2016-11-06 10:43:13 -08:00
Nicolas Graziano 1ed2f8ae91 Update braviarc to 0.3.6 (#4246)
Add HDMI sources.
Sources ordered.
2016-11-06 09:27:55 -08:00
Paulus Schoutsen a343c20404 Async gather wait (#4247)
* Fix config validation for input_*, script

* Allow scheduling coroutines

* Validate entity ids when entity ids set by platform

* Async: gather -> wait

* Script/Group: use async_add_job instead of create_task
2016-11-06 09:26:40 -08:00
Antoine Bertin d4e8b831a0 Add mqtt_template light component (#4233)
* Add mqtt_template component

* Docstring copy paste party on overriden methods

* pep8 E501 🌟

* Add missing docstrings on unittests
2016-11-06 09:09:01 -08:00
Paulus Schoutsen 98f41d6b84 Tweak block_till_done (#4245) 2016-11-06 08:43:32 -08:00
Paulus Schoutsen 7774a03a55 Move Honeywell I/O out of event loop (#4244) 2016-11-06 07:53:54 -08:00
Fabian Affolter c35e5c9997 Upgrade astral to 1.3 (#4238) 2016-11-06 07:36:16 -08:00
Fabian Affolter 5d862e426e Upgrade fuzzywuzzy to 0.14.0 (#4240) 2016-11-06 07:36:03 -08:00
William Scanlon bab8d574fe Wink Thermostat support and NoneType error fixes (#4175) 2016-11-06 07:27:15 -08:00
Fabian Affolter c980d26aae Upgrade distro to 1.0.1 (#4239) 2016-11-06 14:00:41 +02:00
Paulus Schoutsen 08f75f7935 Merge pull request #4235 from home-assistant/release-0-32-1
0.32.1
2016-11-05 17:09:10 -07:00
Brent Hughes 1ad14b8227 Updated Emulated_Hue to send request info as variables to scripts (#4010)
* Updated Emulated_Hue to send request info as variables to scripts

* Updated tests to not use the old mqtt

* Updated test to actualy use and validate the script variables

* Fixed the removal of time in a recent merge

* fixed test to not use a timer
2016-11-05 17:08:54 -07:00
Pascal Vizeli 382ac5c3b5 Async cleanups with new handling and executor (#4234) 2016-11-06 01:01:03 +01:00
Paulus Schoutsen af297aa0dc Version bump to 0.32.1 2016-11-05 17:00:06 -07:00
Paulus Schoutsen 20e1b3eae0 Fix radiotherm I/O inside properties (#4227) 2016-11-05 16:59:52 -07:00
Paulus Schoutsen 28861221ae Remove chunked encoding (#4230) 2016-11-05 16:59:52 -07:00
Pascal Vizeli f367c49fb9 Sonos fix for slow update (#4232)
* Sonos fix for slow update

* fix auto update on discovery

* fix unittest
2016-11-05 16:59:52 -07:00
Pascal Vizeli ad8645baf4 Sonos fix for slow update (#4232)
* Sonos fix for slow update

* fix auto update on discovery

* fix unittest
2016-11-05 16:58:29 -07:00
Paulus Schoutsen 62785c2431 More async tests (#4223)
* Annotate test callbacks to be async

* Convert device_sun_light_trigger to be async
2016-11-05 16:36:20 -07:00
Paulus Schoutsen 22c3d014aa Remove chunked encoding (#4230) 2016-11-05 15:29:22 -07:00
Paulus Schoutsen 3f3127a290 Fix radiotherm I/O inside properties (#4227) 2016-11-05 13:28:11 -07:00
Danijel Stojnic 88fc64c8a0 Add Map support for Locative component (#4174)
* Add Map support for Locative component

The Locative App on the mobile is sending an HTTP request to the
server where also the GPS location is sent.
But the GPS location was not passed to the event device_tracker.see.

Use the passed GPS location from Locative and pass it to the
device_tracker.see event.

With this the device is then also shown on the HA Map component.

* Use existing constants for latitude and longitude

Use the existing constants from homeassistant.consts:

ATTR_LATITUDE for 'latitude'
ATTR_LONGITUDE for 'longitude'

* Reuse the "yield from self.hass.loop.run_in_executor" again

* Use variable gps_location
2016-11-05 13:05:15 -07:00
Samuel Bétrisey 1463fc4fe0 Add Swisscom Internet-Box device tracker (#4123)
* Add Swisscom Internet-Box device tracker

* Add Swisscom device tracker to .coveragerc

* Add timeout to requests
Fix formatting and add missing comments to pass the lint test

* Remove authentication which was not required

I realised that there was no need to be authenticated to get the
connected devices. Thanks Swisscom :/

* Moving config to a PLATFORM_SCHEMA and using voluptuous
2016-11-05 13:04:44 -07:00
Pascal Vizeli ece58ce78f Remove ThreadPool with async executor (#4154)
* Remove ThreadPool with async executor

* Fix zigbee

* update unittest

* fix remote api

* add pending task to remote

* fix lint

* remove unused import

* remove old stuff for lazy tests

* fix bug and add a exception handler to executor

* change executor handling

* change to wait from gather

* fix unittest
2016-11-05 09:27:55 -07:00
Paulus Schoutsen b67f1fed52 Version bump to 0.33.0.dev0 2016-11-05 08:53:13 -07:00
Paulus Schoutsen 4770888d22 Merge pull request #4166 from home-assistant/dev
0.32
2016-11-05 08:52:57 -07:00
Paulus Schoutsen 1d0f3b930f Version bump to 0.32.0 2016-11-05 08:40:32 -07:00
Paulus Schoutsen 22e2262f8e Merge remote-tracking branch 'origin/master' into dev 2016-11-05 08:40:04 -07:00
William Scanlon 53d1a040d4 Stop Octoprint from logging errors during startup (#4220)
* Fix log errors

* Remove discovery code
2016-11-05 07:55:59 -07:00
Fabian Affolter d7d71c97e2 Make the wind details more robust (weather.openweathermap) (#4215)
* Make the wind details more robust

* Return None if values is not available
2016-11-05 09:15:59 +01:00
Paulus Schoutsen c15fd4323e Disable insteon hub (#4221) 2016-11-04 23:38:27 -07:00
Pascal Vizeli 91227d9a2e Refactory nest component/platforms (#4219)
* Refactory nest component/platforms
2016-11-04 20:22:47 -04:00
jbcodemonkey a3db0ec231 add dimmer slide control to imported isy lights (#4152)
Supported attribute added and checks appear to pass. 🐬
2016-11-04 15:28:22 -06:00
Paulus Schoutsen 18e965c3cd Fix flaky group notify test (#4212) 2016-11-03 22:56:55 -07:00
Paulus Schoutsen 4cc417677e Add link to issue in warning slow entity update (#4211) 2016-11-03 22:45:01 -07:00
Paulus Schoutsen 525d735f21 Warn if fetching properties takes too long (#4208)
* Warn if fetching properties takes too long

* Update entity.py
2016-11-03 21:58:25 -07:00
Paulus Schoutsen e88b98f5fa Clean up tests (#4209) 2016-11-03 21:58:18 -07:00
Lewis Juggins 6f68752d1e Speed up Sonos tests (#4196) 2016-11-03 21:23:37 -07:00
Fabian Affolter 61a0976752 Use port instead of url and fix PEP257 issues (#4192) 2016-11-03 18:43:42 -07:00
Pascal Vizeli d7b3c9c38e Fix log owntrack log flooting (#4198) 2016-11-03 18:42:22 -07:00
jgriff2 a01939c6e9 Fix Synology Camera SSL certificate option (#4201) [BREAKING CHANGE]
* Fix Synology SSL config

* Revert "Fix Synology SSL config"

This reverts commit b8dc2a92abee6249b3dd42c99d0786820ebbeb72.

* Revert "Fix Synology SSL config"

This reverts commit 805e87f3af300a1b7627bb5df0792285fcf38901.

* Fix Synology SSL config
2016-11-03 18:41:32 -07:00
Fabian Affolter c128919b5f Remove globally disabled pylint warnings (#4204) 2016-11-03 18:40:43 -07:00
Pascal Vizeli e5d69feb93 Fix blocking/stack trace with empty list (#4191) 2016-11-03 18:33:18 -07:00
Paulus Schoutsen ee5f228309 Make services yield (#4187)
* Make services yield

* Disable pylint abstract-method check

* add input_select

* add input_slider

* change to async vers.

* fix lint

* yield on add_entities as other components does
2016-11-03 18:32:14 -07:00
John Arild Berentsen 15dde7925a Prevent multiple instances of device initialzed (#4179) 2016-11-03 13:08:23 +01:00
Pascal Vizeli fcf318cf53 Bugfix windows have a other default loop now (#4195)
* Bugfix windows have a other default loop now

* fix handling with 3.4.2 that not support ensure_future

* make the same as ensure_future does

* fix spell

* fix lazy test
2016-11-03 11:07:47 +01:00
Pascal Vizeli c2a5f63b1f Bugfix async Yr.no (#4190) 2016-11-03 11:09:03 +02:00
Fabian Affolter 79fa2d4175 CUPS sensor (#4142)
* Add CUPS sensor

* Use CupsData

* Fix requirement
2016-11-03 09:31:50 +01:00
Erik Eriksson 214a18f08c Support for Dovado routers (#4176)
* Implemented support for the Dovado router

* Update .coveragerc
2016-11-02 21:20:21 -07:00
Ferry van Zeelst ded2ea8b19 Synology DSM sensor (#4156)
* Added Synology DSM Sensor

* Fixed balloobbot's comments

* Fixed mistake (should have run lint and flake8 before committing

* Fixed update mechanisme according to balloobs feedback

* Requesting retest as test failure isn't related to changes made
2016-11-02 21:17:29 -07:00
Paulus Schoutsen 1d100dcac9 Bugfix/frontend group urls (#4185)
* Remove unnecessary sleeps

* Frontend: fix serving index when refreshing view page.
2016-11-02 21:15:23 -07:00
Bart274 a3ae96440b Update the icloud device_tracker (#4081)
* Update the icloud device_tracker

* addressed @kellerza 's comments

* GMTT config needs an entity_id

* renamed services

* fix cookiedir and clean up keep_alive function

* fix travis errors

* forgot a self.

* update devices after initializing the API

* changed wording

* addressed changes from @kellerza

* Syntax error solved

* Update icloud.py

* Only use account of username instead of whole username as default for account name

* use slugify instead of slug for schema

* remove Google Maps Travel Time

* Add comment from original tracker back
2016-11-02 21:07:23 -07:00
devdelay 0235626f40 yet another command_line sensor update (#4184) 2016-11-02 21:00:32 -07:00
Paulus Schoutsen d7dd7df5e7 Update frontend 2016-11-02 20:39:42 -07:00
Nicolas Graziano 1e28851280 Media player BraviaTv : Try to connect only if tv is not in off state. (#4140)
When HA is restart with the TV in off state there was error log every 10s until the TV is set ON.
2016-11-02 19:51:53 -07:00
bestlibre df68de8032 Influxdb sensor state set to unknown if query return no points (#4148)
* Influxdb sensor state set to unknown if query return no points

* Update influxdb.py
2016-11-02 19:50:18 -07:00
Johann Kellerman f3595f790a Async version of Yr.no (#4158)
* initial

* feedback

* More feedback. Still need to fix match_url

* url_match

* split_lines
2016-11-02 19:34:12 -07:00
Johann Kellerman 0d14920758 Component setup error messages with markdown (#3919)
* Remove_dev_link_async

* callback
2016-11-02 19:31:09 -07:00
Paulus Schoutsen 2940fb72fb EntityComponent.add_entities now converts generators to a list (#4183) 2016-11-02 19:24:25 -07:00
Daniel Perna 8e0838adeb Added support for Philips TVs with jointSPACE API (#4157)
* Added support for Philips Tvs with JointSpace API

* Flake + Lint fixes

* Lint be like "lol fu"

* Changes as requested by reviewers, except lib-requirement

* Switched to library-usage

* lint... newline-bingo...
2016-11-02 19:19:53 -07:00
Paulus Schoutsen 4e820ea30a Move mocks to async_start (#4182) 2016-11-02 19:16:59 -07:00
Pascal Vizeli 26490109ac Change event loop on windows (#4075)
* Change event loop on windows

* fix

* split PR

* remove set event loop

* Add paulus suggestion

* fix missing import

* revert stuff from PR Splitting

* fix event loop on test
2016-11-02 21:53:52 +01:00
Georgi Kirichkov e4a713207d Fixes in TP-Link Switch logging 0 values on init (#4026)
* Fixes in TP-Link Switch logging 0 values on init

On init of component the emeter would log to influxdb and possibly other inputs a 0 value, instead of not logging anything.
Initial polling should circumvent that behavior and avoid logging inconsistencies.

* Refactors update call in __init__
2016-11-02 12:23:43 -07:00
Greg Dowling cc0d0a38d7 Get temparature units from vera controller. (#4130)
Alrighty 👯‍♀️
2016-11-02 14:20:44 +01:00
Erik Eriksson afde5a6b26 extracted logic into an external package. monitor more attributes. support for more than one vehicle (#4170) 2016-11-01 22:01:00 -07:00
Lewis Juggins a5fb284717 Add new_device_discovered event (#4132) 2016-11-01 21:52:27 -07:00
Johann Kellerman e487a09190 Remove None value before writing known_devices (#4098)
* Remove None

* Replace null
2016-11-01 21:51:31 -07:00
Jan Losinski 52eb816c62 Introduce a send_delay for pilight component (#4051)
* Add a method to throttle calls to services

This adds CallRateDelayThrottle. This is a class that provides an
decorator to throttle calls to services. Instead of the Throttle in
homeassistant.util it does this by delaying all subsequent calls
instead of just dropping them. Dropping of calls would be bad if we
call services to actual change the state of a connected hardware (like
rf controlled power plugs).

Ihe delay is done by rescheduling the call using
track_point_in_utc_time from homeassistant.helpers.event so it should
not block the mainloop at all.

* Add unittests for CallRateDelayThrottle

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

* Introduce a send_delay for pilight component

If pilight is used with a "pilight USB Nano" between the daemon and the
hardware, we must use a delay between sending multiple signals.
Otherwise the hardware will just skip random codes. We hit this
condition for example, if we switch a group of pilight switches on or
off. Without the delay, random switch signals will not be transmitted by
the RF transmitter.

As this seems not necessary, if the transmitter is directly connected
via GPIO, we introduce a optional configuration to set the delay.

* Add unittests for pilight send_delay handling

This adds an unittest to test the delayed calls to the send_code
service.
2016-11-01 21:50:27 -07:00
Jason Carter 90d894a499 Garadget (#4031)
* Initial attempt at implementation

* Adding Garadget cover component

* Updating Device to be Required

* Updating .coveragerc to exclude from testing

* Updating code review items

* Updating per 2nd code review

* Updating configuration to be more like command-line
2016-11-01 21:49:27 -07:00
Jon Caruana ba13951fff Add LiteJet (a lighting control system) component (#4125)
* Initial submission of LiteJet integration.

* Add LiteJet switch pressed automation trigger. (State changes are too slow to catch a press-release.)
Add LiteJet scene, replacing commented out code that treated these as lights.
Include LiteJet numbers in the device state so that it is easy to lookup entity -> number.

* Fix missing global.

* Allow light's brightness to be set explicitly.

* Support optional 'ignore' key to ignore prefixes of loads, switches, and scenes that weren't configured for use in the LiteJet system.

* Fix lint errors and warnings.

* Cleanup header comments.
Default to not creating LiteJet switches as these are generally not useful.

* Lint fixes.

* Fixes from pull request feedback.

* Use hass.data instead of globals for data storage.

* Fix lint warnings.
2016-11-01 20:44:25 -07:00
Sean Dague 2a7b7ebd6a Merge pull request #3985 from postlund/yamaha_additions
Improve support for Yamaha receiver
2016-11-01 21:50:03 -04:00
Bjarni Ivarsson df7d9c3bb2 Fallback to read volume and mute state from speaker. (#4173) 2016-11-01 15:12:18 -07:00
Bjarni Ivarsson c549ea115d Sonos responsiveness improvements + enhancements (#4063)
* Sonos responsiveness improvements (async_ coroutines, event based updating, album art caching) + Better radio station information

* Docstring fixes.

* Docstring fixes.

* Updated SoCo dependency + fixed file permissions.

* Only fetch speaker info if needed.

* PEP8 fixes

* Fixed SoCoMock.get_speaker_info to get test to pass.

* Regenerated requirements_all.txt + async fetching of album art with caching + added http_session to HomeAssistant object.

* Unit test fixed.

* Add blank line as per flake8

* Fixed media image proxy unit test.

* Removed async stuff.

* Removed last remnants of async stuff.
2016-11-01 10:42:38 -07:00
Fabian Affolter dad54bb993 Update to make the sample file validate (#4168) 2016-11-01 15:11:42 +01:00
Fabian Affolter 0211cf29eb Change behavior to be more natural and fix test (#4150) 2016-11-01 10:43:48 +01:00
Nicholas Sideras 1d9ac5f8b3 Update __init__.py (#4155)
Changed manifest.json to respect Android screen rotate lock.
2016-10-31 13:04:54 -07:00
Paulus Schoutsen 7f699b4261 Lazy initialise the worker pool (#4110)
* Lazy initialise the worker pool

* Minimize pool initialization in core tests

* Fix tests on Python 3.4

* Remove passing in thread count to mock HASS

* Tests: Allow pool by default for threaded, disable for async

* Remove JobPriority for thread pool

* Fix wrong block_till_done

* EmulatedHue: Remove unused test code

* Zigbee: do not touch hass.pool

* Init loop in add_job

* Fix core test

* Fix random sensor test
2016-10-31 08:47:29 -07:00
Paulus Schoutsen a1e910f1cf Disable rest switch tests 2016-10-31 08:22:49 -07:00
Fabian Affolter b4899ec469 Allow multiple symbols (sensor.yahoo_finance) (#4126)
* Allow multiple symbols

* Update test
2016-10-31 13:31:09 +01:00
John 06de7053ce Add Emby Server media_player component (#3862)
* Add Emby Server media_player component

* Code cleanup, move to request sessions, generate UUID per session

* Make media image fetch more robust

* Allow for http or https

* Cleanup some Keyerror conditions found through more testing

* Move EmbyRemote to pip, update requirements

* Code cleanup, add SSL config option
2016-10-31 13:29:08 +01:00
Fabian Affolter 4484a7a94b Use voluptuous for Pilight switch (#3819)
* Migrate to voluptuous

* Add protocol

* Update
2016-10-31 13:18:47 +01:00
Jared Beckham a89e635bf3 Added tests for REST switches (#4016)
* Added tests for REST switches

* Remove REST switch from test coverage exclusions
2016-10-31 13:14:23 +01:00
Daniel Høyer Iversen 3ab056ba69 Merge pull request #4149 from home-assistant/flux_led_library
flux led lib
2016-10-31 11:56:00 +01:00
Daniel 5ba815ab21 flux led lib 2016-10-31 09:23:34 +01:00
Fabian Affolter 274e9799b3 Add random number sensor (#4139) 2016-10-31 00:01:25 -07:00
Jared Beckham 5ce9aea65d Added tests for REST sensors (#4115) 2016-10-30 21:51:03 -07:00
Michael 705814cb08 Catch all errors when doing mqtt message unicode-decode. (#4143)
* catch all errors when doing mqtt message unicode-decode.

* added AttributeError and UnicodeDecodeError to exception when decoding an mqtt message payload
2016-10-30 23:17:41 +01:00
Alok Saboo 8e695d1eb0 Fixed typo (#4145) 2016-10-30 23:13:27 +01:00
Fabian Affolter be272ac64a Disable too-many-* (#4107)
* Disable too-many-* and too-few-public-methods

* Remove globally disabled pylint warnings
2016-10-30 22:18:53 +01:00
Fabian Affolter b910a9917d Migrate to async (sensor.statistics) (#4138)
* Migrate to async

* Add async_ prefix and remove stale print
2016-10-30 18:56:26 +01:00
Fabian Affolter 9649097b32 Migrate to async (sensor.min_max) (#4136)
* Migrate to async

* Add async_ prefix
2016-10-30 16:45:53 +01:00
Fabian Affolter 5e76a51db4 Migrate to async (#4135) 2016-10-30 15:23:47 +01:00
Fabian Affolter 27abac85b6 Migrate to async (sensor.time_date) (#4100)
* Migrate to async

* Update acc. #4114
2016-10-30 15:21:23 +01:00
Fabian Affolter 9f2aae1357 Maintenance 2nd (#4106)
* Add link to docs

* Fix link

* Update line breaks

* Update ordering

* Align vera platofrm to only use add_devices
(instead od add_devices_callback)

* Remove line break

* Use consts

* Update ordering

* Update ordering

* Use const, create default name, use string formatting

* Update ordering

* Use const

* Update import style

* Update ordering and line breaks

* update line breaks

* Set default port

* Set defaults and update ordering

* Update ordering

* Minor style updates

* Update ordering, defaults, line breaks, and readability

* Use constants

* Add line breaks

* use string formatting

* Update line breaks

* Update logger
2016-10-30 09:58:34 +01:00
Adam Mills e6ece4bf6d Fix initialization of zwave color bulbs (#4085)
* Fix initialization of zwave color bulbs

Zwave values can be added to the node in any order. This change allows
proper initialization when the multilevel value is added before the
color value.

* Fix incorrect rename of color command class
2016-10-29 17:14:28 -07:00
Hydreliox aea2d1b317 Add support for Yeelight Wifi bulbs (#4065)
* Add support for Yeelight Wifi bulbs

* Fix cache property in instance
2016-10-29 17:03:26 -07:00
Pierre Ståhl 33e46b484f Add service to change visibility of a group (#3998) 2016-10-29 16:54:26 -07:00
wokar 3f6a5564ad lg_netcast platform fails to load if no channels defined (#4083)
* fixes loading of lg_netcast platform if no channels are defined

* turned list comprehension into for loop
2016-10-29 16:52:53 -07:00
Erik Eriksson 3317b4916b OSError is alias for IOException and base class for many other exceptions - no need to catch redundant exceptions if OSError already present in except-clause (#4111) 2016-10-29 15:33:56 -07:00
Pascal Vizeli 9c0455e3dc Allow update entities on add_entities callback (#4114)
* Allow udpate entities on add_entities callback

* fix wrong position

* update force_update to update_before_add

* add unittest for update_befor_add

* fix unittest

* change mocking
2016-10-29 15:33:11 -07:00
Fabian Affolter 5d43d3eb1c Fix error message (#4122) 2016-10-29 15:30:23 -07:00
Paulus Schoutsen 4163e55dbd Introducing hass.data (#4121)
* Hello hass.data

* Migrate setup_component to hass.data
2016-10-29 14:51:17 -07:00
Paulus Schoutsen 3cc4fdaa34 Fix HTTP static file singular (#4118) 2016-10-29 14:45:31 -07:00
Pascal Vizeli edeb31d74e Fix bug with aioHTTP and none authentification (#4116) 2016-10-29 22:47:46 +02:00
Fabian Affolter 54d19e3c53 Maintenance (sensor.currencylayer, sensor.fixer) (#4103)
* Add new const (base)

* Use constant

* Remove second error message, use const, add attribution, add link
to docs, remove unused vars, and a little simplification

* Add quote

* Use const

* Add attribution, simplify the code, and use consts
2016-10-29 13:27:02 -07:00
Fabian Affolter 892f455aee Maintenance (sensor.bitcoin, sensor.yahoo_finance) (#4104)
* Add attribution

* Update ordering

* Update ordering
2016-10-29 13:21:09 -07:00
Fabian Affolter 942d630762 Maintenance zoneminder (#4102)
* Add timeout to requests, fix typos, and defaults

* Clean-up
2016-10-29 13:10:42 -07:00
Paulus Schoutsen 9ea1101aba Fix bootstrap circular imports (#4108)
* Fix bootstrap circular imports

* fix test

* Lint
2016-10-29 12:54:47 -07:00
Pascal Vizeli 08a65a3b31 Async input_*/zone migration (#4095)
* Async input_*

* Async zone component

* rename service callback
2016-10-29 12:19:27 -07:00
Fabian Affolter d4b3f56d53 Maintenance (#4101)
* UPdate ordering, fix typos, and align logger messages

* Update import style, fix PEP257 issue, and align logger messages

* Updaate import style and align logger messages

* Update import style and align logger messages

* Update ordering

* Update import style and ordering

* Update quotes

* Make logger messages more clear

* Fix indentation
2016-10-29 09:12:43 -07:00
Paulus Schoutsen 5a2b4a5376 Core Async improvements (#4087)
* Clean up HomeAssistant.start

* Add missing pieces to remote HA constructor

* Make HomeAssistant constructor async safe

* Code cleanup

* Init websession lazy
2016-10-29 08:57:59 -07:00
Sean Dague 9d836a115a Add zone_ignore option for yamaha. (#4091)
* Add zone_ignore option for yamaha.

We attempt to discover all zones for yamaha receivers. There are times
when users may want to suppress some zones from showing up. When a
Zone isn't actually connected to speakers, or on some newer receivers
where Zone_4 is an HDMI only zone, that doesn't support even basic
media_player UI.

This provide a mechanism for users to do that.

Fixes #4088

* Update yamaha.py
2016-10-28 19:18:31 -07:00
Fabian Affolter bf92aedd38 Add hddtemp sensor (#4092) 2016-10-28 19:06:24 -07:00
devdelay 230c3815f2 Update command_line sensor to use STATE_UNKNOWN (#4093) 2016-10-28 19:03:40 -07:00
Pascal Vizeli 9afe066ec8 Fix name in openalpr cloud api (#4097) 2016-10-28 19:01:14 -07:00
Pascal Vizeli 66541a6a19 Update ha-ffmpeg to version 0.15 (#4096) 2016-10-29 00:12:53 +02:00
Pascal Vizeli 825ee3612d fix some comments spell (#4082)
* fix some comments

* fix in an executor

* address paulus comments
2016-10-28 21:26:52 +02:00
Abhishek Anand 65bd7d2326 Generalized REST switch to enable templating and configurable timeout. (#3329)
* successfully tested the "remote temperature mode" switch for the radio thermostat

* removed logging and interpreted None as Off.

* turn_off value is also templated now -- can depend on state

Also, undid accidental removal of error logging.

* ensured backward compatibility of config file

if value_template is not provided, the update function behaves as before

* ran autopep8 --in-place

* fixed another complaint of tox

* addressed the comments of balloob

* undid acccidental log.error to log.info

* timeout : 50 -> 10

* added a timeout parameter

* removed the stray '-', better names for the failure case

* string comparisons after .lower(), as suggested by balloob

* addressed balloob's latest requests

* making flake happy

* value_template --> is_on_template in config file

* moved CONF_IS_ON_TEMPLATE to local file

* null checks

* addressed flake error

* properly comparing template text when is_on is not a template.
2016-10-27 22:34:22 -07:00
John Arild Berentsen d8c1013b09 Zwave climate, add operating state to attributes (#4069)
* Zwave climate, add operating state to attributes

* Reversed assisgnment
2016-10-27 22:25:17 -07:00
Fabian Affolter 02d1dc6247 Upgrade psutil to 4.4.2 (#4079) 2016-10-27 22:22:43 -07:00
Paulus Schoutsen 726d950522 Update aiohttp.py 2016-10-27 21:45:35 -07:00
Pascal Vizeli 3324995e70 Async clientsession / fix stuff on aiohttp and camera platform (#4084)
* add websession

* convert to websession

* convert camera to async

* fix lint

* fix spell

* add import

* create task to loop

* fix test

* update aiohttp

* fix tests part 2

* Update aiohttp.py
2016-10-27 21:40:10 -07:00
Fabian Affolter 85747fe2ef Upgrade python-telegram-bot to 5.2.0 (#4080) 2016-10-27 21:28:09 -07:00
Pascal Vizeli 09db875ace Fix async bug in automation (#4078) 2016-10-27 18:26:55 +02:00
bestlibre 7d407756c3 Converting unit_of_measurement variable to optional, to be consistent with other sensors (#4076) 2016-10-27 08:50:36 -07:00
Richard Cox 91d682d02c Adding ssl option to zoneminder (#4074) 2016-10-27 15:54:03 +02:00
Benoit BESSET b75c103db4 fixed Up/Down (#4064) 2016-10-27 12:10:38 +02:00
Richard Cox bba323d226 [media_player/onkyo] host should be optional (#4073) 2016-10-27 10:33:35 +02:00
Paulus Schoutsen 7564d539c1 Lint 2016-10-27 00:37:02 -07:00
Paulus Schoutsen 33439aaa22 Update frontend 2016-10-27 00:21:55 -07:00
Pascal Vizeli d5368f6f78 Async bootstrap / component init (#3991)
* Async bootstrap

* Adress comments

* Fix tests

* More fixes

* Tests fixes
2016-10-27 00:16:23 -07:00
Simon Szustkowski d9999f36e8 Added a ThingSpeak component (#4027)
* Added a ThingSpeak component

* Forgot a colon. Fixed it

* Some config variables are better required

* New requirements created by the script

* Updated the .coveragerc

* Fixed small linting errors

* Removed unneccessary validation

* Even more linting error fixes

* Changed the way the component listens to state changes

* Removed unneccessary declaration of 'state' variable, referring to new_state instead
2016-10-26 23:56:51 -07:00
Pierre Ståhl 235e1a0885 Minor improvements to RPi camera platform (#4059)
* Try to create output file instead of checking write permissions

* Kill raspistill process during shutdown
2016-10-26 23:51:13 -07:00
Paulus Schoutsen 541fec0534 Sort .coveragerc alphabetically. 2016-10-26 23:50:11 -07:00
bestlibre b3ad7989ae Influxdb sensor (#4060)
* Influxdb sensor with voluptuous configuration validation

* Adding sensor to coveragerc since there is no test for now
2016-10-26 23:48:57 -07:00
Sean Dague 3d897e0e52 Add discovery for yamaha component (#4061)
This uses the discovery code from netdisco/ha to discover yamaha
receivers. The old discovery code remains if discovery is turned of in
HA, at least for now. Though it probably is worth turning that off in
the future.
2016-10-26 23:46:44 -07:00
Alok Saboo c6d5987109 Create Currencylayer exchange rate sensor (#4062)
* Added Currencylayer exchange rate sensor

* Updated .coveragerc to include currencylayer

* Update currencylayer.py

* Added Conf_name
2016-10-26 23:46:13 -07:00
Matthew Treinish 5d3956ea98 Cleanup use of MQTT in emulated_hue tests (#4068)
* Use unix newlines on test_emulated_hue

This commit switches the test_emulated_hue module to use unix newlines
instead of the DOS style that were there before. (using dos2unix on
the file) This makes it consistent with the other files in the repo.

* Cleanup emulated_hue tests

Previously these tests relied on the mqtt light platform as test devices
to control with the emulated hue. However, this was pretty heavyweight
and required running an MQTT broker in the tests. Instead this commit
switches it to use the demo light platform which is strictly in memory.

Fixes #3549
2016-10-26 23:33:43 -07:00
Marcelo Moreira de Mello 4fb0b27310 Wunderground sensor with alerts exceeds API limits (#4070)
* Fixes issue #4067 - Wunderground sensor with alerts exceeds API limits

 To avoid hitting the max limit of 500 calls per day, this patch keeps weather conditions being updated each 5 minutes
  and weather advisories each 15 minutes.

 This formula will result the following:

   conditions -> 300 seconds -> 5 minutes -> 12 req/h -> 288 req/day
   alerts -> 900 seconds -> 15 minutes -> 4 req/h -> 96 req/day

* Using timedelta in minutes instead seconds
2016-10-26 23:31:49 -07:00
Paulus Schoutsen 4833e992fb Pin cython==0.24.1 (#4057) 2016-10-25 23:38:32 -07:00
Paulus Schoutsen fe3aed0f0c Update .coveragerc 2016-10-25 23:32:58 -07:00
Paulus Schoutsen 57402bcb43 Update .coveragerc 2016-10-25 23:30:43 -07:00
Scott O'Neil 7f48c00793 Adding timer setting functionality to sonos component (#3941)
* Adding timer setting functionality to sonos component

* Adding clear sleep timer for Sonos
2016-10-25 23:22:17 -07:00
Paulus Schoutsen f58647849a Fix Z-Wave: Pin cython in Dockerfile (#4055) 2016-10-25 23:17:34 -07:00
Marcelo Moreira de Mello 1f468fc94d If no weather advisories were issued, state should return 0 instead Unknown (#4029)
* If no weather advisories were issued, state should return 0 instead Unknown

* Updated to keep on the same if statement

* Revert "Updated to keep on the same if statement"

This reverts commit 0e6a94aa0f.
2016-10-25 22:49:51 -07:00
Bjarni Ivarsson 961c02f72a Sonos improvements (#3997)
* Sonos improvements: media_* properties delegate to coordinator if speaker is a slave, media_image_url and media_title now works for radio streams, source selection/list takes speaker model into account, commands on slaves delegate to coordinator.

* Fixed failing unit tests.
2016-10-26 00:37:47 +02:00
Robbie Trencheny 79da1ec0d9 Merge pull request #4037 from home-assistant/remove-deprecated-things
Remove deprecated things
2016-10-25 14:28:01 -07:00
Robbie Trencheny fe174402d2 Remove more deprecated things 2016-10-25 14:16:08 -07:00
Matthew Treinish 1b2dfb8ed1 Handle FreeBSD version in updater component (#4048) 2016-10-25 22:38:22 +02:00
Pierre Ståhl 297a6f6f03 Improve support for Yamaha receiver
* Playback (play, pause, stop, next, previous)

* Media title, artist and album
2016-10-25 20:53:04 +02:00
Per Sandström 0dfcf40d37 [WIP] Config validation error line numbers (#3976)
Config validation error line numbers
2016-10-25 20:13:32 +02:00
Fabian Affolter d308ea69ce Upgrade yahoo-finance to 1.3.2 (#4040) 2016-10-25 08:36:20 -07:00
Sean Dague a8c5c995a0 Merge pull request #4042 from sdague/typo
fix typos in script module strings
2016-10-25 07:40:11 -04:00
Sean Dague 86b318e992 fix typos in script module strings
It looks like some copy / paste in docstrings, clean them up for
posterity.
2016-10-25 07:20:40 -04:00
Bart274 53ea926292 Fix for see service attributes (#4023) 2016-10-25 10:59:20 +02:00
Robbie Trencheny 2b1f4123db Update requirements_all.txt 2016-10-24 22:36:04 -07:00
Robbie Trencheny b36e346ccb Fix helpers.state tests 2016-10-24 22:33:54 -07:00
Adam Mills 89e8fb4066 Configurator support for entity_picture (#4028) 2016-10-24 22:28:34 -07:00
David-Leon Pohl e2d23d902a Unittests for ddwrt device tracker and bugfix (#3996)
* BUG Message data cannot be changed thus use voluptuous to ensure format

* Pilight daemon expects JSON serializable data

Thus dict is needed and not a mapping proxy.

* Add explanation why dict as message data is needed

* Use more obvious voluptuous validation scheme

* Pylint:  Trailing whitespace

* Pilight sensor component

* Python 3.4 compatibility

* D202

* Use pytest-caplog and no unittest.TestCase

* Fix setup/teardown of unittests

* Activate coverage testing

* Bugfix whitelist filter and use bugfixed pilight library

* Use newest pilight library that has a bugfix

* Add unittest for pilight hub component

* PEP257 for docstrings

* Bugfix setting device name from host name and small cleanup

- Init with connection error handling is more clear
- Comments clean-up

* PEP257

* New unittest with full coverage

* Upload missing testfixtures

* D209

* Handle double quotes in reply

* Formatting
2016-10-24 22:18:24 -07:00
Fabian Affolter 2604dd89a6 Add test (#3999) 2016-10-24 22:01:38 -07:00
Robbie Trencheny 044b9caa76 Remove garage_door, hvac, rollershutter and thermostat components/platforms 2016-10-24 22:00:43 -07:00
Daniel Perna 1707cdf9f3 Added support for Notifications for Android TV / FireTV (#3978)
* Added support for Notifications for Android TV / FireTV

* Silly me forgot to commit coverage

* Fixed pylint

* Fixed flake8

* Fixed another flake8 -.-

* Changed option 'ip' to 'host' like most other platforms do
2016-10-24 21:59:09 -07:00
Fabian Affolter 4c86721e70 Update tests, rename variable, and change conversion (#3546) 2016-10-24 21:53:03 -07:00
Matthew Treinish 0ff500ca25 Add mochad component (#3970)
This commit adds a new component for communicating with mochad[1] a
socket interface for the CM15A and CM19A USB X10 controllers. This
commit leverages the pymochad library to interface with a mochad socket
either on a local or remote machine. Mochad is added as as a generic
platform because it supports multiple different classes of device,
however in this patch only the switch device implemented as a starting
point. Future patches will include other devices types. (although
that's dependent on someone gaining access to those)

[1] https://sourceforge.net/projects/mochad/
2016-10-24 21:49:49 -07:00
Robbie Trencheny 23f54b07c7 Dont load notify.ios if no devices exist. Thanks @arsaboo for catching this. 2016-10-24 18:46:47 -07:00
Robbie Trencheny f25ddef4d7 More iOS HTTP Async updates 2016-10-24 17:31:45 -07:00
Robbie Trencheny 7158919346 Missed a wsgi->http on iOS component 2016-10-24 15:03:51 -07:00
Fabian Affolter 627517cbbc Upgrade psutil to 4.4.0 (#4032) 2016-10-24 22:24:33 +02:00
Fabian Affolter 4ecfc7d066 Upgrade sqlalchemy to 1.1.2 (#4003) 2016-10-24 22:00:22 +02:00
Fabian Affolter 72751b95b5 Upgrade slacker to 0.9.29 (#4000) 2016-10-24 22:00:02 +02:00
Fabian Affolter fc3b7907ed Upgrade python-telegram-bot to 5.1.1 (#4001) 2016-10-24 21:59:48 +02:00
Paulus Schoutsen f26a7fc6bb Update http 2016-10-24 00:09:20 -07:00
Robbie Trencheny 0c563f7b14 Minor updater... updates (#4020)
* Enable updater in dev versions

* Code clarity

* Add log line about being on the current version already

* Remove dev check test
2016-10-24 00:01:56 -07:00
Paulus Schoutsen 519d9f2fd0 async HTTP component (#3914)
* Migrate WSGI to asyncio

* Rename wsgi -> http

* Python 3.4 compat

* Move linting to Python 3.4

* lint

* Lint

* Fix Python 3.4 mock_open + binary data

* Surpress logging aiohttp.access

* Spelling

* Sending files is a coroutine

* More callback annotations and naming fixes

* Fix ios
2016-10-23 23:48:01 -07:00
Paulus Schoutsen 3701ac292c Merge pull request #4017 from home-assistant/release-0-31-1
0.31.1
2016-10-23 21:57:38 -07:00
Lewis Juggins 1db18478d2 Exclude dirs/files prefixed with . (#3986) 2016-10-23 21:08:56 -07:00
Robbie Trencheny 3230869f74 Fix a spelling problem on user-facing error 2016-10-23 20:34:31 -07:00
Robbie Trencheny 9aa88819a5 Fix a spelling problem on user-facing error 2016-10-23 20:33:49 -07:00
Paulus Schoutsen 3e92318cb2 Version bump to 0.31.1 2016-10-23 19:20:30 -07:00
Johann Kellerman f0a38dded6 Catch UnicodeDecodeError Error (#4007)
* Catch UnicodeDecodeError Error

Error for #3933

* Forgot (exc)

* catch...

* Tests by @lwis

* Docstring

* Create open
2016-10-23 19:20:13 -07:00
Robbie Trencheny c32f47aea6 iOS component hotfixes (#4015)
* iOS component hot fixes around component/platform loading, logging, and more

* Load device_tracker and zeroconf in deps instead of bootstraping

* Change conditional check on status code
2016-10-23 19:18:13 -07:00
Robbie Trencheny 626763a7c3 iOS component hotfixes (#4015)
* iOS component hot fixes around component/platform loading, logging, and more

* Load device_tracker and zeroconf in deps instead of bootstraping

* Change conditional check on status code
2016-10-23 19:17:34 -07:00
Johann Kellerman 5df8477536 Catch UnicodeDecodeError Error (#4007)
* Catch UnicodeDecodeError Error 

Error for #3933

* Forgot (exc)

* catch...

* Tests by @lwis

* Docstring

* Create open
2016-10-23 18:55:06 -07:00
Fabian Affolter 1f89e6ddba Upgrade pytz to 2016.7 (#4002) 2016-10-23 20:51:41 +02:00
Lewis Juggins 13ab2be5f6 Exclude dirs/files prefixed with . (#3986) 2016-10-23 16:47:06 +02:00
Robbie Trencheny 2bc84af87e Version bump to 0.32.0.dev0 2016-10-22 15:23:01 -07:00
733 changed files with 35596 additions and 15333 deletions
+47 -14
View File
@@ -28,6 +28,9 @@ omit =
homeassistant/components/envisalink.py
homeassistant/components/*/envisalink.py
homeassistant/components/google.py
homeassistant/components/*/google.py
homeassistant/components/insteon_hub.py
homeassistant/components/*/insteon_hub.py
@@ -95,11 +98,12 @@ omit =
homeassistant/components/netatmo.py
homeassistant/components/*/netatmo.py
homeassistant/components/neato.py
homeassistant/components/*/neato.py
homeassistant/components/homematic.py
homeassistant/components/*/homematic.py
homeassistant/components/switch/pilight.py
homeassistant/components/knx.py
homeassistant/components/*/knx.py
@@ -109,14 +113,20 @@ omit =
homeassistant/components/zoneminder.py
homeassistant/components/*/zoneminder.py
homeassistant/components/mochad.py
homeassistant/components/*/mochad.py
homeassistant/components/alarm_control_panel/alarmdotcom.py
homeassistant/components/alarm_control_panel/concord232.py
homeassistant/components/alarm_control_panel/nx584.py
homeassistant/components/alarm_control_panel/simplisafe.py
homeassistant/components/binary_sensor/arest.py
homeassistant/components/binary_sensor/concord232.py
homeassistant/components/binary_sensor/flic.py
homeassistant/components/binary_sensor/hikvision.py
homeassistant/components/binary_sensor/rest.py
homeassistant/components/browser.py
homeassistant/components/camera/amcrest.py
homeassistant/components/camera/bloomsky.py
homeassistant/components/camera/foscam.py
homeassistant/components/camera/mjpeg.py
@@ -128,6 +138,7 @@ omit =
homeassistant/components/climate/knx.py
homeassistant/components/climate/proliphix.py
homeassistant/components/climate/radiotherm.py
homeassistant/components/cover/garadget.py
homeassistant/components/cover/homematic.py
homeassistant/components/cover/rpi_gpio.py
homeassistant/components/cover/scsgate.py
@@ -136,16 +147,18 @@ omit =
homeassistant/components/device_tracker/aruba.py
homeassistant/components/device_tracker/asuswrt.py
homeassistant/components/device_tracker/bbox.py
homeassistant/components/device_tracker/bluetooth_tracker.py
homeassistant/components/device_tracker/bluetooth_le_tracker.py
homeassistant/components/device_tracker/bluetooth_tracker.py
homeassistant/components/device_tracker/bt_home_hub_5.py
homeassistant/components/device_tracker/ddwrt.py
homeassistant/components/device_tracker/cisco_ios.py
homeassistant/components/device_tracker/fritz.py
homeassistant/components/device_tracker/gpslogger.py
homeassistant/components/device_tracker/icloud.py
homeassistant/components/device_tracker/luci.py
homeassistant/components/device_tracker/netgear.py
homeassistant/components/device_tracker/nmap_tracker.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
@@ -154,11 +167,10 @@ omit =
homeassistant/components/discovery.py
homeassistant/components/downloader.py
homeassistant/components/emoncms_history.py
homeassistant/components/emulated_hue/upnp.py
homeassistant/components/fan/mqtt.py
homeassistant/components/feedreader.py
homeassistant/components/foursquare.py
homeassistant/components/garage_door/rpi_gpio.py
homeassistant/components/garage_door/wink.py
homeassistant/components/hdmi_cec.py
homeassistant/components/ifttt.py
homeassistant/components/joaoapps_join.py
@@ -172,12 +184,17 @@ omit =
homeassistant/components/light/limitlessled.py
homeassistant/components/light/osramlightify.py
homeassistant/components/light/x10.py
homeassistant/components/light/yeelight.py
homeassistant/components/lirc.py
homeassistant/components/media_player/aquostv.py
homeassistant/components/media_player/braviatv.py
homeassistant/components/media_player/cast.py
homeassistant/components/media_player/cmus.py
homeassistant/components/media_player/denon.py
homeassistant/components/media_player/denonavr.py
homeassistant/components/media_player/directv.py
homeassistant/components/media_player/dunehd.py
homeassistant/components/media_player/emby.py
homeassistant/components/media_player/firetv.py
homeassistant/components/media_player/gpmdp.py
homeassistant/components/media_player/itunes.py
@@ -188,6 +205,7 @@ omit =
homeassistant/components/media_player/onkyo.py
homeassistant/components/media_player/panasonic_viera.py
homeassistant/components/media_player/pandora.py
homeassistant/components/media_player/philips_js.py
homeassistant/components/media_player/pioneer.py
homeassistant/components/media_player/plex.py
homeassistant/components/media_player/roku.py
@@ -196,6 +214,7 @@ omit =
homeassistant/components/media_player/snapcast.py
homeassistant/components/media_player/sonos.py
homeassistant/components/media_player/squeezebox.py
homeassistant/components/media_player/vlc.py
homeassistant/components/media_player/yamaha.py
homeassistant/components/notify/aws_lambda.py
homeassistant/components/notify/aws_sns.py
@@ -209,6 +228,7 @@ omit =
homeassistant/components/notify/llamalab_automate.py
homeassistant/components/notify/matrix.py
homeassistant/components/notify/message_bird.py
homeassistant/components/notify/nfandroidtv.py
homeassistant/components/notify/nma.py
homeassistant/components/notify/pushbullet.py
homeassistant/components/notify/pushetta.py
@@ -226,17 +246,22 @@ omit =
homeassistant/components/notify/xmpp.py
homeassistant/components/nuimo_controller.py
homeassistant/components/openalpr.py
homeassistant/components/remote/harmony.py
homeassistant/components/scene/hunterdouglas_powerview.py
homeassistant/components/sensor/arest.py
homeassistant/components/sensor/arwn.py
homeassistant/components/sensor/bbox.py
homeassistant/components/sensor/bitcoin.py
homeassistant/components/sensor/bom.py
homeassistant/components/sensor/broadlink.py
homeassistant/components/sensor/coinmarketcap.py
homeassistant/components/sensor/cpuspeed.py
homeassistant/components/sensor/cups.py
homeassistant/components/sensor/currencylayer.py
homeassistant/components/sensor/darksky.py
homeassistant/components/sensor/deutsche_bahn.py
homeassistant/components/sensor/dht.py
homeassistant/components/sensor/dovado.py
homeassistant/components/sensor/dte_energy_bridge.py
homeassistant/components/sensor/efergy.py
homeassistant/components/sensor/eliqonline.py
@@ -250,16 +275,20 @@ omit =
homeassistant/components/sensor/gpsd.py
homeassistant/components/sensor/gtfs.py
homeassistant/components/sensor/haveibeenpwned.py
homeassistant/components/sensor/hddtemp.py
homeassistant/components/sensor/hp_ilo.py
homeassistant/components/sensor/imap.py
homeassistant/components/sensor/imap_email_content.py
homeassistant/components/sensor/influxdb.py
homeassistant/components/sensor/lastfm.py
homeassistant/components/sensor/linux_battery.py
homeassistant/components/sensor/loopenergy.py
homeassistant/components/sensor/mhz19.py
homeassistant/components/sensor/miflora.py
homeassistant/components/sensor/mqtt_room.py
homeassistant/components/sensor/netdata.py
homeassistant/components/sensor/neurio_energy.py
homeassistant/components/sensor/nut.py
homeassistant/components/sensor/nzbget.py
homeassistant/components/sensor/ohmconnect.py
homeassistant/components/sensor/onewire.py
@@ -267,16 +296,19 @@ omit =
homeassistant/components/sensor/openweathermap.py
homeassistant/components/sensor/pi_hole.py
homeassistant/components/sensor/plex.py
homeassistant/components/sensor/rest.py
homeassistant/components/sensor/pvoutput.py
homeassistant/components/sensor/sabnzbd.py
homeassistant/components/sensor/scrape.py
homeassistant/components/sensor/sensehat.py
homeassistant/components/sensor/serial_pm.py
homeassistant/components/sensor/snmp.py
homeassistant/components/sensor/sonarr.py
homeassistant/components/sensor/speedtest.py
homeassistant/components/sensor/steam_online.py
homeassistant/components/sensor/supervisord.py
homeassistant/components/sensor/swiss_hydrological_data.py
homeassistant/components/sensor/swiss_public_transport.py
homeassistant/components/sensor/synologydsm.py
homeassistant/components/sensor/systemmonitor.py
homeassistant/components/sensor/ted5000.py
homeassistant/components/sensor/temper.py
@@ -286,31 +318,32 @@ omit =
homeassistant/components/sensor/twitch.py
homeassistant/components/sensor/uber.py
homeassistant/components/sensor/vasttrafik.py
homeassistant/components/sensor/worldclock.py
homeassistant/components/sensor/waqi.py
homeassistant/components/sensor/xbox_live.py
homeassistant/components/sensor/yweather.py
homeassistant/components/sensor/zamg.py
homeassistant/components/switch/acer_projector.py
homeassistant/components/switch/anel_pwrctrl.py
homeassistant/components/switch/arest.py
homeassistant/components/switch/broadlink.py
homeassistant/components/switch/digitalloggers.py
homeassistant/components/switch/dlink.py
homeassistant/components/switch/edimax.py
homeassistant/components/switch/hikvisioncam.py
homeassistant/components/switch/hook.py
homeassistant/components/switch/mystrom.py
homeassistant/components/switch/neato.py
homeassistant/components/switch/netio.py
homeassistant/components/switch/orvibo.py
homeassistant/components/switch/pilight.py
homeassistant/components/switch/pulseaudio_loopback.py
homeassistant/components/switch/rest.py
homeassistant/components/switch/rpi_rf.py
homeassistant/components/switch/tplink.py
homeassistant/components/switch/transmission.py
homeassistant/components/switch/wake_on_lan.py
homeassistant/components/thermostat/eq3btsmart.py
homeassistant/components/thermostat/heatmiser.py
homeassistant/components/thermostat/homematic.py
homeassistant/components/thermostat/proliphix.py
homeassistant/components/thermostat/radiotherm.py
homeassistant/components/thingspeak.py
homeassistant/components/upnp.py
homeassistant/components/weather/openweathermap.py
homeassistant/components/zeroconf.py
+1
View File
@@ -62,6 +62,7 @@ pip-log.txt
.coverage
.tox
nosetests.xml
htmlcov/
# Translations
*.mo
+2
View File
@@ -0,0 +1,2 @@
python:
enabled: true
+3 -3
View File
@@ -2,11 +2,11 @@ sudo: false
matrix:
fast_finish: true
include:
- python: "3.4"
- python: "3.4.2"
env: TOXENV=py34
- python: "3.4"
- python: "3.4.2"
env: TOXENV=requirements
- python: "3.5"
- python: "3.4.2"
env: TOXENV=lint
- python: "3.5"
env: TOXENV=typing
+3 -2
View File
@@ -1,13 +1,14 @@
# Contributing to Home Assistant
Everybody is invited and welcome to contribute to Home Assistant. There is a lot to do...if you are not a developer perhaps you would like to help with the documentation on [home-assistant.io](https://home-assistant.io/)? If you are a developer and have devices in your home which aren't working with Home Assistant yet, why not spent a couple of hours and help to integrate them?
Everybody is invited and welcome to contribute to Home Assistant. There is a lot to do...if you are not a developer perhaps you would like to help with the documentation on [home-assistant.io](https://home-assistant.io/)? If you are a developer and have devices in your home which aren't working with Home Assistant yet, why not spent a couple of hours and help to integrate them?
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)
- 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.
- Create a Pull Request against the [**dev**](https://github.com/home-assistant/home-assistant/tree/dev) branch of Home Assistant.
Still interested? Then you should take a peak at the [developer documentation](https://home-assistant.io/developers/) to get more details.
Still interested? Then you should take a peak at the [developer documentation](https://home-assistant.io/developers/) to get more details.
+6 -3
View File
@@ -8,9 +8,12 @@ WORKDIR /usr/src/app
RUN pip3 install --no-cache-dir colorlog cython
# For the nmap tracker, bluetooth tracker, Z-Wave
RUN apt-get update && \
apt-get install -y --no-install-recommends nmap net-tools cython3 libudev-dev sudo libglib2.0-dev bluetooth libbluetooth-dev && \
# 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
+22 -58
View File
@@ -1,7 +1,7 @@
homeassistant:
# Omitted values in this section will be auto detected using freegeoip.io
# Location required to calculate the time the sun rises and sets.
# Location required to calculate the time the sun rises and sets.
# Coordinates are also used for location for weather related components.
# Google Maps can be used to determine more precise GPS coordinates.
latitude: 32.87336
@@ -43,12 +43,10 @@ device_tracker:
username: admin
password: PASSWORD
chromecast:
switch:
platform: wemo
thermostat:
climate:
platform: nest
# Required: username and password that are used to login to the Nest thermostat.
username: myemail@mydomain.com
@@ -79,7 +77,6 @@ group:
entities:
- group.awesome_people
- group.climate
kitchen:
name: Kitchen
entities:
@@ -92,52 +89,23 @@ group:
- input_boolean.notify_home
- camera.demo_camera
example:
simple_alarm:
# Which light/light group has to flash when a known device comes home
known_light: light.Bowl
# Which light/light group has to flash red when light turns on while no one home
unknown_light: group.living_room
browser:
keyboard:
# https://home-assistant.io/getting-started/automation/
automation:
- alias: 'Rule 1 Light on in the evening'
trigger:
- platform: sun
- alias: Turn on light when sun sets
trigger:
platform: sun
event: sunset
offset: "-01:00:00"
- platform: state
condition:
condition: state
entity_id: group.all_devices
state: home
condition:
- platform: state
entity_id: group.all_devices
state: home
- platform: time
after: "16:00:00"
before: "23:00:00"
action:
service: homeassistant.turn_on
entity_id: group.living_room
state: 'home'
action:
service: light.turn_on
- alias: 'Rule 2 - Away Mode'
trigger:
- platform: state
entity_id: group.all_devices
state: 'not_home'
condition: use_trigger_values
action:
service: light.turn_off
entity_id: group.all_lights
# Sensors need to be added into the configuration.yaml as sensor:, sensor 2:, sensor 3:, etc.
# Each sensor label should be unique or your sensors might not load correctly.
# Another way to do is to collect all entries under one "sensor:"
# sensor:
# - platform: mqtt
@@ -154,34 +122,30 @@ sensor:
arg: '/'
- type: 'disk_use_percent'
arg: '/home'
- type: 'disk_use'
arg: '/home'
sensor 2:
platform: forecast
api_key: <register on Forecast.io for your PRIVATE API>
monitored_conditions:
- summary
- precip_type
- precip_intensity
- temperature
platform: cpuspeed
script:
# Turns on the bedroom lights and then the living room lights 1 minute later
wakeup:
alias: Wake Up
sequence:
# alias is optional
- event: LOGBOOK_ENTRY
event_data:
name: Paulus
message: is waking up
entity_id: device_tracker.paulus
domain: light
- alias: Bedroom lights on
execute_service: light.turn_on
service_data:
service: light.turn_on
data:
entity_id: group.bedroom
brightness: 100
- delay:
# supports seconds, milliseconds, minutes, hours, etc.
minutes: 1
- alias: Living room lights on
execute_service: light.turn_on
service_data:
service: light.turn_on
data:
entity_id: group.living_room
scene:
+8 -3
View File
@@ -14,6 +14,7 @@ from homeassistant.const import (
__version__,
EVENT_HOMEASSISTANT_START,
REQUIRED_PYTHON_VER,
REQUIRED_PYTHON_VER_WIN,
RESTART_EXIT_CODE,
)
from homeassistant.util.async import run_callback_threadsafe
@@ -44,8 +45,7 @@ def monkey_patch_asyncio():
See https://bugs.python.org/issue26617 for details of the Python
bug.
"""
# pylint: disable=no-self-use, too-few-public-methods, protected-access
# pylint: disable=bare-except
# pylint: disable=no-self-use, protected-access, bare-except
import asyncio.tasks
class IgnoreCalls:
@@ -64,7 +64,12 @@ def monkey_patch_asyncio():
def validate_python() -> None:
"""Validate we're running the right Python version."""
if sys.version_info[:3] < REQUIRED_PYTHON_VER:
if sys.platform == "win32" and \
sys.version_info[:3] < REQUIRED_PYTHON_VER_WIN:
print("Home Assistant requires at least Python {}.{}.{}".format(
*REQUIRED_PYTHON_VER_WIN))
sys.exit(1)
elif sys.version_info[:3] < REQUIRED_PYTHON_VER:
print("Home Assistant requires at least Python {}.{}.{}".format(
*REQUIRED_PYTHON_VER))
sys.exit(1)
+290 -111
View File
@@ -1,11 +1,10 @@
"""Provides methods to bootstrap a home assistant instance."""
import asyncio
import logging
import logging.handlers
import os
import sys
from collections import defaultdict
from threading import RLock
from collections import OrderedDict
from types import ModuleType
from typing import Any, Optional, Dict
@@ -19,6 +18,9 @@ import homeassistant.config as conf_util
import homeassistant.core as core
import homeassistant.loader as loader
import homeassistant.util.package as pkg_util
from homeassistant.util.async import (
run_coroutine_threadsafe, run_callback_threadsafe)
from homeassistant.util.logging import AsyncHandler
from homeassistant.util.yaml import clear_secret_cache
from homeassistant.const import EVENT_COMPONENT_LOADED, PLATFORM_FORMAT
from homeassistant.exceptions import HomeAssistantError
@@ -26,37 +28,50 @@ from homeassistant.helpers import (
event_decorators, service, config_per_platform, extract_domain_configs)
_LOGGER = logging.getLogger(__name__)
_SETUP_LOCK = RLock()
_CURRENT_SETUP = []
ATTR_COMPONENT = 'component'
ERROR_LOG_FILENAME = 'home-assistant.log'
_PERSISTENT_PLATFORMS = set()
_PERSISTENT_VALIDATION = set()
_PERSISTENT_ERRORS = {}
HA_COMPONENT_URL = '[{}](https://home-assistant.io/components/{}/)'
def setup_component(hass: core.HomeAssistant, domain: str,
config: Optional[Dict]=None) -> bool:
"""Setup a component and all its dependencies."""
return run_coroutine_threadsafe(
async_setup_component(hass, domain, config), loop=hass.loop).result()
@asyncio.coroutine
def async_setup_component(hass: core.HomeAssistant, domain: str,
config: Optional[Dict]=None) -> bool:
"""Setup a component and all its dependencies.
This method is a coroutine.
"""
if domain in hass.config.components:
_LOGGER.debug('Component %s already set up.', domain)
return True
_ensure_loader_prepared(hass)
if not loader.PREPARED:
yield from hass.loop.run_in_executor(None, loader.prepare, hass)
if config is None:
config = defaultdict(dict)
config = {}
components = loader.load_order_component(domain)
# OrderedSet is empty if component or dependencies could not be resolved
if not components:
_async_persistent_notification(hass, domain, True)
return False
for component in components:
if not _setup_component(hass, component, config):
res = yield from _async_setup_component(hass, component, config)
if not res:
_LOGGER.error('Component %s failed to setup', component)
_async_persistent_notification(hass, component, True)
return False
return True
@@ -64,7 +79,10 @@ def setup_component(hass: core.HomeAssistant, domain: str,
def _handle_requirements(hass: core.HomeAssistant, component,
name: str) -> bool:
"""Install the requirements for a component."""
"""Install the requirements for a component.
This method needs to run in an executor.
"""
if hass.config.skip_pip or not hasattr(component, 'REQUIREMENTS'):
return True
@@ -72,70 +90,109 @@ def _handle_requirements(hass: core.HomeAssistant, component,
if not pkg_util.install_package(req, target=hass.config.path('deps')):
_LOGGER.error('Not initializing %s because could not install '
'dependency %s', name, req)
_async_persistent_notification(hass, name)
return False
return True
def _setup_component(hass: core.HomeAssistant, domain: str, config) -> bool:
"""Setup a component for Home Assistant."""
# pylint: disable=too-many-return-statements,too-many-branches
# pylint: disable=too-many-statements
@asyncio.coroutine
def _async_setup_component(hass: core.HomeAssistant,
domain: str, config) -> bool:
"""Setup a component for Home Assistant.
This method is a coroutine.
"""
# pylint: disable=too-many-return-statements
if domain in hass.config.components:
return True
with _SETUP_LOCK:
# It might have been loaded while waiting for lock
if domain in hass.config.components:
return True
setup_lock = hass.data.get('setup_lock')
if setup_lock is None:
setup_lock = hass.data['setup_lock'] = asyncio.Lock(loop=hass.loop)
if domain in _CURRENT_SETUP:
_LOGGER.error('Attempt made to setup %s during setup of %s',
domain, domain)
return False
setup_progress = hass.data.get('setup_progress')
if setup_progress is None:
setup_progress = hass.data['setup_progress'] = []
config = prepare_setup_component(hass, config, domain)
if domain in setup_progress:
_LOGGER.error('Attempt made to setup %s during setup of %s',
domain, domain)
_async_persistent_notification(hass, domain, True)
return False
try:
# Used to indicate to discovery that a setup is ongoing and allow it
# to wait till it is done.
did_lock = False
if not setup_lock.locked():
yield from setup_lock.acquire()
did_lock = True
setup_progress.append(domain)
config = yield from async_prepare_setup_component(hass, config, domain)
if config is None:
return False
component = loader.get_component(domain)
_CURRENT_SETUP.append(domain)
if component is None:
_async_persistent_notification(hass, domain)
return False
async_comp = hasattr(component, 'async_setup')
try:
result = component.setup(hass, config)
if result is False:
_LOGGER.error('component %s failed to initialize', domain)
return False
elif result is not True:
_LOGGER.error('component %s did not return boolean if setup '
'was successful. Disabling component.', domain)
loader.set_component(domain, None)
return False
_LOGGER.info("Setting up %s", domain)
if async_comp:
result = yield from component.async_setup(hass, config)
else:
result = yield from hass.loop.run_in_executor(
None, component.setup, hass, config)
except Exception: # pylint: disable=broad-except
_LOGGER.exception('Error during setup of component %s', domain)
_async_persistent_notification(hass, domain, True)
return False
if result is False:
_LOGGER.error('component %s failed to initialize', domain)
_async_persistent_notification(hass, domain, True)
return False
elif result is not True:
_LOGGER.error('component %s did not return boolean if setup '
'was successful. Disabling component.', domain)
_async_persistent_notification(hass, domain, True)
loader.set_component(domain, None)
return False
finally:
_CURRENT_SETUP.remove(domain)
hass.config.components.append(component.DOMAIN)
# Assumption: if a component does not depend on groups
# it communicates with devices
if 'group' not in getattr(component, 'DEPENDENCIES', []) and \
hass.pool.worker_count <= 10:
hass.pool.add_worker()
hass.bus.fire(
hass.bus.async_fire(
EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: component.DOMAIN}
)
return True
finally:
setup_progress.remove(domain)
if did_lock:
setup_lock.release()
def prepare_setup_component(hass: core.HomeAssistant, config: dict,
domain: str):
"""Prepare setup of a component and return processed config."""
return run_coroutine_threadsafe(
async_prepare_setup_component(hass, config, domain), loop=hass.loop
).result()
@asyncio.coroutine
def async_prepare_setup_component(hass: core.HomeAssistant, config: dict,
domain: str):
"""Prepare setup of a component and return processed config.
This method is a coroutine.
"""
# pylint: disable=too-many-return-statements
component = loader.get_component(domain)
missing_deps = [dep for dep in getattr(component, 'DEPENDENCIES', [])
@@ -151,7 +208,7 @@ def prepare_setup_component(hass: core.HomeAssistant, config: dict,
try:
config = component.CONFIG_SCHEMA(config)
except vol.Invalid as ex:
log_exception(ex, domain, config, hass)
async_log_exception(ex, domain, config, hass)
return None
elif hasattr(component, 'PLATFORM_SCHEMA'):
@@ -161,7 +218,7 @@ def prepare_setup_component(hass: core.HomeAssistant, config: dict,
try:
p_validated = component.PLATFORM_SCHEMA(p_config)
except vol.Invalid as ex:
log_exception(ex, domain, config, hass)
async_log_exception(ex, domain, config, hass)
continue
# Not all platform components follow same pattern for platforms
@@ -171,8 +228,8 @@ def prepare_setup_component(hass: core.HomeAssistant, config: dict,
platforms.append(p_validated)
continue
platform = prepare_setup_platform(hass, config, domain,
p_name)
platform = yield from async_prepare_setup_platform(
hass, config, domain, p_name)
if platform is None:
continue
@@ -180,10 +237,11 @@ def prepare_setup_component(hass: core.HomeAssistant, config: dict,
# Validate platform specific schema
if hasattr(platform, 'PLATFORM_SCHEMA'):
try:
# pylint: disable=no-member
p_validated = platform.PLATFORM_SCHEMA(p_validated)
except vol.Invalid as ex:
log_exception(ex, '{}.{}'.format(domain, p_name),
p_validated, hass)
async_log_exception(ex, '{}.{}'.format(domain, p_name),
p_validated, hass)
continue
platforms.append(p_validated)
@@ -195,7 +253,9 @@ def prepare_setup_component(hass: core.HomeAssistant, config: dict,
if key not in filter_keys}
config[domain] = platforms
if not _handle_requirements(hass, component, domain):
res = yield from hass.loop.run_in_executor(
None, _handle_requirements, hass, component, domain)
if not res:
return None
return config
@@ -204,7 +264,22 @@ def prepare_setup_component(hass: core.HomeAssistant, config: dict,
def prepare_setup_platform(hass: core.HomeAssistant, config, domain: str,
platform_name: str) -> Optional[ModuleType]:
"""Load a platform and makes sure dependencies are setup."""
_ensure_loader_prepared(hass)
return run_coroutine_threadsafe(
async_prepare_setup_platform(hass, config, domain, platform_name),
loop=hass.loop
).result()
@asyncio.coroutine
def async_prepare_setup_platform(hass: core.HomeAssistant, config, domain: str,
platform_name: str) \
-> Optional[ModuleType]:
"""Load a platform and makes sure dependencies are setup.
This method is a coroutine.
"""
if not loader.PREPARED:
yield from hass.loop.run_in_executor(None, loader.prepare, hass)
platform_path = PLATFORM_FORMAT.format(domain, platform_name)
@@ -213,13 +288,7 @@ def prepare_setup_platform(hass: core.HomeAssistant, config, domain: str,
# Not found
if platform is None:
_LOGGER.error('Unable to find platform %s', platform_path)
_PERSISTENT_PLATFORMS.add(platform_path)
message = ('Unable to find the following platforms: ' +
', '.join(list(_PERSISTENT_PLATFORMS)) +
'(please check your configuration)')
persistent_notification.create(
hass, message, 'Invalid platforms', 'platform_errors')
_async_persistent_notification(hass, platform_path)
return None
# Already loaded
@@ -228,20 +297,23 @@ def prepare_setup_platform(hass: core.HomeAssistant, config, domain: str,
# Load dependencies
for component in getattr(platform, 'DEPENDENCIES', []):
if not setup_component(hass, component, config):
res = yield from async_setup_component(hass, component, config)
if not res:
_LOGGER.error(
'Unable to prepare setup for platform %s because '
'dependency %s could not be initialized', platform_path,
component)
_async_persistent_notification(hass, platform_path, True)
return None
if not _handle_requirements(hass, platform, platform_path):
res = yield from hass.loop.run_in_executor(
None, _handle_requirements, hass, platform, platform_path)
if not res:
return None
return platform
# pylint: disable=too-many-branches, too-many-statements, too-many-arguments
def from_config_dict(config: Dict[str, Any],
hass: Optional[core.HomeAssistant]=None,
config_dir: Optional[str]=None,
@@ -261,15 +333,56 @@ def from_config_dict(config: Dict[str, Any],
hass.config.config_dir = config_dir
mount_local_lib_path(config_dir)
@asyncio.coroutine
def _async_init_from_config_dict(future):
try:
re_hass = yield from async_from_config_dict(
config, hass, config_dir, enable_log, verbose, skip_pip,
log_rotate_days)
future.set_result(re_hass)
# pylint: disable=broad-except
except Exception as exc:
future.set_exception(exc)
# run task
future = asyncio.Future(loop=hass.loop)
hass.async_add_job(_async_init_from_config_dict(future))
hass.loop.run_until_complete(future)
return future.result()
@asyncio.coroutine
def async_from_config_dict(config: Dict[str, Any],
hass: core.HomeAssistant,
config_dir: Optional[str]=None,
enable_log: bool=True,
verbose: bool=False,
skip_pip: bool=False,
log_rotate_days: Any=None) \
-> Optional[core.HomeAssistant]:
"""Try to configure Home Assistant from a config dict.
Dynamically loads required components and its dependencies.
This method is a coroutine.
"""
hass.async_track_tasks()
setup_lock = hass.data.get('setup_lock')
if setup_lock is None:
setup_lock = hass.data['setup_lock'] = asyncio.Lock(loop=hass.loop)
yield from setup_lock.acquire()
core_config = config.get(core.DOMAIN, {})
try:
conf_util.process_ha_core_config(hass, core_config)
yield from conf_util.async_process_ha_core_config(hass, core_config)
except vol.Invalid as ex:
log_exception(ex, 'homeassistant', core_config, hass)
async_log_exception(ex, 'homeassistant', core_config, hass)
return None
conf_util.process_ha_config_upgrade(hass)
yield from hass.loop.run_in_executor(
None, conf_util.process_ha_config_upgrade, hass)
if enable_log:
enable_logging(hass, verbose, log_rotate_days)
@@ -279,41 +392,44 @@ def from_config_dict(config: Dict[str, Any],
_LOGGER.warning('Skipping pip installation of required modules. '
'This may cause issues.')
_ensure_loader_prepared(hass)
if not loader.PREPARED:
yield from hass.loop.run_in_executor(None, loader.prepare, hass)
# Make a copy because we are mutating it.
# Convert it to defaultdict so components can always have config dict
# Use OrderedDict in case original one was one.
# Convert values to dictionaries if they are None
config = defaultdict(
dict, {key: value or {} for key, value in config.items()})
new_config = OrderedDict()
for key, value in config.items():
new_config[key] = value or {}
config = new_config
# Filter out the repeating and common config section [homeassistant]
components = set(key.split(' ')[0] for key in config.keys()
if key != core.DOMAIN)
# Setup in a thread to avoid blocking
def component_setup():
"""Set up a component."""
if not core_components.setup(hass, config):
_LOGGER.error('Home Assistant core failed to initialize. '
'Further initialization aborted.')
return hass
# setup components
# pylint: disable=not-an-iterable
res = yield from core_components.async_setup(hass, config)
if not res:
_LOGGER.error('Home Assistant core failed to initialize. '
'Further initialization aborted.')
return hass
persistent_notification.setup(hass, config)
yield from persistent_notification.async_setup(hass, config)
_LOGGER.info('Home Assistant core initialized')
_LOGGER.info('Home Assistant core initialized')
# Give event decorators access to HASS
event_decorators.HASS = hass
service.HASS = hass
# Give event decorators access to HASS
event_decorators.HASS = hass
service.HASS = hass
# Setup the components
for domain in loader.load_order_components(components):
_setup_component(hass, domain, config)
# Setup the components
for domain in loader.load_order_components(components):
yield from _async_setup_component(hass, domain, config)
hass.loop.run_until_complete(
hass.loop.run_in_executor(None, component_setup)
)
setup_lock.release()
yield from hass.async_stop_track_tasks()
return hass
@@ -331,27 +447,62 @@ def from_config_file(config_path: str,
if hass is None:
hass = core.HomeAssistant()
@asyncio.coroutine
def _async_init_from_config_file(future):
try:
re_hass = yield from async_from_config_file(
config_path, hass, verbose, skip_pip, log_rotate_days)
future.set_result(re_hass)
# pylint: disable=broad-except
except Exception as exc:
future.set_exception(exc)
# run task
future = asyncio.Future(loop=hass.loop)
hass.loop.create_task(_async_init_from_config_file(future))
hass.loop.run_until_complete(future)
return future.result()
@asyncio.coroutine
def async_from_config_file(config_path: str,
hass: core.HomeAssistant,
verbose: bool=False,
skip_pip: bool=True,
log_rotate_days: Any=None):
"""Read the configuration file and try to start all the functionality.
Will add functionality to 'hass' parameter.
This method is a coroutine.
"""
# Set config dir to directory holding config file
config_dir = os.path.abspath(os.path.dirname(config_path))
hass.config.config_dir = config_dir
mount_local_lib_path(config_dir)
yield from hass.loop.run_in_executor(
None, mount_local_lib_path, config_dir)
enable_logging(hass, verbose, log_rotate_days)
try:
config_dict = conf_util.load_yaml_config_file(config_path)
config_dict = yield from hass.loop.run_in_executor(
None, conf_util.load_yaml_config_file, config_path)
except HomeAssistantError:
return None
finally:
clear_secret_cache()
return from_config_dict(config_dict, hass, enable_log=False,
skip_pip=skip_pip)
hass = yield from async_from_config_dict(
config_dict, hass, enable_log=False, skip_pip=skip_pip)
return hass
def enable_logging(hass: core.HomeAssistant, verbose: bool=False,
log_rotate_days=None) -> None:
"""Setup the logging."""
"""Setup the logging.
Async friendly.
"""
logging.basicConfig(level=logging.INFO)
fmt = ("%(log_color)s%(asctime)s %(levelname)s (%(threadName)s) "
"[%(name)s] %(message)s%(reset)s")
@@ -359,6 +510,7 @@ def enable_logging(hass: core.HomeAssistant, verbose: bool=False,
# suppress overly verbose logs from libraries that aren't helpful
logging.getLogger("requests").setLevel(logging.WARNING)
logging.getLogger("urllib3").setLevel(logging.WARNING)
logging.getLogger("aiohttp.access").setLevel(logging.WARNING)
try:
from colorlog import ColoredFormatter
@@ -377,6 +529,10 @@ def enable_logging(hass: core.HomeAssistant, verbose: bool=False,
except ImportError:
pass
# AsyncHandler allready exists?
if hass.data.get(core.DATA_ASYNCHANDLER):
return
# Log errors to a file if we have write access to file or config dir
err_log_path = hass.config.path(ERROR_LOG_FILENAME)
err_path_exists = os.path.isfile(err_log_path)
@@ -397,8 +553,12 @@ def enable_logging(hass: core.HomeAssistant, verbose: bool=False,
err_handler.setFormatter(
logging.Formatter('%(asctime)s %(name)s: %(message)s',
datefmt='%y-%m-%d %H:%M:%S'))
async_handler = AsyncHandler(hass.loop, err_handler)
hass.data[core.DATA_ASYNCHANDLER] = async_handler
logger = logging.getLogger('')
logger.addHandler(err_handler)
logger.addHandler(async_handler)
logger.setLevel(logging.INFO)
else:
@@ -406,43 +566,62 @@ def enable_logging(hass: core.HomeAssistant, verbose: bool=False,
'Unable to setup error log %s (access denied)', err_log_path)
def _ensure_loader_prepared(hass: core.HomeAssistant) -> None:
"""Ensure Home Assistant loader is prepared."""
if not loader.PREPARED:
loader.prepare(hass)
def log_exception(ex, domain, config, hass=None):
def log_exception(ex, domain, config, hass):
"""Generate log exception for config validation."""
run_callback_threadsafe(
hass.loop, async_log_exception, ex, domain, config, hass).result()
@core.callback
def _async_persistent_notification(hass: core.HomeAssistant, component: str,
link: Optional[bool]=False):
"""Print a persistent notification.
This method must be run in the event loop.
"""
_PERSISTENT_ERRORS[component] = _PERSISTENT_ERRORS.get(component) or link
_lst = [HA_COMPONENT_URL.format(name.replace('_', '-'), name)
if link else name for name, link in _PERSISTENT_ERRORS.items()]
message = ('The following components and platforms could not be set up:\n'
'* ' + '\n* '.join(list(_lst)) + '\nPlease check your config')
persistent_notification.async_create(
hass, message, 'Invalid config', 'invalid_config')
@core.callback
def async_log_exception(ex, domain, config, hass):
"""Generate log exception for config validation.
This method must be run in the event loop.
"""
message = 'Invalid config for [{}]: '.format(domain)
if hass is not None:
_PERSISTENT_VALIDATION.add(domain)
message = ('The following platforms contain invalid configuration: ' +
', '.join(list(_PERSISTENT_VALIDATION)) +
' (please check your configuration)')
persistent_notification.create(
hass, message, 'Invalid config', 'invalid_config')
_async_persistent_notification(hass, domain, True)
if 'extra keys not allowed' in ex.error_message:
message += '[{}] is an invalid option for [{}]. Check: {}->{}.'\
.format(ex.path[-1], domain, domain,
'->'.join('%s' % m for m in ex.path))
'->'.join(str(m) for m in ex.path))
else:
message += '{}.'.format(humanize_error(config, ex))
if hasattr(config, '__line__'):
message += " (See {}:{})".format(
config.__config_file__, config.__line__ or '?')
domain_config = config.get(domain, config)
message += " (See {}:{}). ".format(
getattr(domain_config, '__config_file__', '?'),
getattr(domain_config, '__line__', '?'))
if domain != 'homeassistant':
message += (' Please check the docs at '
message += ('Please check the docs at '
'https://home-assistant.io/components/{}/'.format(domain))
_LOGGER.error(message)
def mount_local_lib_path(config_dir: str) -> str:
"""Add local library to Python Path."""
"""Add local library to Python Path.
Async friendly.
"""
deps_dir = os.path.join(config_dir, 'deps')
if deps_dir not in sys.path:
sys.path.insert(0, os.path.join(config_dir, 'deps'))
+22 -10
View File
@@ -7,6 +7,7 @@ Component design guidelines:
format "<DOMAIN>.<OBJECT_ID>".
- Each component should publish services only under its own domain.
"""
import asyncio
import itertools as it
import logging
@@ -79,8 +80,10 @@ def reload_core_config(hass):
hass.services.call(ha.DOMAIN, SERVICE_RELOAD_CORE_CONFIG)
def setup(hass, config):
@asyncio.coroutine
def async_setup(hass, config):
"""Setup general services related to Home Assistant."""
@asyncio.coroutine
def handle_turn_service(service):
"""Method to handle calls to homeassistant.turn_on/off."""
entity_ids = extract_entity_ids(hass, service)
@@ -96,6 +99,8 @@ def setup(hass, config):
by_domain = it.groupby(sorted(entity_ids),
lambda item: ha.split_entity_id(item)[0])
tasks = []
for domain, ent_ids in by_domain:
# We want to block for all calls and only return when all calls
# have been processed. If a service does not exist it causes a 10
@@ -111,27 +116,34 @@ def setup(hass, config):
# ent_ids is a generator, convert it to a list.
data[ATTR_ENTITY_ID] = list(ent_ids)
hass.services.call(domain, service.service, data, blocking)
tasks.append(hass.services.async_call(
domain, service.service, data, blocking))
hass.services.register(ha.DOMAIN, SERVICE_TURN_OFF, handle_turn_service)
hass.services.register(ha.DOMAIN, SERVICE_TURN_ON, handle_turn_service)
hass.services.register(ha.DOMAIN, SERVICE_TOGGLE, handle_turn_service)
yield from asyncio.wait(tasks, loop=hass.loop)
hass.services.async_register(
ha.DOMAIN, SERVICE_TURN_OFF, handle_turn_service)
hass.services.async_register(
ha.DOMAIN, SERVICE_TURN_ON, handle_turn_service)
hass.services.async_register(
ha.DOMAIN, SERVICE_TOGGLE, handle_turn_service)
@asyncio.coroutine
def handle_reload_config(call):
"""Service handler for reloading core config."""
from homeassistant.exceptions import HomeAssistantError
from homeassistant import config as conf_util
try:
path = conf_util.find_config_file(hass.config.config_dir)
conf = conf_util.load_yaml_config_file(path)
conf = yield from conf_util.async_hass_config_yaml(hass)
except HomeAssistantError as err:
_LOGGER.error(err)
return
conf_util.process_ha_core_config(hass, conf.get(ha.DOMAIN) or {})
yield from conf_util.async_process_ha_core_config(
hass, conf.get(ha.DOMAIN) or {})
hass.services.register(ha.DOMAIN, SERVICE_RELOAD_CORE_CONFIG,
handle_reload_config)
hass.services.async_register(
ha.DOMAIN, SERVICE_RELOAD_CORE_CONFIG, handle_reload_config)
return True
@@ -4,6 +4,7 @@ Component to interface with an alarm control panel.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel/
"""
import asyncio
import logging
import os
@@ -42,36 +43,6 @@ ALARM_SERVICE_SCHEMA = vol.Schema({
})
def setup(hass, config):
"""Track states and offer events for sensors."""
component = EntityComponent(
logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL)
component.setup(config)
def alarm_service_handler(service):
"""Map services to methods on Alarm."""
target_alarms = component.extract_from_service(service)
code = service.data.get(ATTR_CODE)
method = SERVICE_TO_METHOD[service.service]
for alarm in target_alarms:
getattr(alarm, method)(code)
if alarm.should_poll:
alarm.update_ha_state(True)
descriptions = load_yaml_config_file(
os.path.join(os.path.dirname(__file__), 'services.yaml'))
for service in SERVICE_TO_METHOD:
hass.services.register(DOMAIN, service, alarm_service_handler,
descriptions.get(service),
schema=ALARM_SERVICE_SCHEMA)
return True
def alarm_disarm(hass, code=None, entity_id=None):
"""Send the alarm the command for disarm."""
data = {}
@@ -116,6 +87,53 @@ def alarm_trigger(hass, code=None, entity_id=None):
hass.services.call(DOMAIN, SERVICE_ALARM_TRIGGER, data)
@asyncio.coroutine
def async_setup(hass, config):
"""Track states and offer events for sensors."""
component = EntityComponent(
logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL)
yield from component.async_setup(config)
@asyncio.coroutine
def async_alarm_service_handler(service):
"""Map services to methods on Alarm."""
target_alarms = component.async_extract_from_service(service)
code = service.data.get(ATTR_CODE)
method = "async_{}".format(SERVICE_TO_METHOD[service.service])
for alarm in target_alarms:
yield from getattr(alarm, method)(code)
update_tasks = []
for alarm in target_alarms:
if not alarm.should_poll:
continue
update_coro = hass.loop.create_task(
alarm.async_update_ha_state(True))
if hasattr(alarm, 'async_update'):
update_tasks.append(hass.loop.create_task(update_coro))
else:
yield from update_coro
if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)
descriptions = yield from hass.loop.run_in_executor(
None, load_yaml_config_file, os.path.join(
os.path.dirname(__file__), 'services.yaml'))
for service in SERVICE_TO_METHOD:
hass.services.async_register(
DOMAIN, service, async_alarm_service_handler,
descriptions.get(service), schema=ALARM_SERVICE_SCHEMA)
return True
# pylint: disable=no-self-use
class AlarmControlPanel(Entity):
"""An abstract class for alarm control devices."""
@@ -134,18 +152,42 @@ 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(
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(
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(
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(
None, self.alarm_trigger, code)
@property
def state_attributes(self):
"""Return the state attributes."""
@@ -39,11 +39,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
add_devices([AlarmDotCom(hass, name, code, username, password)])
add_devices([AlarmDotCom(hass, name, code, username, password)], True)
# pylint: disable=too-many-arguments, too-many-instance-attributes
# pylint: disable=abstract-method
class AlarmDotCom(alarm.AlarmControlPanel):
"""Represent an Alarm.com status."""
@@ -56,11 +54,11 @@ class AlarmDotCom(alarm.AlarmControlPanel):
self._code = str(code) if code else None
self._username = username
self._password = password
self._state = STATE_UNKNOWN
@property
def should_poll(self):
"""No polling needed."""
return True
def update(self):
"""Fetch the latest state."""
self._state = self._alarm.state
@property
def name(self):
@@ -75,11 +73,11 @@ class AlarmDotCom(alarm.AlarmControlPanel):
@property
def state(self):
"""Return the state of the device."""
if self._alarm.state == 'Disarmed':
if self._state == 'Disarmed':
return STATE_ALARM_DISARMED
elif self._alarm.state == 'Armed Stay':
elif self._state == 'Armed Stay':
return STATE_ALARM_ARMED_HOME
elif self._alarm.state == 'Armed Away':
elif self._state == 'Armed Away':
return STATE_ALARM_ARMED_AWAY
else:
return STATE_UNKNOWN
@@ -4,23 +4,19 @@ Support for Concord232 alarm control panels.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.concord232/
"""
import datetime
import logging
import requests
import voluptuous as vol
import homeassistant.components.alarm_control_panel as alarm
from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA
from homeassistant.const import (
CONF_HOST, CONF_NAME, CONF_PORT,
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME,
STATE_ALARM_DISARMED, STATE_UNKNOWN)
CONF_HOST, CONF_NAME, CONF_PORT, STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_UNKNOWN)
import homeassistant.helpers.config_validation as cv
import requests
import voluptuous as vol
REQUIREMENTS = ['concord232==0.14']
_LOGGER = logging.getLogger(__name__)
@@ -29,17 +25,17 @@ DEFAULT_HOST = 'localhost'
DEFAULT_NAME = 'CONCORD232'
DEFAULT_PORT = 5007
SCAN_INTERVAL = 1
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string,
})
SCAN_INTERVAL = 1
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup concord232 platform."""
"""Set up the Concord232 alarm control panel platform."""
name = config.get(CONF_NAME)
host = config.get(CONF_HOST)
port = config.get(CONF_PORT)
@@ -49,7 +45,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
try:
add_devices([Concord232Alarm(hass, url, name)])
except requests.exceptions.ConnectionError as ex:
_LOGGER.error('Unable to connect to Concord232: %s', str(ex))
_LOGGER.error("Unable to connect to Concord232: %s", str(ex))
return False
@@ -57,7 +53,7 @@ class Concord232Alarm(alarm.AlarmControlPanel):
"""Represents the Concord232-based alarm panel."""
def __init__(self, hass, url, name):
"""Initalize the concord232 alarm panel."""
"""Initialize the Concord232 alarm panel."""
from concord232 import client as concord232_client
self._state = STATE_UNKNOWN
@@ -68,18 +64,13 @@ class Concord232Alarm(alarm.AlarmControlPanel):
try:
client = concord232_client.Client(self._url)
except requests.exceptions.ConnectionError as ex:
_LOGGER.error('Unable to connect to Concord232: %s', str(ex))
_LOGGER.error("Unable to connect to Concord232: %s", str(ex))
self._alarm = client
self._alarm.partitions = self._alarm.list_partitions()
self._alarm.last_partition_update = datetime.datetime.now()
self.update()
@property
def should_poll(self):
"""Polling needed."""
return True
@property
def name(self):
"""Return the name of the device."""
@@ -100,16 +91,16 @@ class Concord232Alarm(alarm.AlarmControlPanel):
try:
part = self._alarm.list_partitions()[0]
except requests.exceptions.ConnectionError as ex:
_LOGGER.error('Unable to connect to %(host)s: %(reason)s',
_LOGGER.error("Unable to connect to %(host)s: %(reason)s",
dict(host=self._url, reason=ex))
newstate = STATE_UNKNOWN
except IndexError:
_LOGGER.error('concord232 reports no partitions')
_LOGGER.error("Concord232 reports no partitions")
newstate = STATE_UNKNOWN
if part['arming_level'] == "Off":
if part['arming_level'] == 'Off':
newstate = STATE_ALARM_DISARMED
elif "Home" in part['arming_level']:
elif 'Home' in part['arming_level']:
newstate = STATE_ALARM_ARMED_HOME
else:
newstate = STATE_ALARM_ARMED_AWAY
@@ -130,7 +121,3 @@ class Concord232Alarm(alarm.AlarmControlPanel):
def alarm_arm_away(self, code=None):
"""Send arm away command."""
self._alarm.arm('auto')
def alarm_trigger(self, code=None):
"""Alarm trigger command."""
raise NotImplementedError()
@@ -4,26 +4,48 @@ Support for Envisalink-based alarm control panels (Honeywell/DSC).
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.envisalink/
"""
from os import path
import logging
import voluptuous as vol
import homeassistant.components.alarm_control_panel as alarm
from homeassistant.components.envisalink import (EVL_CONTROLLER,
EnvisalinkDevice,
PARTITION_SCHEMA,
CONF_CODE,
CONF_PANIC,
CONF_PARTITIONNAME,
SIGNAL_PARTITION_UPDATE,
SIGNAL_KEYPAD_UPDATE)
import homeassistant.helpers.config_validation as cv
from homeassistant.config import load_yaml_config_file
from homeassistant.components.envisalink import (
EVL_CONTROLLER, EnvisalinkDevice, PARTITION_SCHEMA, CONF_CODE, CONF_PANIC,
CONF_PARTITIONNAME, SIGNAL_PARTITION_UPDATE, SIGNAL_KEYPAD_UPDATE)
from homeassistant.const import (
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED,
STATE_UNKNOWN, STATE_ALARM_TRIGGERED)
STATE_UNKNOWN, STATE_ALARM_TRIGGERED, STATE_ALARM_PENDING, ATTR_ENTITY_ID)
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['envisalink']
_LOGGER = logging.getLogger(__name__)
DEVICES = []
SERVICE_ALARM_KEYPRESS = 'envisalink_alarm_keypress'
ATTR_KEYPRESS = 'keypress'
ALARM_KEYPRESS_SCHEMA = vol.Schema({
vol.Required(ATTR_ENTITY_ID): cv.entity_ids,
vol.Required(ATTR_KEYPRESS): cv.string
})
def alarm_keypress_handler(service):
"""Map services to methods on Alarm."""
entity_ids = service.data.get(ATTR_ENTITY_ID)
keypress = service.data.get(ATTR_KEYPRESS)
_target_devices = [device for device in DEVICES
if device.entity_id in entity_ids]
for device in _target_devices:
EnvisalinkAlarm.alarm_keypress(device, keypress)
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Perform the setup for Envisalink alarm panels."""
_configured_partitions = discovery_info['partitions']
_code = discovery_info[CONF_CODE]
@@ -38,74 +60,105 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
_panic_type,
EVL_CONTROLLER.alarm_state['partition'][part_num],
EVL_CONTROLLER)
add_devices_callback([_device])
DEVICES.append(_device)
add_devices(DEVICES)
# Register Envisalink specific services
descriptions = load_yaml_config_file(
path.join(path.dirname(__file__), 'services.yaml'))
hass.services.register(alarm.DOMAIN, SERVICE_ALARM_KEYPRESS,
alarm_keypress_handler,
descriptions.get(SERVICE_ALARM_KEYPRESS),
schema=ALARM_KEYPRESS_SCHEMA)
return True
class EnvisalinkAlarm(EnvisalinkDevice, alarm.AlarmControlPanel):
"""Represents the Envisalink-based alarm panel."""
"""Representation of an Envisalink-based alarm panel."""
# pylint: disable=too-many-arguments
def __init__(self, partition_number, alarm_name,
code, panic_type, info, controller):
def __init__(self, partition_number, alarm_name, code, panic_type, info,
controller):
"""Initialize the alarm panel."""
from pydispatch import dispatcher
self._partition_number = partition_number
self._code = code
self._panic_type = panic_type
_LOGGER.debug('Setting up alarm: ' + alarm_name)
_LOGGER.debug("Setting up alarm: %s", alarm_name)
EnvisalinkDevice.__init__(self, alarm_name, info, controller)
dispatcher.connect(self._update_callback,
signal=SIGNAL_PARTITION_UPDATE,
sender=dispatcher.Any)
dispatcher.connect(self._update_callback,
signal=SIGNAL_KEYPAD_UPDATE,
sender=dispatcher.Any)
dispatcher.connect(
self._update_callback, signal=SIGNAL_PARTITION_UPDATE,
sender=dispatcher.Any)
dispatcher.connect(
self._update_callback, signal=SIGNAL_KEYPAD_UPDATE,
sender=dispatcher.Any)
def _update_callback(self, partition):
"""Update HA state, if needed."""
if partition is None or int(partition) == self._partition_number:
self.hass.async_add_job(self.update_ha_state)
self.hass.async_add_job(self.async_update_ha_state())
@property
def code_format(self):
"""The characters if code is defined."""
return self._code
"""Regex for code format or None if no code is required."""
if self._code:
return None
else:
return '^\\d{4,6}$'
@property
def state(self):
"""Return the state of the device."""
state = STATE_UNKNOWN
if self._info['status']['alarm']:
return STATE_ALARM_TRIGGERED
state = STATE_ALARM_TRIGGERED
elif self._info['status']['armed_away']:
return STATE_ALARM_ARMED_AWAY
state = STATE_ALARM_ARMED_AWAY
elif self._info['status']['armed_stay']:
return STATE_ALARM_ARMED_HOME
state = STATE_ALARM_ARMED_HOME
elif self._info['status']['exit_delay']:
state = STATE_ALARM_PENDING
elif self._info['status']['entry_delay']:
state = STATE_ALARM_PENDING
elif self._info['status']['alpha']:
return STATE_ALARM_DISARMED
else:
return STATE_UNKNOWN
state = STATE_ALARM_DISARMED
return state
def alarm_disarm(self, code=None):
"""Send disarm command."""
if self._code:
if code:
EVL_CONTROLLER.disarm_partition(str(code),
self._partition_number)
else:
EVL_CONTROLLER.disarm_partition(str(self._code),
self._partition_number)
def alarm_arm_home(self, code=None):
"""Send arm home command."""
if self._code:
if code:
EVL_CONTROLLER.arm_stay_partition(str(code),
self._partition_number)
else:
EVL_CONTROLLER.arm_stay_partition(str(self._code),
self._partition_number)
def alarm_arm_away(self, code=None):
"""Send arm away command."""
if self._code:
if code:
EVL_CONTROLLER.arm_away_partition(str(code),
self._partition_number)
else:
EVL_CONTROLLER.arm_away_partition(str(self._code),
self._partition_number)
def alarm_trigger(self, code=None):
"""Alarm trigger command. Will be used to trigger a panic alarm."""
if self._code:
EVL_CONTROLLER.panic_alarm(self._panic_type)
EVL_CONTROLLER.panic_alarm(self._panic_type)
def alarm_keypress(self, keypress=None):
"""Send custom keypress."""
if keypress:
EVL_CONTROLLER.keypresses_to_partition(self._partition_number,
keypress)
@@ -50,8 +50,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
)])
# pylint: disable=too-many-arguments, too-many-instance-attributes
# pylint: disable=abstract-method
class ManualAlarm(alarm.AlarmControlPanel):
"""
Represents an alarm status.
@@ -118,7 +116,7 @@ class ManualAlarm(alarm.AlarmControlPanel):
self._state = STATE_ALARM_DISARMED
self._state_ts = dt_util.utcnow()
self.update_ha_state()
self.schedule_update_ha_state()
def alarm_arm_home(self, code=None):
"""Send arm home command."""
@@ -127,11 +125,11 @@ class ManualAlarm(alarm.AlarmControlPanel):
self._state = STATE_ALARM_ARMED_HOME
self._state_ts = dt_util.utcnow()
self.update_ha_state()
self.schedule_update_ha_state()
if self._pending_time:
track_point_in_time(
self._hass, self.update_ha_state,
self._hass, self.async_update_ha_state,
self._state_ts + self._pending_time)
def alarm_arm_away(self, code=None):
@@ -141,11 +139,11 @@ class ManualAlarm(alarm.AlarmControlPanel):
self._state = STATE_ALARM_ARMED_AWAY
self._state_ts = dt_util.utcnow()
self.update_ha_state()
self.schedule_update_ha_state()
if self._pending_time:
track_point_in_time(
self._hass, self.update_ha_state,
self._hass, self.async_update_ha_state,
self._state_ts + self._pending_time)
def alarm_trigger(self, code=None):
@@ -153,15 +151,15 @@ class ManualAlarm(alarm.AlarmControlPanel):
self._pre_trigger_state = self._state
self._state = STATE_ALARM_TRIGGERED
self._state_ts = dt_util.utcnow()
self.update_ha_state()
self.schedule_update_ha_state()
if self._trigger_time:
track_point_in_time(
self._hass, self.update_ha_state,
self._hass, self.async_update_ha_state,
self._state_ts + self._pending_time)
track_point_in_time(
self._hass, self.update_ha_state,
self._hass, self.async_update_ha_state,
self._state_ts + self._pending_time + self._trigger_time)
def _validate_code(self, code, state):
@@ -55,8 +55,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
config.get(CONF_CODE))])
# pylint: disable=too-many-arguments, too-many-instance-attributes
# pylint: disable=abstract-method
class MqttAlarm(alarm.AlarmControlPanel):
"""Representation of a MQTT alarm status."""
@@ -62,11 +62,6 @@ class NX584Alarm(alarm.AlarmControlPanel):
self._alarm.list_zones()
self._state = STATE_UNKNOWN
@property
def should_poll(self):
"""Polling needed."""
return True
@property
def name(self):
"""Return the name of the device."""
@@ -117,12 +112,8 @@ class NX584Alarm(alarm.AlarmControlPanel):
def alarm_arm_home(self, code=None):
"""Send arm home command."""
self._alarm.arm('home')
self._alarm.arm('stay')
def alarm_arm_away(self, code=None):
"""Send arm away command."""
self._alarm.arm('auto')
def alarm_trigger(self, code=None):
"""Alarm trigger command."""
raise NotImplementedError()
self._alarm.arm('exit')
@@ -41,3 +41,14 @@ alarm_trigger:
code:
description: An optional code to trigger the alarm control panel with
example: 1234
envisalink_alarm_keypress:
description: Send custom keypresses to the alarm
fields:
entity_id:
description: Name of the alarm control panel to trigger
example: 'alarm_control_panel.downstairs'
keypress:
description: 'String to send to the alarm panel (1-6 characters)'
example: '*71'
@@ -41,7 +41,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
add_devices([SimpliSafeAlarm(name, username, password, code)])
# pylint: disable=abstract-method
class SimpliSafeAlarm(alarm.AlarmControlPanel):
"""Representation a SimpliSafe alarm."""
@@ -62,11 +61,6 @@ class SimpliSafeAlarm(alarm.AlarmControlPanel):
else:
self._state = STATE_UNKNOWN
@property
def should_poll(self):
"""Poll the SimpliSafe API."""
return True
@property
def name(self):
"""Return the name of the device."""
@@ -105,7 +99,6 @@ class SimpliSafeAlarm(alarm.AlarmControlPanel):
return
self.simplisafe.set_state('off')
_LOGGER.info('SimpliSafe alarm disarming')
self.update()
def alarm_arm_home(self, code=None):
"""Send arm home command."""
@@ -113,7 +106,6 @@ class SimpliSafeAlarm(alarm.AlarmControlPanel):
return
self.simplisafe.set_state('home')
_LOGGER.info('SimpliSafe alarm arming home')
self.update()
def alarm_arm_away(self, code=None):
"""Send arm away command."""
@@ -121,7 +113,6 @@ class SimpliSafeAlarm(alarm.AlarmControlPanel):
return
self.simplisafe.set_state('away')
_LOGGER.info('SimpliSafe alarm arming away')
self.update()
def _validate_code(self, code, state):
"""Validate given code."""
@@ -28,7 +28,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
add_devices(alarms)
# pylint: disable=abstract-method
class VerisureAlarm(alarm.AlarmControlPanel):
"""Represent a Verisure alarm status."""
@@ -85,18 +84,15 @@ class VerisureAlarm(alarm.AlarmControlPanel):
hub.my_pages.alarm.set(code, 'DISARMED')
_LOGGER.info('verisure alarm disarming')
hub.my_pages.alarm.wait_while_pending()
self.update()
def alarm_arm_home(self, code=None):
"""Send arm home command."""
hub.my_pages.alarm.set(code, 'ARMED_HOME')
_LOGGER.info('verisure alarm arming home')
hub.my_pages.alarm.wait_while_pending()
self.update()
def alarm_arm_away(self, code=None):
"""Send arm away command."""
hub.my_pages.alarm.set(code, 'ARMED_AWAY')
_LOGGER.info('verisure alarm arming away')
hub.my_pages.alarm.wait_while_pending()
self.update()
+21 -18
View File
@@ -4,6 +4,7 @@ Support for Alexa skill service end point.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/alexa/
"""
import asyncio
import copy
import enum
import logging
@@ -12,6 +13,7 @@ from datetime import datetime
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.const import HTTP_BAD_REQUEST
from homeassistant.helpers import template, script, config_validation as cv
from homeassistant.components.http import HomeAssistantView
@@ -20,7 +22,7 @@ import homeassistant.util.dt as dt_util
_LOGGER = logging.getLogger(__name__)
INTENTS_API_ENDPOINT = '/api/alexa'
FLASH_BRIEFINGS_API_ENDPOINT = '/api/alexa/flash_briefings/<briefing_id>'
FLASH_BRIEFINGS_API_ENDPOINT = '/api/alexa/flash_briefings/{briefing_id}'
CONF_ACTION = 'action'
CONF_CARD = 'card'
@@ -102,8 +104,8 @@ def setup(hass, config):
intents = config[DOMAIN].get(CONF_INTENTS, {})
flash_briefings = config[DOMAIN].get(CONF_FLASH_BRIEFINGS, {})
hass.wsgi.register_view(AlexaIntentsView(hass, intents))
hass.wsgi.register_view(AlexaFlashBriefingView(hass, flash_briefings))
hass.http.register_view(AlexaIntentsView(hass, intents))
hass.http.register_view(AlexaFlashBriefingView(hass, flash_briefings))
return True
@@ -116,7 +118,7 @@ class AlexaIntentsView(HomeAssistantView):
def __init__(self, hass, intents):
"""Initialize Alexa view."""
super().__init__(hass)
super().__init__()
intents = copy.deepcopy(intents)
template.attach(hass, intents)
@@ -128,9 +130,10 @@ class AlexaIntentsView(HomeAssistantView):
self.intents = intents
@asyncio.coroutine
def post(self, request):
"""Handle Alexa."""
data = request.json
data = yield from request.json()
_LOGGER.debug('Received Alexa request: %s', data)
@@ -147,7 +150,7 @@ class AlexaIntentsView(HomeAssistantView):
return None
intent = req.get('intent')
response = AlexaResponse(self.hass, intent)
response = AlexaResponse(request.app['hass'], intent)
if req_type == 'LaunchRequest':
response.add_speech(
@@ -176,7 +179,7 @@ class AlexaIntentsView(HomeAssistantView):
action = config.get(CONF_ACTION)
if action is not None:
action.run(response.variables)
yield from action.async_run(response.variables)
# pylint: disable=unsubscriptable-object
if speech is not None:
@@ -218,8 +221,8 @@ class AlexaResponse(object):
self.card = card
return
card["title"] = title.render(self.variables)
card["content"] = content.render(self.variables)
card["title"] = title.async_render(self.variables)
card["content"] = content.async_render(self.variables)
self.card = card
def add_speech(self, speech_type, text):
@@ -229,7 +232,7 @@ class AlexaResponse(object):
key = 'ssml' if speech_type == SpeechType.ssml else 'text'
if isinstance(text, template.Template):
text = text.render(self.variables)
text = text.async_render(self.variables)
self.speech = {
'type': speech_type.value,
@@ -244,7 +247,7 @@ class AlexaResponse(object):
self.reprompt = {
'type': speech_type.value,
key: text.render(self.variables)
key: text.async_render(self.variables)
}
def as_dict(self):
@@ -279,11 +282,11 @@ class AlexaFlashBriefingView(HomeAssistantView):
def __init__(self, hass, flash_briefings):
"""Initialize Alexa view."""
super().__init__(hass)
super().__init__()
self.flash_briefings = copy.deepcopy(flash_briefings)
template.attach(hass, self.flash_briefings)
# pylint: disable=too-many-branches
@callback
def get(self, request, briefing_id):
"""Handle Alexa Flash Briefing request."""
_LOGGER.debug('Received Alexa flash briefing request for: %s',
@@ -292,7 +295,7 @@ class AlexaFlashBriefingView(HomeAssistantView):
if self.flash_briefings.get(briefing_id) is None:
err = 'No configured Alexa flash briefing was found for: %s'
_LOGGER.error(err, briefing_id)
return self.Response(status=404)
return b'', 404
briefing = []
@@ -300,13 +303,13 @@ class AlexaFlashBriefingView(HomeAssistantView):
output = {}
if item.get(CONF_TITLE) is not None:
if isinstance(item.get(CONF_TITLE), template.Template):
output[ATTR_TITLE_TEXT] = item[CONF_TITLE].render()
output[ATTR_TITLE_TEXT] = item[CONF_TITLE].async_render()
else:
output[ATTR_TITLE_TEXT] = item.get(CONF_TITLE)
if item.get(CONF_TEXT) is not None:
if isinstance(item.get(CONF_TEXT), template.Template):
output[ATTR_MAIN_TEXT] = item[CONF_TEXT].render()
output[ATTR_MAIN_TEXT] = item[CONF_TEXT].async_render()
else:
output[ATTR_MAIN_TEXT] = item.get(CONF_TEXT)
@@ -315,7 +318,7 @@ class AlexaFlashBriefingView(HomeAssistantView):
if item.get(CONF_AUDIO) is not None:
if isinstance(item.get(CONF_AUDIO), template.Template):
output[ATTR_STREAM_URL] = item[CONF_AUDIO].render()
output[ATTR_STREAM_URL] = item[CONF_AUDIO].async_render()
else:
output[ATTR_STREAM_URL] = item.get(CONF_AUDIO)
@@ -323,7 +326,7 @@ class AlexaFlashBriefingView(HomeAssistantView):
if isinstance(item.get(CONF_DISPLAY_URL),
template.Template):
output[ATTR_REDIRECTION_URL] = \
item[CONF_DISPLAY_URL].render()
item[CONF_DISPLAY_URL].async_render()
else:
output[ATTR_REDIRECTION_URL] = item.get(CONF_DISPLAY_URL)
+130 -86
View File
@@ -7,7 +7,9 @@ https://home-assistant.io/developers/api/
import asyncio
import json
import logging
import queue
from aiohttp import web
import async_timeout
import homeassistant.core as ha
import homeassistant.remote as rem
@@ -21,7 +23,7 @@ from homeassistant.const import (
URL_API_STATES, URL_API_STATES_ENTITY, URL_API_STREAM, URL_API_TEMPLATE,
__version__)
from homeassistant.exceptions import TemplateError
from homeassistant.helpers.state import TrackStates
from homeassistant.helpers.state import AsyncTrackStates
from homeassistant.helpers import template
from homeassistant.components.http import HomeAssistantView
@@ -36,20 +38,20 @@ _LOGGER = logging.getLogger(__name__)
def setup(hass, config):
"""Register the API with the HTTP interface."""
hass.wsgi.register_view(APIStatusView)
hass.wsgi.register_view(APIEventStream)
hass.wsgi.register_view(APIConfigView)
hass.wsgi.register_view(APIDiscoveryView)
hass.wsgi.register_view(APIStatesView)
hass.wsgi.register_view(APIEntityStateView)
hass.wsgi.register_view(APIEventListenersView)
hass.wsgi.register_view(APIEventView)
hass.wsgi.register_view(APIServicesView)
hass.wsgi.register_view(APIDomainServicesView)
hass.wsgi.register_view(APIEventForwardingView)
hass.wsgi.register_view(APIComponentsView)
hass.wsgi.register_view(APIErrorLogView)
hass.wsgi.register_view(APITemplateView)
hass.http.register_view(APIStatusView)
hass.http.register_view(APIEventStream)
hass.http.register_view(APIConfigView)
hass.http.register_view(APIDiscoveryView)
hass.http.register_view(APIStatesView)
hass.http.register_view(APIEntityStateView)
hass.http.register_view(APIEventListenersView)
hass.http.register_view(APIEventView)
hass.http.register_view(APIServicesView)
hass.http.register_view(APIDomainServicesView)
hass.http.register_view(APIEventForwardingView)
hass.http.register_view(APIComponentsView)
hass.http.register_view(APIErrorLogView)
hass.http.register_view(APITemplateView)
return True
@@ -60,6 +62,7 @@ class APIStatusView(HomeAssistantView):
url = URL_API
name = "api:status"
@ha.callback
def get(self, request):
"""Retrieve if API is running."""
return self.json_message('API running.')
@@ -71,12 +74,15 @@ class APIEventStream(HomeAssistantView):
url = URL_API_STREAM
name = "api:stream"
@asyncio.coroutine
def get(self, request):
"""Provide a streaming interface for the event bus."""
# pylint: disable=no-self-use
hass = request.app['hass']
stop_obj = object()
to_write = queue.Queue()
to_write = asyncio.Queue(loop=hass.loop)
restrict = request.args.get('restrict')
restrict = request.GET.get('restrict')
if restrict:
restrict = restrict.split(',') + [EVENT_HOMEASSISTANT_STOP]
@@ -96,38 +102,40 @@ class APIEventStream(HomeAssistantView):
else:
data = json.dumps(event, cls=rem.JSONEncoder)
to_write.put(data)
yield from to_write.put(data)
def stream():
"""Stream events to response."""
unsub_stream = self.hass.bus.listen(MATCH_ALL, forward_events)
response = web.StreamResponse()
response.content_type = 'text/event-stream'
yield from response.prepare(request)
try:
_LOGGER.debug('STREAM %s ATTACHED', id(stop_obj))
unsub_stream = hass.bus.async_listen(MATCH_ALL, forward_events)
# Fire off one message so browsers fire open event right away
to_write.put(STREAM_PING_PAYLOAD)
try:
_LOGGER.debug('STREAM %s ATTACHED', id(stop_obj))
while True:
try:
payload = to_write.get(timeout=STREAM_PING_INTERVAL)
# Fire off one message so browsers fire open event right away
yield from to_write.put(STREAM_PING_PAYLOAD)
if payload is stop_obj:
break
while True:
try:
with async_timeout.timeout(STREAM_PING_INTERVAL,
loop=hass.loop):
payload = yield from to_write.get()
msg = "data: {}\n\n".format(payload)
_LOGGER.debug('STREAM %s WRITING %s', id(stop_obj),
msg.strip())
yield msg.encode("UTF-8")
except queue.Empty:
to_write.put(STREAM_PING_PAYLOAD)
except GeneratorExit:
if payload is stop_obj:
break
finally:
_LOGGER.debug('STREAM %s RESPONSE CLOSED', id(stop_obj))
unsub_stream()
return self.Response(stream(), mimetype='text/event-stream')
msg = "data: {}\n\n".format(payload)
_LOGGER.debug('STREAM %s WRITING %s', id(stop_obj),
msg.strip())
response.write(msg.encode("UTF-8"))
yield from response.drain()
except asyncio.TimeoutError:
yield from to_write.put(STREAM_PING_PAYLOAD)
finally:
_LOGGER.debug('STREAM %s RESPONSE CLOSED', id(stop_obj))
unsub_stream()
class APIConfigView(HomeAssistantView):
@@ -136,9 +144,10 @@ class APIConfigView(HomeAssistantView):
url = URL_API_CONFIG
name = "api:config"
@ha.callback
def get(self, request):
"""Get current configuration."""
return self.json(self.hass.config.as_dict())
return self.json(request.app['hass'].config.as_dict())
class APIDiscoveryView(HomeAssistantView):
@@ -148,12 +157,14 @@ class APIDiscoveryView(HomeAssistantView):
url = URL_API_DISCOVERY_INFO
name = "api:discovery"
@ha.callback
def get(self, request):
"""Get discovery info."""
needs_auth = self.hass.config.api.api_password is not None
hass = request.app['hass']
needs_auth = hass.config.api.api_password is not None
return self.json({
'base_url': self.hass.config.api.base_url,
'location_name': self.hass.config.location_name,
'base_url': hass.config.api.base_url,
'location_name': hass.config.location_name,
'requires_api_password': needs_auth,
'version': __version__
})
@@ -165,53 +176,62 @@ class APIStatesView(HomeAssistantView):
url = URL_API_STATES
name = "api:states"
@ha.callback
def get(self, request):
"""Get current states."""
return self.json(self.hass.states.all())
return self.json(request.app['hass'].states.async_all())
class APIEntityStateView(HomeAssistantView):
"""View to handle EntityState requests."""
url = "/api/states/<entity(exist=False):entity_id>"
url = "/api/states/{entity_id}"
name = "api:entity-state"
@ha.callback
def get(self, request, entity_id):
"""Retrieve state of entity."""
state = self.hass.states.get(entity_id)
state = request.app['hass'].states.get(entity_id)
if state:
return self.json(state)
else:
return self.json_message('Entity not found', HTTP_NOT_FOUND)
@asyncio.coroutine
def post(self, request, entity_id):
"""Update state of entity."""
hass = request.app['hass']
try:
new_state = request.json['state']
except KeyError:
data = yield from request.json()
except ValueError:
return self.json_message('Invalid JSON specified',
HTTP_BAD_REQUEST)
new_state = data.get('state')
if not new_state:
return self.json_message('No state specified', HTTP_BAD_REQUEST)
attributes = request.json.get('attributes')
force_update = request.json.get('force_update', False)
attributes = data.get('attributes')
force_update = data.get('force_update', False)
is_new_state = self.hass.states.get(entity_id) is None
is_new_state = hass.states.get(entity_id) is None
# Write state
self.hass.states.set(entity_id, new_state, attributes, force_update)
hass.states.async_set(entity_id, new_state, attributes, force_update)
# Read the state back for our response
resp = self.json(self.hass.states.get(entity_id))
if is_new_state:
resp.status_code = HTTP_CREATED
status_code = HTTP_CREATED if is_new_state else 200
resp = self.json(hass.states.get(entity_id), status_code)
resp.headers.add('Location', URL_API_STATES_ENTITY.format(entity_id))
return resp
@ha.callback
def delete(self, request, entity_id):
"""Remove entity."""
if self.hass.states.remove(entity_id):
if request.app['hass'].states.async_remove(entity_id):
return self.json_message('Entity removed')
else:
return self.json_message('Entity not found', HTTP_NOT_FOUND)
@@ -223,20 +243,23 @@ class APIEventListenersView(HomeAssistantView):
url = URL_API_EVENTS
name = "api:event-listeners"
@ha.callback
def get(self, request):
"""Get event listeners."""
return self.json(events_json(self.hass))
return self.json(async_events_json(request.app['hass']))
class APIEventView(HomeAssistantView):
"""View to handle Event requests."""
url = '/api/events/<event_type>'
url = '/api/events/{event_type}'
name = "api:event"
@asyncio.coroutine
def post(self, request, event_type):
"""Fire events."""
event_data = request.json
body = yield from request.text()
event_data = json.loads(body) if body else None
if event_data is not None and not isinstance(event_data, dict):
return self.json_message('Event data should be a JSON object',
@@ -251,7 +274,8 @@ class APIEventView(HomeAssistantView):
if state:
event_data[key] = state
self.hass.bus.fire(event_type, event_data, ha.EventOrigin.remote)
request.app['hass'].bus.async_fire(event_type, event_data,
ha.EventOrigin.remote)
return self.json_message("Event {} fired.".format(event_type))
@@ -262,24 +286,30 @@ class APIServicesView(HomeAssistantView):
url = URL_API_SERVICES
name = "api:services"
@ha.callback
def get(self, request):
"""Get registered services."""
return self.json(services_json(self.hass))
return self.json(async_services_json(request.app['hass']))
class APIDomainServicesView(HomeAssistantView):
"""View to handle DomainServices requests."""
url = "/api/services/<domain>/<service>"
url = "/api/services/{domain}/{service}"
name = "api:domain-services"
@asyncio.coroutine
def post(self, request, domain, service):
"""Call a service.
Returns a list of changed states.
"""
with TrackStates(self.hass) as changed_states:
self.hass.services.call(domain, service, request.json, True)
hass = request.app['hass']
body = yield from request.text()
data = json.loads(body) if body else None
with AsyncTrackStates(hass) as changed_states:
yield from hass.services.async_call(domain, service, data, True)
return self.json(changed_states)
@@ -291,11 +321,15 @@ class APIEventForwardingView(HomeAssistantView):
name = "api:event-forward"
event_forwarder = None
@asyncio.coroutine
def post(self, request):
"""Setup an event forwarder."""
data = request.json
if data is None:
hass = request.app['hass']
try:
data = yield from request.json()
except ValueError:
return self.json_message("No data received.", HTTP_BAD_REQUEST)
try:
host = data['host']
api_password = data['api_password']
@@ -311,21 +345,25 @@ class APIEventForwardingView(HomeAssistantView):
api = rem.API(host, api_password, port)
if not api.validate_api():
valid = yield from hass.loop.run_in_executor(
None, api.validate_api)
if not valid:
return self.json_message("Unable to validate API.",
HTTP_UNPROCESSABLE_ENTITY)
if self.event_forwarder is None:
self.event_forwarder = rem.EventForwarder(self.hass)
self.event_forwarder = rem.EventForwarder(hass)
self.event_forwarder.connect(api)
self.event_forwarder.async_connect(api)
return self.json_message("Event forwarding setup.")
@asyncio.coroutine
def delete(self, request):
"""Remove event forwarer."""
data = request.json
if data is None:
"""Remove event forwarder."""
try:
data = yield from request.json()
except ValueError:
return self.json_message("No data received.", HTTP_BAD_REQUEST)
try:
@@ -342,7 +380,7 @@ class APIEventForwardingView(HomeAssistantView):
if self.event_forwarder is not None:
api = rem.API(host, None, port)
self.event_forwarder.disconnect(api)
self.event_forwarder.async_disconnect(api)
return self.json_message("Event forwarding cancelled.")
@@ -353,9 +391,10 @@ class APIComponentsView(HomeAssistantView):
url = URL_API_COMPONENTS
name = "api:components"
@ha.callback
def get(self, request):
"""Get current loaded components."""
return self.json(self.hass.config.components)
return self.json(request.app['hass'].config.components)
class APIErrorLogView(HomeAssistantView):
@@ -364,9 +403,12 @@ class APIErrorLogView(HomeAssistantView):
url = URL_API_ERROR_LOG
name = "api:error-log"
@asyncio.coroutine
def get(self, request):
"""Serve error log."""
return self.file(request, self.hass.config.path(ERROR_LOG_FILENAME))
resp = yield from self.file(
request, request.app['hass'].config.path(ERROR_LOG_FILENAME))
return resp
class APITemplateView(HomeAssistantView):
@@ -375,23 +417,25 @@ class APITemplateView(HomeAssistantView):
url = URL_API_TEMPLATE
name = "api:template"
@asyncio.coroutine
def post(self, request):
"""Render a template."""
try:
tpl = template.Template(request.json['template'], self.hass)
return tpl.render(request.json.get('variables'))
except TemplateError as ex:
data = yield from request.json()
tpl = template.Template(data['template'], request.app['hass'])
return tpl.async_render(data.get('variables'))
except (ValueError, TemplateError) as ex:
return self.json_message('Error rendering template: {}'.format(ex),
HTTP_BAD_REQUEST)
def services_json(hass):
def async_services_json(hass):
"""Generate services data to JSONify."""
return [{"domain": key, "services": value}
for key, value in hass.services.services.items()]
for key, value in hass.services.async_services().items()]
def events_json(hass):
def async_events_json(hass):
"""Generate event data to JSONify."""
return [{"event": key, "listener_count": value}
for key, value in hass.bus.listeners.items()]
for key, value in hass.bus.async_listeners().items()]
+50 -40
View File
@@ -11,8 +11,7 @@ import os
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.bootstrap import prepare_setup_platform
from homeassistant.bootstrap import async_prepare_setup_platform
from homeassistant import config as conf_util
from homeassistant.const import (
ATTR_ENTITY_ID, CONF_PLATFORM, STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF,
@@ -25,7 +24,6 @@ from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.loader import get_platform
from homeassistant.util.dt import utcnow
import homeassistant.helpers.config_validation as cv
from homeassistant.util.async import run_coroutine_threadsafe
DOMAIN = 'automation'
ENTITY_ID_FORMAT = DOMAIN + '.{}'
@@ -68,6 +66,7 @@ def _platform_validator(config):
return getattr(platform, 'TRIGGER_SCHEMA')(config)
_TRIGGER_SCHEMA = vol.All(
cv.ensure_list,
[
@@ -144,42 +143,56 @@ def reload(hass):
hass.services.call(DOMAIN, SERVICE_RELOAD)
def setup(hass, config):
@asyncio.coroutine
def async_setup(hass, config):
"""Setup the automation."""
component = EntityComponent(_LOGGER, DOMAIN, hass,
group_name=GROUP_NAME_ALL_AUTOMATIONS)
success = run_coroutine_threadsafe(
_async_process_config(hass, config, component), hass.loop).result()
success = yield from _async_process_config(hass, config, component)
if not success:
return False
descriptions = conf_util.load_yaml_config_file(
os.path.join(os.path.dirname(__file__), 'services.yaml'))
descriptions = yield from hass.loop.run_in_executor(
None, conf_util.load_yaml_config_file, os.path.join(
os.path.dirname(__file__), 'services.yaml')
)
@callback
@asyncio.coroutine
def trigger_service_handler(service_call):
"""Handle automation triggers."""
tasks = []
for entity in component.async_extract_from_service(service_call):
hass.loop.create_task(entity.async_trigger(
tasks.append(entity.async_trigger(
service_call.data.get(ATTR_VARIABLES), True))
@callback
if tasks:
yield from asyncio.wait(tasks, loop=hass.loop)
@asyncio.coroutine
def turn_onoff_service_handler(service_call):
"""Handle automation turn on/off service calls."""
tasks = []
method = 'async_{}'.format(service_call.service)
for entity in component.async_extract_from_service(service_call):
hass.loop.create_task(getattr(entity, method)())
tasks.append(getattr(entity, method)())
@callback
if tasks:
yield from asyncio.wait(tasks, loop=hass.loop)
@asyncio.coroutine
def toggle_service_handler(service_call):
"""Handle automation toggle service calls."""
tasks = []
for entity in component.async_extract_from_service(service_call):
if entity.is_on:
hass.loop.create_task(entity.async_turn_off())
tasks.append(entity.async_turn_off())
else:
hass.loop.create_task(entity.async_turn_on())
tasks.append(entity.async_turn_on())
if tasks:
yield from asyncio.wait(tasks, loop=hass.loop)
@asyncio.coroutine
def reload_service_handler(service_call):
@@ -187,24 +200,24 @@ def setup(hass, config):
conf = yield from component.async_prepare_reload()
if conf is None:
return
hass.loop.create_task(_async_process_config(hass, conf, component))
yield from _async_process_config(hass, conf, component)
hass.services.register(DOMAIN, SERVICE_TRIGGER, trigger_service_handler,
descriptions.get(SERVICE_TRIGGER),
schema=TRIGGER_SERVICE_SCHEMA)
hass.services.async_register(
DOMAIN, SERVICE_TRIGGER, trigger_service_handler,
descriptions.get(SERVICE_TRIGGER), schema=TRIGGER_SERVICE_SCHEMA)
hass.services.register(DOMAIN, SERVICE_RELOAD, reload_service_handler,
descriptions.get(SERVICE_RELOAD),
schema=RELOAD_SERVICE_SCHEMA)
hass.services.async_register(
DOMAIN, SERVICE_RELOAD, reload_service_handler,
descriptions.get(SERVICE_RELOAD), schema=RELOAD_SERVICE_SCHEMA)
hass.services.register(DOMAIN, SERVICE_TOGGLE, toggle_service_handler,
descriptions.get(SERVICE_TOGGLE),
schema=SERVICE_SCHEMA)
hass.services.async_register(
DOMAIN, SERVICE_TOGGLE, toggle_service_handler,
descriptions.get(SERVICE_TOGGLE), schema=SERVICE_SCHEMA)
for service in (SERVICE_TURN_ON, SERVICE_TURN_OFF):
hass.services.register(DOMAIN, service, turn_onoff_service_handler,
descriptions.get(service),
schema=SERVICE_SCHEMA)
hass.services.async_register(
DOMAIN, service, turn_onoff_service_handler,
descriptions.get(service), schema=SERVICE_SCHEMA)
return True
@@ -212,8 +225,6 @@ def setup(hass, config):
class AutomationEntity(ToggleEntity):
"""Entity to show status of entity."""
# pylint: disable=abstract-method
# pylint: disable=too-many-arguments, too-many-instance-attributes
def __init__(self, name, async_attach_triggers, cond_func, async_action,
hidden):
"""Initialize an automation entity."""
@@ -260,7 +271,7 @@ class AutomationEntity(ToggleEntity):
return
yield from self.async_enable()
self.hass.loop.create_task(self.async_update_ha_state())
yield from self.async_update_ha_state()
@asyncio.coroutine
def async_turn_off(self, **kwargs) -> None:
@@ -271,8 +282,6 @@ class AutomationEntity(ToggleEntity):
self._async_detach_triggers()
self._async_detach_triggers = None
self._enabled = False
# It's important that the update is finished before this method
# ends because async_remove depends on it.
yield from self.async_update_ha_state()
@asyncio.coroutine
@@ -284,7 +293,7 @@ class AutomationEntity(ToggleEntity):
if skip_condition or self._cond_func(variables):
yield from self._async_action(self.entity_id, variables)
self._last_triggered = utcnow()
self.hass.loop.create_task(self.async_update_ha_state())
yield from self.async_update_ha_state()
@asyncio.coroutine
def async_remove(self):
@@ -346,8 +355,10 @@ def _async_process_config(hass, config, component):
tasks.append(entity.async_enable())
entities.append(entity)
yield from asyncio.gather(*tasks, loop=hass.loop)
hass.loop.create_task(component.async_add_entities(entities))
if tasks:
yield from asyncio.wait(tasks, loop=hass.loop)
if entities:
yield from component.async_add_entities(entities)
return len(entities) > 0
@@ -362,7 +373,7 @@ def _async_get_action(hass, config, name):
_LOGGER.info('Executing %s', name)
logbook.async_log_entry(
hass, name, 'has been triggered', DOMAIN, entity_id)
hass.loop.create_task(script_obj.async_run(variables))
yield from script_obj.async_run(variables)
return action
@@ -395,9 +406,8 @@ def _async_process_trigger(hass, config, trigger_configs, name, action):
removes = []
for conf in trigger_configs:
platform = yield from hass.loop.run_in_executor(
None, prepare_setup_platform, hass, config, DOMAIN,
conf.get(CONF_PLATFORM))
platform = yield from async_prepare_setup_platform(
hass, config, DOMAIN, conf.get(CONF_PLATFORM))
if platform is None:
return None
@@ -0,0 +1,98 @@
"""
Trigger an automation when a LiteJet switch is released.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/automation.litejet/
"""
import logging
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.const import CONF_PLATFORM
import homeassistant.helpers.config_validation as cv
import homeassistant.util.dt as dt_util
from homeassistant.helpers.event import track_point_in_utc_time
DEPENDENCIES = ['litejet']
_LOGGER = logging.getLogger(__name__)
CONF_NUMBER = 'number'
CONF_HELD_MORE_THAN = 'held_more_than'
CONF_HELD_LESS_THAN = 'held_less_than'
TRIGGER_SCHEMA = vol.Schema({
vol.Required(CONF_PLATFORM): 'litejet',
vol.Required(CONF_NUMBER): cv.positive_int,
vol.Optional(CONF_HELD_MORE_THAN):
vol.All(cv.time_period, cv.positive_timedelta),
vol.Optional(CONF_HELD_LESS_THAN):
vol.All(cv.time_period, cv.positive_timedelta)
})
def async_trigger(hass, config, action):
"""Listen for events based on configuration."""
number = config.get(CONF_NUMBER)
held_more_than = config.get(CONF_HELD_MORE_THAN)
held_less_than = config.get(CONF_HELD_LESS_THAN)
pressed_time = None
cancel_pressed_more_than = None
@callback
def call_action():
"""Call action with right context."""
hass.async_run_job(action, {
'trigger': {
CONF_PLATFORM: 'litejet',
CONF_NUMBER: number,
CONF_HELD_MORE_THAN: held_more_than,
CONF_HELD_LESS_THAN: held_less_than
},
})
# held_more_than and held_less_than: trigger on released (if in time range)
# held_more_than: trigger after pressed with calculation
# held_less_than: trigger on released with calculation
# neither: trigger on pressed
@callback
def pressed_more_than_satisfied(now):
"""Handle the LiteJet's switch's button pressed >= held_more_than."""
call_action()
def pressed():
"""Handle the press of the LiteJet switch's button."""
nonlocal cancel_pressed_more_than, pressed_time
nonlocal held_less_than, held_more_than
pressed_time = dt_util.utcnow()
if held_more_than is None and held_less_than is None:
call_action()
if held_more_than is not None and held_less_than is None:
cancel_pressed_more_than = track_point_in_utc_time(
hass,
pressed_more_than_satisfied,
dt_util.utcnow() + held_more_than)
def released():
"""Handle the release of the LiteJet switch's button."""
nonlocal cancel_pressed_more_than, pressed_time
nonlocal held_less_than, held_more_than
# pylint: disable=not-callable
if cancel_pressed_more_than is not None:
cancel_pressed_more_than()
cancel_pressed_more_than = None
held_time = dt_util.utcnow() - pressed_time
if held_less_than is not None and held_time < held_less_than:
if held_more_than is None or held_time > held_more_than:
call_action()
hass.data['litejet_system'].on_switch_pressed(number, pressed)
hass.data['litejet_system'].on_switch_released(number, released)
def async_remove():
"""Remove all subscriptions used for this trigger."""
return
return async_remove
+15 -6
View File
@@ -4,6 +4,8 @@ Offer MQTT listening automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#mqtt-trigger
"""
import json
import voluptuous as vol
from homeassistant.core import callback
@@ -31,13 +33,20 @@ def async_trigger(hass, config, action):
def mqtt_automation_listener(msg_topic, msg_payload, qos):
"""Listen for MQTT messages."""
if payload is None or payload == msg_payload:
data = {
'platform': 'mqtt',
'topic': msg_topic,
'payload': msg_payload,
'qos': qos,
}
try:
data['payload_json'] = json.loads(msg_payload)
except ValueError:
pass
hass.async_run_job(action, {
'trigger': {
'platform': 'mqtt',
'topic': msg_topic,
'payload': msg_payload,
'qos': qos,
}
'trigger': data
})
return mqtt.async_subscribe(hass, topic, mqtt_automation_listener)
@@ -64,10 +64,19 @@ def async_trigger(hass, config, action):
call_action()
return
@callback
def clear_listener():
"""Clear all unsub listener."""
nonlocal async_remove_state_for_cancel
nonlocal async_remove_state_for_listener
async_remove_state_for_listener = None
async_remove_state_for_cancel = None
@callback
def state_for_listener(now):
"""Fire on state changes after a delay and calls action."""
async_remove_state_for_cancel()
clear_listener()
call_action()
@callback
@@ -77,6 +86,7 @@ def async_trigger(hass, config, action):
return
async_remove_state_for_listener()
async_remove_state_for_cancel()
clear_listener()
async_remove_state_for_listener = async_track_point_in_utc_time(
hass, state_for_listener, dt_util.utcnow() + time_delta)
@@ -4,6 +4,7 @@ Component to interface with binary sensors.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/binary_sensor/
"""
import asyncio
import logging
import voluptuous as vol
@@ -39,13 +40,13 @@ SENSOR_CLASSES = [
SENSOR_CLASSES_SCHEMA = vol.All(vol.Lower, vol.In(SENSOR_CLASSES))
def setup(hass, config):
@asyncio.coroutine
def async_setup(hass, config):
"""Track states and offer events for binary sensors."""
component = EntityComponent(
logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL)
component.setup(config)
yield from component.async_setup(config)
return True
@@ -54,7 +54,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
sensor_class, pin)])
# pylint: disable=too-many-instance-attributes, too-many-arguments
class ArestBinarySensor(BinarySensorDevice):
"""Implement an aREST binary sensor for a pin."""
@@ -93,7 +92,6 @@ class ArestBinarySensor(BinarySensorDevice):
self.arest.update()
# pylint: disable=too-few-public-methods
class ArestData(object):
"""Class for handling the data retrieval for pins."""
@@ -5,7 +5,6 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.command_line/
"""
import logging
from datetime import timedelta
import voluptuous as vol
@@ -23,7 +22,7 @@ DEFAULT_NAME = 'Binary Command Sensor'
DEFAULT_PAYLOAD_ON = 'ON'
DEFAULT_PAYLOAD_OFF = 'OFF'
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60)
SCAN_INTERVAL = 60
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_COMMAND): cv.string,
@@ -53,7 +52,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
value_template)])
# pylint: disable=too-many-arguments, too-many-instance-attributes
class CommandBinarySensor(BinarySensorDevice):
"""Represent a command line binary sensor."""
@@ -5,20 +5,16 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.concord232/
"""
import datetime
import logging
import requests
import voluptuous as vol
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA, SENSOR_CLASSES)
from homeassistant.const import (CONF_HOST, CONF_PORT)
import homeassistant.helpers.config_validation as cv
import requests
import voluptuous as vol
REQUIREMENTS = ['concord232==0.14']
_LOGGER = logging.getLogger(__name__)
@@ -27,9 +23,12 @@ CONF_EXCLUDE_ZONES = 'exclude_zones'
CONF_ZONE_TYPES = 'zone_types'
DEFAULT_HOST = 'localhost'
DEFAULT_NAME = 'Alarm'
DEFAULT_PORT = '5007'
DEFAULT_SSL = False
SCAN_INTERVAL = 1
ZONE_TYPES_SCHEMA = vol.Schema({
cv.positive_int: vol.In(SENSOR_CLASSES),
})
@@ -42,14 +41,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_ZONE_TYPES, default={}): ZONE_TYPES_SCHEMA,
})
SCAN_INTERVAL = 1
DEFAULT_NAME = "Alarm"
# pylint: disable=too-many-locals
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Concord232 binary sensor platform."""
"""Set up the Concord232 binary sensor platform."""
from concord232 import client as concord232_client
host = config.get(CONF_HOST)
@@ -59,24 +53,23 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
sensors = []
try:
_LOGGER.debug('Initializing Client.')
client = concord232_client.Client('http://{}:{}'
.format(host, port))
_LOGGER.debug("Initializing Client")
client = concord232_client.Client('http://{}:{}'.format(host, port))
client.zones = client.list_zones()
client.last_zone_update = datetime.datetime.now()
except requests.exceptions.ConnectionError as ex:
_LOGGER.error('Unable to connect to Concord232: %s', str(ex))
_LOGGER.error("Unable to connect to Concord232: %s", str(ex))
return False
for zone in client.zones:
_LOGGER.info('Loading Zone found: %s', zone['name'])
_LOGGER.info("Loading Zone found: %s", zone['name'])
if zone['number'] not in exclude:
sensors.append(Concord232ZoneSensor(
hass,
client,
zone,
zone_types.get(zone['number'], get_opening_type(zone))))
sensors.append(
Concord232ZoneSensor(
hass, client, zone, zone_types.get(zone['number'],
get_opening_type(zone)))
)
add_devices(sensors)
@@ -84,16 +77,16 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
def get_opening_type(zone):
"""Helper function to try to guess sensor type frm name."""
if "MOTION" in zone["name"]:
return "motion"
if "KEY" in zone["name"]:
return "safety"
if "SMOKE" in zone["name"]:
return "smoke"
if "WATER" in zone["name"]:
return "water"
return "opening"
"""Helper function to try to guess sensor type from name."""
if 'MOTION' in zone['name']:
return 'motion'
if 'KEY' in zone['name']:
return 'safety'
if 'SMOKE' in zone['name']:
return 'smoke'
if 'WATER' in zone['name']:
return 'water'
return 'opening'
class Concord232ZoneSensor(BinarySensorDevice):
@@ -20,7 +20,7 @@ DEPENDENCIES = ['enocean']
DEFAULT_NAME = 'EnOcean binary sensor'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_ID): cv.string,
vol.Required(CONF_ID): vol.All(cv.ensure_list, [vol.Coerce(int)]),
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_SENSOR_CLASS, default=None): SENSOR_CLASSES_SCHEMA,
})
@@ -35,7 +35,6 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
class EnvisalinkBinarySensor(EnvisalinkDevice, BinarySensorDevice):
"""Representation of an Envisalink binary sensor."""
# pylint: disable=too-many-arguments
def __init__(self, zone_number, zone_name, zone_type, info, controller):
"""Initialize the binary_sensor."""
from pydispatch import dispatcher
@@ -81,7 +81,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
from haffmpeg import SensorNoise, SensorMotion
# check source
if not run_test(config.get(CONF_INPUT)):
if not run_test(hass, config.get(CONF_INPUT)):
return
# generate sensor object
@@ -138,7 +138,7 @@ class FFmpegBinarySensor(BinarySensorDevice):
def _callback(self, state):
"""HA-FFmpeg callback for noise detection."""
self._state = state
self.update_ha_state()
self.schedule_update_ha_state()
def _start_ffmpeg(self, config):
"""Start a FFmpeg instance."""
@@ -0,0 +1,257 @@
"""Contains functionality to use flic buttons as a binary sensor."""
import asyncio
import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.const import (
CONF_HOST, CONF_PORT, CONF_DISCOVERY, CONF_TIMEOUT,
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']
_LOGGER = logging.getLogger(__name__)
DEFAULT_TIMEOUT = 3
CLICK_TYPE_SINGLE = "single"
CLICK_TYPE_DOUBLE = "double"
CLICK_TYPE_HOLD = "hold"
CLICK_TYPES = [CLICK_TYPE_SINGLE, CLICK_TYPE_DOUBLE, CLICK_TYPE_HOLD]
CONF_IGNORED_CLICK_TYPES = "ignored_click_types"
EVENT_NAME = "flic_click"
EVENT_DATA_NAME = "button_name"
EVENT_DATA_ADDRESS = "button_address"
EVENT_DATA_TYPE = "click_type"
EVENT_DATA_QUEUED_TIME = "queued_time"
# Validation of the user's configuration
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_HOST, default='localhost'): cv.string,
vol.Optional(CONF_PORT, default=5551): cv.port,
vol.Optional(CONF_DISCOVERY, default=True): cv.boolean,
vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
vol.Optional(CONF_IGNORED_CLICK_TYPES): vol.All(cv.ensure_list,
[vol.In(CLICK_TYPES)])
})
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
"""Setup the flic platform."""
import pyflic
# Initialize flic client responsible for
# connecting to buttons and retrieving events
host = config.get(CONF_HOST)
port = config.get(CONF_PORT)
discovery = config.get(CONF_DISCOVERY)
try:
client = pyflic.FlicClient(host, port)
except ConnectionRefusedError:
_LOGGER.error("Failed to connect to flic server.")
return
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))
client.on_new_verified_button = new_button_callback
if discovery:
start_scanning(hass, config, async_add_entities, client)
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP,
lambda event: client.close())
hass.loop.run_in_executor(None, client.handle_events)
# 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)
def start_scanning(hass, config, async_add_entities, client):
"""Start a new flic client for scanning & connceting to new buttons."""
import pyflic
scan_wizard = pyflic.ScanWizard()
def scan_completed_callback(scan_wizard, result, address, name):
"""Restart scan wizard to constantly check for new buttons."""
if result == pyflic.ScanWizardResult.WizardSuccess:
_LOGGER.info("Found new button (%s)", address)
elif result != pyflic.ScanWizardResult.WizardFailedTimeout:
_LOGGER.warning("Failed to connect to button (%s). Reason: %s",
address, result)
# Restart scan wizard
start_scanning(hass, config, async_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):
"""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
class FlicButton(BinarySensorDevice):
"""Representation of a flic button."""
def __init__(self, hass, client, address, timeout, ignored_click_types):
"""Initialize the flic button."""
import pyflic
self._hass = hass
self._address = address
self._timeout = timeout
self._is_down = False
self._ignored_click_types = ignored_click_types or []
self._hass_click_types = {
pyflic.ClickType.ButtonClick: CLICK_TYPE_SINGLE,
pyflic.ClickType.ButtonSingleClick: CLICK_TYPE_SINGLE,
pyflic.ClickType.ButtonDoubleClick: CLICK_TYPE_DOUBLE,
pyflic.ClickType.ButtonHold: CLICK_TYPE_HOLD,
}
self._channel = self._create_channel()
client.add_connection_channel(self._channel)
def _create_channel(self):
"""Create a new connection channel to the button."""
import pyflic
channel = pyflic.ButtonConnectionChannel(self._address)
channel.on_button_up_or_down = self._on_up_down
# If all types of clicks should be ignored, skip registering callbacks
if set(self._ignored_click_types) == set(CLICK_TYPES):
return channel
if CLICK_TYPE_DOUBLE in self._ignored_click_types:
# Listen to all but double click type events
channel.on_button_click_or_hold = self._on_click
elif CLICK_TYPE_HOLD in self._ignored_click_types:
# Listen to all but hold click type events
channel.on_button_single_or_double_click = self._on_click
else:
# Listen to all click type events
channel.on_button_single_or_double_click_or_hold = self._on_click
return channel
@property
def name(self):
"""Return the name of the device."""
return "flic_%s" % self.address.replace(":", "")
@property
def address(self):
"""Return the bluetooth address of the device."""
return self._address
@property
def is_on(self):
"""Return true if sensor is on."""
return self._is_down
@property
def should_poll(self):
"""No polling needed."""
return False
@property
def state_attributes(self):
"""Return device specific state attributes."""
attr = super(FlicButton, self).state_attributes
attr["address"] = self.address
return attr
def _queued_event_check(self, click_type, time_diff):
"""Generate a log message and returns true if timeout exceeded."""
time_string = "{:d} {}".format(
time_diff, "second" if time_diff == 1 else "seconds")
if time_diff > self._timeout:
_LOGGER.warning(
"Queued %s dropped for %s. Time in queue was %s.",
click_type, self.address, time_string)
return True
else:
_LOGGER.info(
"Queued %s allowed for %s. Time in queue was %s.",
click_type, self.address, time_string)
return False
def _on_up_down(self, channel, click_type, was_queued, time_diff):
"""Update device state, if event was not queued."""
import pyflic
if was_queued and self._queued_event_check(click_type, time_diff):
return
self._is_down = click_type == pyflic.ClickType.ButtonDown
self.schedule_update_ha_state()
def _on_click(self, channel, click_type, was_queued, time_diff):
"""Fire click event, if event was not queued."""
# Return if click event was queued beyond allowed timeout
if was_queued and self._queued_event_check(click_type, time_diff):
return
# Return if click event is in ignored click types
hass_click_type = self._hass_click_types[click_type]
if hass_click_type in self._ignored_click_types:
return
self._hass.bus.fire(EVENT_NAME, {
EVENT_DATA_NAME: self.name,
EVENT_DATA_ADDRESS: self.address,
EVENT_DATA_QUEUED_TIME: time_diff,
EVENT_DATA_TYPE: hass_click_type
})
def _connection_status_changed(self, channel,
connection_status, disconnect_reason):
"""Remove device, if button disconnects."""
import pyflic
if connection_status == pyflic.ConnectionStatus.Disconnected:
_LOGGER.info("Button (%s) disconnected. Reason: %s",
self.address, disconnect_reason)
self.remove()
@@ -0,0 +1,262 @@
"""
Support for Hikvision event stream events represented as binary sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.hikvision/
"""
import logging
from datetime import timedelta
import voluptuous as vol
from homeassistant.helpers.event import track_point_in_utc_time
from homeassistant.util.dt import utcnow
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA)
import homeassistant.helpers.config_validation as cv
from homeassistant.const import (
CONF_HOST, CONF_PORT, CONF_NAME, CONF_USERNAME, CONF_PASSWORD,
CONF_SSL, EVENT_HOMEASSISTANT_STOP, ATTR_LAST_TRIP_TIME, CONF_CUSTOMIZE)
REQUIREMENTS = ['pyhik==0.0.6', 'pydispatcher==2.0.5']
_LOGGER = logging.getLogger(__name__)
CONF_IGNORED = 'ignored'
CONF_DELAY = 'delay'
DEFAULT_PORT = 80
DEFAULT_IGNORED = False
DEFAULT_DELAY = 0
ATTR_DELAY = 'delay'
SENSOR_CLASS_MAP = {
'Motion': 'motion',
'Line Crossing': 'motion',
'IO Trigger': None,
'Field Detection': 'motion',
'Video Loss': None,
'Tamper Detection': 'motion',
'Shelter Alarm': None,
'Disk Full': None,
'Disk Error': None,
'Net Interface Broken': 'connectivity',
'IP Conflict': 'connectivity',
'Illegal Access': None,
'Video Mismatch': None,
'Bad Video': None,
'PIR Alarm': 'motion',
'Face Detection': 'motion',
}
CUSTOMIZE_SCHEMA = vol.Schema({
vol.Optional(CONF_IGNORED, default=DEFAULT_IGNORED): cv.boolean,
vol.Optional(CONF_DELAY, default=DEFAULT_DELAY): cv.positive_int
})
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=None): cv.string,
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_SSL, default=False): cv.boolean,
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_CUSTOMIZE, default={}):
vol.Schema({cv.string: CUSTOMIZE_SCHEMA}),
})
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Setup Hikvision binary sensor devices."""
name = config.get(CONF_NAME)
host = config.get(CONF_HOST)
port = config.get(CONF_PORT)
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
customize = config.get(CONF_CUSTOMIZE)
if config.get(CONF_SSL):
protocol = "https"
else:
protocol = "http"
url = '{}://{}'.format(protocol, host)
data = HikvisionData(hass, url, port, name, username, password)
if data.sensors is None:
_LOGGER.error('Hikvision event stream has no data, unable to setup.')
return False
entities = []
for sensor in data.sensors:
# Build sensor name, then parse customize config.
sensor_name = sensor.replace(' ', '_')
custom = customize.get(sensor_name.lower(), {})
ignore = custom.get(CONF_IGNORED)
delay = custom.get(CONF_DELAY)
_LOGGER.debug('Entity: %s - %s, Options - Ignore: %s, Delay: %s',
data.name, sensor_name, ignore, delay)
if not ignore:
entities.append(HikvisionBinarySensor(hass, sensor, data, delay))
add_entities(entities)
class HikvisionData(object):
"""Hikvision camera event stream object."""
def __init__(self, hass, url, port, name, username, password):
"""Initialize the data oject."""
from pyhik.hikvision import HikCamera
self._url = url
self._port = port
self._name = name
self._username = username
self._password = password
# Establish camera
self._cam = HikCamera(self._url, self._port,
self._username, self._password)
if self._name is None:
self._name = self._cam.get_name
# Start event stream
self._cam.start_stream()
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, self.stop_hik)
def stop_hik(self, event):
"""Shutdown Hikvision subscriptions and subscription thread on exit."""
self._cam.disconnect()
@property
def sensors(self):
"""Return list of available sensors and their states."""
return self._cam.current_event_states
@property
def cam_id(self):
"""Return camera id."""
return self._cam.get_id
@property
def name(self):
"""Return camera name."""
return self._name
class HikvisionBinarySensor(BinarySensorDevice):
"""Representation of a Hikvision binary sensor."""
def __init__(self, hass, sensor, cam, delay):
"""Initialize the binary_sensor."""
from pydispatch import dispatcher
self._hass = hass
self._cam = cam
self._name = self._cam.name + ' ' + sensor
self._id = self._cam.cam_id + '.' + sensor
self._sensor = sensor
if delay is None:
self._delay = 0
else:
self._delay = delay
self._timer = None
# Form signal for dispatcher
signal = 'ValueChanged.{}'.format(self._cam.cam_id)
dispatcher.connect(self._update_callback,
signal=signal,
sender=self._sensor)
def _sensor_state(self):
"""Extract sensor state."""
return self._cam.sensors[self._sensor][0]
def _sensor_last_update(self):
"""Extract sensor last update time."""
return self._cam.sensors[self._sensor][3]
@property
def name(self):
"""Return the name of the Hikvision sensor."""
return self._name
@property
def unique_id(self):
"""Return an unique ID."""
return '{}.{}'.format(self.__class__, self._id)
@property
def is_on(self):
"""Return true if sensor is on."""
return self._sensor_state()
@property
def sensor_class(self):
"""Return the class of this sensor, from SENSOR_CLASSES."""
try:
return SENSOR_CLASS_MAP[self._sensor]
except KeyError:
# Sensor must be unknown to us, add as generic
return None
@property
def should_poll(self):
"""No polling needed."""
return False
@property
def device_state_attributes(self):
"""Return the state attributes."""
attr = {}
attr[ATTR_LAST_TRIP_TIME] = self._sensor_last_update()
if self._delay != 0:
attr[ATTR_DELAY] = self._delay
return attr
def _update_callback(self, signal, sender):
"""Update the sensor's state, if needed."""
_LOGGER.debug('Dispatcher callback, signal: %s, sender: %s',
signal, sender)
if sender is not self._sensor:
return
if self._delay > 0 and not self.is_on:
# Set timer to wait until updating the state
def _delay_update(now):
"""Timer callback for sensor update."""
_LOGGER.debug('%s Called delayed (%ssec) update.',
self._name, self._delay)
self.schedule_update_ha_state()
self._timer = None
if self._timer is not None:
self._timer()
self._timer = None
self._timer = track_point_in_utc_time(
self._hass, _delay_update,
utcnow() + timedelta(seconds=self._delay))
elif self._delay > 0 and self.is_on:
# For delayed sensors kill any callbacks on true events and update
if self._timer is not None:
self._timer()
self._timer = None
self.schedule_update_ha_state()
else:
self.schedule_update_ha_state()
@@ -7,7 +7,8 @@ https://home-assistant.io/components/binary_sensor.homematic/
import logging
from homeassistant.const import STATE_UNKNOWN
from homeassistant.components.binary_sensor import BinarySensorDevice
import homeassistant.components.homematic as homematic
from homeassistant.components.homematic import HMDevice
from homeassistant.loader import get_component
_LOGGER = logging.getLogger(__name__)
@@ -32,14 +33,16 @@ def setup_platform(hass, config, add_callback_devices, discovery_info=None):
if discovery_info is None:
return
homematic = get_component("homematic")
return homematic.setup_hmdevice_discovery_helper(
hass,
HMBinarySensor,
discovery_info,
add_callback_devices
)
class HMBinarySensor(homematic.HMDevice, BinarySensorDevice):
class HMBinarySensor(HMDevice, BinarySensorDevice):
"""Representation of a binary Homematic device."""
@property
@@ -8,6 +8,7 @@ import logging
import voluptuous as vol
from homeassistant.core import callback
import homeassistant.components.mqtt as mqtt
from homeassistant.components.binary_sensor import (
BinarySensorDevice, SENSOR_CLASSES)
@@ -51,7 +52,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
)])
# pylint: disable=too-many-arguments, too-many-instance-attributes
class MqttBinarySensor(BinarySensorDevice):
"""Representation a binary sensor that is updated by MQTT."""
@@ -67,17 +67,18 @@ class MqttBinarySensor(BinarySensorDevice):
self._payload_off = payload_off
self._qos = qos
@callback
def message_received(topic, payload, qos):
"""A new MQTT message has been received."""
if value_template is not None:
payload = value_template.render_with_possible_json_value(
payload = value_template.async_render_with_possible_json_value(
payload)
if payload == self._payload_on:
self._state = True
self.update_ha_state()
hass.async_add_job(self.async_update_ha_state())
elif payload == self._payload_off:
self._state = False
self.update_ha_state()
hass.async_add_job(self.async_update_ha_state())
mqtt.subscribe(hass, self._state_topic, message_received, self._qos)
@@ -22,7 +22,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
if discovery_info is None:
return
for gateway in mysensors.GATEWAYS.values():
gateways = hass.data.get(mysensors.MYSENSORS_GATEWAYS)
if not gateways:
return
for gateway in gateways:
# Define the S_TYPES and V_TYPES that the platform should handle as
# states. Map them in a dict of lists.
pres = gateway.const.Presentation
+98 -16
View File
@@ -4,40 +4,100 @@ Support for Nest Thermostat Binary Sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.nest/
"""
from itertools import chain
import logging
import voluptuous as vol
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA)
from homeassistant.components.sensor.nest import NestSensor
from homeassistant.const import (CONF_SCAN_INTERVAL, CONF_MONITORED_CONDITIONS)
import homeassistant.components.nest as nest
from homeassistant.components.nest import (
DATA_NEST, is_thermostat, is_camera)
import homeassistant.helpers.config_validation as cv
DEPENDENCIES = ['nest']
BINARY_TYPES = ['fan',
'hvac_ac_state',
'hvac_aux_heater_state',
'hvac_heater_state',
'hvac_heat_x2_state',
'hvac_heat_x3_state',
'hvac_alt_heat_state',
'hvac_alt_heat_x2_state',
'hvac_emer_heat_state',
'online']
BINARY_TYPES = ['online']
CLIMATE_BINARY_TYPES = ['fan',
'is_using_emergency_heat',
'is_locked',
'has_leaf']
CAMERA_BINARY_TYPES = [
'motion_detected',
'sound_detected',
'person_detected']
_BINARY_TYPES_DEPRECATED = [
'hvac_ac_state',
'hvac_aux_heater_state',
'hvac_heater_state',
'hvac_heat_x2_state',
'hvac_heat_x3_state',
'hvac_alt_heat_state',
'hvac_alt_heat_x2_state',
'hvac_emer_heat_state']
_VALID_BINARY_SENSOR_TYPES = BINARY_TYPES + CLIMATE_BINARY_TYPES \
+ CAMERA_BINARY_TYPES
_VALID_BINARY_SENSOR_TYPES_WITH_DEPRECATED = _VALID_BINARY_SENSOR_TYPES \
+ _BINARY_TYPES_DEPRECATED
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_SCAN_INTERVAL):
vol.All(vol.Coerce(int), vol.Range(min=1)),
vol.Required(CONF_MONITORED_CONDITIONS):
vol.All(cv.ensure_list, [vol.In(BINARY_TYPES)]),
vol.All(cv.ensure_list,
[vol.In(_VALID_BINARY_SENSOR_TYPES_WITH_DEPRECATED)])
})
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup Nest binary sensors."""
for structure, device in nest.devices():
add_devices([NestBinarySensor(structure, device, variable)
for variable in config[CONF_MONITORED_CONDITIONS]])
if discovery_info is None:
return
nest = hass.data[DATA_NEST]
conf = config.get(CONF_MONITORED_CONDITIONS, _VALID_BINARY_SENSOR_TYPES)
for variable in conf:
if variable in _BINARY_TYPES_DEPRECATED:
wstr = (variable + " is no a longer supported "
"monitored_conditions. See "
"https://home-assistant.io/components/binary_sensor.nest/ "
"for valid options, or remove monitored_conditions "
"entirely to get a reasonable default")
_LOGGER.error(wstr)
sensors = []
device_chain = chain(nest.devices(),
nest.protect_devices(),
nest.camera_devices())
for structure, device in device_chain:
sensors += [NestBinarySensor(structure, device, variable)
for variable in conf
if variable in BINARY_TYPES]
sensors += [NestBinarySensor(structure, device, variable)
for variable in conf
if variable in CLIMATE_BINARY_TYPES
and is_thermostat(device)]
if is_camera(device):
sensors += [NestBinarySensor(structure, device, variable)
for variable in conf
if variable in CAMERA_BINARY_TYPES]
for activity_zone in device.activity_zones:
sensors += [NestActivityZoneSensor(structure,
device,
activity_zone)]
add_devices(sensors, True)
class NestBinarySensor(NestSensor, BinarySensorDevice):
@@ -46,4 +106,26 @@ class NestBinarySensor(NestSensor, BinarySensorDevice):
@property
def is_on(self):
"""True if the binary sensor is on."""
return bool(getattr(self.device, self.variable))
return self._state
def update(self):
"""Retrieve latest state."""
self._state = bool(getattr(self.device, self.variable))
class NestActivityZoneSensor(NestBinarySensor):
"""Represents a Nest binary sensor for activity in a zone."""
def __init__(self, structure, device, zone):
"""Initialize the sensor."""
super(NestActivityZoneSensor, self).__init__(structure, device, None)
self.zone = zone
@property
def name(self):
"""Return the name of the nest, if any."""
return "{} {} activity".format(self._name, self.zone.name)
def update(self):
"""Retrieve latest state."""
self._state = self.device.has_ongoing_motion_in_zone(self.zone.zone_id)
@@ -13,7 +13,7 @@ from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA)
from homeassistant.components.netatmo import WelcomeData
from homeassistant.loader import get_component
from homeassistant.const import CONF_MONITORED_CONDITIONS
from homeassistant.const import CONF_MONITORED_CONDITIONS, CONF_TIMEOUT
from homeassistant.helpers import config_validation as cv
DEPENDENCIES = ["netatmo"]
@@ -23,9 +23,11 @@ _LOGGER = logging.getLogger(__name__)
# These are the available sensors mapped to binary_sensor class
SENSOR_TYPES = {
"Someone known": "motion",
"Someone unknown": "motion",
"Motion": "motion",
"Someone known": 'occupancy',
"Someone unknown": 'motion',
"Motion": 'motion',
"Tag Vibration": 'vibration',
"Tag Open": 'opening',
}
CONF_HOME = 'home'
@@ -33,6 +35,7 @@ CONF_CAMERAS = 'cameras'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_HOME): cv.string,
vol.Optional(CONF_TIMEOUT): cv.positive_int,
vol.Optional(CONF_CAMERAS, default=[]):
vol.All(cv.ensure_list, [cv.string]),
vol.Optional(CONF_MONITORED_CONDITIONS, default=SENSOR_TYPES.keys()):
@@ -45,6 +48,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup access to Netatmo binary sensor."""
netatmo = get_component('netatmo')
home = config.get(CONF_HOME, None)
timeout = config.get(CONF_TIMEOUT, 15)
module_name = None
import lnetatmo
try:
@@ -62,22 +68,35 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
camera_name not in config[CONF_CAMERAS]:
continue
for variable in sensors:
add_devices([WelcomeBinarySensor(data, camera_name, home,
variable)])
if variable in ('Tag Vibration', 'Tag Open'):
continue
add_devices([WelcomeBinarySensor(data, camera_name, module_name,
home, timeout, variable)])
for module_name in data.get_module_names(camera_name):
for variable in sensors:
if variable in ('Tag Vibration', 'Tag Open'):
add_devices([WelcomeBinarySensor(data, camera_name,
module_name, home,
timeout, variable)])
class WelcomeBinarySensor(BinarySensorDevice):
"""Represent a single binary sensor in a Netatmo Welcome device."""
def __init__(self, data, camera_name, home, sensor):
def __init__(self, data, camera_name, module_name, home, timeout, sensor):
"""Setup for access to the Netatmo camera events."""
self._data = data
self._camera_name = camera_name
self._module_name = module_name
self._home = home
self._timeout = timeout
if home:
self._name = home + ' / ' + camera_name
else:
self._name = camera_name
if module_name:
self._name += ' / ' + module_name
self._sensor_name = sensor
self._name += ' ' + sensor
camera_id = data.welcomedata.cameraByName(camera=camera_name,
@@ -109,19 +128,33 @@ class WelcomeBinarySensor(BinarySensorDevice):
def update(self):
"""Request an update from the Netatmo API."""
self._data.update()
self._data.welcomedata.updateEvent(home=self._data.home)
self._data.update_event()
if self._sensor_name == "Someone known":
self._state =\
self._data.welcomedata.someoneKnownSeen(self._home,
self._camera_name)
self._camera_name,
self._timeout*60)
elif self._sensor_name == "Someone unknown":
self._state =\
self._data.welcomedata.someoneUnknownSeen(self._home,
self._camera_name)
self._camera_name,
self._timeout*60)
elif self._sensor_name == "Motion":
self._state =\
self._data.welcomedata.motionDetected(self._home,
self._camera_name)
self._camera_name,
self._timeout*60)
elif self._sensor_name == "Tag Vibration":
self._state =\
self._data.welcomedata.moduleMotionDetected(self._home,
self._module_name,
self._camera_name,
self._timeout*60)
elif self._sensor_name == "Tag Open":
self._state =\
self._data.welcomedata.moduleOpened(self._home,
self._module_name,
self._camera_name)
else:
return None
@@ -123,7 +123,7 @@ class NX584Watcher(threading.Thread):
if not zone_sensor:
return
zone_sensor._zone['state'] = event['zone_state']
zone_sensor.update_ha_state()
zone_sensor.schedule_update_ha_state()
def _process_events(self, events):
for event in events:
@@ -58,11 +58,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
add_devices(devices)
# pylint: disable=too-many-instance-attributes
class OctoPrintBinarySensor(BinarySensorDevice):
"""Representation an OctoPrint binary sensor."""
# pylint: disable=too-many-arguments
def __init__(self, api, condition, sensor_type, sensor_name, unit,
endpoint, group, tool=None):
"""Initialize a new OctoPrint sensor."""
@@ -41,7 +41,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
# pylint: disable=unused-variable, too-many-locals
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the REST binary sensor."""
name = config.get(CONF_NAME)
@@ -76,7 +75,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
hass, rest, name, sensor_class, value_template)])
# pylint: disable=too-many-arguments
class RestBinarySensor(BinarySensorDevice):
"""Representation of a REST binary sensor."""
@@ -54,7 +54,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
add_devices(binary_sensors)
# pylint: disable=too-many-arguments, too-many-instance-attributes
class RPiGPIOBinarySensor(BinarySensorDevice):
"""Represent a binary sensor that uses Raspberry Pi GPIO."""
@@ -73,7 +72,7 @@ class RPiGPIOBinarySensor(BinarySensorDevice):
def read_gpio(port):
"""Read state from GPIO."""
self._state = rpi_gpio.read_input(self._port)
self.update_ha_state()
self.schedule_update_ha_state()
rpi_gpio.edge_detect(self._port, read_gpio, self._bouncetime)
@@ -25,7 +25,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
add_devices(dev)
# pylint: disable=too-many-instance-attributes
class IsInBedBinarySensor(sleepiq.SleepIQSensor, BinarySensorDevice):
"""Implementation of a SleepIQ presence sensor."""
@@ -63,14 +63,13 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
_LOGGER.error('No sensors added')
return False
hass.loop.create_task(async_add_devices(sensors))
yield from async_add_devices(sensors, True)
return True
class BinarySensorTemplate(BinarySensorDevice):
"""A virtual binary sensor that triggers from another sensor."""
# pylint: disable=too-many-arguments
def __init__(self, hass, device, friendly_name, sensor_class,
value_template, entity_ids):
"""Initialize the Template binary sensor."""
@@ -82,12 +81,10 @@ class BinarySensorTemplate(BinarySensorDevice):
self._template = value_template
self._state = None
self._async_render()
@callback
def template_bsensor_state_listener(entity, old_state, new_state):
"""Called when the target device changes state."""
hass.loop.create_task(self.async_update_ha_state(True))
hass.async_add_job(self.async_update_ha_state, True)
async_track_state_change(
hass, entity_ids, template_bsensor_state_listener)
@@ -115,10 +112,6 @@ class BinarySensorTemplate(BinarySensorDevice):
@asyncio.coroutine
def async_update(self):
"""Update the state from the template."""
self._async_render()
def _async_render(self):
"""Render the state from the template."""
try:
self._state = self._template.async_render().lower() == 'true'
except TemplateError as ex:
@@ -0,0 +1,128 @@
"""
Support for monitoring if a sensor value is below/above a threshold.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.threshold/
"""
import asyncio
import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA, SENSOR_CLASSES_SCHEMA)
from homeassistant.const import (
CONF_NAME, CONF_ENTITY_ID, CONF_TYPE, STATE_UNKNOWN, CONF_SENSOR_CLASS,
ATTR_ENTITY_ID)
from homeassistant.core import callback
from homeassistant.helpers.event import async_track_state_change
_LOGGER = logging.getLogger(__name__)
ATTR_SENSOR_VALUE = 'sensor_value'
ATTR_THRESHOLD = 'threshold'
ATTR_TYPE = 'type'
CONF_LOWER = 'lower'
CONF_THRESHOLD = 'threshold'
CONF_UPPER = 'upper'
DEFAULT_NAME = 'Threshold'
SENSOR_TYPES = [CONF_LOWER, CONF_UPPER]
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_ENTITY_ID): cv.entity_id,
vol.Required(CONF_THRESHOLD): vol.Coerce(float),
vol.Required(CONF_TYPE): vol.In(SENSOR_TYPES),
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_SENSOR_CLASS, default=None): SENSOR_CLASSES_SCHEMA,
})
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up the Threshold sensor."""
entity_id = config.get(CONF_ENTITY_ID)
name = config.get(CONF_NAME)
threshold = config.get(CONF_THRESHOLD)
limit_type = config.get(CONF_TYPE)
sensor_class = config.get(CONF_SENSOR_CLASS)
yield from async_add_devices(
[ThresholdSensor(hass, entity_id, name, threshold, limit_type,
sensor_class)], True)
return True
class ThresholdSensor(BinarySensorDevice):
"""Representation of a Threshold sensor."""
def __init__(self, hass, entity_id, name, threshold, limit_type,
sensor_class):
"""Initialize the Threshold sensor."""
self._hass = hass
self._entity_id = entity_id
self.is_upper = limit_type == 'upper'
self._name = name
self._threshold = threshold
self._sensor_class = sensor_class
self._deviation = False
self.sensor_value = 0
@callback
# pylint: disable=invalid-name
def async_threshold_sensor_state_listener(
entity, old_state, new_state):
"""Called when the sensor changes state."""
if new_state.state == STATE_UNKNOWN:
return
try:
self.sensor_value = float(new_state.state)
except ValueError:
_LOGGER.error("State is not numerical")
hass.async_add_job(self.async_update_ha_state, True)
async_track_state_change(
hass, entity_id, async_threshold_sensor_state_listener)
@property
def name(self):
"""Return the name of the sensor."""
return self._name
@property
def is_on(self):
"""Return true if sensor is on."""
return self._deviation
@property
def should_poll(self):
"""No polling needed."""
return False
@property
def sensor_class(self):
"""Return the sensor class of the sensor."""
return self._sensor_class
@property
def state_attributes(self):
"""Return the state attributes of the sensor."""
return {
ATTR_ENTITY_ID: self._entity_id,
ATTR_SENSOR_VALUE: self.sensor_value,
ATTR_THRESHOLD: self._threshold,
ATTR_TYPE: CONF_UPPER if self.is_upper else CONF_LOWER,
}
@asyncio.coroutine
def async_update(self):
"""Get the latest data and updates the states."""
if self.is_upper:
self._deviation = bool(self.sensor_value > self._threshold)
else:
self._deviation = bool(self.sensor_value < self._threshold)
@@ -4,8 +4,11 @@ A sensor that monitors trands in other components.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.trend/
"""
import asyncio
import logging
import voluptuous as vol
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
from homeassistant.components.binary_sensor import (
@@ -72,7 +75,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class SensorTrend(BinarySensorDevice):
"""Representation of a trend Sensor."""
# pylint: disable=too-many-arguments, too-many-instance-attributes
def __init__(self, hass, device_id, friendly_name,
target_entity, attribute, sensor_class, invert):
"""Initialize the sensor."""
@@ -88,13 +90,12 @@ class SensorTrend(BinarySensorDevice):
self.from_state = None
self.to_state = None
self.update()
@callback
def trend_sensor_state_listener(entity, old_state, new_state):
"""Called when the target device changes state."""
self.from_state = old_state
self.to_state = new_state
self.update_ha_state(True)
hass.async_add_job(self.async_update_ha_state(True))
track_state_change(hass, target_entity,
trend_sensor_state_listener)
@@ -119,7 +120,8 @@ class SensorTrend(BinarySensorDevice):
"""No polling needed."""
return False
def update(self):
@asyncio.coroutine
def async_update(self):
"""Get the latest data and update the states."""
if self.from_state is None or self.to_state is None:
return
@@ -16,9 +16,9 @@ DEPENDENCIES = ['vera']
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Perform the setup for Vera controller devices."""
add_devices_callback(
add_devices(
VeraBinarySensor(device, VERA_CONTROLLER)
for device in VERA_DEVICES['binary_sensor'])
@@ -45,10 +45,10 @@ class WemoBinarySensor(BinarySensorDevice):
_LOGGER.info(
'Subscription update for %s',
_device)
self.update()
if not hasattr(self, 'hass'):
self.update()
return
self.update_ha_state(True)
self.schedule_update_ha_state()
@property
def should_poll(self):
+29 -14
View File
@@ -4,7 +4,6 @@ Support for Wink binary sensors.
For more details about this platform, please refer to the documentation at
at https://home-assistant.io/components/binary_sensor.wink/
"""
import json
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.sensor.wink import WinkDevice
@@ -33,33 +32,28 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
for sensor in pywink.get_sensors():
if sensor.capability() in SENSOR_TYPES:
add_devices([WinkBinarySensorDevice(sensor)])
add_devices([WinkBinarySensorDevice(sensor, hass)])
for key in pywink.get_keys():
add_devices([WinkBinarySensorDevice(key)])
add_devices([WinkBinarySensorDevice(key, hass)])
for sensor in pywink.get_smoke_and_co_detectors():
add_devices([WinkBinarySensorDevice(sensor)])
add_devices([WinkBinarySensorDevice(sensor, hass)])
for hub in pywink.get_hubs():
add_devices([WinkHub(hub, hass)])
class WinkBinarySensorDevice(WinkDevice, BinarySensorDevice, Entity):
"""Representation of a Wink binary sensor."""
def __init__(self, wink):
def __init__(self, wink, hass):
"""Initialize the Wink binary sensor."""
super().__init__(wink)
super().__init__(wink, hass)
wink = get_component('wink')
self._unit_of_measurement = self.wink.UNIT
self.capability = self.wink.capability()
def _pubnub_update(self, message, channel):
if 'data' in message:
json_data = json.dumps(message.get('data'))
else:
json_data = message
self.wink.pubnub_update(json.loads(json_data))
self.update_ha_state()
@property
def is_on(self):
"""Return true if the binary sensor is on."""
@@ -88,3 +82,24 @@ class WinkBinarySensorDevice(WinkDevice, BinarySensorDevice, Entity):
def sensor_class(self):
"""Return the class of this sensor, from SENSOR_CLASSES."""
return SENSOR_TYPES.get(self.capability)
class WinkHub(WinkDevice, BinarySensorDevice, Entity):
"""Representation of a Wink Hub."""
def __init(self, wink, hass):
"""Initialize the hub sensor."""
WinkDevice.__init__(self, wink, hass)
@property
def device_state_attributes(self):
"""Return the state attributes."""
return {
'update needed': self.wink.update_needed(),
'firmware version': self.wink.firmware_version()
}
@property
def is_on(self):
"""Return true if the binary sensor is on."""
return self.wink.state()
@@ -96,7 +96,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:
self.update_ha_state()
self.schedule_update_ha_state()
class ZWaveTriggerSensor(ZWaveBinarySensor, Entity):
@@ -112,19 +112,19 @@ class ZWaveTriggerSensor(ZWaveBinarySensor, Entity):
# If it's active make sure that we set the timeout tracker
if sensor_value.data:
track_point_in_time(
self._hass, self.update_ha_state,
self._hass, self.async_update_ha_state,
self.invalidate_after)
def value_changed(self, value):
"""Called when a value has changed on the network."""
if self._value.value_id == value.value_id:
self.update_ha_state()
self.schedule_update_ha_state()
if value.data:
# only allow this value to be true for re_arm secs
self.invalidate_after = dt_util.utcnow() + datetime.timedelta(
seconds=self.re_arm_sec)
track_point_in_time(
self._hass, self.update_ha_state,
self._hass, self.async_update_ha_state,
self.invalidate_after)
@property
+1 -1
View File
@@ -33,7 +33,7 @@ CONFIG_SCHEMA = vol.Schema({
}, extra=vol.ALLOW_EXTRA)
# pylint: disable=unused-argument,too-few-public-methods
# pylint: disable=unused-argument
def setup(hass, config):
"""Setup BloomSky component."""
api_key = config[DOMAIN][CONF_API_KEY]
@@ -0,0 +1,183 @@
"""
Support for Google Calendar event device sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/calendar/
"""
import logging
import re
from homeassistant.components.google import (CONF_OFFSET,
CONF_DEVICE_ID,
CONF_NAME)
from homeassistant.const import STATE_OFF, STATE_ON
from homeassistant.helpers.config_validation import time_period_str
from homeassistant.helpers.entity import Entity, generate_entity_id
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.template import DATE_STR_FORMAT
from homeassistant.util import dt
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'calendar'
ENTITY_ID_FORMAT = DOMAIN + '.{}'
def setup(hass, config):
"""Track states and offer events for calendars."""
component = EntityComponent(
logging.getLogger(__name__), DOMAIN, hass, 60, DOMAIN)
component.setup(config)
return True
DEFAULT_CONF_TRACK_NEW = True
DEFAULT_CONF_OFFSET = '!!'
# pylint: disable=too-many-instance-attributes
class CalendarEventDevice(Entity):
"""A calendar event device."""
# Classes overloading this must set data to an object
# with an update() method
data = None
# pylint: disable=too-many-arguments
def __init__(self, hass, data):
"""Create the Calendar Event Device."""
self._name = data.get(CONF_NAME)
self.dev_id = data.get(CONF_DEVICE_ID)
self._offset = data.get(CONF_OFFSET, DEFAULT_CONF_OFFSET)
self.entity_id = generate_entity_id(ENTITY_ID_FORMAT,
self.dev_id,
hass=hass)
self._cal_data = {
'all_day': False,
'offset_time': dt.dt.timedelta(),
'message': '',
'start': None,
'end': None,
'location': '',
'description': '',
}
self.update()
def offset_reached(self):
"""Have we reached the offset time specified in the event title."""
if self._cal_data['start'] is None or \
self._cal_data['offset_time'] == dt.dt.timedelta():
return False
return self._cal_data['start'] + self._cal_data['offset_time'] <= \
dt.now(self._cal_data['start'].tzinfo)
@property
def name(self):
"""Return the name of the entity."""
return self._name
@property
def device_state_attributes(self):
"""State Attributes for HA."""
start = self._cal_data.get('start', None)
end = self._cal_data.get('end', None)
start = start.strftime(DATE_STR_FORMAT) if start is not None else None
end = end.strftime(DATE_STR_FORMAT) if end is not None else None
return {
'message': self._cal_data.get('message', ''),
'all_day': self._cal_data.get('all_day', False),
'offset_reached': self.offset_reached(),
'start_time': start,
'end_time': end,
'location': self._cal_data.get('location', None),
'description': self._cal_data.get('description', None),
}
@property
def state(self):
"""Return the state of the calendar event."""
start = self._cal_data.get('start', None)
end = self._cal_data.get('end', None)
if start is None or end is None:
return STATE_OFF
now = dt.now()
if start <= now and end > now:
return STATE_ON
if now >= end:
self.cleanup()
return STATE_OFF
def cleanup(self):
"""Cleanup any start/end listeners that were setup."""
self._cal_data = {
'all_day': False,
'offset_time': 0,
'message': '',
'start': None,
'end': None,
'location': None,
'description': None
}
def update(self):
"""Search for the next event."""
if not self.data or not self.data.update():
# update cached, don't do anything
return
if not self.data.event:
# we have no event to work on, make sure we're clean
self.cleanup()
return
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()))
else:
return dt.parse_datetime(date['dateTime'])
start = _get_date(self.data.event['start'])
end = _get_date(self.data.event['end'])
summary = self.data.event['summary']
# check if we have an offset tag in the message
# time is HH:MM or MM
reg = '{}([+-]?[0-9]{{0,2}}(:[0-9]{{0,2}})?)'.format(self._offset)
search = re.search(reg, summary)
if search and search.group(1):
time = search.group(1)
if ':' not in time:
if time[0] == '+' or time[0] == '-':
time = '{}0:{}'.format(time[0], time[1:])
else:
time = '0:{}'.format(time)
offset_time = time_period_str(time)
summary = (summary[:search.start()] + summary[search.end():]) \
.strip()
else:
offset_time = dt.dt.timedelta() # default it
# cleanup the string so we don't have a bunch of double+ spaces
self._cal_data['message'] = re.sub(' +', '', summary).strip()
self._cal_data['offset_time'] = offset_time
self._cal_data['location'] = self.data.event.get('location', '')
self._cal_data['description'] = self.data.event.get('description', '')
self._cal_data['start'] = start
self._cal_data['end'] = end
self._cal_data['all_day'] = 'date' in self.data.event['start']
+82
View File
@@ -0,0 +1,82 @@
"""
Demo platform that has two fake binary sensors.
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/demo/
"""
import homeassistant.util.dt as dt_util
from homeassistant.components.calendar import CalendarEventDevice
from homeassistant.components.google import CONF_DEVICE_ID, CONF_NAME
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Demo binary sensor platform."""
calendar_data_future = DemoGoogleCalendarDataFuture()
calendar_data_current = DemoGoogleCalendarDataCurrent()
add_devices([
DemoGoogleCalendar(hass, calendar_data_future, {
CONF_NAME: 'Future Event',
CONF_DEVICE_ID: 'future_event',
}),
DemoGoogleCalendar(hass, calendar_data_current, {
CONF_NAME: 'Current Event',
CONF_DEVICE_ID: 'current_event',
}),
])
class DemoGoogleCalendarData(object):
"""Setup base class for data."""
# pylint: disable=no-self-use
def update(self):
"""Return true so entity knows we have new data."""
return True
class DemoGoogleCalendarDataFuture(DemoGoogleCalendarData):
"""Setup future data event."""
def __init__(self):
"""Set the event to a future event."""
one_hour_from_now = dt_util.now() \
+ dt_util.dt.timedelta(minutes=30)
self.event = {
'start': {
'dateTime': one_hour_from_now.isoformat()
},
'end': {
'dateTime': (one_hour_from_now + dt_util.dt.
timedelta(minutes=60)).isoformat()
},
'summary': 'Future Event',
}
class DemoGoogleCalendarDataCurrent(DemoGoogleCalendarData):
"""Create a current event we're in the middle of."""
def __init__(self):
"""Set the event data."""
middle_of_event = dt_util.now() \
- dt_util.dt.timedelta(minutes=30)
self.event = {
'start': {
'dateTime': middle_of_event.isoformat()
},
'end': {
'dateTime': (middle_of_event + dt_util.dt.
timedelta(minutes=60)).isoformat()
},
'summary': 'Current Event',
}
class DemoGoogleCalendar(CalendarEventDevice):
"""A Demo binary sensor."""
def __init__(self, hass, calendar_data, data):
"""The same as a google calendar but without the api calls."""
self.data = calendar_data
super().__init__(hass, data)
@@ -0,0 +1,79 @@
"""
Support for Google Calendar Search binary sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.google_calendar/
"""
# pylint: disable=import-error
import logging
from datetime import timedelta
from homeassistant.components.calendar import CalendarEventDevice
from homeassistant.components.google import (CONF_CAL_ID, CONF_ENTITIES,
CONF_TRACK, TOKEN_FILE,
GoogleCalendarService)
from homeassistant.util import Throttle, dt
DEFAULT_GOOGLE_SEARCH_PARAMS = {
'orderBy': 'startTime',
'maxResults': 1,
'singleEvents': True,
}
# Return cached results if last scan was less then this time ago
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=15)
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, disc_info=None):
"""Setup the calendar platform for event devices."""
if disc_info is None:
return
if not any([data[CONF_TRACK] for data in disc_info[CONF_ENTITIES]]):
return
calendar_service = GoogleCalendarService(hass.config.path(TOKEN_FILE))
add_devices([GoogleCalendarEventDevice(hass, calendar_service,
disc_info[CONF_CAL_ID], data)
for data in disc_info[CONF_ENTITIES] if data[CONF_TRACK]])
# pylint: disable=too-many-instance-attributes
class GoogleCalendarEventDevice(CalendarEventDevice):
"""A calendar event device."""
def __init__(self, hass, calendar_service, calendar, data):
"""Create the Calendar event device."""
self.data = GoogleCalendarData(calendar_service, calendar,
data.get('search', None))
super().__init__(hass, data)
class GoogleCalendarData(object):
"""Class to utilize calendar service object to get next event."""
def __init__(self, calendar_service, calendar_id, search=None):
"""Setup how we are going to search the google calendar."""
self.calendar_service = calendar_service
self.calendar_id = calendar_id
self.search = search
self.event = None
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Get the latest data."""
service = self.calendar_service.get()
params = dict(DEFAULT_GOOGLE_SEARCH_PARAMS)
params['timeMin'] = dt.utcnow().isoformat('T')
params['calendarId'] = self.calendar_id
if self.search:
params['q'] = self.search
events = service.events() # pylint: disable=no-member
result = events.list(**params).execute()
items = result.get('items', [])
self.event = items[0] if len(items) == 1 else None
return True
+78 -48
View File
@@ -5,13 +5,15 @@ Component to interface with cameras.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/camera/
"""
import asyncio
import logging
import time
from aiohttp import web
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
from homeassistant.components.http import HomeAssistantView, KEY_AUTHENTICATED
DOMAIN = 'camera'
DEPENDENCIES = ['http']
@@ -25,17 +27,16 @@ STATE_IDLE = 'idle'
ENTITY_IMAGE_URL = '/api/camera_proxy/{0}?token={1}'
# pylint: disable=too-many-branches
def setup(hass, config):
@asyncio.coroutine
def async_setup(hass, config):
"""Setup the camera component."""
component = EntityComponent(
logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL)
hass.wsgi.register_view(CameraImageView(hass, component.entities))
hass.wsgi.register_view(CameraMjpegStream(hass, component.entities))
component.setup(config)
hass.http.register_view(CameraImageView(component.entities))
hass.http.register_view(CameraMjpegStream(component.entities))
yield from component.async_setup(config)
return True
@@ -80,33 +81,58 @@ class Camera(Entity):
"""Return bytes of camera image."""
raise NotImplementedError()
def mjpeg_stream(self, response):
"""Generate an HTTP MJPEG stream from camera images."""
def stream():
"""Stream images as mjpeg stream."""
try:
last_image = None
while True:
img_bytes = self.camera_image()
@asyncio.coroutine
def async_camera_image(self):
"""Return bytes of camera image.
if img_bytes is not None and img_bytes != last_image:
yield bytes(
'--jpegboundary\r\n'
'Content-Type: image/jpeg\r\n'
'Content-Length: {}\r\n\r\n'.format(
len(img_bytes)), 'utf-8') + img_bytes + b'\r\n'
This method must be run in the event loop.
"""
image = yield from self.hass.loop.run_in_executor(
None, self.camera_image)
return image
last_image = img_bytes
@asyncio.coroutine
def handle_async_mjpeg_stream(self, request):
"""Generate an HTTP MJPEG stream from camera images.
time.sleep(0.5)
except GeneratorExit:
pass
This method must be run in the event loop.
"""
response = web.StreamResponse()
return response(
stream(),
content_type=('multipart/x-mixed-replace; '
'boundary=--jpegboundary')
)
response.content_type = ('multipart/x-mixed-replace; '
'boundary=--jpegboundary')
yield from response.prepare(request)
def write(img_bytes):
"""Write image to stream."""
response.write(bytes(
'--jpegboundary\r\n'
'Content-Type: image/jpeg\r\n'
'Content-Length: {}\r\n\r\n'.format(
len(img_bytes)), 'utf-8') + img_bytes + b'\r\n')
last_image = None
try:
while True:
img_bytes = yield from self.async_camera_image()
if not img_bytes:
break
if img_bytes is not None and img_bytes != last_image:
write(img_bytes)
# Chrome seems to always ignore first picture,
# print it twice.
if last_image is None:
write(img_bytes)
last_image = img_bytes
yield from response.drain()
yield from asyncio.sleep(.5)
finally:
yield from response.write_eof()
@property
def state(self):
@@ -139,27 +165,29 @@ class CameraView(HomeAssistantView):
requires_auth = False
def __init__(self, hass, entities):
def __init__(self, entities):
"""Initialize a basic camera view."""
super().__init__(hass)
self.entities = entities
@asyncio.coroutine
def get(self, request, entity_id):
"""Start a get request."""
camera = self.entities.get(entity_id)
if camera is None:
return self.Response(status=404)
return web.Response(status=404)
authenticated = (request.authenticated or
request.args.get('token') == camera.access_token)
authenticated = (request[KEY_AUTHENTICATED] or
request.GET.get('token') == camera.access_token)
if not authenticated:
return self.Response(status=401)
return web.Response(status=401)
return self.handle(camera)
response = yield from self.handle(request, camera)
return response
def handle(self, camera):
@asyncio.coroutine
def handle(self, request, camera):
"""Hanlde the camera request."""
raise NotImplementedError()
@@ -167,25 +195,27 @@ class CameraView(HomeAssistantView):
class CameraImageView(CameraView):
"""Camera view to serve an image."""
url = "/api/camera_proxy/<entity(domain=camera):entity_id>"
url = "/api/camera_proxy/{entity_id}"
name = "api:camera:image"
def handle(self, camera):
@asyncio.coroutine
def handle(self, request, camera):
"""Serve camera image."""
response = camera.camera_image()
image = yield from camera.async_camera_image()
if response is None:
return self.Response(status=500)
if image is None:
return web.Response(status=500)
return self.Response(response)
return web.Response(body=image)
class CameraMjpegStream(CameraView):
"""Camera View to serve an MJPEG stream."""
url = "/api/camera_proxy_stream/<entity(domain=camera):entity_id>"
url = "/api/camera_proxy_stream/{entity_id}"
name = "api:camera:stream"
def handle(self, camera):
@asyncio.coroutine
def handle(self, request, camera):
"""Serve camera image."""
return camera.mjpeg_stream(self.Response)
yield from camera.handle_async_mjpeg_stream(request)
@@ -0,0 +1,90 @@
"""
This component provides basic support for Amcrest IP cameras.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.amcrest/
"""
import logging
import voluptuous as vol
import homeassistant.loader as loader
from homeassistant.components.camera import (Camera, PLATFORM_SCHEMA)
from homeassistant.const import (
CONF_HOST, CONF_NAME, CONF_USERNAME, CONF_PASSWORD, CONF_PORT)
from homeassistant.helpers import config_validation as cv
REQUIREMENTS = ['amcrest==1.0.0']
_LOGGER = logging.getLogger(__name__)
CONF_RESOLUTION = 'resolution'
DEFAULT_NAME = 'Amcrest Camera'
DEFAULT_PORT = 80
DEFAULT_RESOLUTION = 'high'
NOTIFICATION_ID = 'amcrest_notification'
NOTIFICATION_TITLE = 'Amcrest Camera Setup'
RESOLUTION_LIST = {
'high': 0,
'low': 1,
}
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_RESOLUTION, default=DEFAULT_RESOLUTION):
vol.All(vol.In(RESOLUTION_LIST)),
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up an Amcrest IP Camera."""
from amcrest import AmcrestCamera
data = AmcrestCamera(
config.get(CONF_HOST), config.get(CONF_PORT),
config.get(CONF_USERNAME), config.get(CONF_PASSWORD))
persistent_notification = loader.get_component('persistent_notification')
try:
data.camera.current_time
# pylint: disable=broad-except
except Exception as ex:
_LOGGER.error("Unable to connect to Amcrest camera: %s", str(ex))
persistent_notification.create(
hass, 'Error: {}<br />'
'You will need to restart hass after fixing.'
''.format(ex),
title=NOTIFICATION_TITLE,
notification_id=NOTIFICATION_ID)
return False
add_devices([AmcrestCam(config, data)])
return True
class AmcrestCam(Camera):
"""An implementation of an Amcrest IP camera."""
def __init__(self, device_info, data):
"""Initialize an Amcrest camera."""
super(AmcrestCam, self).__init__()
self._data = data
self._name = device_info.get(CONF_NAME)
self._resolution = RESOLUTION_LIST[device_info.get(CONF_RESOLUTION)]
def camera_image(self):
"""Return a still image reponse from the camera."""
# Send the request to snap a picture and return raw jpg data
response = self._data.camera.snapshot(channel=self._resolution)
return response.data
@property
def name(self):
"""Return the name of this camera."""
return self._name
+43 -19
View File
@@ -4,15 +4,18 @@ Support for Cameras with FFmpeg as decoder.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.ffmpeg/
"""
import asyncio
import logging
import voluptuous as vol
from aiohttp import web
from homeassistant.components.camera import (Camera, PLATFORM_SCHEMA)
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
from homeassistant.components.ffmpeg import (
run_test, get_binary, CONF_INPUT, CONF_EXTRA_ARGUMENTS)
async_run_test, get_binary, CONF_INPUT, CONF_EXTRA_ARGUMENTS)
import homeassistant.helpers.config_validation as cv
from homeassistant.const import CONF_NAME
from homeassistant.util.async import run_coroutine_threadsafe
DEPENDENCIES = ['ffmpeg']
@@ -27,17 +30,18 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
def setup_platform(hass, config, add_devices, discovery_info=None):
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Setup a FFmpeg Camera."""
if not run_test(config.get(CONF_INPUT)):
if not async_run_test(hass, config.get(CONF_INPUT)):
return
add_devices([FFmpegCamera(config)])
yield from async_add_devices([FFmpegCamera(hass, config)])
class FFmpegCamera(Camera):
"""An implementation of an FFmpeg camera."""
def __init__(self, config):
def __init__(self, hass, config):
"""Initialize a FFmpeg camera."""
super().__init__()
self._name = config.get(CONF_NAME)
@@ -45,24 +49,44 @@ class FFmpegCamera(Camera):
self._extra_arguments = config.get(CONF_EXTRA_ARGUMENTS)
def camera_image(self):
"""Return bytes of camera image."""
return run_coroutine_threadsafe(
self.async_camera_image(), self.hass.loop).result()
@asyncio.coroutine
def async_camera_image(self):
"""Return a still image response from the camera."""
from haffmpeg import ImageSingle, IMAGE_JPEG
ffmpeg = ImageSingle(get_binary())
from haffmpeg import ImageSingleAsync, IMAGE_JPEG
ffmpeg = ImageSingleAsync(get_binary(), loop=self.hass.loop)
return ffmpeg.get_image(self._input, output_format=IMAGE_JPEG,
extra_cmd=self._extra_arguments)
image = yield from ffmpeg.get_image(
self._input, output_format=IMAGE_JPEG,
extra_cmd=self._extra_arguments)
return image
def mjpeg_stream(self, response):
@asyncio.coroutine
def handle_async_mjpeg_stream(self, request):
"""Generate an HTTP MJPEG stream from the camera."""
from haffmpeg import CameraMjpeg
from haffmpeg import CameraMjpegAsync
stream = CameraMjpeg(get_binary())
stream.open_camera(self._input, extra_cmd=self._extra_arguments)
return response(
stream,
mimetype='multipart/x-mixed-replace;boundary=ffserver',
direct_passthrough=True
)
stream = CameraMjpegAsync(get_binary(), loop=self.hass.loop)
yield from stream.open_camera(
self._input, extra_cmd=self._extra_arguments)
response = web.StreamResponse()
response.content_type = 'multipart/x-mixed-replace;boundary=ffserver'
yield from response.prepare(request)
try:
while True:
data = yield from stream.read(102400)
if not data:
break
response.write(data)
finally:
self.hass.async_add_job(stream.close())
yield from response.write_eof()
@property
def name(self):
@@ -36,7 +36,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
add_devices([FoscamCamera(config)])
# pylint: disable=too-many-instance-attributes
class FoscamCamera(Camera):
"""An implementation of a Foscam IP camera."""
+50 -14
View File
@@ -4,10 +4,13 @@ Support for IP Cameras.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.generic/
"""
import asyncio
import logging
import aiohttp
import async_timeout
import requests
from requests.auth import HTTPBasicAuth, HTTPDigestAuth
from requests.auth import HTTPDigestAuth
import voluptuous as vol
from homeassistant.const import (
@@ -15,7 +18,9 @@ from homeassistant.const import (
HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION)
from homeassistant.exceptions import TemplateError
from homeassistant.components.camera import (PLATFORM_SCHEMA, Camera)
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers import config_validation as cv
from homeassistant.util.async import run_coroutine_threadsafe
_LOGGER = logging.getLogger(__name__)
@@ -35,13 +40,13 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
@asyncio.coroutine
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Setup a generic IP Camera."""
add_devices([GenericCamera(hass, config)])
yield from async_add_devices([GenericCamera(hass, config)])
# pylint: disable=too-many-instance-attributes
class GenericCamera(Camera):
"""A generic implementation of an IP camera."""
@@ -49,6 +54,7 @@ class GenericCamera(Camera):
"""Initialize a generic camera."""
super().__init__()
self.hass = hass
self._authentication = device_info.get(CONF_AUTHENTICATION)
self._name = device_info.get(CONF_NAME)
self._still_image_url = device_info[CONF_STILL_IMAGE_URL]
self._still_image_url.hass = hass
@@ -58,10 +64,10 @@ class GenericCamera(Camera):
password = device_info.get(CONF_PASSWORD)
if username and password:
if device_info[CONF_AUTHENTICATION] == HTTP_DIGEST_AUTHENTICATION:
if self._authentication == HTTP_DIGEST_AUTHENTICATION:
self._auth = HTTPDigestAuth(username, password)
else:
self._auth = HTTPBasicAuth(username, password)
self._auth = aiohttp.BasicAuth(username, password=password)
else:
self._auth = None
@@ -69,9 +75,15 @@ class GenericCamera(Camera):
self._last_image = None
def camera_image(self):
"""Return bytes of camera image."""
return run_coroutine_threadsafe(
self.async_camera_image(), self.hass.loop).result()
@asyncio.coroutine
def async_camera_image(self):
"""Return a still image response from the camera."""
try:
url = self._still_image_url.render()
url = self._still_image_url.async_render()
except TemplateError as err:
_LOGGER.error('Error parsing template %s: %s',
self._still_image_url, err)
@@ -80,16 +92,40 @@ class GenericCamera(Camera):
if url == self._last_url and self._limit_refetch:
return self._last_image
kwargs = {'timeout': 10, 'auth': self._auth}
# aiohttp don't support DigestAuth yet
if self._authentication == HTTP_DIGEST_AUTHENTICATION:
def fetch():
"""Read image from a URL."""
try:
response = requests.get(url, timeout=10, auth=self._auth)
return response.content
except requests.exceptions.RequestException as error:
_LOGGER.error('Error getting camera image: %s', error)
return self._last_image
try:
response = requests.get(url, **kwargs)
except requests.exceptions.RequestException as error:
_LOGGER.error('Error getting camera image: %s', error)
return None
self._last_image = yield from self.hass.loop.run_in_executor(
None, fetch)
# async
else:
response = None
try:
websession = async_get_clientsession(self.hass)
with async_timeout.timeout(10, loop=self.hass.loop):
response = yield from websession.get(
url, auth=self._auth)
self._last_image = yield from response.read()
except asyncio.TimeoutError:
_LOGGER.error('Timeout getting camera image')
return self._last_image
except (aiohttp.errors.ClientError,
aiohttp.errors.ClientDisconnectedError) as err:
_LOGGER.error('Error getting new camera image: %s', err)
return self._last_image
finally:
if response is not None:
self.hass.async_add_job(response.release())
self._last_url = url
self._last_image = response.content
return self._last_image
@property
+59 -21
View File
@@ -4,9 +4,14 @@ Support for IP Cameras.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.mjpeg/
"""
import asyncio
import logging
from contextlib import closing
import aiohttp
from aiohttp import web
from aiohttp.web_exceptions import HTTPGatewayTimeout
import async_timeout
import requests
from requests.auth import HTTPBasicAuth, HTTPDigestAuth
import voluptuous as vol
@@ -15,6 +20,7 @@ from homeassistant.const import (
CONF_NAME, CONF_USERNAME, CONF_PASSWORD, CONF_AUTHENTICATION,
HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION)
from homeassistant.components.camera import (PLATFORM_SCHEMA, Camera)
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers import config_validation as cv
_LOGGER = logging.getLogger(__name__)
@@ -34,10 +40,11 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
@asyncio.coroutine
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Setup a MJPEG IP Camera."""
add_devices([MjpegCamera(config)])
yield from async_add_devices([MjpegCamera(hass, config)])
def extract_image_from_mjpeg(stream):
@@ -52,11 +59,10 @@ def extract_image_from_mjpeg(stream):
return jpg
# pylint: disable=too-many-instance-attributes
class MjpegCamera(Camera):
"""An implementation of an IP camera that is reachable over a URL."""
def __init__(self, device_info):
def __init__(self, hass, device_info):
"""Initialize a MJPEG camera."""
super().__init__()
self._name = device_info.get(CONF_NAME)
@@ -65,32 +71,64 @@ class MjpegCamera(Camera):
self._password = device_info.get(CONF_PASSWORD)
self._mjpeg_url = device_info[CONF_MJPEG_URL]
def camera_stream(self):
"""Return a MJPEG stream image response directly from the camera."""
self._auth = None
if self._username and self._password:
if self._authentication == HTTP_BASIC_AUTHENTICATION:
self._auth = aiohttp.BasicAuth(
self._username, password=self._password
)
def camera_image(self):
"""Return a still image response from the camera."""
if self._username and self._password:
if self._authentication == HTTP_DIGEST_AUTHENTICATION:
auth = HTTPDigestAuth(self._username, self._password)
else:
auth = HTTPBasicAuth(self._username, self._password)
return requests.get(self._mjpeg_url,
auth=auth,
stream=True, timeout=10)
req = requests.get(
self._mjpeg_url, auth=auth, stream=True, timeout=10)
else:
return requests.get(self._mjpeg_url, stream=True, timeout=10)
req = requests.get(self._mjpeg_url, stream=True, timeout=10)
def camera_image(self):
"""Return a still image response from the camera."""
with closing(self.camera_stream()) as response:
return extract_image_from_mjpeg(response.iter_content(1024))
with closing(req) as response:
return extract_image_from_mjpeg(response.iter_content(102400))
def mjpeg_stream(self, response):
@asyncio.coroutine
def handle_async_mjpeg_stream(self, request):
"""Generate an HTTP MJPEG stream from the camera."""
stream = self.camera_stream()
return response(
stream.iter_content(chunk_size=1024),
mimetype=stream.headers[CONTENT_TYPE_HEADER],
direct_passthrough=True
)
# aiohttp don't support DigestAuth -> Fallback
if self._authentication == HTTP_DIGEST_AUTHENTICATION:
yield from super().handle_async_mjpeg_stream(request)
return
# connect to stream
websession = async_get_clientsession(self.hass)
stream = None
response = None
try:
with async_timeout.timeout(10, loop=self.hass.loop):
stream = yield from websession.get(self._mjpeg_url,
auth=self._auth)
response = web.StreamResponse()
response.content_type = stream.headers.get(CONTENT_TYPE_HEADER)
yield from response.prepare(request)
while True:
data = yield from stream.content.read(102400)
if not data:
break
response.write(data)
except asyncio.TimeoutError:
raise HTTPGatewayTimeout()
finally:
if stream is not None:
self.hass.async_add_job(stream.release())
if response is not None:
yield from response.write_eof()
@property
def name(self):
+109
View File
@@ -0,0 +1,109 @@
"""
Support for Nest Cameras.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.nest/
"""
import logging
from datetime import timedelta
import requests
import homeassistant.components.nest as nest
from homeassistant.components.camera import (PLATFORM_SCHEMA, Camera)
from homeassistant.util.dt import utcnow
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['nest']
NEST_BRAND = 'Nest'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up a Nest Cam."""
if discovery_info is None:
return
camera_devices = hass.data[nest.DATA_NEST].camera_devices()
cameras = [NestCamera(structure, device)
for structure, device in camera_devices]
add_devices(cameras, True)
class NestCamera(Camera):
"""Representation of a Nest Camera."""
def __init__(self, structure, device):
"""Initialize a Nest Camera."""
super(NestCamera, self).__init__()
self.structure = structure
self.device = device
self._location = None
self._name = None
self._is_online = None
self._is_streaming = None
self._is_video_history_enabled = False
# Default to non-NestAware subscribed, but will be fixed during update
self._time_between_snapshots = timedelta(seconds=30)
self._last_image = None
self._next_snapshot_at = None
@property
def name(self):
"""Return the name of the nest, if any."""
return self._name
@property
def should_poll(self):
"""Nest camera should poll periodically."""
return True
@property
def is_recording(self):
"""Return true if the device is recording."""
return self._is_streaming
@property
def brand(self):
"""Return the brand of the camera."""
return NEST_BRAND
# This doesn't seem to be getting called regularly, for some reason
def update(self):
"""Cache value from Python-nest."""
self._location = self.device.where
self._name = self.device.name
self._is_online = self.device.is_online
self._is_streaming = self.device.is_streaming
self._is_video_history_enabled = self.device.is_video_history_enabled
if self._is_video_history_enabled:
# NestAware allowed 10/min
self._time_between_snapshots = timedelta(seconds=6)
else:
# Otherwise, 2/min
self._time_between_snapshots = timedelta(seconds=30)
def _ready_for_snapshot(self, now):
return (self._next_snapshot_at is None or
now > self._next_snapshot_at)
def camera_image(self):
"""Return a still image response from the camera."""
now = utcnow()
if self._ready_for_snapshot(now):
url = self.device.snapshot_url
try:
response = requests.get(url)
except requests.exceptions.RequestException as error:
_LOGGER.error("Error getting camera image: %s", error)
return None
self._next_snapshot_at = now + self._time_between_snapshots
self._last_image = response.content
return self._last_image
+24 -9
View File
@@ -12,7 +12,8 @@ import shutil
import voluptuous as vol
from homeassistant.components.camera import (Camera, PLATFORM_SCHEMA)
from homeassistant.const import (CONF_NAME, CONF_FILE_PATH)
from homeassistant.const import (CONF_NAME, CONF_FILE_PATH,
EVENT_HOMEASSISTANT_STOP)
from homeassistant.helpers import config_validation as cv
_LOGGER = logging.getLogger(__name__)
@@ -35,10 +36,10 @@ DEFAULT_TIMELAPSE = 1000
DEFAULT_VERTICAL_FLIP = 0
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_FILE_PATH): cv.isfile,
vol.Optional(CONF_FILE_PATH): cv.string,
vol.Optional(CONF_HORIZONTAL_FLIP, default=DEFAULT_HORIZONTAL_FLIP):
vol.All(vol.Coerce(int), vol.Range(min=0, max=1)),
vol.Optional(CONF_IMAGE_HEIGHT, default=DEFAULT_HORIZONTAL_FLIP):
vol.Optional(CONF_IMAGE_HEIGHT, default=DEFAULT_IMAGE_HEIGHT):
vol.Coerce(int),
vol.Optional(CONF_IMAGE_QUALITY, default=DEFAULT_IMAGE_QUALITIY):
vol.All(vol.Coerce(int), vol.Range(min=0, max=100)),
@@ -53,6 +54,13 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
def kill_raspistill(*args):
"""Kill any previously running raspistill process.."""
subprocess.Popen(['killall', 'raspistill'],
stdout=subprocess.DEVNULL,
stderr=subprocess.STDOUT)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Raspberry Camera."""
if shutil.which("raspistill") is None:
@@ -75,11 +83,20 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
}
)
if not os.access(setup_config[CONF_FILE_PATH], os.W_OK):
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, kill_raspistill)
try:
# Try to create an empty file (or open existing) to ensure we have
# proper permissions.
open(setup_config[CONF_FILE_PATH], 'a').close()
add_devices([RaspberryCamera(setup_config)])
except PermissionError:
_LOGGER.error("File path is not writable")
return False
add_devices([RaspberryCamera(setup_config)])
except FileNotFoundError:
_LOGGER.error("Could not create output file (missing directory?)")
return False
class RaspberryCamera(Camera):
@@ -93,9 +110,7 @@ class RaspberryCamera(Camera):
self._config = device_info
# Kill if there's raspistill instance
subprocess.Popen(['killall', 'raspistill'],
stdout=subprocess.DEVNULL,
stderr=subprocess.STDOUT)
kill_raspistill()
cmd_args = [
'raspistill', '--nopreview', '-o', device_info[CONF_FILE_PATH],
+195 -130
View File
@@ -4,28 +4,33 @@ Support for Synology Surveillance Station Cameras.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.synology/
"""
import asyncio
import logging
import voluptuous as vol
import requests
import aiohttp
from aiohttp import web
from aiohttp.web_exceptions import HTTPGatewayTimeout
import async_timeout
from homeassistant.const import (
CONF_NAME, CONF_USERNAME, CONF_PASSWORD,
CONF_URL, CONF_WHITELIST)
CONF_URL, CONF_WHITELIST, CONF_VERIFY_SSL)
from homeassistant.components.camera import (
Camera, PLATFORM_SCHEMA)
from homeassistant.helpers.aiohttp_client import (
async_get_clientsession, async_create_clientsession)
import homeassistant.helpers.config_validation as cv
from homeassistant.util.async import run_coroutine_threadsafe
_LOGGER = logging.getLogger(__name__)
# pylint: disable=too-many-locals
DEFAULT_NAME = 'Synology Camera'
DEFAULT_STREAM_ID = '0'
TIMEOUT = 5
CONF_CAMERA_NAME = 'camera_name'
CONF_STREAM_ID = 'stream_id'
CONF_VALID_CERT = 'valid_cert'
QUERY_CGI = 'query.cgi'
QUERY_API = 'SYNO.API.Info'
@@ -38,6 +43,7 @@ WEBAPI_PATH = '/webapi/'
AUTH_PATH = 'auth.cgi'
CAMERA_PATH = 'camera.cgi'
STREAMING_PATH = 'SurveillanceStation/videoStreaming.cgi'
CONTENT_TYPE_HEADER = 'Content-Type'
SYNO_API_URL = '{0}{1}{2}'
@@ -47,175 +53,234 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_URL): cv.string,
vol.Optional(CONF_WHITELIST, default=[]): cv.ensure_list,
vol.Optional(CONF_VALID_CERT, default=True): cv.boolean,
vol.Optional(CONF_VERIFY_SSL, default=True): cv.boolean,
})
def setup_platform(hass, config, add_devices, discovery_info=None):
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Setup a Synology IP Camera."""
verify_ssl = config.get(CONF_VERIFY_SSL)
websession_init = async_get_clientsession(hass, verify_ssl)
# Determine API to use for authentication
syno_api_url = SYNO_API_URL.format(config.get(CONF_URL),
WEBAPI_PATH,
QUERY_CGI)
query_payload = {'api': QUERY_API,
'method': 'Query',
'version': '1',
'query': 'SYNO.'}
query_req = requests.get(syno_api_url,
params=query_payload,
verify=config.get(CONF_VALID_CERT),
timeout=TIMEOUT)
query_resp = query_req.json()
auth_path = query_resp['data'][AUTH_API]['path']
camera_api = query_resp['data'][CAMERA_API]['path']
camera_path = query_resp['data'][CAMERA_API]['path']
streaming_path = query_resp['data'][STREAMING_API]['path']
syno_api_url = SYNO_API_URL.format(
config.get(CONF_URL), WEBAPI_PATH, QUERY_CGI)
query_payload = {
'api': QUERY_API,
'method': 'Query',
'version': '1',
'query': 'SYNO.'
}
query_req = None
try:
with async_timeout.timeout(TIMEOUT, loop=hass.loop):
query_req = yield from websession_init.get(
syno_api_url,
params=query_payload
)
query_resp = yield from query_req.json()
auth_path = query_resp['data'][AUTH_API]['path']
camera_api = query_resp['data'][CAMERA_API]['path']
camera_path = query_resp['data'][CAMERA_API]['path']
streaming_path = query_resp['data'][STREAMING_API]['path']
except (asyncio.TimeoutError, aiohttp.errors.ClientError):
_LOGGER.exception("Error on %s", syno_api_url)
return False
finally:
if query_req is not None:
yield from query_req.release()
# Authticate to NAS to get a session id
syno_auth_url = SYNO_API_URL.format(config.get(CONF_URL),
WEBAPI_PATH,
auth_path)
session_id = get_session_id(config.get(CONF_USERNAME),
config.get(CONF_PASSWORD),
syno_auth_url,
config.get(CONF_VALID_CERT))
syno_auth_url = SYNO_API_URL.format(
config.get(CONF_URL), WEBAPI_PATH, auth_path)
session_id = yield from get_session_id(
hass,
websession_init,
config.get(CONF_USERNAME),
config.get(CONF_PASSWORD),
syno_auth_url
)
# init websession
websession = async_create_clientsession(
hass, verify_ssl, cookies={'id': session_id})
# Use SessionID to get cameras in system
syno_camera_url = SYNO_API_URL.format(config.get(CONF_URL),
WEBAPI_PATH,
camera_api)
camera_payload = {'api': CAMERA_API,
'method': 'List',
'version': '1'}
camera_req = requests.get(syno_camera_url,
params=camera_payload,
verify=config.get(CONF_VALID_CERT),
timeout=TIMEOUT,
cookies={'id': session_id})
camera_resp = camera_req.json()
syno_camera_url = SYNO_API_URL.format(
config.get(CONF_URL), WEBAPI_PATH, camera_api)
camera_payload = {
'api': CAMERA_API,
'method': 'List',
'version': '1'
}
try:
with async_timeout.timeout(TIMEOUT, loop=hass.loop):
camera_req = yield from websession.get(
syno_camera_url,
params=camera_payload
)
except (asyncio.TimeoutError, aiohttp.errors.ClientError):
_LOGGER.exception("Error on %s", syno_camera_url)
return False
camera_resp = yield from camera_req.json()
cameras = camera_resp['data']['cameras']
yield from camera_req.release()
# add cameras
devices = []
for camera in cameras:
if not config.get(CONF_WHITELIST):
camera_id = camera['id']
snapshot_path = camera['snapshot_path']
add_devices([SynologyCamera(config,
camera_id,
camera['name'],
snapshot_path,
streaming_path,
camera_path,
auth_path)])
device = SynologyCamera(
hass,
websession,
config,
camera_id,
camera['name'],
snapshot_path,
streaming_path,
camera_path,
auth_path
)
devices.append(device)
yield from async_add_devices(devices)
def get_session_id(username, password, login_url, valid_cert):
@asyncio.coroutine
def get_session_id(hass, websession, username, password, login_url):
"""Get a session id."""
auth_payload = {'api': AUTH_API,
'method': 'Login',
'version': '2',
'account': username,
'passwd': password,
'session': 'SurveillanceStation',
'format': 'sid'}
auth_req = requests.get(login_url,
params=auth_payload,
verify=valid_cert,
timeout=TIMEOUT)
auth_resp = auth_req.json()
return auth_resp['data']['sid']
auth_payload = {
'api': AUTH_API,
'method': 'Login',
'version': '2',
'account': username,
'passwd': password,
'session': 'SurveillanceStation',
'format': 'sid'
}
auth_req = None
try:
with async_timeout.timeout(TIMEOUT, loop=hass.loop):
auth_req = yield from websession.get(
login_url,
params=auth_payload
)
auth_resp = yield from auth_req.json()
return auth_resp['data']['sid']
except (asyncio.TimeoutError, aiohttp.errors.ClientError):
_LOGGER.exception("Error on %s", login_url)
return False
finally:
if auth_req is not None:
yield from auth_req.release()
# pylint: disable=too-many-instance-attributes
class SynologyCamera(Camera):
"""An implementation of a Synology NAS based IP camera."""
# pylint: disable=too-many-arguments
def __init__(self, config, camera_id, camera_name,
snapshot_path, streaming_path, camera_path, auth_path):
def __init__(self, hass, websession, config, camera_id,
camera_name, snapshot_path, streaming_path, camera_path,
auth_path):
"""Initialize a Synology Surveillance Station camera."""
super().__init__()
self.hass = hass
self._websession = websession
self._name = camera_name
self._username = config.get(CONF_USERNAME)
self._password = config.get(CONF_PASSWORD)
self._synology_url = config.get(CONF_URL)
self._api_url = config.get(CONF_URL) + 'webapi/'
self._login_url = config.get(CONF_URL) + '/webapi/' + 'auth.cgi'
self._camera_name = config.get(CONF_CAMERA_NAME)
self._stream_id = config.get(CONF_STREAM_ID)
self._valid_cert = config.get(CONF_VALID_CERT)
self._camera_id = camera_id
self._snapshot_path = snapshot_path
self._streaming_path = streaming_path
self._camera_path = camera_path
self._auth_path = auth_path
self._session_id = get_session_id(self._username,
self._password,
self._login_url,
self._valid_cert)
def get_sid(self):
"""Get a session id."""
auth_payload = {'api': AUTH_API,
'method': 'Login',
'version': '2',
'account': self._username,
'passwd': self._password,
'session': 'SurveillanceStation',
'format': 'sid'}
auth_req = requests.get(self._login_url,
params=auth_payload,
verify=self._valid_cert,
timeout=TIMEOUT)
auth_resp = auth_req.json()
self._session_id = auth_resp['data']['sid']
def camera_image(self):
"""Return bytes of camera image."""
return run_coroutine_threadsafe(
self.async_camera_image(), self.hass.loop).result()
@asyncio.coroutine
def async_camera_image(self):
"""Return a still image response from the camera."""
image_url = SYNO_API_URL.format(self._synology_url,
WEBAPI_PATH,
self._camera_path)
image_payload = {'api': CAMERA_API,
'method': 'GetSnapshot',
'version': '1',
'cameraId': self._camera_id}
image_url = SYNO_API_URL.format(
self._synology_url, WEBAPI_PATH, self._camera_path)
image_payload = {
'api': CAMERA_API,
'method': 'GetSnapshot',
'version': '1',
'cameraId': self._camera_id
}
try:
response = requests.get(image_url,
params=image_payload,
timeout=TIMEOUT,
verify=self._valid_cert,
cookies={'id': self._session_id})
except requests.exceptions.RequestException as error:
_LOGGER.error('Error getting camera image: %s', error)
with async_timeout.timeout(TIMEOUT, loop=self.hass.loop):
response = yield from self._websession.get(
image_url,
params=image_payload
)
except (asyncio.TimeoutError, aiohttp.errors.ClientError):
_LOGGER.exception("Error on %s", image_url)
return None
return response.content
image = yield from response.read()
yield from response.release()
def camera_stream(self):
return image
@asyncio.coroutine
def handle_async_mjpeg_stream(self, request):
"""Return a MJPEG stream image response directly from the camera."""
streaming_url = SYNO_API_URL.format(self._synology_url,
WEBAPI_PATH,
self._streaming_path)
streaming_payload = {'api': STREAMING_API,
'method': 'Stream',
'version': '1',
'cameraId': self._camera_id,
'format': 'mjpeg'}
response = requests.get(streaming_url,
payload=streaming_payload,
stream=True,
timeout=TIMEOUT,
cookies={'id': self._session_id})
return response
streaming_url = SYNO_API_URL.format(
self._synology_url, WEBAPI_PATH, self._streaming_path)
def mjpeg_steam(self, response):
"""Generate an HTTP MJPEG Stream from the Synology NAS."""
stream = self.camera_stream()
return response(
stream.iter_content(chunk_size=1024),
mimetype=stream.headers['CONTENT_TYPE_HEADER'],
direct_passthrough=True
)
streaming_payload = {
'api': STREAMING_API,
'method': 'Stream',
'version': '1',
'cameraId': self._camera_id,
'format': 'mjpeg'
}
stream = None
response = None
try:
with async_timeout.timeout(TIMEOUT, loop=self.hass.loop):
stream = yield from self._websession.get(
streaming_url,
params=streaming_payload
)
response = web.StreamResponse()
response.content_type = stream.headers.get(CONTENT_TYPE_HEADER)
yield from response.prepare(request)
while True:
data = yield from stream.content.read(102400)
if not data:
break
response.write(data)
except (asyncio.TimeoutError, aiohttp.errors.ClientError):
_LOGGER.exception("Error on %s", streaming_url)
raise HTTPGatewayTimeout()
finally:
if stream is not None:
self.hass.async_add_job(stream.release())
if response is not None:
yield from response.write_eof()
@property
def name(self):
+23 -10
View File
@@ -58,6 +58,11 @@ ATTR_OPERATION_LIST = "operation_list"
ATTR_SWING_MODE = "swing_mode"
ATTR_SWING_LIST = "swing_list"
# The degree of precision for each platform
PRECISION_WHOLE = 1
PRECISION_HALVES = 0.5
PRECISION_TENTHS = 0.1
CONVERTIBLE_ATTRIBUTE = [
ATTR_TEMPERATURE,
ATTR_TARGET_TEMP_LOW,
@@ -123,7 +128,6 @@ def set_aux_heat(hass, aux_heat, entity_id=None):
hass.services.call(DOMAIN, SERVICE_SET_AUX_HEAT, data)
# pylint: disable=too-many-arguments
def set_temperature(hass, temperature=None, entity_id=None,
target_temp_high=None, target_temp_low=None,
operation_mode=None):
@@ -181,7 +185,6 @@ def set_swing_mode(hass, swing_mode, entity_id=None):
hass.services.call(DOMAIN, SERVICE_SET_SWING_MODE, data)
# pylint: disable=too-many-branches
def setup(hass, config):
"""Setup climate devices."""
component = EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL)
@@ -364,7 +367,7 @@ def setup(hass, config):
class ClimateDevice(Entity):
"""Representation of a climate device."""
# pylint: disable=too-many-public-methods,no-self-use
# pylint: disable=no-self-use
@property
def state(self):
"""Return the current state."""
@@ -373,6 +376,14 @@ class ClimateDevice(Entity):
else:
return STATE_UNKNOWN
@property
def precision(self):
"""Return the precision of the system."""
if self.unit_of_measurement == TEMP_CELSIUS:
return PRECISION_TENTHS
else:
return PRECISION_WHOLE
@property
def state_attributes(self):
"""Return the optional state attributes."""
@@ -564,16 +575,18 @@ class ClimateDevice(Entity):
def _convert_for_display(self, temp):
"""Convert temperature into preferred units for display purposes."""
if temp is None or not isinstance(temp, Number):
if (temp is None or not isinstance(temp, Number) or
self.temperature_unit == self.unit_of_measurement):
return temp
value = convert_temperature(temp, self.temperature_unit,
self.unit_of_measurement)
if self.unit_of_measurement is TEMP_CELSIUS:
decimal_count = 1
# Round in the units appropriate
if self.precision == PRECISION_HALVES:
return round(value * 2) / 2.0
elif self.precision == PRECISION_TENTHS:
return round(value, 1)
else:
# Users of fahrenheit generally expect integer units.
decimal_count = 0
return round(value, decimal_count)
# PRECISION_WHOLE as a fall back
return round(value)
-2
View File
@@ -21,11 +21,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
])
# pylint: disable=too-many-arguments, too-many-public-methods
class DemoClimate(ClimateDevice):
"""Representation of a demo climate device."""
# pylint: disable=too-many-instance-attributes
def __init__(self, name, target_temperature, unit_of_measurement,
away, current_temperature, current_fan_mode,
target_humidity, current_humidity, current_swing_mode,
+3 -3
View File
@@ -72,7 +72,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
schema=SET_FAN_MIN_ON_TIME_SCHEMA)
# pylint: disable=too-many-public-methods, abstract-method
class Thermostat(ClimateDevice):
"""A thermostat class for Ecobee."""
@@ -196,8 +195,9 @@ class Thermostat(ClimateDevice):
mode = self.mode
events = self.thermostat['events']
for event in events:
if event['running']:
mode = event['holdClimateRef']
if event['holdClimateRef'] == 'away' or \
event['type'] == 'autoAway':
mode = "away"
break
return 'away' in mode
@@ -42,7 +42,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
add_devices(devices)
# pylint: disable=too-many-instance-attributes, import-error, abstract-method
# pylint: disable=import-error
class EQ3BTSmartThermostat(ClimateDevice):
"""Representation of a eQ-3 Bluetooth Smart thermostat."""
@@ -21,10 +21,10 @@ _LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['switch', 'sensor']
TOL_TEMP = 0.3
DEFAULT_TOLERANCE = 0.3
DEFAULT_NAME = 'Generic Thermostat'
CONF_NAME = 'name'
DEFAULT_NAME = 'Generic Thermostat'
CONF_HEATER = 'heater'
CONF_SENSOR = 'target_sensor'
CONF_MIN_TEMP = 'min_temp'
@@ -32,6 +32,7 @@ CONF_MAX_TEMP = 'max_temp'
CONF_TARGET_TEMP = 'target_temp'
CONF_AC_MODE = 'ac_mode'
CONF_MIN_DUR = 'min_cycle_duration'
CONF_TOLERANCE = 'tolerance'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@@ -42,6 +43,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_MIN_DUR): vol.All(cv.time_period, cv.positive_timedelta),
vol.Optional(CONF_MIN_TEMP): vol.Coerce(float),
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_TOLERANCE, default=DEFAULT_TOLERANCE): vol.Coerce(float),
vol.Optional(CONF_TARGET_TEMP): vol.Coerce(float),
})
@@ -56,25 +58,26 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
target_temp = config.get(CONF_TARGET_TEMP)
ac_mode = config.get(CONF_AC_MODE)
min_cycle_duration = config.get(CONF_MIN_DUR)
tolerance = config.get(CONF_TOLERANCE)
add_devices([GenericThermostat(
hass, name, heater_entity_id, sensor_entity_id, min_temp, max_temp,
target_temp, ac_mode, min_cycle_duration)])
target_temp, ac_mode, min_cycle_duration, tolerance)])
# pylint: disable=too-many-instance-attributes, abstract-method
class GenericThermostat(ClimateDevice):
"""Representation of a GenericThermostat device."""
# pylint: disable=too-many-arguments
def __init__(self, hass, name, heater_entity_id, sensor_entity_id,
min_temp, max_temp, target_temp, ac_mode, min_cycle_duration):
min_temp, max_temp, target_temp, ac_mode, min_cycle_duration,
tolerance):
"""Initialize the thermostat."""
self.hass = hass
self._name = name
self.heater_entity_id = heater_entity_id
self.ac_mode = ac_mode
self.min_cycle_duration = min_cycle_duration
self._tolerance = tolerance
self._active = False
self._cur_temp = None
@@ -147,7 +150,7 @@ class GenericThermostat(ClimateDevice):
def max_temp(self):
"""Return the maximum temperature."""
# pylint: disable=no-member
if self._min_temp:
if self._max_temp:
return self._max_temp
else:
# Get default temp from super class
@@ -160,7 +163,7 @@ class GenericThermostat(ClimateDevice):
self._update_temp(new_state)
self._control_heating()
self.update_ha_state()
self.schedule_update_ha_state()
def _update_temp(self, state):
"""Update thermostat with latest state from sensor."""
@@ -195,24 +198,30 @@ class GenericThermostat(ClimateDevice):
return
if self.ac_mode:
too_hot = self._cur_temp - self._target_temp > TOL_TEMP
is_cooling = self._is_device_active
if too_hot and not is_cooling:
_LOGGER.info('Turning on AC %s', self.heater_entity_id)
switch.turn_on(self.hass, self.heater_entity_id)
elif not too_hot and is_cooling:
_LOGGER.info('Turning off AC %s', self.heater_entity_id)
switch.turn_off(self.hass, self.heater_entity_id)
if is_cooling:
too_cold = self._target_temp - self._cur_temp > self._tolerance
if too_cold:
_LOGGER.info('Turning off AC %s', self.heater_entity_id)
switch.turn_off(self.hass, self.heater_entity_id)
else:
too_hot = self._cur_temp - self._target_temp > self._tolerance
if too_hot:
_LOGGER.info('Turning on AC %s', self.heater_entity_id)
switch.turn_on(self.hass, self.heater_entity_id)
else:
too_cold = self._target_temp - self._cur_temp > TOL_TEMP
is_heating = self._is_device_active
if too_cold and not is_heating:
_LOGGER.info('Turning on heater %s', self.heater_entity_id)
switch.turn_on(self.hass, self.heater_entity_id)
elif not too_cold and is_heating:
_LOGGER.info('Turning off heater %s', self.heater_entity_id)
switch.turn_off(self.hass, self.heater_entity_id)
if is_heating:
too_hot = self._cur_temp - self._target_temp > self._tolerance
if too_hot:
_LOGGER.info('Turning off heater %s',
self.heater_entity_id)
switch.turn_off(self.hass, self.heater_entity_id)
else:
too_cold = self._target_temp - self._cur_temp > self._tolerance
if too_cold:
_LOGGER.info('Turning on heater %s', self.heater_entity_id)
switch.turn_on(self.hass, self.heater_entity_id)
@property
def _is_device_active(self):
@@ -56,7 +56,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class HeatmiserV3Thermostat(ClimateDevice):
"""Representation of a HeatmiserV3 thermostat."""
# pylint: disable=too-many-instance-attributes, abstract-method
def __init__(self, heatmiser, device, name, serport):
"""Initialize the thermostat."""
self.heatmiser = heatmiser
@@ -5,10 +5,11 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.homematic/
"""
import logging
import homeassistant.components.homematic as homematic
from homeassistant.components.climate import ClimateDevice, STATE_AUTO
from homeassistant.components.homematic import HMDevice
from homeassistant.util.temperature import convert
from homeassistant.const import TEMP_CELSIUS, STATE_UNKNOWN, ATTR_TEMPERATURE
from homeassistant.loader import get_component
DEPENDENCIES = ['homematic']
@@ -29,15 +30,16 @@ def setup_platform(hass, config, add_callback_devices, discovery_info=None):
if discovery_info is None:
return
homematic = get_component("homematic")
return homematic.setup_hmdevice_discovery_helper(
hass,
HMThermostat,
discovery_info,
add_callback_devices
)
# pylint: disable=abstract-method
class HMThermostat(homematic.HMDevice, ClimateDevice):
class HMThermostat(HMDevice, ClimateDevice):
"""Representation of a Homematic thermostat."""
@property
@@ -95,13 +97,9 @@ class HMThermostat(homematic.HMDevice, ClimateDevice):
def set_temperature(self, **kwargs):
"""Set new target temperature."""
temperature = kwargs.get(ATTR_TEMPERATURE)
if not self.available:
if not self.available or temperature is None:
return None
if temperature is None:
return
if self.current_operation == STATE_AUTO:
return self._hmdevice.actionNodeData('MANU_MODE', temperature)
self._hmdevice.set_temperature(temperature)
def set_operation_mode(self, operation_mode):
@@ -100,7 +100,6 @@ def _setup_us(username, password, config, add_devices):
class RoundThermostat(ClimateDevice):
"""Representation of a Honeywell Round Connected thermostat."""
# pylint: disable=too-many-instance-attributes, abstract-method
def __init__(self, device, zone_id, master, away_temp):
"""Initialize the thermostat."""
self.device = device
@@ -197,7 +196,6 @@ class RoundThermostat(ClimateDevice):
self._is_dhw = False
# pylint: disable=abstract-method
class HoneywellUSThermostat(ClimateDevice):
"""Representation of a Honeywell US Thermostat."""
@@ -225,7 +223,6 @@ class HoneywellUSThermostat(ClimateDevice):
@property
def current_temperature(self):
"""Return the current temperature."""
self._device.refresh()
return self._device.current_temperature
@property
@@ -276,3 +273,7 @@ class HoneywellUSThermostat(ClimateDevice):
"""Set the system mode (Cool, Heat, etc)."""
if hasattr(self._device, ATTR_SYSTEM_MODE):
self._device.system_mode = operation_mode
def update(self):
"""Update the state."""
self._device.refresh()
+13 -6
View File
@@ -56,6 +56,8 @@ class KNXThermostat(KNXMultiAddressDevice, ClimateDevice):
self._unit_of_measurement = TEMP_CELSIUS # KNX always used celsius
self._away = False # not yet supported
self._is_fan_on = False # not yet supported
self._current_temp = None
self._target_temp = None
@property
def should_poll(self):
@@ -70,16 +72,12 @@ class KNXThermostat(KNXMultiAddressDevice, ClimateDevice):
@property
def current_temperature(self):
"""Return the current temperature."""
from knxip.conversion import knx2_to_float
return knx2_to_float(self.value('temperature'))
return self._current_temp
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
from knxip.conversion import knx2_to_float
return knx2_to_float(self.value('setpoint'))
return self._target_temp
def set_temperature(self, **kwargs):
"""Set new target temperature."""
@@ -94,3 +92,12 @@ class KNXThermostat(KNXMultiAddressDevice, ClimateDevice):
def set_operation_mode(self, operation_mode):
"""Set operation mode."""
raise NotImplementedError()
def update(self):
"""Update KNX climate."""
from knxip.conversion import knx2_to_float
super().update()
self._current_temp = knx2_to_float(self.value('temperature'))
self._target_temp = knx2_to_float(self.value('setpoint'))
@@ -24,7 +24,12 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the mysensors climate."""
if discovery_info is None:
return
for gateway in mysensors.GATEWAYS.values():
gateways = hass.data.get(mysensors.MYSENSORS_GATEWAYS)
if not gateways:
return
for gateway in gateways:
if float(gateway.protocol_version) < 1.5:
continue
pres = gateway.const.Presentation
@@ -37,7 +42,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
map_sv_types, devices, add_devices, MySensorsHVAC))
# pylint: disable=too-many-arguments, too-many-public-methods
class MySensorsHVAC(mysensors.MySensorsDeviceEntity, ClimateDevice):
"""Representation of a MySensorsHVAC hvac."""
+89 -73
View File
@@ -5,14 +5,17 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.nest/
"""
import logging
import voluptuous as vol
import homeassistant.components.nest as nest
from homeassistant.components.nest import DATA_NEST
from homeassistant.components.climate import (
STATE_AUTO, STATE_COOL, STATE_HEAT, ClimateDevice,
PLATFORM_SCHEMA, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
ATTR_TEMPERATURE)
from homeassistant.const import (
TEMP_CELSIUS, CONF_SCAN_INTERVAL, STATE_ON, STATE_OFF, STATE_UNKNOWN)
TEMP_CELSIUS, TEMP_FAHRENHEIT,
CONF_SCAN_INTERVAL, STATE_ON, STATE_OFF, STATE_UNKNOWN)
DEPENDENCIES = ['nest']
_LOGGER = logging.getLogger(__name__)
@@ -22,15 +25,26 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.All(vol.Coerce(int), vol.Range(min=1)),
})
STATE_ECO = 'eco'
STATE_HEAT_COOL = 'heat-cool'
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Nest thermostat."""
if discovery_info is None:
return
_LOGGER.debug("Setting up nest thermostat")
temp_unit = hass.config.units.temperature_unit
add_devices([NestThermostat(structure, device, temp_unit)
for structure, device in nest.devices()])
add_devices(
[NestThermostat(structure, device, temp_unit)
for structure, device in hass.data[DATA_NEST].devices()],
True
)
# pylint: disable=abstract-method,too-many-public-methods
class NestThermostat(ClimateDevice):
"""Representation of a Nest thermostat."""
@@ -54,98 +68,95 @@ class NestThermostat(ClimateDevice):
if self.device.can_heat and self.device.can_cool:
self._operation_list.append(STATE_AUTO)
self._operation_list.append(STATE_ECO)
# feature of device
self._has_fan = self.device.has_fan
# data attributes
self._away = None
self._location = None
self._name = None
self._humidity = None
self._target_temperature = None
self._temperature = None
self._temperature_scale = None
self._mode = None
self._fan = None
self._eco_temperature = None
self._is_locked = None
self._locked_temperature = None
self._min_temperature = None
self._max_temperature = None
@property
def name(self):
"""Return the name of the nest, if any."""
location = self.device.where
name = self.device.name
if location is None:
return name
else:
if name == '':
return location.capitalize()
else:
return location.capitalize() + '(' + name + ')'
return self._name
@property
def temperature_unit(self):
"""Return the unit of measurement."""
return TEMP_CELSIUS
@property
def device_state_attributes(self):
"""Return the device specific state attributes."""
if self.device.has_humidifier or self.device.has_dehumidifier:
# Move these to Thermostat Device and make them global
return {
"humidity": self.device.humidity,
"target_humidity": self.device.target_humidity,
}
else:
# No way to control humidity not show setting
return {}
return self._temperature_scale
@property
def current_temperature(self):
"""Return the current temperature."""
return self.device.temperature
return self._temperature
@property
def current_operation(self):
"""Return current operation ie. heat, cool, idle."""
if self.device.mode == 'cool':
return STATE_COOL
elif self.device.mode == 'heat':
return STATE_HEAT
elif self.device.mode == 'range':
if self._mode in [STATE_HEAT, STATE_COOL, STATE_OFF, STATE_ECO]:
return self._mode
elif self._mode == STATE_HEAT_COOL:
return STATE_AUTO
elif self.device.mode == 'off':
return STATE_OFF
else:
return STATE_UNKNOWN
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
if self.device.mode != 'range' and not self.is_away_mode_on:
return self.device.target
if self._mode != STATE_HEAT_COOL and not self.is_away_mode_on:
return self._target_temperature
else:
return None
@property
def target_temperature_low(self):
"""Return the lower bound temperature we try to reach."""
if self.is_away_mode_on and self.device.away_temperature[0]:
# away_temperature is always a low, high tuple
return self.device.away_temperature[0]
if self.device.mode == 'range':
return self.device.target[0]
if (self.is_away_mode_on or self._mode == STATE_ECO) and \
self._eco_temperature[0]:
# eco_temperature is always a low, high tuple
return self._eco_temperature[0]
if self._mode == STATE_HEAT_COOL:
return self._target_temperature[0]
else:
return None
@property
def target_temperature_high(self):
"""Return the upper bound temperature we try to reach."""
if self.is_away_mode_on and self.device.away_temperature[1]:
# away_temperature is always a low, high tuple
return self.device.away_temperature[1]
if self.device.mode == 'range':
return self.device.target[1]
if (self.is_away_mode_on or self._mode == STATE_ECO) and \
self._eco_temperature[1]:
# eco_temperature is always a low, high tuple
return self._eco_temperature[1]
if self._mode == STATE_HEAT_COOL:
return self._target_temperature[1]
else:
return None
@property
def is_away_mode_on(self):
"""Return if away mode is on."""
return self.structure.away
return self._away
def set_temperature(self, **kwargs):
"""Set new target temperature."""
target_temp_low = kwargs.get(ATTR_TARGET_TEMP_LOW)
target_temp_high = kwargs.get(ATTR_TARGET_TEMP_HIGH)
if target_temp_low is not None and target_temp_high is not None:
if self.device.mode == 'range':
if self._mode == STATE_HEAT_COOL:
if target_temp_low is not None and target_temp_high is not None:
temp = (target_temp_low, target_temp_high)
else:
temp = kwargs.get(ATTR_TEMPERATURE)
@@ -154,14 +165,11 @@ class NestThermostat(ClimateDevice):
def set_operation_mode(self, operation_mode):
"""Set operation mode."""
if operation_mode == STATE_HEAT:
self.device.mode = 'heat'
elif operation_mode == STATE_COOL:
self.device.mode = 'cool'
if operation_mode in [STATE_HEAT, STATE_COOL, STATE_OFF, STATE_ECO]:
device_mode = operation_mode
elif operation_mode == STATE_AUTO:
self.device.mode = 'range'
elif operation_mode == STATE_OFF:
self.device.mode = 'off'
device_mode = STATE_HEAT_COOL
self.device.mode = device_mode
@property
def operation_list(self):
@@ -179,9 +187,9 @@ class NestThermostat(ClimateDevice):
@property
def current_fan_mode(self):
"""Return whether the fan is on."""
if self.device.has_fan:
if self._has_fan:
# Return whether the fan is on
return STATE_ON if self.device.fan else STATE_AUTO
return STATE_ON if self._fan else STATE_AUTO
else:
# No Fan available so disable slider
return None
@@ -198,21 +206,29 @@ class NestThermostat(ClimateDevice):
@property
def min_temp(self):
"""Identify min_temp in Nest API or defaults if not available."""
temp = self.device.away_temperature.low
if temp is None:
return super().min_temp
else:
return temp
return self._min_temperature
@property
def max_temp(self):
"""Identify max_temp in Nest API or defaults if not available."""
temp = self.device.away_temperature.high
if temp is None:
return super().max_temp
else:
return temp
return self._max_temperature
def update(self):
"""Python-nest has its own mechanism for staying up to date."""
pass
"""Cache value from Python-nest."""
self._location = self.device.where
self._name = self.device.name
self._humidity = self.device.humidity,
self._temperature = self.device.temperature
self._mode = self.device.mode
self._target_temperature = self.device.target
self._fan = self.device.fan
self._away = self.structure.away == 'away'
self._eco_temperature = self.device.eco_temperature
self._locked_temperature = self.device.locked_temperature
self._min_temperature = self.device.min_temperature
self._max_temperature = self.device.max_temperature
self._is_locked = self.device.is_locked
if self.device.temperature_scale == 'C':
self._temperature_scale = TEMP_CELSIUS
else:
self._temperature_scale = TEMP_FAHRENHEIT
@@ -54,7 +54,6 @@ def setup_platform(hass, config, add_callback_devices, discovery_info=None):
return None
# pylint: disable=abstract-method
class NetatmoThermostat(ClimateDevice):
"""Representation a Netatmo thermostat."""
+12 -3
View File
@@ -7,12 +7,13 @@ https://home-assistant.io/components/climate.proliphix/
import voluptuous as vol
from homeassistant.components.climate import (
STATE_COOL, STATE_HEAT, STATE_IDLE, ClimateDevice, PLATFORM_SCHEMA)
PRECISION_TENTHS, STATE_COOL, STATE_HEAT, STATE_IDLE,
ClimateDevice, PLATFORM_SCHEMA)
from homeassistant.const import (
CONF_HOST, CONF_PASSWORD, CONF_USERNAME, TEMP_FAHRENHEIT, ATTR_TEMPERATURE)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['proliphix==0.4.0']
REQUIREMENTS = ['proliphix==0.4.1']
ATTR_FAN = 'fan'
@@ -36,7 +37,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
add_devices([ProliphixThermostat(pdp)])
# pylint: disable=abstract-method
class ProliphixThermostat(ClimateDevice):
"""Representation a Proliphix thermostat."""
@@ -61,6 +61,15 @@ class ProliphixThermostat(ClimateDevice):
"""Return the name of the thermostat."""
return self._name
@property
def precision(self):
"""Return the precision of the system.
Proliphix temperature values are passed back and forth in the
API as tenths of degrees F (i.e. 690 for 69 degrees).
"""
return PRECISION_TENTHS
@property
def device_state_attributes(self):
"""Return the device specific state attributes."""
+59 -15
View File
@@ -6,7 +6,6 @@ https://home-assistant.io/components/climate.radiotherm/
"""
import datetime
import logging
from urllib.error import URLError
import voluptuous as vol
@@ -24,10 +23,19 @@ ATTR_FAN = 'fan'
ATTR_MODE = 'mode'
CONF_HOLD_TEMP = 'hold_temp'
CONF_AWAY_TEMPERATURE_HEAT = 'away_temperature_heat'
CONF_AWAY_TEMPERATURE_COOL = 'away_temperature_cool'
DEFAULT_AWAY_TEMPERATURE_HEAT = 60
DEFAULT_AWAY_TEMPERATURE_COOL = 85
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_HOST): vol.All(cv.ensure_list, [cv.string]),
vol.Optional(CONF_HOLD_TEMP, default=False): cv.boolean,
vol.Optional(CONF_AWAY_TEMPERATURE_HEAT,
default=DEFAULT_AWAY_TEMPERATURE_HEAT): vol.Coerce(float),
vol.Optional(CONF_AWAY_TEMPERATURE_COOL,
default=DEFAULT_AWAY_TEMPERATURE_COOL): vol.Coerce(float),
})
@@ -46,24 +54,27 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
return False
hold_temp = config.get(CONF_HOLD_TEMP)
away_temps = [
config.get(CONF_AWAY_TEMPERATURE_HEAT),
config.get(CONF_AWAY_TEMPERATURE_COOL)
]
tstats = []
for host in hosts:
try:
tstat = radiotherm.get_thermostat(host)
tstats.append(RadioThermostat(tstat, hold_temp))
except (URLError, OSError):
tstats.append(RadioThermostat(tstat, hold_temp, away_temps))
except OSError:
_LOGGER.exception("Unable to connect to Radio Thermostat: %s",
host)
add_devices(tstats)
# pylint: disable=abstract-method
class RadioThermostat(ClimateDevice):
"""Representation of a Radio Thermostat."""
def __init__(self, device, hold_temp):
def __init__(self, device, hold_temp, away_temps):
"""Initialize the thermostat."""
self.device = device
self.set_time()
@@ -71,7 +82,12 @@ class RadioThermostat(ClimateDevice):
self._current_temperature = None
self._current_operation = STATE_IDLE
self._name = None
self.hold_temp = hold_temp
self._fmode = None
self._tmode = None
self._hold_temp = hold_temp
self._away = False
self._away_temps = away_temps
self._prev_temp = None
self.update()
self._operation_list = [STATE_AUTO, STATE_COOL, STATE_HEAT, STATE_OFF]
@@ -89,8 +105,8 @@ class RadioThermostat(ClimateDevice):
def device_state_attributes(self):
"""Return the device specific state attributes."""
return {
ATTR_FAN: self.device.fmode['human'],
ATTR_MODE: self.device.tmode['human']
ATTR_FAN: self._fmode,
ATTR_MODE: self._tmode,
}
@property
@@ -113,14 +129,22 @@ class RadioThermostat(ClimateDevice):
"""Return the temperature we try to reach."""
return self._target_temperature
@property
def is_away_mode_on(self):
"""Return true if away mode is on."""
return self._away
def update(self):
"""Update the data from the thermostat."""
self._current_temperature = self.device.temp['raw']
self._name = self.device.name['raw']
if self.device.tmode['human'] == 'Cool':
self._fmode = self.device.fmode['human']
self._tmode = self.device.tmode['human']
if self._tmode == 'Cool':
self._target_temperature = self.device.t_cool['raw']
self._current_operation = STATE_COOL
elif self.device.tmode['human'] == 'Heat':
elif self._tmode == 'Heat':
self._target_temperature = self.device.t_heat['raw']
self._current_operation = STATE_HEAT
else:
@@ -132,10 +156,10 @@ class RadioThermostat(ClimateDevice):
if temperature is None:
return
if self._current_operation == STATE_COOL:
self.device.t_cool = temperature
self.device.t_cool = round(temperature * 2.0) / 2.0
elif self._current_operation == STATE_HEAT:
self.device.t_heat = temperature
if self.hold_temp:
self.device.t_heat = round(temperature * 2.0) / 2.0
if self._hold_temp or self._away:
self.device.hold = 1
else:
self.device.hold = 0
@@ -156,6 +180,26 @@ class RadioThermostat(ClimateDevice):
elif operation_mode == STATE_AUTO:
self.device.tmode = 3
elif operation_mode == STATE_COOL:
self.device.t_cool = self._target_temperature
self.device.t_cool = round(self._target_temperature * 2.0) / 2.0
elif operation_mode == STATE_HEAT:
self.device.t_heat = self._target_temperature
self.device.t_heat = round(self._target_temperature * 2.0) / 2.0
def turn_away_mode_on(self):
"""Turn away on.
The RTCOA app simulates away mode by using a hold.
"""
away_temp = None
if not self._away:
self._prev_temp = self._target_temperature
if self._current_operation == STATE_HEAT:
away_temp = self._away_temps[0]
elif self._current_operation == STATE_COOL:
away_temp = self._away_temps[1]
self._away = True
self.set_temperature(temperature=away_temp)
def turn_away_mode_off(self):
"""Turn away off."""
self._away = False
self.set_temperature(temperature=self._prev_temp)
+11 -3
View File
@@ -8,7 +8,10 @@ import logging
from homeassistant.util import convert
from homeassistant.components.climate import ClimateDevice
from homeassistant.const import TEMP_FAHRENHEIT, ATTR_TEMPERATURE
from homeassistant.const import (
TEMP_FAHRENHEIT,
TEMP_CELSIUS,
ATTR_TEMPERATURE)
from homeassistant.components.vera import (
VeraDevice, VERA_DEVICES, VERA_CONTROLLER)
@@ -28,7 +31,6 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
device in VERA_DEVICES['climate'])
# pylint: disable=abstract-method
class VeraThermostat(VeraDevice, ClimateDevice):
"""Representation of a Vera Thermostat."""
@@ -95,7 +97,13 @@ class VeraThermostat(VeraDevice, ClimateDevice):
@property
def temperature_unit(self):
"""Return the unit of measurement."""
return TEMP_FAHRENHEIT
vera_temp_units = (
self.vera_device.vera_controller.temperature_units)
if vera_temp_units == 'F':
return TEMP_FAHRENHEIT
return TEMP_CELSIUS
@property
def current_temperature(self):
+331
View File
@@ -0,0 +1,331 @@
"""
Support for Wink thermostats.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.wink/
"""
from homeassistant.components.wink import WinkDevice
from homeassistant.components.climate import (
STATE_AUTO, STATE_COOL, STATE_HEAT, ClimateDevice,
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
ATTR_TEMPERATURE,
ATTR_CURRENT_HUMIDITY)
from homeassistant.const import (
TEMP_CELSIUS, STATE_ON,
STATE_OFF, STATE_UNKNOWN)
from homeassistant.loader import get_component
DEPENDENCIES = ['wink']
STATE_AUX = 'aux'
STATE_ECO = 'eco'
ATTR_EXTERNAL_TEMPERATURE = "external_temperature"
ATTR_SMART_TEMPERATURE = "smart_temperature"
ATTR_ECO_TARGET = "eco_target"
ATTR_OCCUPIED = "occupied"
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Wink thermostat."""
import pywink
temp_unit = hass.config.units.temperature_unit
add_devices(WinkThermostat(thermostat, hass, temp_unit)
for thermostat in pywink.get_thermostats())
# pylint: disable=abstract-method,too-many-public-methods, too-many-branches
class WinkThermostat(WinkDevice, ClimateDevice):
"""Representation of a Wink thermostat."""
def __init__(self, wink, hass, temp_unit):
"""Initialize the Wink device."""
super().__init__(wink, hass)
wink = get_component('wink')
self._config_temp_unit = temp_unit
@property
def temperature_unit(self):
"""Return the unit of measurement."""
# The Wink API always returns temp in Celsius
return TEMP_CELSIUS
@property
def device_state_attributes(self):
"""Return the optional state attributes."""
data = {}
target_temp_high = self.target_temperature_high
target_temp_low = self.target_temperature_low
if target_temp_high is not None:
data[ATTR_TARGET_TEMP_HIGH] = self._convert_for_display(
self.target_temperature_high)
if target_temp_low is not None:
data[ATTR_TARGET_TEMP_LOW] = self._convert_for_display(
self.target_temperature_low)
if self.external_temperature:
data[ATTR_EXTERNAL_TEMPERATURE] = self._convert_for_display(
self.external_temperature)
if self.smart_temperature:
data[ATTR_SMART_TEMPERATURE] = self.smart_temperature
if self.occupied:
data[ATTR_OCCUPIED] = self.occupied
if self.eco_target:
data[ATTR_ECO_TARGET] = self.eco_target
current_humidity = self.current_humidity
if current_humidity is not None:
data[ATTR_CURRENT_HUMIDITY] = current_humidity
return data
@property
def current_temperature(self):
"""Return the current temperature."""
return self.wink.current_temperature()
@property
def current_humidity(self):
"""Return the current humidity."""
if self.wink.current_humidity() is not None:
# The API states humidity will be a float 0-1
# the only example API response with humidity listed show an int
# This will address both possibilities
if self.wink.current_humidity() < 1:
return self.wink.current_humidity() * 100
else:
return self.wink.current_humidity()
@property
def external_temperature(self):
"""Return the current external temperature."""
return self.wink.current_external_temperature()
@property
def smart_temperature(self):
"""Return the current average temp of all remote sensor."""
return self.wink.current_smart_temperature()
@property
def eco_target(self):
"""Return status of eco target (Is the termostat in eco mode)."""
return self.wink.eco_target()
@property
def occupied(self):
"""Return status of if the thermostat has detected occupancy."""
return self.wink.occupied()
@property
def current_operation(self):
"""Return current operation ie. heat, cool, idle."""
if not self.wink.is_on():
current_op = STATE_OFF
elif self.wink.current_hvac_mode() == 'cool_only':
current_op = STATE_COOL
elif self.wink.current_hvac_mode() == 'heat_only':
current_op = STATE_HEAT
elif self.wink.current_hvac_mode() == 'aux':
current_op = STATE_HEAT
elif self.wink.current_hvac_mode() == 'auto':
current_op = STATE_AUTO
elif self.wink.current_hvac_mode() == 'eco':
current_op = STATE_ECO
else:
current_op = STATE_UNKNOWN
return current_op
@property
def target_humidity(self):
"""Return the humidity we try to reach."""
target_hum = None
if self.wink.current_humidifier_mode() == 'on':
if self.wink.current_humidifier_set_point() is not None:
target_hum = self.wink.current_humidifier_set_point() * 100
elif self.wink.current_dehumidifier_mode() == 'on':
if self.wink.current_dehumidifier_set_point() is not None:
target_hum = self.wink.current_dehumidifier_set_point() * 100
else:
target_hum = None
return target_hum
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
if self.current_operation != STATE_AUTO and not self.is_away_mode_on:
if self.current_operation == STATE_COOL:
return self.wink.current_max_set_point()
elif self.current_operation == STATE_HEAT:
return self.wink.current_min_set_point()
else:
return None
else:
return None
@property
def target_temperature_low(self):
"""Return the lower bound temperature we try to reach."""
if self.current_operation == STATE_AUTO:
return self.wink.current_min_set_point()
return None
@property
def target_temperature_high(self):
"""Return the higher bound temperature we try to reach."""
if self.current_operation == STATE_AUTO:
return self.wink.current_max_set_point()
return None
@property
def is_away_mode_on(self):
"""Return if away mode is on."""
return self.wink.away()
@property
def is_aux_heat_on(self):
"""Return true if aux heater."""
if self.wink.current_hvac_mode() == 'aux' and self.wink.is_on():
return True
elif self.wink.current_hvac_mode() == 'aux' and not self.wink.is_on():
return False
else:
return None
def set_temperature(self, **kwargs):
"""Set new target temperature."""
target_temp = kwargs.get(ATTR_TEMPERATURE)
target_temp_low = kwargs.get(ATTR_TARGET_TEMP_LOW)
target_temp_high = kwargs.get(ATTR_TARGET_TEMP_HIGH)
if target_temp is not None:
if self.current_operation == STATE_COOL:
target_temp_high = target_temp
if self.current_operation == STATE_HEAT:
target_temp_low = target_temp
if target_temp_low is not None:
target_temp_low = target_temp_low
if target_temp_high is not None:
target_temp_high = target_temp_high
self.wink.set_temperature(target_temp_low, target_temp_high)
def set_operation_mode(self, operation_mode):
"""Set operation mode."""
if operation_mode == STATE_HEAT:
self.wink.set_operation_mode('heat_only')
elif operation_mode == STATE_COOL:
self.wink.set_operation_mode('cool_only')
elif operation_mode == STATE_AUTO:
self.wink.set_operation_mode('auto')
elif operation_mode == STATE_OFF:
self.wink.set_operation_mode('off')
elif operation_mode == STATE_AUX:
self.wink.set_operation_mode('aux')
elif operation_mode == STATE_ECO:
self.wink.set_operation_mode('eco')
@property
def operation_list(self):
"""List of available operation modes."""
op_list = ['off']
modes = self.wink.hvac_modes()
if 'cool_only' in modes:
op_list.append(STATE_COOL)
if 'heat_only' in modes or 'aux' in modes:
op_list.append(STATE_HEAT)
if 'auto' in modes:
op_list.append(STATE_AUTO)
if 'eco' in modes:
op_list.append(STATE_ECO)
return op_list
def turn_away_mode_on(self):
"""Turn away on."""
self.wink.set_away_mode()
def turn_away_mode_off(self):
"""Turn away off."""
self.wink.set_away_mode(False)
@property
def current_fan_mode(self):
"""Return whether the fan is on."""
if self.wink.current_fan_mode() == 'on':
return STATE_ON
elif self.wink.current_fan_mode() == 'auto':
return STATE_AUTO
else:
# No Fan available so disable slider
return None
@property
def fan_list(self):
"""List of available fan modes."""
if self.wink.has_fan():
return self.wink.fan_modes()
return None
def set_fan_mode(self, fan):
"""Turn fan on/off."""
self.wink.set_fan_mode(fan.lower())
def turn_aux_heat_on(self):
"""Turn auxillary heater on."""
self.set_operation_mode(STATE_AUX)
def turn_aux_heat_off(self):
"""Turn auxillary heater off."""
self.set_operation_mode(STATE_AUTO)
@property
def min_temp(self):
"""Return the minimum temperature."""
minimum = 7 # Default minimum
min_min = self.wink.min_min_set_point()
min_max = self.wink.min_max_set_point()
return_value = minimum
if self.current_operation == STATE_HEAT:
if min_min:
return_value = min_min
else:
return_value = minimum
elif self.current_operation == STATE_COOL:
if min_max:
return_value = min_max
else:
return_value = minimum
elif self.current_operation == STATE_AUTO:
if min_min and min_max:
return_value = min(min_min, min_max)
else:
return_value = minimum
else:
return_value = minimum
return return_value
@property
def max_temp(self):
"""Return the maximum temperature."""
maximum = 35 # Default maximum
max_min = self.wink.max_min_set_point()
max_max = self.wink.max_max_set_point()
return_value = maximum
if self.current_operation == STATE_HEAT:
if max_min:
return_value = max_min
else:
return_value = maximum
elif self.current_operation == STATE_COOL:
if max_max:
return_value = max_max
else:
return_value = maximum
elif self.current_operation == STATE_AUTO:
if max_min and max_max:
return_value = min(max_min, max_max)
else:
return_value = maximum
else:
return_value = maximum
return return_value
+68 -109
View File
@@ -1,5 +1,5 @@
"""
Support for ZWave climate devices.
Support for Z-Wave climate devices.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.zwave/
@@ -8,8 +8,7 @@ https://home-assistant.io/components/climate.zwave/
# pylint: disable=import-error
import logging
from homeassistant.components.climate import DOMAIN
from homeassistant.components.climate import (
ClimateDevice, ATTR_OPERATION_MODE)
from homeassistant.components.climate import ClimateDevice
from homeassistant.components.zwave import ZWaveDeviceEntity
from homeassistant.components import zwave
from homeassistant.const import (
@@ -18,44 +17,23 @@ from homeassistant.const import (
_LOGGER = logging.getLogger(__name__)
CONF_NAME = 'name'
DEFAULT_NAME = 'ZWave Climate'
DEFAULT_NAME = 'Z-Wave Climate'
REMOTEC = 0x5254
REMOTEC_ZXT_120 = 0x8377
REMOTEC_ZXT_120_THERMOSTAT = (REMOTEC, REMOTEC_ZXT_120)
HORSTMANN = 0x0059
HORSTMANN_HRT4_ZW = 0x3
HORSTMANN_HRT4_ZW_THERMOSTAT = (HORSTMANN, HORSTMANN_HRT4_ZW)
ATTR_OPERATING_STATE = 'operating_state'
ATTR_FAN_STATE = 'fan_state'
WORKAROUND_ZXT_120 = 'zxt_120'
WORKAROUND_HRT4_ZW = 'hrt4_zw'
DEVICE_MAPPINGS = {
REMOTEC_ZXT_120_THERMOSTAT: WORKAROUND_ZXT_120,
HORSTMANN_HRT4_ZW_THERMOSTAT: WORKAROUND_HRT4_ZW
}
SET_TEMP_TO_INDEX = {
'Heat': 1,
'Cool': 2,
'Auto': 3,
'Aux Heat': 4,
'Resume': 5,
'Fan Only': 6,
'Furnace': 7,
'Dry Air': 8,
'Moist Air': 9,
'Auto Changeover': 10,
'Heat Econ': 11,
'Cool Econ': 12,
'Away': 13,
'Unknown': 14
REMOTEC_ZXT_120_THERMOSTAT: WORKAROUND_ZXT_120
}
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the ZWave Climate devices."""
"""Set up the Z-Wave Climate devices."""
if discovery_info is None or zwave.NETWORK is None:
_LOGGER.debug("No discovery_info=%s or no NETWORK=%s",
discovery_info, zwave.NETWORK)
@@ -69,30 +47,29 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
discovery_info, zwave.NETWORK)
# pylint: disable=abstract-method
class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
"""Represents a ZWave Climate device."""
"""Representation of a Z-Wave Climate device."""
# pylint: disable=too-many-instance-attributes
def __init__(self, value, temp_unit):
"""Initialize the zwave climate device."""
"""Initialize the Z-Wave climate device."""
from openzwave.network import ZWaveNetwork
from pydispatch import dispatcher
ZWaveDeviceEntity.__init__(self, value, DOMAIN)
self._index = value.index
self._node = value.node
self._target_temperature = None
self._current_temperature = None
self._current_operation = None
self._operation_list = None
self._operating_state = None
self._current_fan_mode = None
self._fan_list = None
self._fan_state = None
self._current_swing_mode = None
self._swing_list = None
self._unit = temp_unit
self._index_operation = None
_LOGGER.debug("temp_unit is %s", self._unit)
self._zxt_120 = None
self._hrt4_zw = None
self.update_properties()
# register listener
dispatcher.connect(
@@ -107,17 +84,13 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
_LOGGER.debug("Remotec ZXT-120 Zwave Thermostat"
" workaround")
self._zxt_120 = 1
if DEVICE_MAPPINGS[specific_sensor_key] == WORKAROUND_HRT4_ZW:
_LOGGER.debug("Horstmann HRT4-ZW Zwave Thermostat"
" workaround")
self._hrt4_zw = 1
def value_changed(self, value):
"""Called when a value has changed on the network."""
if self._value.value_id == value.value_id or \
self._value.node == value.node:
self.update_properties()
self.update_ha_state()
self.schedule_update_ha_state()
_LOGGER.debug("Value changed on network %s", value)
def update_properties(self):
@@ -126,23 +99,23 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
for value in self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_MODE).values():
self._current_operation = value.data
self._index_operation = SET_TEMP_TO_INDEX.get(
self._current_operation)
self._operation_list = list(value.data_items)
_LOGGER.debug("self._operation_list=%s", self._operation_list)
_LOGGER.debug("self._current_operation=%s",
self._current_operation)
# Current Temp
for value in (self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_SENSOR_MULTILEVEL)
.values()):
for value in (
self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_SENSOR_MULTILEVEL)
.values()):
if value.label == 'Temperature':
self._current_temperature = int(value.data)
self._current_temperature = round((float(value.data)), 1)
self._unit = value.units
# Fan Mode
for value in (self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_FAN_MODE)
.values()):
for value in (
self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_FAN_MODE)
.values()):
self._current_fan_mode = value.data
self._fan_list = list(value.data_items)
_LOGGER.debug("self._fan_list=%s", self._fan_list)
@@ -150,9 +123,10 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
self._current_fan_mode)
# Swing mode
if self._zxt_120 == 1:
for value in (self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_CONFIGURATION)
.values()):
for value in (
self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_CONFIGURATION)
.values()):
if value.command_class == \
zwave.const.COMMAND_CLASS_CONFIGURATION and \
value.index == 33:
@@ -162,30 +136,39 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
_LOGGER.debug("self._current_swing_mode=%s",
self._current_swing_mode)
# Set point
for value in (self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_SETPOINT)
.values()):
if value.data == 0:
_LOGGER.debug("Setpoint is 0, setting default to "
"current_temperature=%s",
self._current_temperature)
self._target_temperature = int(self._current_temperature)
break
if self.current_operation is not None and \
self.current_operation != 'Off':
if self._index_operation != value.index:
continue
if self._zxt_120:
temps = []
for value in (
self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_SETPOINT)
.values()):
temps.append((round(float(value.data)), 1))
if value.index == self._index:
if value.data == 0:
_LOGGER.debug("Setpoint is 0, setting default to "
"current_temperature=%s",
self._current_temperature)
self._target_temperature = (
round((float(self._current_temperature)), 1))
break
self._target_temperature = int(value.data)
break
_LOGGER.debug("Device can't set setpoint based on operation mode."
" Defaulting to index=1")
self._target_temperature = int(value.data)
else:
self._target_temperature = round((float(value.data)), 1)
# Operating state
for value in (
self._node.get_values(
class_id=zwave.const
.COMMAND_CLASS_THERMOSTAT_OPERATING_STATE).values()):
self._operating_state = value.data
# Fan operating state
for value in (
self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_FAN_STATE)
.values()):
self._fan_state = value.data
@property
def should_poll(self):
"""No polling on ZWave."""
"""No polling on Z-Wave."""
return False
@property
@@ -244,53 +227,19 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
temperature = kwargs.get(ATTR_TEMPERATURE)
else:
return
operation_mode = kwargs.get(ATTR_OPERATION_MODE)
_LOGGER.debug("set_temperature operation_mode=%s", operation_mode)
for value in (self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_SETPOINT)
.values()):
if operation_mode is not None:
setpoint_mode = SET_TEMP_TO_INDEX.get(operation_mode)
if value.index != setpoint_mode:
continue
_LOGGER.debug("setpoint_mode=%s", setpoint_mode)
value.data = temperature
break
if self.current_operation is not None:
if self._hrt4_zw and self.current_operation == 'Off':
# HRT4-ZW can change setpoint when off.
value.data = int(temperature)
if self._index_operation != value.index:
continue
_LOGGER.debug("self._index_operation=%s and"
" self._current_operation=%s",
self._index_operation,
self._current_operation)
if value.index == self._index:
if self._zxt_120:
_LOGGER.debug("zxt_120: Setting new setpoint for %s, "
" operation=%s, temp=%s",
self._index_operation,
self._current_operation, temperature)
# ZXT-120 does not support get setpoint
self._target_temperature = temperature
# ZXT-120 responds only to whole int
value.data = round(temperature, 0)
self._target_temperature = temperature
self.update_ha_state()
break
else:
_LOGGER.debug("Setting new setpoint for %s, "
"operation=%s, temp=%s",
self._index_operation,
self._current_operation, temperature)
value.data = temperature
break
else:
_LOGGER.debug("Setting new setpoint for no known "
"operation mode. Index=1 and "
"temperature=%s", temperature)
value.data = temperature
self.update_ha_state()
break
def set_fan_mode(self, fan):
@@ -323,3 +272,13 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
value.index == 33:
value.data = bytes(swing_mode, 'utf-8')
break
@property
def device_state_attributes(self):
"""Return the device specific state attributes."""
data = super().device_state_attributes
if self._operating_state:
data[ATTR_OPERATING_STATE] = self._operating_state,
if self._fan_state:
data[ATTR_FAN_STATE] = self._fan_state
return data
+7 -6
View File
@@ -8,7 +8,8 @@ the user has submitted configuration information.
"""
import logging
from homeassistant.const import EVENT_TIME_CHANGED, ATTR_FRIENDLY_NAME
from homeassistant.const import EVENT_TIME_CHANGED, ATTR_FRIENDLY_NAME, \
ATTR_ENTITY_PICTURE
from homeassistant.helpers.entity import generate_entity_id
_INSTANCES = {}
@@ -33,10 +34,10 @@ STATE_CONFIGURE = 'configure'
STATE_CONFIGURED = 'configured'
# pylint: disable=too-many-arguments
def request_config(
hass, name, callback, description=None, description_image=None,
submit_caption=None, fields=None, link_name=None, link_url=None):
submit_caption=None, fields=None, link_name=None, link_url=None,
entity_picture=None):
"""Create a new request for configuration.
Will return an ID to be used for sequent calls.
@@ -46,7 +47,7 @@ def request_config(
request_id = instance.request_config(
name, callback,
description, description_image, submit_caption,
fields, link_name, link_url)
fields, link_name, link_url, entity_picture)
_REQUESTS[request_id] = instance
@@ -100,11 +101,10 @@ class Configurator(object):
hass.services.register(
DOMAIN, SERVICE_CONFIGURE, self.handle_service_call)
# pylint: disable=too-many-arguments
def request_config(
self, name, callback,
description, description_image, submit_caption,
fields, link_name, link_url):
fields, link_name, link_url, entity_picture):
"""Setup a request for configuration."""
entity_id = generate_entity_id(ENTITY_ID_FORMAT, name, hass=self.hass)
@@ -119,6 +119,7 @@ class Configurator(object):
ATTR_CONFIGURE_ID: request_id,
ATTR_FIELDS: fields,
ATTR_FRIENDLY_NAME: name,
ATTR_ENTITY_PICTURE: entity_picture,
}
data.update({
+1 -1
View File
@@ -15,7 +15,7 @@ from homeassistant.const import (
ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['fuzzywuzzy==0.12.0']
REQUIREMENTS = ['fuzzywuzzy==0.14.0']
ATTR_TEXT = 'text'
+12 -5
View File
@@ -135,12 +135,19 @@ def setup(hass, config):
params = service.data.copy()
params.pop(ATTR_ENTITY_ID, None)
if method:
for cover in component.extract_from_service(service):
getattr(cover, method['method'])(**params)
if not method:
return
if cover.should_poll:
cover.update_ha_state(True)
covers = component.extract_from_service(service)
for cover in covers:
getattr(cover, method['method'])(**params)
for cover in covers:
if not cover.should_poll:
continue
cover.update_ha_state(True)
descriptions = load_yaml_config_file(
os.path.join(os.path.dirname(__file__), 'services.yaml'))
@@ -60,11 +60,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
add_devices(covers)
# pylint: disable=too-many-arguments, too-many-instance-attributes
class CommandCover(CoverDevice):
"""Representation a command line cover."""
# pylint: disable=too-many-arguments
def __init__(self, hass, name, command_open, command_close, command_stop,
command_state, value_template):
"""Initialize the cover."""
+1 -1
View File
@@ -20,7 +20,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class DemoCover(CoverDevice):
"""Representation of a demo cover."""
# pylint: disable=no-self-use, too-many-instance-attributes
# pylint: disable=no-self-use
def __init__(self, hass, name, position=None, tilt_position=None):
"""Initialize the cover."""
self.hass = hass
+275
View File
@@ -0,0 +1,275 @@
"""
Platform for the garadget cover component.
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/garadget/
"""
import logging
import voluptuous as vol
import requests
from homeassistant.components.cover import CoverDevice, PLATFORM_SCHEMA
from homeassistant.helpers.event import track_utc_time_change
from homeassistant.const import CONF_DEVICE, CONF_USERNAME, CONF_PASSWORD,\
CONF_ACCESS_TOKEN, CONF_NAME, STATE_UNKNOWN, STATE_CLOSED, STATE_OPEN,\
CONF_COVERS
import homeassistant.helpers.config_validation as cv
DEFAULT_NAME = 'Garadget'
ATTR_SIGNAL_STRENGTH = "wifi signal strength (dB)"
ATTR_TIME_IN_STATE = "time in state"
ATTR_SENSOR_STRENGTH = "sensor reflection rate"
ATTR_AVAILABLE = "available"
STATE_OPENING = "opening"
STATE_CLOSING = "closing"
STATE_STOPPED = "stopped"
STATE_OFFLINE = "offline"
STATES_MAP = {
"open": STATE_OPEN,
"opening": STATE_OPENING,
"closed": STATE_CLOSED,
"closing": STATE_CLOSING,
"stopped": STATE_STOPPED
}
# Validation of the user's configuration
COVER_SCHEMA = vol.Schema({
vol.Optional(CONF_DEVICE): cv.string,
vol.Optional(CONF_USERNAME): cv.string,
vol.Optional(CONF_PASSWORD): cv.string,
vol.Optional(CONF_ACCESS_TOKEN): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string
})
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_COVERS): vol.Schema({cv.slug: COVER_SCHEMA}),
})
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Demo covers."""
covers = []
devices = config.get(CONF_COVERS, {})
_LOGGER.debug(devices)
for device_id, device_config in devices.items():
args = {
"name": device_config.get(CONF_NAME),
"device_id": device_config.get(CONF_DEVICE, device_id),
"username": device_config.get(CONF_USERNAME),
"password": device_config.get(CONF_PASSWORD),
"access_token": device_config.get(CONF_ACCESS_TOKEN)
}
covers.append(GaradgetCover(hass, args))
add_devices(covers)
class GaradgetCover(CoverDevice):
"""Representation of a demo cover."""
# pylint: disable=no-self-use, too-many-instance-attributes
def __init__(self, hass, args):
"""Initialize the cover."""
self.particle_url = 'https://api.particle.io'
self.hass = hass
self._name = args['name']
self.device_id = args['device_id']
self.access_token = args['access_token']
self.obtained_token = False
self._username = args['username']
self._password = args['password']
self._state = STATE_UNKNOWN
self.time_in_state = None
self.signal = None
self.sensor = None
self._unsub_listener_cover = None
self._available = True
if self.access_token is None:
self.access_token = self.get_token()
self._obtained_token = True
# Lets try to get the configured name if not provided.
try:
if self._name is None:
doorconfig = self._get_variable("doorConfig")
if doorconfig["nme"] is not None:
self._name = doorconfig["nme"]
self.update()
except requests.exceptions.ConnectionError as ex:
_LOGGER.error('Unable to connect to server: %(reason)s',
dict(reason=ex))
self._state = STATE_OFFLINE
self._available = False
self._name = DEFAULT_NAME
except KeyError as ex:
_LOGGER.warning('Garadget device %(device)s seems to be offline',
dict(device=self.device_id))
self._name = DEFAULT_NAME
self._state = STATE_OFFLINE
self._available = False
def __del__(self):
"""Try to remove token."""
if self._obtained_token is True:
if self.access_token is not None:
self.remove_token()
@property
def name(self):
"""Return the name of the cover."""
return self._name
@property
def should_poll(self):
"""No polling needed for a demo cover."""
return True
@property
def available(self):
"""Return True if entity is available."""
return self._available
@property
def device_state_attributes(self):
"""Return the device state attributes."""
data = {}
if self.signal is not None:
data[ATTR_SIGNAL_STRENGTH] = self.signal
if self.time_in_state is not None:
data[ATTR_TIME_IN_STATE] = self.time_in_state
if self.sensor is not None:
data[ATTR_SENSOR_STRENGTH] = self.sensor
if self.access_token is not None:
data[CONF_ACCESS_TOKEN] = self.access_token
return data
@property
def is_closed(self):
"""Return if the cover is closed."""
if self._state == STATE_UNKNOWN:
return None
else:
return self._state == STATE_CLOSED
def get_token(self):
"""Get new token for usage during this session."""
args = {
'grant_type': 'password',
'username': self._username,
'password': self._password
}
url = '{}/oauth/token'.format(self.particle_url)
ret = requests.post(url,
auth=('particle', 'particle'),
data=args)
return ret.json()['access_token']
def remove_token(self):
"""Remove authorization token from API."""
ret = requests.delete('{}/v1/access_tokens/{}'.format(
self.particle_url,
self.access_token),
auth=(self._username, self._password))
return ret.text
def _start_watcher(self, command):
"""Start watcher."""
_LOGGER.debug("Starting Watcher for command: %s ", command)
if self._unsub_listener_cover is None:
self._unsub_listener_cover = track_utc_time_change(
self.hass, self._check_state)
def _check_state(self, now):
"""Check the state of the service during an operation."""
self.update()
self.update_ha_state()
def close_cover(self):
"""Close the cover."""
if self._state not in ["close", "closing"]:
ret = self._put_command("setState", "close")
self._start_watcher('close')
return ret.get('return_value') == 1
def open_cover(self):
"""Open the cover."""
if self._state not in ["open", "opening"]:
ret = self._put_command("setState", "open")
self._start_watcher('open')
return ret.get('return_value') == 1
def stop_cover(self):
"""Stop the door where it is."""
if self._state not in ["stopped"]:
ret = self._put_command("setState", "stop")
self._start_watcher('stop')
return ret['return_value'] == 1
def update(self):
"""Get updated status from API."""
try:
status = self._get_variable("doorStatus")
_LOGGER.debug("Current Status: %s", status['status'])
self._state = STATES_MAP.get(status['status'], STATE_UNKNOWN)
self.time_in_state = status['time']
self.signal = status['signal']
self.sensor = status['sensor']
self._availble = True
except requests.exceptions.ConnectionError as ex:
_LOGGER.error('Unable to connect to server: %(reason)s',
dict(reason=ex))
self._state = STATE_OFFLINE
except KeyError as ex:
_LOGGER.warning('Garadget device %(device)s seems to be offline',
dict(device=self.device_id))
self._state = STATE_OFFLINE
if self._state not in [STATE_CLOSING, STATE_OPENING]:
if self._unsub_listener_cover is not None:
self._unsub_listener_cover()
self._unsub_listener_cover = None
def _get_variable(self, var):
"""Get latest status."""
url = '{}/v1/devices/{}/{}?access_token={}'.format(
self.particle_url,
self.device_id,
var,
self.access_token,
)
ret = requests.get(url)
result = {}
for pairs in ret.json()['result'].split('|'):
key = pairs.split('=')
result[key[0]] = key[1]
return result
def _put_command(self, func, arg=None):
"""Send commands to API."""
params = {'access_token': self.access_token}
if arg:
params['command'] = arg
url = '{}/v1/devices/{}/{}'.format(
self.particle_url,
self.device_id,
func)
ret = requests.post(url, data=params)
return ret.json()
+5 -3
View File
@@ -12,7 +12,8 @@ import logging
from homeassistant.const import STATE_UNKNOWN
from homeassistant.components.cover import CoverDevice,\
ATTR_POSITION
import homeassistant.components.homematic as homematic
from homeassistant.components.homematic import HMDevice
from homeassistant.loader import get_component
_LOGGER = logging.getLogger(__name__)
@@ -24,15 +25,16 @@ def setup_platform(hass, config, add_callback_devices, discovery_info=None):
if discovery_info is None:
return
homematic = get_component("homematic")
return homematic.setup_hmdevice_discovery_helper(
hass,
HMCover,
discovery_info,
add_callback_devices
)
# pylint: disable=abstract-method
class HMCover(homematic.HMDevice, CoverDevice):
class HMCover(HMDevice, CoverDevice):
"""Represents a Homematic Cover in Home Assistant."""
@property
+7 -6
View File
@@ -8,6 +8,7 @@ import logging
import voluptuous as vol
from homeassistant.core import callback
import homeassistant.components.mqtt as mqtt
from homeassistant.components.cover import CoverDevice
from homeassistant.const import (
@@ -67,7 +68,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
)])
# pylint: disable=too-many-arguments, too-many-instance-attributes
class MqttCover(CoverDevice):
"""Representation of a cover that can be controlled using MQTT."""
@@ -90,29 +90,30 @@ class MqttCover(CoverDevice):
self._retain = retain
self._optimistic = optimistic or state_topic is None
@callback
def message_received(topic, payload, qos):
"""A new MQTT message has been received."""
if value_template is not None:
payload = value_template.render_with_possible_json_value(
payload = value_template.async_render_with_possible_json_value(
payload)
if payload == self._state_open:
self._state = False
_LOGGER.warning("state=%s", int(self._state))
self.update_ha_state()
hass.async_add_job(self.async_update_ha_state())
elif payload == self._state_closed:
self._state = True
self.update_ha_state()
hass.async_add_job(self.async_update_ha_state())
elif payload.isnumeric() and 0 <= int(payload) <= 100:
if int(payload) > 0:
self._state = False
else:
self._state = True
self._position = int(payload)
self.update_ha_state()
hass.async_add_job(self.async_update_ha_state())
else:
_LOGGER.warning(
"Payload is not True, False, or integer (0-100): %s",
payload)
if self._state_topic is None:
# Force into optimistic mode.
self._optimistic = True
+6 -1
View File
@@ -18,7 +18,12 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the mysensors platform for covers."""
if discovery_info is None:
return
for gateway in mysensors.GATEWAYS.values():
gateways = hass.data.get(mysensors.MYSENSORS_GATEWAYS)
if not gateways:
return
for gateway in gateways:
pres = gateway.const.Presentation
set_req = gateway.const.SetReq
map_sv_types = {
-1
View File
@@ -40,7 +40,6 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
rfxtrx.RECEIVED_EVT_SUBSCRIBERS.append(cover_update)
# pylint: disable=abstract-method
class RfxtrxCover(rfxtrx.RfxtrxDevice, CoverDevice):
"""Representation of an rfxtrx cover."""
@@ -63,11 +63,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
add_devices(covers)
# pylint: disable=abstract-method
class RPiGPIOCover(CoverDevice):
"""Representation of a Raspberry GPIO cover."""
# pylint: disable=too-many-arguments
def __init__(self, name, relay_pin, state_pin, state_pull_mode,
relay_time):
"""Initialize the cover."""
@@ -45,7 +45,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
add_devices(covers)
# pylint: disable=too-many-arguments, too-many-instance-attributes
class SCSGateCover(CoverDevice):
"""Representation of SCSGate cover."""
@@ -0,0 +1,46 @@
"""
Support for Tellstick covers using Tellstick Net.
This platform uses the Telldus Live online service.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/cover.tellduslive/
"""
import logging
from homeassistant.components.cover import CoverDevice
from homeassistant.components.tellduslive import TelldusLiveEntity
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup covers."""
if discovery_info is None:
return
add_devices(TelldusLiveCover(hass, cover) for cover in discovery_info)
class TelldusLiveCover(TelldusLiveEntity, CoverDevice):
"""Representation of a cover."""
@property
def is_closed(self):
"""Return the current position of the cover."""
return self.device.is_down
def close_cover(self, **kwargs):
"""Close the cover."""
self.device.down()
self.changed()
def open_cover(self, **kwargs):
"""Open the cover."""
self.device.up()
self.changed()
def stop_cover(self, **kwargs):
"""Stop the cover."""
self.device.stop()
self.changed()
+2 -3
View File
@@ -15,14 +15,13 @@ DEPENDENCIES = ['vera']
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Find and return Vera covers."""
add_devices_callback(
add_devices(
VeraCover(device, VERA_CONTROLLER) for
device in VERA_DEVICES['cover'])
# pylint: disable=abstract-method
class VeraCover(VeraDevice, CoverDevice):
"""Represents a Vera Cover in Home Assistant."""
+4 -4
View File
@@ -15,18 +15,18 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Wink cover platform."""
import pywink
add_devices(WinkCoverDevice(shade) for shade in
add_devices(WinkCoverDevice(shade, hass) for shade in
pywink.get_shades())
add_devices(WinkCoverDevice(door) for door in
add_devices(WinkCoverDevice(door, hass) for door in
pywink.get_garage_doors())
class WinkCoverDevice(WinkDevice, CoverDevice):
"""Representation of a Wink cover device."""
def __init__(self, wink):
def __init__(self, wink, hass):
"""Initialize the cover."""
WinkDevice.__init__(self, wink)
WinkDevice.__init__(self, wink, hass)
def close_cover(self):
"""Close the shade."""
+8 -8
View File
@@ -32,14 +32,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
node = zwave.NETWORK.nodes[discovery_info[zwave.const.ATTR_NODE_ID]]
value = node.values[discovery_info[zwave.const.ATTR_VALUE_ID]]
if node.has_command_class(zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL) \
and value.index == 0:
if (value.command_class == zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL
and value.index == 0):
value.set_change_verified(False)
add_devices([ZwaveRollershutter(value)])
elif node.has_command_class(zwave.const.COMMAND_CLASS_SWITCH_BINARY) or \
node.has_command_class(zwave.const.COMMAND_CLASS_BARRIER_OPERATOR):
if value.type != zwave.const.TYPE_BOOL and \
value.genre != zwave.const.GENRE_USER:
elif (value.command_class == zwave.const.COMMAND_CLASS_SWITCH_BINARY or
value.command_class == zwave.const.COMMAND_CLASS_BARRIER_OPERATOR):
if (value.type != zwave.const.TYPE_BOOL and
value.genre != zwave.const.GENRE_USER):
return
value.set_change_verified(False)
add_devices([ZwaveGarageDoor(value)])
@@ -122,7 +122,7 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice):
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL and value.label == \
'Open' or value.command_class == \
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL and value.label == \
'Down':
'Up':
self._lozwmgr.pressButton(value.value_id)
break
@@ -132,7 +132,7 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice):
class_id=zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL).values():
if value.command_class == \
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL and value.label == \
'Up' or value.command_class == \
'Down' or value.command_class == \
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL and value.label == \
'Close':
self._lozwmgr.pressButton(value.value_id)
+2 -6
View File
@@ -17,6 +17,7 @@ DOMAIN = 'demo'
COMPONENTS_WITH_DEMO_PLATFORM = [
'alarm_control_panel',
'binary_sensor',
'calendar',
'camera',
'climate',
'cover',
@@ -85,16 +86,11 @@ def setup(hass, config):
group.Group.create_group(hass, 'people', [
'device_tracker.demo_anne_therese', 'device_tracker.demo_home_boy',
'device_tracker.demo_paulus'])
group.Group.create_group(hass, 'thermostats', [
'thermostat.nest', 'thermostat.thermostat'])
group.Group.create_group(hass, 'downstairs', [
'group.living_room', 'group.kitchen',
'scene.romantic_lights', 'rollershutter.kitchen_window',
'rollershutter.living_room_window', 'group.doors',
'thermostat.nest',
], view=True)
group.Group.create_group(hass, 'Upstairs', [
'thermostat.thermostat', 'group.bedroom',
'thermostat.ecobee',
], view=True)
# Setup scripts
@@ -9,6 +9,7 @@ from datetime import timedelta
import voluptuous as vol
from homeassistant.core import callback
import homeassistant.util.dt as dt_util
from homeassistant.const import STATE_HOME, STATE_NOT_HOME
from homeassistant.helpers.event import track_point_in_time
@@ -41,7 +42,6 @@ CONFIG_SCHEMA = vol.Schema({
}, extra=vol.ALLOW_EXTRA)
# pylint: disable=too-many-locals
def setup(hass, config):
"""The triggers to turn lights on or off based on device presence."""
logger = logging.getLogger(__name__)
@@ -80,21 +80,22 @@ def setup(hass, config):
return None
return next_setting - LIGHT_TRANSITION_TIME * len(light_ids)
def turn_light_on_before_sunset(light_id):
def async_turn_on_before_sunset(light_id):
"""Helper function to turn on lights.
Speed is slow if there are devices home and the light is not on yet.
"""
if not device_tracker.is_on(hass) or light.is_on(hass, light_id):
return
light.turn_on(hass, light_id,
transition=LIGHT_TRANSITION_TIME.seconds,
profile=light_profile)
light.async_turn_on(hass, light_id,
transition=LIGHT_TRANSITION_TIME.seconds,
profile=light_profile)
# Track every time sun rises so we can schedule a time-based
# pre-sun set event
@track_state_change(sun.ENTITY_ID, sun.STATE_BELOW_HORIZON,
sun.STATE_ABOVE_HORIZON)
@callback
def schedule_lights_at_sun_set(hass, entity, old_state, new_state):
"""The moment sun sets we want to have all the lights on.
@@ -105,16 +106,21 @@ def setup(hass, config):
if not start_point:
return
def turn_on(light_id):
def async_turn_on_factory(light_id):
"""Lambda can keep track of function parameters.
No local parameters. If we put the lambda directly in the below
statement only the last light will be turned on.
"""
return lambda now: turn_light_on_before_sunset(light_id)
@callback
def async_turn_on_light(now):
"""Turn on specific light."""
async_turn_on_before_sunset(light_id)
return async_turn_on_light
for index, light_id in enumerate(light_ids):
track_point_in_time(hass, turn_on(light_id),
track_point_in_time(hass, async_turn_on_factory(light_id),
start_point + index * LIGHT_TRANSITION_TIME)
# If the sun is already above horizon schedule the time-based pre-sun set
@@ -123,6 +129,7 @@ def setup(hass, config):
schedule_lights_at_sun_set(hass, None, None, None)
@track_state_change(device_entity_ids, STATE_NOT_HOME, STATE_HOME)
@callback
def check_light_on_dev_state_change(hass, entity, old_state, new_state):
"""Handle tracked device state changes."""
# pylint: disable=unused-variable
@@ -137,7 +144,7 @@ def setup(hass, config):
# Do we need lights?
if light_needed:
logger.info("Home coming event for %s. Turning lights on", entity)
light.turn_on(hass, light_ids, profile=light_profile)
light.async_turn_on(hass, light_ids, profile=light_profile)
# Are we in the time span were we would turn on the lights
# if someone would be home?
@@ -150,7 +157,7 @@ def setup(hass, config):
# when the fading in started and turn it on if so
for index, light_id in enumerate(light_ids):
if now > start_point + index * LIGHT_TRANSITION_TIME:
light.turn_on(hass, light_id)
light.async_turn_on(hass, light_id)
else:
# If this light didn't happen to be turned on yet so
@@ -159,6 +166,7 @@ def setup(hass, config):
if not disable_turn_off:
@track_state_change(device_group, STATE_HOME, STATE_NOT_HOME)
@callback
def turn_off_lights_when_all_leave(hass, entity, old_state, new_state):
"""Handle device group state change."""
# pylint: disable=unused-variable
@@ -167,6 +175,6 @@ def setup(hass, config):
logger.info(
"Everyone has left but there are lights on. Turning them off")
light.turn_off(hass, light_ids)
light.async_turn_off(hass, light_ids)
return True

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