Compare commits

..

849 Commits

Author SHA1 Message Date
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
Robbie Trencheny
ef2ed7bfc9 Merge pull request #3937 from home-assistant/dev
0.31
2016-10-22 15:22:07 -07:00
Robbie Trencheny
6040a40af2 Update version 2016-10-22 15:09:05 -07:00
jbags81
fb352c20d9 Update wink.py (#3957)
* Update wink.py

added lambda and smoke detector call in component loading routine to fix broken functionality.

* Update wink.py

fixed extra space.

* Update wink.py

applied cleaner refactor per comments

* Update wink.py

fixed spacing

* Update wink.py

fixed lint error #1
2016-10-22 16:59:20 -04:00
John Arild Berentsen
678f30def1 Prevent Verisure cam to delete a file when it is None (#3988) 2016-10-22 21:01:12 +02:00
Paulus Schoutsen
0fce5ccc7f Update frontend 2016-10-22 11:24:06 -07:00
John Arild Berentsen
02afc98668 Prevent zwave from firing event at shutdown (#3987) 2016-10-22 14:08:24 +02:00
Eric Hagan
57777ef79a Adds support for Pioneer AVR interface port number (#3878)
* Adds support for Pioneer AVR interface port number

https://community.home-assistant.io/t/support-for-pioneer-avr/503
telnetlib supports a port number so adding port as
an optional config element with a default of 23 resolves this.

* Adds timeout to Pioneer AVR

timeout in telnetlib defaults to socket._GLOBAL_DEFAULT_TIMEOUT
which is not a value, but rather a bare Object used for comparison.

telnetlib says the following about the timeout optional argument:
"The optional timeout parameter specifies a timeout in seconds
 for blocking operations like the connection attempt (if not
 specified, the global default timeout setting will be used)."

From the documentation for sockets:
"Sockets are by default always created in blocking mode"

Catching connect and timeout errors, logging to debug
and continuing.

* Catches timeout exceptions, logs and continues.
2016-10-22 11:05:00 +02:00
Paulus Schoutsen
ca6fa1313e Fix updater, add new fields (#3982)
* Fix updater

* Add Docker and virtualenv checks

* Add log line informing user of update/analytics

* Remove str
2016-10-21 23:30:40 -07:00
Robbie Trencheny
ea91d24eb2 HA iOS support (#3752)
* Initial commit of the iOS component and platform

* Allow extra

* Add battery to identify, a new function to get devices, and load the upcoming sensor

* Add iOS sensor platform, currently for battery state & level

* Add discoverability for the iOS app

* Convert single quote to double quotes

* Load all required components and platforms when loading the iOS component for the best experience

* Unify quote style to double

* Change to hass_ios

* Update push URL, add support for logging based on status code, log rate limit updates

* Block iOS from coverage checks for now...
2016-10-21 23:20:15 -07:00
Brent Hughes
6e5a3c0a94 Fixed statsd stopping if state is not numeric or only attributes changed (#3981)
* Fixed statsd stopping if attribute changed by not the state

* Fixed tests which exposed a new bug.

* Fixed another issue. whoops
2016-10-21 23:18:13 -07:00
dasos
754d536974 Work better with password-protected Squeezebox / LMS servers (#3953)
* Work better with password-protected Squeezebox / LMS servers, including getting file art. Refactored to only have a single method calling the telent lib. (Should make it easier to convert to the more appropriate JSON interface)

* Update squeezebox.py

* Update squeezebox.py

* Update squeezebox.py

* Update squeezebox.py

* Update squeezebox.py
2016-10-21 22:37:35 -07:00
Georgi Kirichkov
4f6ed09a99 Adds energy monitoring capabilities to the TP-Link HS110 (#3917)
* Adds energy monitoring capabilities to the TP-Link HS110

Energy monitoring works only on the HS110 model

* Reverts to using GadgetReactor's module

* Updates requirements_all.txt

* Refactors tplink switch to use attribute caching

* Update tplink.py
2016-10-21 21:45:36 -07:00
Johann Kellerman
8d375e2d47 Improve known_device.yaml writing (#3955)
* Better known_device.yaml writing

* yaml dump
2016-10-21 21:41:27 -07:00
Hydreliox
1d2d338cd0 Add Bbox Router bandwidth as sensors (#3956)
* Add Bbox Routeur bandwidth as sensors

Add possibility to monitor max and currently used bandwidth of your xdsl connection for Bbox Routeur

* Minor Fixes

Unit constant get back into the main sensor file

* Unused round removed
2016-10-21 21:34:22 -07:00
Hugo Dupras
cb47507002 Add support for Neato Connected robot as a switch (#3935)
* Add support for Neato Connected robot as a switch

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

* Add checklist items

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

* Add missing docstring

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

* [Neato] Add update function to retrieve robot state

* Add docstring for update function + catch exception when retrieving state

* Change type of HTTPError when updating state

* Fix pylint errors
2016-10-21 21:14:45 -07:00
Fabian Affolter
fae620f3b3 Migrate to voluptuous (#3748) 2016-10-21 21:14:35 -07:00
Paulus Schoutsen
b821a82417 Merge remote-tracking branch 'origin/master' into dev 2016-10-21 20:57:07 -07:00
Paulus Schoutsen
da7837af73 Update frontend 2016-10-21 20:52:58 -07:00
Johann Kellerman
6e903fd429 Updater component - rename opt_out to reporting (#3979)
* Updater component - rename opt_out to reporting

* Fix tests
2016-10-21 20:40:23 -07:00
Martin Hjelmare
9f7e167669 Fix test using libsodium and SECRET_KEY (#3975)
* Move test to class with custom config setups and with config
  validation.
2016-10-21 20:23:29 -07:00
John Arild Berentsen
54a64fb8d9 Add support for verisure file camera. (#3952) 2016-10-21 22:41:17 +02:00
Nick Vella
2d89c3ecf4 Telstra SMS API notification component (#3949)
* Telstra API component

* import PLATFORM_SCHEMA

* Exclude Telstra notify component in coveragerc

* fix authentication issues

* Include title in SMS if it's provided

* pass lint

* Fix many code styling issues

* Confirm credentials are correct on component setup
2016-10-20 23:47:21 -07:00
Teemu Mikkonen
9f6d1c4e7b Device tracker: SNMPv3 (#3961)
* Add initial SNMPv3 support for better security

* Fixed indentation errors

* Fixed flake8 E128 on row 65

* Disabled warning about too many instance-attributes

* Removed extra code, added Inclusive to make better config validation
2016-10-20 23:01:30 -07:00
AlucardZero
62b8e54235 Upgrade SoCo to 0.12 (#3951)
Changelog: https://github.com/SoCo/SoCo/releases/tag/v0.12

Backwards Compatability changes:

    Dropped support for Python 3.2
    Methods relating to the music library (get_artists, get_album_artists, get_albums and others) have been moved to the music_library module. Instead of device.get_album_artists(), please now use device.music_library.get_album_artists() etc. Old code will continue to work for the moment, but will raise deprecation warnings
    Made a hard deprecation of the Spotify plugin since the API it relied on has been deprecated and it therefore no longer worked
    Dropped pylint checks for Python 2.6
2016-10-20 22:51:00 -07:00
Dustin S
1ceac8407d Adding security contexts to the resources. (#3840) 2016-10-20 22:43:39 -07:00
Jason Carter
0c0b02eb3d Moving updates out of state property (#3966) 2016-10-20 22:35:25 -07:00
Johann Kellerman
c70722dbae Updater component with basic system reporting (#3781) 2016-10-20 21:30:44 +02:00
Martin Hjelmare
a05fb4cef8 Upgrade pymysensors to 0.8 (#3954) 2016-10-20 20:56:26 +02:00
Marcelo Moreira de Mello
fee01fcccc Added unit test to the Yahoo Finance sensor (#3943) 2016-10-20 20:14:50 +02:00
henryk
2b37b4251b Fritzbox - Only report a MAC address if it's really there (#3964)
Fixes 'Neither mac or device id passed in'
2016-10-20 20:04:11 +02:00
Fabian Affolter
3aa1b6a3f8 Fix PEP257 issues (#3962) 2016-10-20 19:10:12 +02:00
Pascal Vizeli
c32afcd961 Bugfix sonos (#3926)
* Bugfix Sonos

* lint

* Use player uid for looking of exists

* fix lint

* fix unittest

* Change player_id to unique_id
2016-10-20 08:36:48 -07:00
hcooper
d60c2d604f Add support for multiple inputs to nmap tracking module as a list (#3944) 2016-10-20 07:15:00 +02:00
David-Leon Pohl
081e61528d Bugfixes for pilight hub component and unit tests (#3948) 2016-10-19 22:02:11 +02:00
Richard Cox
5799d1aec9 Fixing verbage (#3940) 2016-10-18 18:25:47 -07:00
Adam Mills
7d32e5eeeb Logbook filtering of automations by entity_id (#3927)
* Logbook filtering of automations by entity_id

* Trigger action function parameters required
2016-10-18 18:11:35 -07:00
Johann Kellerman
57f32fa629 Fixup device_tracekt.mqtt voluptuous & unit tests (#3904) 2016-10-18 18:10:28 -07:00
Sean Dague
7da47852d4 Yamaha zones (#3920)
* Enhance yamaha component

This enhances the yamaha component to create 1 player per zone,
instead of only one play, which provides direct control of the various
zones for the player.

It also exposes play_media for NET_RADIO sources, which allows direct
setting of that.

This requires code changes in rxv 0.2.0, so the requirement dependency
is raised.

* Support current playback metadata for NET_RADIO

When on NET RADIO, support the currently playing information.
2016-10-18 18:04:15 -07:00
Justin Weberg
f1b658ea5d Fix Typo (#3942) 2016-10-18 17:59:14 -07:00
Pascal Vizeli
754e93ff6a Rename add_devices to async_add_devices like dev guide (#3938) 2016-10-19 00:35:22 +02:00
David-Leon Pohl
947c1efca2 New pilight sensor component (#3822)
* 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 pytest-caplog and no unittest.TestCase
2016-10-18 23:16:20 +02:00
hexa-
a1239077d9 Add support for matrix notifications (#3827) 2016-10-18 21:13:00 +02:00
Paulus Schoutsen
53b5dc8e84 Build frontend 2016-10-18 09:10:22 -07:00
Lewis Juggins
09bcd7321a Reset Bravia playing info to ensure state reflects correctly (#3903) 2016-10-17 23:13:35 -07:00
Pascal Vizeli
14ef0ca786 Bugfix Template sensors (#3931) 2016-10-18 07:27:32 +02:00
Rob Capellini
272539105f Replacing tempfile with mock_open in tests (#3753)
- test_bootstrap.py
- test_config.py
- components/test_init.py
- components/test_panel_custom.py
- components/test_api.py
- components/notify/test_file.py
- components/notify/test_demo.py
- components/camera/test_local_file.py
- helpers/test_config_validation.py
- util/test_package.py
- util/test_yaml.py

No changes needed in:
- components/cover/test_command_line.py
- components/switch/test_command_line.py
- components/rollershutter/test_command_line.py
- components/test_shell_command.py
- components/notify/test_command_line.py

Misc changes in:
- components/mqtt/test_server.py

Also, removed some unused mock parameters in tests/components/mqtt/test_server.py.
2016-10-17 20:16:36 -07:00
Fabian Affolter
7d67017de7 Upgrade python-digitalocean to 1.10.0 (#3921) 2016-10-17 20:13:43 -07:00
juggie
d921073e77 Ecobee - Celsius (#3906)
* #3899 - Ecobee tempoeratures

* #3899 Remove unused import

* #3899 Implement min/max_temp in ecobee.py as these temperatures have to be in F for ecobee api.

* #3899 Stale print

* #3899 Use min/max_temp from base class

* #3899 Removed unused import (again) so tests pass since changing to use super class

* #3899 Fix long lines

* #3899 Install tox locally... make it happy, commit

* #3899 Remove overridden min/max_temp and instead update __init__:min/max_temp to convert to self.temperature_unit (of the thermostat) as opposed to self.unit_of_measurement (of the system), which is wrong

* Remove unused import from ecobee
2016-10-17 20:06:03 -07:00
Jason Carter
9cf2acb495 Concord232 alarm panel (#3842)
* Adding Concord232 Alarm Panel

* Adding Concord232 Zones as Sensors

* Updating requirements_all.txt

* Adding DOCType and making helper function a closure for clarity

* Fixing D400 error in build

* Fixing pylint errors

* Adding # pylint: disable=too-many-locals

* Updating with proper polling methods

* Fixing Merge issues

* Fixing DocStyle Issue

* Moving Import out of setup

* Fixing DocString

* Removing overthought GLOBAL
2016-10-17 19:59:41 -07:00
Giel Janssens
3b424b034a Netatmo thermostat (#3888)
* Added Netatmo-thermostat

* Remove-CONF_DEVICES
2016-10-17 19:57:02 -07:00
Marcelo Moreira de Mello
76598bc4d2 #3829 - Fixed issued LowHighTuple doesn't define __round__ method for Nest Sensor (#3881)
* #3829 - Fixed type LowHighTuple doesn't define __round__ method issue when Nest is operating in range mode

* Testing if temperature is a tuple instead int or float
2016-10-17 19:55:53 -07:00
William Scanlon
c54476b62f Protocol is an int (#3928) 2016-10-17 19:53:50 -07:00
sam-io
ae8a8e22ad Support for Apple Push Notification Service (#3756)
* added push notification implementation

* some lint changes

* added docs

* added push notification implementation

* some lint changes

* added docs

* Fixed comment formatting issues

* Added requirments

* Update requirements_all.txt

* Update apns.py

* re-generated requirments_all.txt

* Added link to online docs

* added push notification implementation

* some lint changes

* added docs

* added push notification implementation

* some lint changes

* added docs

* Fixed comment formatting issues

* Added requirments

* Update requirements_all.txt

* Update apns.py

* re-generated requirments_all.txt

* Added link to online docs

* changed to use http/2 library for push notifications

* fixed lint issue

* fixed test that fails on CI

* another go at fixing test that fails on CI

* another go at fixing test that fails on CI

* another go at fixing test that fails on CI

* added missing docstring

* moved service description to main services.yaml file

* renamed apns service
2016-10-17 19:41:49 -07:00
Paulus Schoutsen
4c8d1d9d2f Clean up some async stuff (#3915)
* Clean up some async stuff

* Adjust comments

* Pass hass instance to eventbus
2016-10-17 19:38:41 -07:00
Sean Dague
daea93d9f9 Suppress requests/urllib3 connection pool log messages (#3854)
requests/urllib3 is notorious for using the INFO log level for very
DEBUG kinds of information. Given the configurability of python
logging it's actually pretty easy to just set requests to WARN by
default. This cleans out a bunch of largely unuseful log lines from
home assistant output.
2016-10-17 21:14:10 +02:00
Pascal Vizeli
1540bb1279 Async template (#3909)
* port binary_sensor/template

* port sensor/template

* port switch/template

* fix unittest

* fix

* use task instead yield on it

* fix unittest

* fix unittest v2

* fix invalid config

* fix lint

* fix unuset import
2016-10-17 07:00:55 +02:00
Jan Harkes
555e533f67 Added tests for the template is_defined filter 2016-10-17 03:20:07 +02:00
Jan Harkes
118f2f0bad Use a filter to fail rendering undefined variables
Instead of globally using StrictUndefined, introduce a filter that
will trigger a render failure on undefined variables.

Use as `{{ value_json.someval | is_defined }}`.
2016-10-17 03:20:07 +02:00
Jan Harkes
c8add59ea5 Fail when rendering undefined objects in Jinja2 templates
By not successfully rendering unknown objects to an empty string
the caller provided error_value actually gets used.

This allows, for instance, the MQTT sensor to retain its state when an
unexpected or unwanted message (#2733/#3834) is received.
2016-10-17 03:20:07 +02:00
Willems Davy
18f5258aaf Emoncms history component (#3531)
* Emoncms_history component, fix git mess

* - switch to track_point_in_time to send all data at foxed interval
- don't use json_dump
- switch to http post instead of http get
2016-10-16 17:05:01 -07:00
Justin Weberg
207c9e8575 Fix Comment (#3913) 2016-10-16 16:56:02 -07:00
Rob Capellini
4891ca1610 Removing calls to mock.assert_called_once_with (#3896)
If a mock's assert_called_once_with method is misspelled (e.g. asert_called_once_with) then the test will appear as passing.  Therefore, this commit removes all instances of assert_called_once_with calls and replaces them with two assertions:

        self.assertEqual(mock.call_count, 1)
        self.assertEqual(mock.call_args, mock.call(call_args))
2016-10-16 16:13:27 -07:00
Lewis Juggins
10c9132046 Resolve issue with delay not passing variables to render (#3901) 2016-10-16 16:08:12 -07:00
Fabian Affolter
71ee847aee Add web scrape sensor (#3841)
* Add web scrape sensor

* Add support for 'value_template', set 'verify_ssl' to true,
and remove 'before', 'after' & 'element'

* Fix pylint issue
2016-10-16 16:06:07 -07:00
Per Sandström
d9ae7ceb0c Tellstick switch force update (#3874) 2016-10-16 15:56:55 -07:00
Justin Weberg
2a972b2334 Move micromarkdown to HA (#3908)
* Move micromarkdown to HA

* Fix requests

* Update micromarkdown-js.html& .gz

* Update micromarkdown-js files
2016-10-16 15:47:34 -07:00
Pascal Vizeli
7484152be1 Async speedup add_device callback (#3910)
* Speed up entities processing from add_device callback

* fix lint

* fix bug
2016-10-17 00:35:57 +02:00
Paulus Schoutsen
6581dc2381 Document more core pieces 2016-10-16 13:45:17 -07:00
Paulus Schoutsen
31ec0ac6a7 Add util.async to the dev docs 2016-10-16 13:35:01 -07:00
John Arild Berentsen
8b2edc1514 ZWave: Add association service (#3894)
* Add association service

* Refactor service

* Requested changes

* Grammar in pydocstyle
2016-10-16 20:36:06 +02:00
Adam Mills
2612c6d6b8 Zwave alt delay workaround for HS-WD100+ (#3893) 2016-10-16 20:35:39 +02:00
Pascal Vizeli
0b8b9ecb94 Async EntitiesComponent (#3820)
* first version

* First draft component entities

* Change add_entities to callback from coroutine

* Fix bug add async_prepare_reload

* Group draft v1

* group async

* bugfix

* bugfix v2

* fix lint

* fix extract_entity_ids

* fix other things

* move get_component out of executor

* bugfix

* Address minor changes

* lint

* bugfix - should work now

* make group init async only

* change update handling to old stuff

* fix group handling, remove generator from init

* fix lint

* protect loop for spaming with updates

* fix lint

* update test_group

* fix

* update group handling

* fix __init__ async trouble

* move device_tracker to new layout

* lint

* fix group unittest

* Test with coroutine

* fix bug

* now it works 💯

* ups

* first part of suggestion

* add_entities to coroutine

* change group

* convert add async_add_entity to coroutine

* fix unit tests

* fix lint

* fix lint part 2

* fix wrong import delete

* change async_update_tracked_entity_ids to coroutine

* fix

* revert last change

* fix unittest entity id

* fix unittest

* fix unittest

* fix unittest entity_component

* fix group

* fix group_test

* try part 2 to fix test_group

* fix all entity_component

* rename _process_config

* Change Group to init with factory

* fix lint

* fix lint

* fix callback

* Tweak entity component and group

* More fixes

* Final fixes

* No longer needed blocks

* Address @bbangert comments

* Add test for group.stop

* More callbacks for automation
2016-10-16 09:35:46 -07:00
Fabian Affolter
a0fdb2778d Fix name allocation (#3890) 2016-10-16 18:07:34 +02:00
Fabian Affolter
5ef8ca9b03 Upgrade sendgrid to 3.6.0 (#3887) 2016-10-16 09:09:48 +02:00
Fabian Affolter
a10fa90357 Update link to docs repo (#3886) 2016-10-15 13:46:45 +02:00
Fabian Affolter
9743e17d62 Minimum/maximum/mean sensor (#3852)
* Add min/max sensor

* Update min_max.py
2016-10-14 21:43:46 -07:00
Marcelo Moreira de Mello
6fcb1b548e Added the ability to Weather Underground to track severe weather alerts (#3505)
*  Added the ability to Weather Underground to track severe weather alerts

*   * Added message on the advisory attr

  * Updated tests

* * Making use of guard clause

* Checking multiple_alerts prior loop

* Using a better way to create dict

* Fixed issue to set to None only the object that failed

* Added unittest

* Split update() method to different calls with their one throttle control to minimize API calls

* Updated unittest and make sure the alert sensor will not return 'unknown' status'

* Removed update() method from state property

* Branch rebased and include Weather Underground attribution

* Update wunderground.py
2016-10-14 21:35:27 -07:00
Adam Mills
1bf5554017 Zwave rgb fix (#3879)
* _zw098 must be set before update_properties

* Fix problematic zwave color light value matching

See https://community.home-assistant.io/t/color-issues-with-aeotec-zwave-zw098/2830
2016-10-14 21:17:48 -07:00
Georgi Kirichkov
49b1643ff0 Relaxes the configuration options for influxdb (#3869)
* Relaxes the configuration options for influxdb

By default influxdb allows unauthenticated access
Home Assistant required at least username and password to be present to properly submit data to influxdb

* Removes unused import of 'copy'

The copy module was used only in the removed test case responsible for testing the missing keys

* Updates InfluxDB config schema to require user and password

Current InfluxDB (v 1.0) can work without any authentication, but when authentication is enabled both username and password should be set.

* Removes extra white space in test_influxdb.py
2016-10-14 21:10:04 -07:00
Michael
ce19e6367f Catch MQTT encoding errors (#3749)
* added error handling to mqtt message receive if payload is not utf-8 unicode
added mqtt test for above code as well

* change permission back to 644

* attempting to test new code

* changed exception to AttributeError
fixed test for above

* fixed lint errors I made in tests....mqtt/test_init.py

* more lint fixes for my added test

* remove dual decode of MQTT payload

* convert if to try, except, else statement for mqtt payload decode

* rework mqtt unicode testing code to properly check for log file entriy on unicode decode exception

* fixed lint error

* Update test_init.py
2016-10-14 21:08:44 -07:00
Fabian Affolter
180e146e14 Upgrade uber_rides to 0.2.7 (#3876) 2016-10-14 21:00:27 -07:00
Fabian Affolter
bead274b20 Upgrade slacker to 0.9.28 (#3877) 2016-10-14 20:58:49 -07:00
Richard Cox
1cbf8c8049 Zoneminder component (#3795)
* Initial Zoneminder commit

* Fixing bug when ZM sets its function to 'None'

* Adding zoneminder to coverage

* Quick Doc fix

* Update zoneminder.py

Doc Fix

* making the url base optional
2016-10-14 20:56:40 -07:00
Lukas
ad259ead50 Add ignore option to zwave customize configuration (#3865) 2016-10-14 08:36:55 -07:00
Daniel Høyer Iversen
bb457f47cc Merge pull request #3868 from home-assistant/flux_led_lib
update flux led library
2016-10-14 11:41:18 +02:00
Daniel
df3e904fe7 update flux led library 2016-10-14 11:17:03 +02:00
Hugo Dupras
7697cdef0a Pushbullet push an url note if an url is provided inside data (#3758) 2016-10-14 00:11:48 -07:00
Igor Shults
6951b6f60b Allow any positive integer for Z-Wave polling intensity (#3859) 2016-10-14 00:09:52 -07:00
John Arild Berentsen
6ca0d4cd14 Use pass instead of return None (#3856) 2016-10-14 00:09:24 -07:00
Sean Dague
a5b756e1e5 Bump proliphix library to 0.4.0 (#3855)
There was a bug in setback setting which is now fixed in 0.4.0. This
ensures that any users have working setback code.
2016-10-14 00:06:53 -07:00
Sean Dague
7848791a04 add arwn sensor platform (#3846)
This adds a sensor component that builds sensors based on the arwn
project (https://github.com/sdague/arwn). This uses a 433mhz receiver
to collect weather data and publish it over mqtt in a well defined
schema, which home-assistant can display as sensors.
2016-10-14 00:06:04 -07:00
Paulus Schoutsen
f916fc04f9 Update frontend 2016-10-14 00:02:21 -07:00
John Arild Berentsen
8f4608c654 Use only node id to identify node in set_config_parameter (#3801) 2016-10-13 23:45:00 -07:00
Per Sandström
399a0b470a select next and previous of input select (#3839) 2016-10-13 21:53:47 -07:00
Jan Harkes
4d716cec2b Bump pychromecast dependency to 0.7.6 (#3864) 2016-10-13 21:24:54 -07:00
Lukas
1373db8b60 Include index and instance in object_id of zwave devices (#3759)
* Include index and instance in object_id of zwave devices

* Add the instance id if there is more than one instance for the value
2016-10-13 21:13:05 -07:00
Hydreliox
7771cc2ccf Add Bbox Modem Routeur for device tracker (#3848) 2016-10-13 19:43:51 -07:00
Per Sandström
c5ad7996fb Merge pull request #3857 from persandstrom/vsure0.11.1
vsure 0.11.1
2016-10-13 22:18:42 +02:00
Per Sandström
3d94f77998 vsure 0.11.1 2016-10-13 21:53:50 +02:00
Marcelo Moreira de Mello
6330d9cde6 Fixed Fitbit resting heart rate attribute (#3835)
* Fixed Fitbit restingHeartRate field to grab the correct field from dictionary

In [31]: r['activities-heart'][-1].get('value')
Out[31]:
{'customHeartRateZones': [],
 'heartRateZones': [{'caloriesOut': 126.18348,
   'max': 94,
   'min': 30,
   'minutes': 67,
   'name': 'Out of Range'},
  {'caloriesOut': 27.21339,
   'max': 131,
   'min': 94,
   'minutes': 5,
   'name': 'Fat Burn'},
  {'caloriesOut': 0, 'max': 159, 'min': 131, 'minutes': 0, 'name': 'Cardio'},
  {'caloriesOut': 0, 'max': 220, 'min': 159, 'minutes': 0, 'name': 'Peak'}],
 'restingHeartRate': 69}

In [32]: r['activities-heart'][-1].get('value').get('restingHeartRate')
Out[32]: 69

* Renamed sensor to Resting Heart Rate to match it

* Fixed lint style
2016-10-13 09:22:05 -07:00
Hugo Dupras
9a0bb62654 Hotfix for Netatmo discovery (#3837)
This should definetly fix #2601

Signed-off-by: Hugo D. (jabesq) <jabesq@gmail.com>
2016-10-13 09:21:22 -07:00
Fabian Affolter
d873a7baf0 Use async_render_* and fix config validation (#3847) 2016-10-13 09:20:49 -07:00
Robbie Trencheny
c663d85129 Add Alexa Flash Briefing Skill API support (#3745)
* Add Alexa Flash Briefing Skill API support

* Set default value for text to empty string as per API docs

* Clean up existing Alexa tests

* Update configuration parsing and validation

* Add tests for the Flash Briefing API

* Update test_alexa.py
2016-10-13 09:14:22 -07:00
John
8c13d3ed4c Update zwave lights to increase update delay timer as needed (#3741)
Fix permissions
2016-10-13 09:09:41 -07:00
Johann Kellerman
cb322f72db Add persistent notifications to bootstrap (#3738)
* Add persistent notifications to bootstrap

* Rebase, Fix test
2016-10-13 09:09:07 -07:00
Scott Reston
39a446c43c Proper title, added album and artist for Squeezebox (#3735)
* Proper title, added album and artist

Title had previously concatenated artist - title.

* Made changes suggested by @balloobbot
2016-10-13 09:07:10 -07:00
wokar
aa8622f8e8 Added include and exclude functionality to history component (#3674)
* added include and exclude functionality to history component

* fixed summary lines in test method doc.

* cleanup of query filter creation

* o improved config validation
o move move IGNORE_DOMAINS to Filter.apply()
o removed config from Last5StatesView
o Filters instance is now created on setup
o config values are processed in setup and set to the Filters instance
o function _set_filters_in_query() moved to Filters class and renamed to apply()

* added unittests for more include/exclude filter combinations

* make pylint happy
2016-10-13 08:54:45 -07:00
Vittorio Monaco
e031b8078f Fixes an issue where Chromecast audio groups were not properly discovered (#3630)
* Fixes an issue where Chromecast audio groups were not properly discovered

* Forgot to commit the main fix

* Removes unused variable

* Doesn't use a protected API anymore

* PR remarks

* Fixes tests, adds comment

* Restores line as it was in the original commit, rephrases comment

* Should fix lint issues

* Trailing whitespace

* Some more lint
2016-10-13 08:51:43 -07:00
jgriff2
e1647fb6ac Add synology ss cameras (#3626)
* Add files via upload

* Update .coveragerc

* test

* Update synology camera

* Use voluptuous for synology

* Use voluptuous for synology

* Use voluptuous for synology

* Use voluptuous for synology

* Conform synology to flake8

* Added Whitelist to synology

* Sync to dev branch

* Added helper function to synology
2016-10-13 08:49:58 -07:00
Lewis Juggins
10feac11d9 Support recursive config inclusions (#3783) 2016-10-12 12:05:41 +02:00
Paulus Schoutsen
d4e2332ce0 Merge pull request #3814 from home-assistant/release-0-30-1
0.30.2
2016-10-11 22:28:00 -07:00
Jean Regisser
1d7169403b Add again certifi to Docker image (#3813)
Latest versions of certifi (>= 2016.8.31) don't seem to break anything
anymore.
See home-assistant/home-assistant#2554
2016-10-11 22:25:17 -07:00
Keith Lamprecht
e4685de459 Restore Optional Target Config Attribute (notify.pushover) (#3769)
* Restore Optional Target Config Attribute

* Fix Tabs

* Change indents to spaces

* Make a target fix

* Change to simpler not syntax
2016-10-11 21:59:45 -07:00
Keith Lamprecht
d83de36c32 Restore Optional Target Config Attribute (notify.pushover) (#3769)
* Restore Optional Target Config Attribute

* Fix Tabs

* Change indents to spaces

* Make a target fix

* Change to simpler not syntax
2016-10-11 21:59:34 -07:00
Fabian Affolter
73547c8c4b Fix slack targets (#3826) 2016-10-11 21:58:54 -07:00
Fabian Affolter
40094cecae Fix slack targets (#3826) 2016-10-11 21:16:11 -07:00
Robbie Trencheny
0b327cd4d9 Notify: Only attach target if in call data (#3831)
* Only pass through the target if it has a value

* Target will no longer be none
2016-10-11 20:34:14 -07:00
Robbie Trencheny
d302dbec2d Notify: Only attach target if in call data (#3831)
* Only pass through the target if it has a value

* Target will no longer be none
2016-10-11 20:33:41 -07:00
Fabian Affolter
e135691bd6 Migrate to voluptuous (#3817) 2016-10-11 08:33:22 -07:00
Fabian Affolter
d4dc2707a1 Use voluptuous for eQ-3 thermostat (#3729)
* Migrate to voluptuous

* Fix requirement and typo
2016-10-11 11:27:31 +02:00
Paulus Schoutsen
a8cdf36d5c Update recorder callback (#3812) 2016-10-11 00:58:43 -07:00
Fabian Affolter
a99f36f519 Migrate to voluptuous (#3737) 2016-10-11 00:56:57 -07:00
Fabian Affolter
0568ef025b Use voluptuous for Heatmiser (#3732) 2016-10-11 00:53:24 -07:00
Fabian Affolter
8ded8f572a Add/adjust attribution of sensor platform (#3719)
* Add/adjust attribution

* Fix typo
2016-10-11 00:28:19 -07:00
Fabian Affolter
7cf2c48175 Use voluptuous for FitBit (#3686)
* Migrate to voluptuous

* Fix default
2016-10-11 00:27:15 -07:00
Fabian Affolter
7cf9ff83bc Migrate to voluptuous (#3293) [BREAKING CHANGE] 2016-10-11 00:26:11 -07:00
Paulus Schoutsen
dfe9af7110 Version bump to 0.30.2 2016-10-11 00:21:01 -07:00
Erik Eriksson
016e8f833d slugify (#3777) 2016-10-11 00:20:49 -07:00
Teemu Mikkonen
574df0f420 Fix for html5 notification tag problem. Fixes #3774 (#3790) 2016-10-11 00:20:49 -07:00
Pascal Vizeli
f18f181962 Hotfix device name with autodiscovery (#3791) 2016-10-11 00:20:49 -07:00
Ellis Percival
4754455295 Make 'pin' optional for zigbee device config. (#3799) 2016-10-11 00:20:49 -07:00
Robbie Trencheny
8c1317f278 Wrap found target in list (#3809)
* Wrap found target in list

* Fix test_messages_to_targets_route
2016-10-11 00:20:49 -07:00
Russell Cloran
7c2cb6cffd Separate climate platform and presentation units (#3755)
* Separate platform and presentation units in climate

* Fix unit tests

Maybe

* Fix unit tests some more

Maybe

* Rename _platform_unit_of_measurement to temperature_unit

* Fix tests for renamed attribute
2016-10-11 00:00:29 -07:00
Ferry van Zeelst
8c9d1d9af1 Added additional checks which hides functions which are not supported by Nest (#3751)
* Added additional checks which hides functions which are not support (like fans / humidity / cooling)

* Fixed pylint and flake8 errors (not test file available)

* Fixed pydocstyle error

* Refactored Code and Comments as described in pull-request

* Added additional comment and requesting retest

* Upgraded to python-nest 2.11 which contains previously hidden functions
2016-10-10 23:57:14 -07:00
Lukas
941fccd3fc Update python-lirc to 1.2.3 (#3784)
* Upgrade to latest python-lirc 1.2.3

* update requirements_all.txt
2016-10-10 23:44:41 -07:00
Clemens Wolff
711526e574 Cache condition in helpers.Script (#3797)
* Cache condition in helpers.Script

The caching is a simple in-memory, per-instance dictionary.
We use __str__ to format cache keys.

This naive implementation has some disadvantages (e.g., we won't be able
to cache two conditions that contain references to
distinct-but-equivalent object instances and we don't have any control
over the size of the condition cache), but for most simple use-cases the
approach should be good enough.

Resolves #3629

* Fix docstring style
2016-10-10 23:36:38 -07:00
Fabian Affolter
a2503e4d13 Upgrade sqlalchemy to 1.1.1 (#3796) 2016-10-10 23:29:43 -07:00
Fabian Affolter
656ee52435 Upgrade cherrypy to 8.1.2 (#3805) 2016-10-10 23:27:53 -07:00
Fabian Affolter
002660fd9e Upgrade dnspython3 to 1.15.0 (#3804) 2016-10-10 23:24:10 -07:00
Fabian Affolter
63580f9e03 Upgrade pyowm to 2.5.0 (#3806) 2016-10-10 23:23:32 -07:00
Teemu Mikkonen
87d9cdd78f Fix for html5 notification tag problem. Fixes #3774 (#3790) 2016-10-10 23:17:27 -07:00
phardy
c8ca66b671 Stop GTFS component from overwriting friendly_name (#3798)
* Prevent update from clobbering configured name.

* linted

* Fixing awkward line breaking.
2016-10-10 22:36:20 -07:00
Robbie Trencheny
9b98d470c2 Wrap found target in list (#3809)
* Wrap found target in list

* Fix test_messages_to_targets_route
2016-10-10 22:31:15 -07:00
sander76
b19ec21e88 Changing import as powerview api did change. (#3780) 2016-10-10 22:30:27 -07:00
Fabian Affolter
3b331eac56 Update docstrings (sensor.pi_hole, sensor.haveibeenpwned) (#3793)
* Fix docstrings

* Update docstrings
2016-10-10 19:38:32 +02:00
Fabian Affolter
552265bc31 No longer use old name (#3792) 2016-10-10 19:38:20 +02:00
Ellis Percival
df58f718ab Make 'pin' optional for zigbee device config. (#3799) 2016-10-10 16:53:18 +02:00
Pascal Vizeli
76a1a54369 Hotfix device name with autodiscovery (#3791) 2016-10-10 13:46:23 +02:00
Per Sandström
b6e008be71 Merge pull request #3782 from persandstrom/vsure0.11.0
vsure0.11.0
2016-10-09 22:01:46 +02:00
Per Sandström
9d5c20b629 vsure0.11.0 2016-10-09 21:47:35 +02:00
Erik Eriksson
63d9ea6643 slugify (#3777) 2016-10-09 09:15:58 -07:00
Hugo Dupras
1d0df63615 Netatmo binary sensor (#3455)
* Basic support for Netatmo welcome binary sensors

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

* Bug fixes and optimization for Netatmo devices

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

* pylint fixes

* Bug Fixing and optimization for Netatmo devices

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

* Add unique_id for cameras to avoid duplicate
And global config to disable discovery for netatmo devices

Signed-off-by: Hugo D. (jabesq) <jabesq@gmail.com>
2016-10-09 08:45:12 -07:00
hexa-
154eacef6c Http: Change approved_ips from string to cidr validation (#3532) [BREAKING CHANGE]
* Change approved_ips from string to cidr validation

Relabel to trusted_networks, better reflecting its expected inputs,
everything that ipaddress.ip_networks recognizes as an ip network
is possible:
- 127.0.0.1      (single ipv4 addresses)
- 192.168.0.0/24 (ipv4 networks)
- ::1            (single ipv6 addresses)
- 2001:DB8::/48  (ipv6 networks)

* Add support for the X-Forwarded-For header
2016-10-09 08:13:30 -07:00
Fabian Affolter
dc95b28487 Use voluptuous for Russound RNET (#3689)
* Migrate to voluptuous

* Remove wrong default
2016-10-09 16:40:54 +02:00
Johann Kellerman
4d9bac6f9c Coerce device IDs from known_devices to be slugs (#3764)
* Slugify & consider_home test fix [due to load valid PR]

* undo schema change

* Fix slugify error
2016-10-08 14:40:50 -07:00
Roi Dayan
6419d273ea Fix command line cover template (#3754)
The command line cover value template is optional so we
need to check it's not none before assigning hass to it.

Fixes #3649

Signed-off-by: Roi Dayan <roi.dayan@gmail.com>
2016-10-08 13:03:32 -07:00
mtl010957
7882b19dc5 Fixed issue #3760, handle X10 unit numbers greater than 9. (#3763) 2016-10-08 12:57:40 -07:00
Willems Davy
8f8bba4ad7 Haveibeenpwned sensor platform (#3618)
* Initial version of "haveibeenpwned" sensor component

* 2 flake8 fixes

* remove debugging error message

* Increase scan_interval as well as throttle to make sure that during initial startup of hass the request happens with 5 seconds delays and after startup with 15 minutes delays. Scan_interval is increased also to not call update as often

* update .coveragerc

* remove (ssl) verify=False

* - use dict to keep the request values with email as key
- use track_point_in_time system to make sure data updates initially at 5 seconds between each call until all sensor's email have a result in the dict.

* fix a pylint error that happend on the py35 tests
2016-10-08 11:38:58 -07:00
Johann Kellerman
7b40a641ec Continue on invalid platforms and new setup_component unit tests (#3736) 2016-10-08 11:27:35 -07:00
wokar
1b26b5ad14 add include configuration to logbook (#3739) 2016-10-08 11:26:14 -07:00
Hugo Dupras
bbbb4441ea Add discovery support for Netatmo weather Station (#3714)
* Add discovery support for Netatmo Weather station

Only The weather information are currently supported (No battery or wifi status supported)

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

* Add unique_id for netatmo sensors to avoid duplicate

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

* Update requirements_all.txt and PEP8 fixes

Signed-off-by: Hugo D. (jabesq) <jabesq@gmail.com>
2016-10-08 11:26:01 -07:00
Paulus Schoutsen
1e2e877302 Version bump to 0.31.0.dev0 2016-10-08 09:58:28 -07:00
776 changed files with 45037 additions and 17558 deletions

View File

@@ -28,9 +28,15 @@ 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
homeassistant/components/ios.py
homeassistant/components/*/ios.py
homeassistant/components/isy994.py
homeassistant/components/*/isy994.py
@@ -92,34 +98,47 @@ 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/pilight.py
homeassistant/components/*/pilight.py
homeassistant/components/knx.py
homeassistant/components/*/knx.py
homeassistant/components/ffmpeg.py
homeassistant/components/*/ffmpeg.py
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
homeassistant/components/camera/rpi_camera.py
homeassistant/components/camera/synology.py
homeassistant/components/climate/eq3btsmart.py
homeassistant/components/climate/heatmiser.py
homeassistant/components/climate/homematic.py
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
@@ -127,16 +146,19 @@ omit =
homeassistant/components/device_tracker/actiontec.py
homeassistant/components/device_tracker/aruba.py
homeassistant/components/device_tracker/asuswrt.py
homeassistant/components/device_tracker/bluetooth_tracker.py
homeassistant/components/device_tracker/bbox.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
@@ -144,11 +166,11 @@ omit =
homeassistant/components/device_tracker/volvooncall.py
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
@@ -162,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
@@ -178,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
@@ -186,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
@@ -197,7 +226,9 @@ omit =
homeassistant/components/notify/joaoapps_join.py
homeassistant/components/notify/kodi.py
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
@@ -209,20 +240,28 @@ omit =
homeassistant/components/notify/smtp.py
homeassistant/components/notify/syslog.py
homeassistant/components/notify/telegram.py
homeassistant/components/notify/telstra.py
homeassistant/components/notify/twilio_sms.py
homeassistant/components/notify/twitter.py
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
@@ -235,16 +274,21 @@ omit =
homeassistant/components/sensor/google_travel_time.py
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
@@ -252,15 +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
@@ -270,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/yahoo_finance.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/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

View File

@@ -1,9 +1,9 @@
**Description:**
**Related issue (if applicable):** fixes #
**Related issue (if applicable):** fixes #<home-assistant issue number goes here>
**Pull request in [home-assistant.io](https://github.com/home-assistant/home-assistant.io) with documentation (if applicable):** home-assistant/home-assistant.io#
**Pull request in [home-assistant.github.io](https://github.com/home-assistant/home-assistant.github.io) with documentation (if applicable):** home-assistant/home-assistant.github.io#<home-assistant.github.io PR number goes here>
**Example entry for `configuration.yaml` (if applicable):**
```yaml
@@ -13,7 +13,7 @@
**Checklist:**
If user exposed functionality or configuration variables are added/changed:
- [ ] Documentation added/updated in [home-assistant.io](https://github.com/home-assistant/home-assistant.io)
- [ ] Documentation added/updated in [home-assistant.github.io](https://github.com/home-assistant/home-assistant.github.io)
If the code communicates with devices, web services, or third-party tools:
- [ ] Local tests with `tox` run successfully. **Your PR cannot be merged unless tests pass**

1
.gitignore vendored
View File

@@ -62,6 +62,7 @@ pip-log.txt
.coverage
.tox
nosetests.xml
htmlcov/
# Translations
*.mo

2
.hound.yml Normal file
View File

@@ -0,0 +1,2 @@
python:
enabled: true

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

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.

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
@@ -19,8 +22,7 @@ RUN script/build_python_openzwave && \
ln -sf /usr/src/app/build/python-openzwave/openzwave/config /usr/local/share/python-openzwave/config
COPY requirements_all.txt requirements_all.txt
# certifi breaks Debian based installs
RUN pip3 install --no-cache-dir -r requirements_all.txt && pip3 uninstall -y certifi && \
RUN pip3 install --no-cache-dir -r requirements_all.txt && \
pip3 install mysqlclient psycopg2 uvloop
# Copy source

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:

View File

@@ -8,11 +8,31 @@
.. autoclass:: Config
:members:
.. autoclass:: Event
:members:
.. autoclass:: EventBus
:members:
.. autoclass:: HomeAssistant
:members:
.. autoclass:: State
:members:
.. autoclass:: StateMachine
:members:
.. autoclass:: ServiceCall
:members:
.. autoclass:: ServiceRegistry
:members:
Module contents
---------------
.. automodule:: homeassistant.core
:members:
:undoc-members:
:show-inheritance:

View File

@@ -4,6 +4,14 @@ homeassistant.util package
Submodules
----------
homeassistant.util.async module
-------------------------------
.. automodule:: homeassistant.util.async
:members:
:undoc-members:
:show-inheritance:
homeassistant.util.color module
-------------------------------

View File

@@ -2,7 +2,7 @@ swagger: '2.0'
info:
title: Home Assistant
description: Home Assistant REST API
version: "1.0.0"
version: "1.0.1"
# the domain of the service
host: localhost:8123
@@ -12,17 +12,17 @@ schemes:
- https
securityDefinitions:
api_key:
type: apiKey
description: API password
name: api_password
in: query
# api_key:
#api_key:
# type: apiKey
# description: API password
# name: x-ha-access
# in: header
# name: api_password
# in: query
api_key:
type: apiKey
description: API password
name: x-ha-access
in: header
# will be prefixed to all paths
basePath: /api
@@ -38,6 +38,8 @@ paths:
description: Returns message if API is up and running.
tags:
- Core
security:
- api_key: []
responses:
200:
description: API is up and running
@@ -53,6 +55,8 @@ paths:
description: Returns the current configuration as JSON.
tags:
- Core
security:
- api_key: []
responses:
200:
description: Current configuration
@@ -81,6 +85,8 @@ paths:
summary: Returns all data needed to bootstrap Home Assistant.
tags:
- Core
security:
- api_key: []
responses:
200:
description: Bootstrap information
@@ -96,6 +102,8 @@ paths:
description: Returns an array of event objects. Each event object contain event name and listener count.
tags:
- Events
security:
- api_key: []
responses:
200:
description: Events
@@ -113,6 +121,8 @@ paths:
description: Returns an array of service objects. Each object contains the domain and which services it contains.
tags:
- Services
security:
- api_key: []
responses:
200:
description: Services
@@ -130,6 +140,8 @@ paths:
description: Returns an array of state changes in the past. Each object contains further detail for the entities.
tags:
- State
security:
- api_key: []
responses:
200:
description: State changes
@@ -148,6 +160,8 @@ paths:
Returns an array of state objects. Each state has the following attributes: entity_id, state, last_changed and attributes.
tags:
- State
security:
- api_key: []
responses:
200:
description: States
@@ -166,6 +180,8 @@ paths:
Returns a state object for specified entity_id.
tags:
- State
security:
- api_key: []
parameters:
- name: entity_id
in: path
@@ -223,6 +239,8 @@ paths:
Retrieve all errors logged during the current session of Home Assistant as a plaintext response.
tags:
- Core
security:
- api_key: []
produces:
- text/plain
responses:
@@ -239,6 +257,8 @@ paths:
Returns the data (image) from the specified camera entity_id.
tags:
- Camera
security:
- api_key: []
produces:
- image/jpeg
parameters:
@@ -262,6 +282,8 @@ paths:
Fires an event with event_type
tags:
- Events
security:
- api_key: []
consumes:
- application/json
parameters:
@@ -286,6 +308,8 @@ paths:
Calls a service within a specific domain. Will return when the service has been executed or 10 seconds has past, whichever comes first.
tags:
- Services
security:
- api_key: []
consumes:
- application/json
parameters:
@@ -317,6 +341,8 @@ paths:
Render a Home Assistant template.
tags:
- Template
security:
- api_key: []
consumes:
- application/json
produces:
@@ -338,6 +364,8 @@ paths:
Setup event forwarding to another Home Assistant instance.
tags:
- Core
security:
- api_key: []
consumes:
- application/json
parameters:
@@ -376,6 +404,8 @@ paths:
tags:
- Core
- Events
security:
- api_key: []
produces:
- text/event-stream
parameters:
@@ -420,8 +450,16 @@ definitions:
location_name:
type: string
unit_system:
type: string
description: The system for measurement units
type: object
properties:
length:
type: string
mass:
type: string
temperature:
type: string
volume:
type: string
time_zone:
type: string
version:

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)

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,33 +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_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
@@ -60,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
@@ -68,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', [])
@@ -147,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)
async_log_exception(ex, domain, config, hass)
return None
elif hasattr(component, 'PLATFORM_SCHEMA'):
@@ -157,8 +218,8 @@ 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)
return None
async_log_exception(ex, domain, config, hass)
continue
# Not all platform components follow same pattern for platforms
# So if p_name is None we are not going to validate platform
@@ -167,20 +228,21 @@ 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:
return None
continue
# Validate platform specific schema
if hasattr(platform, 'PLATFORM_SCHEMA'):
try:
# pylint: disable=no-member
p_validated = platform.PLATFORM_SCHEMA(p_validated)
except vol.Invalid as ex:
log_exception(ex, '{}.{}'.format(domain, p_name),
p_validated)
return None
async_log_exception(ex, '{}.{}'.format(domain, p_name),
p_validated, hass)
continue
platforms.append(p_validated)
@@ -191,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
@@ -200,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)
@@ -209,6 +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)
_async_persistent_notification(hass, platform_path)
return None
# Already loaded
@@ -217,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,
@@ -250,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)
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)
@@ -268,41 +392,45 @@ 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)
setup_lock.release()
yield from hass.async_stop_track_tasks()
hass.loop.run_until_complete(
hass.loop.run_in_executor(None, component_setup)
)
return hass
@@ -319,30 +447,71 @@ 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")
# 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
logging.getLogger().handlers[0].setFormatter(ColoredFormatter(
@@ -360,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)
@@ -380,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:
@@ -389,36 +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):
def log_exception(ex, domain, config, hass):
"""Generate log exception for config validation."""
run_callback_threadsafe(
hass.loop, async_log_exception, ex, domain, config, hass).result()
@core.callback
def _async_persistent_notification(hass: core.HomeAssistant, component: str,
link: Optional[bool]=False):
"""Print a persistent notification.
This method must be run in the event loop.
"""
_PERSISTENT_ERRORS[component] = _PERSISTENT_ERRORS.get(component) or link
_lst = [HA_COMPONENT_URL.format(name.replace('_', '-'), name)
if link else name for name, link in _PERSISTENT_ERRORS.items()]
message = ('The following components and platforms could not be set up:\n'
'* ' + '\n* '.join(list(_lst)) + '\nPlease check your config')
persistent_notification.async_create(
hass, message, 'Invalid config', 'invalid_config')
@core.callback
def async_log_exception(ex, domain, config, hass):
"""Generate log exception for config validation.
This method must be run in the event loop.
"""
message = 'Invalid config for [{}]: '.format(domain)
if hass is not None:
_async_persistent_notification(hass, domain, True)
if 'extra keys not allowed' in ex.error_message:
message += '[{}] is an invalid option for [{}]. Check: {}->{}.'\
.format(ex.path[-1], domain, domain,
'->'.join('%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'))

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

View File

@@ -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."""

View File

@@ -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

View File

@@ -0,0 +1,123 @@
"""
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)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['concord232==0.14']
_LOGGER = logging.getLogger(__name__)
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,
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Concord232 alarm control panel platform."""
name = config.get(CONF_NAME)
host = config.get(CONF_HOST)
port = config.get(CONF_PORT)
url = 'http://{}:{}'.format(host, port)
try:
add_devices([Concord232Alarm(hass, url, name)])
except requests.exceptions.ConnectionError as ex:
_LOGGER.error("Unable to connect to Concord232: %s", str(ex))
return False
class Concord232Alarm(alarm.AlarmControlPanel):
"""Represents the Concord232-based alarm panel."""
def __init__(self, hass, url, name):
"""Initialize the Concord232 alarm panel."""
from concord232 import client as concord232_client
self._state = STATE_UNKNOWN
self._hass = hass
self._name = name
self._url = url
try:
client = concord232_client.Client(self._url)
except requests.exceptions.ConnectionError as 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 name(self):
"""Return the name of the device."""
return self._name
@property
def code_format(self):
"""The characters if code is defined."""
return '[0-9]{4}([0-9]{2})?'
@property
def state(self):
"""Return the state of the device."""
return self._state
def update(self):
"""Update values from API."""
try:
part = self._alarm.list_partitions()[0]
except requests.exceptions.ConnectionError as ex:
_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")
newstate = STATE_UNKNOWN
if part['arming_level'] == 'Off':
newstate = STATE_ALARM_DISARMED
elif 'Home' in part['arming_level']:
newstate = STATE_ALARM_ARMED_HOME
else:
newstate = STATE_ALARM_ARMED_AWAY
if not newstate == self._state:
_LOGGER.info("State Chnage from %s to %s", self._state, newstate)
self._state = newstate
return self._state
def alarm_disarm(self, code=None):
"""Send disarm command."""
self._alarm.disarm(code)
def alarm_arm_home(self, code=None):
"""Send arm home command."""
self._alarm.arm('home')
def alarm_arm_away(self, code=None):
"""Send arm away command."""
self._alarm.arm('auto')

View File

@@ -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)

View File

@@ -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):

View File

@@ -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."""

View File

@@ -60,11 +60,7 @@ class NX584Alarm(alarm.AlarmControlPanel):
# talk to the API and trigger a requests exception for setup_platform()
# to catch
self._alarm.list_zones()
@property
def should_poll(self):
"""Polling needed."""
return True
self._state = STATE_UNKNOWN
@property
def name(self):
@@ -79,16 +75,20 @@ class NX584Alarm(alarm.AlarmControlPanel):
@property
def state(self):
"""Return the state of the device."""
return self._state
def update(self):
"""Process new events from panel."""
try:
part = self._alarm.list_partitions()[0]
zones = self._alarm.list_zones()
except requests.exceptions.ConnectionError as ex:
_LOGGER.error('Unable to connect to %(host)s: %(reason)s',
dict(host=self._url, reason=ex))
return STATE_UNKNOWN
self._state = STATE_UNKNOWN
except IndexError:
_LOGGER.error('nx584 reports no partitions')
return STATE_UNKNOWN
self._state = STATE_UNKNOWN
bypassed = False
for zone in zones:
@@ -100,11 +100,11 @@ class NX584Alarm(alarm.AlarmControlPanel):
break
if not part['armed']:
return STATE_ALARM_DISARMED
self._state = STATE_ALARM_DISARMED
elif bypassed:
return STATE_ALARM_ARMED_HOME
self._state = STATE_ALARM_ARMED_HOME
else:
return STATE_ALARM_ARMED_AWAY
self._state = STATE_ALARM_ARMED_AWAY
def alarm_disarm(self, code=None):
"""Send disarm command."""
@@ -112,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')

View File

@@ -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'

View File

@@ -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."""

View File

@@ -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()

View File

@@ -4,19 +4,25 @@ 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
import uuid
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
import homeassistant.util.dt as dt_util
_LOGGER = logging.getLogger(__name__)
API_ENDPOINT = '/api/alexa'
INTENTS_API_ENDPOINT = '/api/alexa'
FLASH_BRIEFINGS_API_ENDPOINT = '/api/alexa/flash_briefings/{briefing_id}'
CONF_ACTION = 'action'
CONF_CARD = 'card'
@@ -28,6 +34,23 @@ CONF_TITLE = 'title'
CONF_CONTENT = 'content'
CONF_TEXT = 'text'
CONF_FLASH_BRIEFINGS = 'flash_briefings'
CONF_UID = 'uid'
CONF_DATE = 'date'
CONF_TITLE = 'title'
CONF_AUDIO = 'audio'
CONF_TEXT = 'text'
CONF_DISPLAY_URL = 'display_url'
ATTR_UID = 'uid'
ATTR_UPDATE_DATE = 'updateDate'
ATTR_TITLE_TEXT = 'titleText'
ATTR_STREAM_URL = 'streamUrl'
ATTR_MAIN_TEXT = 'mainText'
ATTR_REDIRECTION_URL = 'redirectionURL'
DATE_FORMAT = '%Y-%m-%dT%H:%M:%S.0Z'
DOMAIN = 'alexa'
DEPENDENCIES = ['http']
@@ -61,6 +84,16 @@ CONFIG_SCHEMA = vol.Schema({
vol.Required(CONF_TEXT): cv.template,
}
}
},
CONF_FLASH_BRIEFINGS: {
cv.string: vol.All(cv.ensure_list, [{
vol.Required(CONF_UID, default=str(uuid.uuid4())): cv.string,
vol.Optional(CONF_DATE, default=datetime.utcnow()): cv.string,
vol.Required(CONF_TITLE): cv.template,
vol.Optional(CONF_AUDIO): cv.template,
vol.Required(CONF_TEXT, default=""): cv.template,
vol.Optional(CONF_DISPLAY_URL): cv.template,
}]),
}
}
}, extra=vol.ALLOW_EXTRA)
@@ -68,21 +101,24 @@ CONFIG_SCHEMA = vol.Schema({
def setup(hass, config):
"""Activate Alexa component."""
hass.wsgi.register_view(AlexaView(hass,
config[DOMAIN].get(CONF_INTENTS, {})))
intents = config[DOMAIN].get(CONF_INTENTS, {})
flash_briefings = config[DOMAIN].get(CONF_FLASH_BRIEFINGS, {})
hass.http.register_view(AlexaIntentsView(hass, intents))
hass.http.register_view(AlexaFlashBriefingView(hass, flash_briefings))
return True
class AlexaView(HomeAssistantView):
class AlexaIntentsView(HomeAssistantView):
"""Handle Alexa requests."""
url = API_ENDPOINT
url = INTENTS_API_ENDPOINT
name = 'api:alexa'
def __init__(self, hass, intents):
"""Initialize Alexa view."""
super().__init__(hass)
super().__init__()
intents = copy.deepcopy(intents)
template.attach(hass, intents)
@@ -94,9 +130,10 @@ class AlexaView(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)
@@ -113,7 +150,7 @@ class AlexaView(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(
@@ -142,7 +179,7 @@ class AlexaView(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:
@@ -184,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):
@@ -195,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,
@@ -210,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):
@@ -235,3 +272,69 @@ class AlexaResponse(object):
'sessionAttributes': self.session_attributes,
'response': response,
}
class AlexaFlashBriefingView(HomeAssistantView):
"""Handle Alexa Flash Briefing skill requests."""
url = FLASH_BRIEFINGS_API_ENDPOINT
name = 'api:alexa:flash_briefings'
def __init__(self, hass, flash_briefings):
"""Initialize Alexa view."""
super().__init__()
self.flash_briefings = copy.deepcopy(flash_briefings)
template.attach(hass, self.flash_briefings)
@callback
def get(self, request, briefing_id):
"""Handle Alexa Flash Briefing request."""
_LOGGER.debug('Received Alexa flash briefing request for: %s',
briefing_id)
if self.flash_briefings.get(briefing_id) is None:
err = 'No configured Alexa flash briefing was found for: %s'
_LOGGER.error(err, briefing_id)
return b'', 404
briefing = []
for item in self.flash_briefings.get(briefing_id, []):
output = {}
if item.get(CONF_TITLE) is not None:
if isinstance(item.get(CONF_TITLE), template.Template):
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].async_render()
else:
output[ATTR_MAIN_TEXT] = item.get(CONF_TEXT)
if item.get(CONF_UID) is not None:
output[ATTR_UID] = item.get(CONF_UID)
if item.get(CONF_AUDIO) is not None:
if isinstance(item.get(CONF_AUDIO), template.Template):
output[ATTR_STREAM_URL] = item[CONF_AUDIO].async_render()
else:
output[ATTR_STREAM_URL] = item.get(CONF_AUDIO)
if item.get(CONF_DISPLAY_URL) is not None:
if isinstance(item.get(CONF_DISPLAY_URL),
template.Template):
output[ATTR_REDIRECTION_URL] = \
item[CONF_DISPLAY_URL].async_render()
else:
output[ATTR_REDIRECTION_URL] = item.get(CONF_DISPLAY_URL)
if isinstance(item[CONF_DATE], str):
item[CONF_DATE] = dt_util.parse_datetime(item[CONF_DATE])
output[ATTR_UPDATE_DATE] = item[CONF_DATE].strftime(DATE_FORMAT)
briefing.append(output)
return self.json(briefing)

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()]

View File

@@ -11,7 +11,7 @@ import os
import voluptuous as vol
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,
@@ -24,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 + '.{}'
@@ -67,6 +66,7 @@ def _platform_validator(config):
return getattr(platform, 'TRIGGER_SCHEMA')(config)
_TRIGGER_SCHEMA = vol.All(
cv.ensure_list,
[
@@ -143,68 +143,81 @@ 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')
)
@asyncio.coroutine
def trigger_service_handler(service_call):
"""Handle automation triggers."""
for entity in component.extract_from_service(service_call):
hass.loop.create_task(entity.async_trigger(
tasks = []
for entity in component.async_extract_from_service(service_call):
tasks.append(entity.async_trigger(
service_call.data.get(ATTR_VARIABLES), True))
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.extract_from_service(service_call):
hass.loop.create_task(getattr(entity, method)())
for entity in component.async_extract_from_service(service_call):
tasks.append(getattr(entity, method)())
if tasks:
yield from asyncio.wait(tasks, loop=hass.loop)
@asyncio.coroutine
def toggle_service_handler(service_call):
"""Handle automation toggle service calls."""
for entity in component.extract_from_service(service_call):
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):
"""Remove all automations and load new ones from config."""
conf = yield from hass.loop.run_in_executor(
None, component.prepare_reload)
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,7 +282,7 @@ class AutomationEntity(ToggleEntity):
self._async_detach_triggers()
self._async_detach_triggers = None
self._enabled = False
self.hass.loop.create_task(self.async_update_ha_state())
yield from self.async_update_ha_state()
@asyncio.coroutine
def async_trigger(self, variables, skip_condition=False):
@@ -280,15 +291,15 @@ class AutomationEntity(ToggleEntity):
This method is a coroutine.
"""
if skip_condition or self._cond_func(variables):
yield from self._async_action(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()
def remove(self):
@asyncio.coroutine
def async_remove(self):
"""Remove automation from HASS."""
run_coroutine_threadsafe(self.async_turn_off(),
self.hass.loop).result()
super().remove()
yield from self.async_turn_off()
yield from super().async_remove()
@asyncio.coroutine
def async_enable(self):
@@ -341,12 +352,13 @@ def _async_process_config(hass, config, component):
entity = AutomationEntity(name, async_attach_triggers, cond_func,
action, hidden)
if config_block[CONF_INITIAL_STATE]:
tasks.append(hass.loop.create_task(entity.async_enable()))
tasks.append(entity.async_enable())
entities.append(entity)
yield from asyncio.gather(*tasks, loop=hass.loop)
yield from hass.loop.run_in_executor(
None, component.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
@@ -356,11 +368,12 @@ def _async_get_action(hass, config, name):
script_obj = script.Script(hass, config, name)
@asyncio.coroutine
def action(variables=None):
def action(entity_id, variables):
"""Action to be executed."""
_LOGGER.info('Executing %s', name)
logbook.async_log_entry(hass, name, 'has been triggered', DOMAIN)
hass.loop.create_task(script_obj.async_run(variables))
logbook.async_log_entry(
hass, name, 'has been triggered', DOMAIN, entity_id)
yield from script_obj.async_run(variables)
return action
@@ -393,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

View File

@@ -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

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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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."""

View File

@@ -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."""

View File

@@ -0,0 +1,136 @@
"""
Support for exposing Concord232 elements as sensors.
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
REQUIREMENTS = ['concord232==0.14']
_LOGGER = logging.getLogger(__name__)
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),
})
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_EXCLUDE_ZONES, default=[]):
vol.All(cv.ensure_list, [cv.positive_int]),
vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_ZONE_TYPES, default={}): ZONE_TYPES_SCHEMA,
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Concord232 binary sensor platform."""
from concord232 import client as concord232_client
host = config.get(CONF_HOST)
port = config.get(CONF_PORT)
exclude = config.get(CONF_EXCLUDE_ZONES)
zone_types = config.get(CONF_ZONE_TYPES)
sensors = []
try:
_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))
return False
for zone in client.zones:
_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)))
)
add_devices(sensors)
return True
def get_opening_type(zone):
"""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):
"""Representation of a Concord232 zone as a sensor."""
def __init__(self, hass, client, zone, zone_type):
"""Initialize the Concord232 binary sensor."""
self._hass = hass
self._client = client
self._zone = zone
self._number = zone['number']
self._zone_type = zone_type
self.update()
@property
def sensor_class(self):
"""Return the class of this sensor, from SENSOR_CLASSES."""
return self._zone_type
@property
def should_poll(self):
"""No polling needed."""
return True
@property
def name(self):
"""Return the name of the binary sensor."""
return self._zone['name']
@property
def is_on(self):
"""Return true if the binary sensor is on."""
# True means "faulted" or "open" or "abnormal state"
return bool(self._zone['state'] == 'Normal')
def update(self):
""""Get updated stats from API."""
last_update = datetime.datetime.now() - self._client.last_zone_update
_LOGGER.debug("Zone: %s ", self._zone)
if last_update > datetime.timedelta(seconds=1):
self._client.zones = self._client.list_zones()
self._client.last_zone_update = datetime.datetime.now()
_LOGGER.debug("Updated from Zone: %s", self._zone['name'])
if hasattr(self._client, 'zones'):
self._zone = next((x for x in self._client.zones
if x['number'] == self._number), None)

View File

@@ -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,
})

View File

@@ -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

View File

@@ -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."""

View File

@@ -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()

View File

@@ -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()

View File

@@ -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

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.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)

View File

@@ -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

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)

View File

@@ -0,0 +1,160 @@
"""
Support for the Netatmo binary sensors.
The binary sensors based on events seen by the NetatmoCamera
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.netatmo/
"""
import logging
import voluptuous as vol
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, CONF_TIMEOUT
from homeassistant.helpers import config_validation as cv
DEPENDENCIES = ["netatmo"]
_LOGGER = logging.getLogger(__name__)
# These are the available sensors mapped to binary_sensor class
SENSOR_TYPES = {
"Someone known": 'occupancy',
"Someone unknown": 'motion',
"Motion": 'motion',
"Tag Vibration": 'vibration',
"Tag Open": 'opening',
}
CONF_HOME = 'home'
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()):
vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]),
})
# pylint: disable=unused-argument
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:
data = WelcomeData(netatmo.NETATMO_AUTH, home)
if data.get_camera_names() == []:
return None
except lnetatmo.NoDevice:
return None
sensors = config.get(CONF_MONITORED_CONDITIONS, SENSOR_TYPES)
for camera_name in data.get_camera_names():
if CONF_CAMERAS in config:
if config[CONF_CAMERAS] != [] and \
camera_name not in config[CONF_CAMERAS]:
continue
for variable in sensors:
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, 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,
home=home)['id']
self._unique_id = "Welcome_binary_sensor {0} - {1}".format(self._name,
camera_id)
self.update()
@property
def name(self):
"""The name of the Netatmo device and this sensor."""
return self._name
@property
def unique_id(self):
"""Return the unique ID for this sensor."""
return self._unique_id
@property
def sensor_class(self):
"""Return the class of this sensor, from SENSOR_CLASSES."""
return SENSOR_TYPES.get(self._sensor_name)
@property
def is_on(self):
"""Return true if binary sensor is on."""
return self._state
def update(self):
"""Request an update from the Netatmo API."""
self._data.update()
self._data.update_event()
if self._sensor_name == "Someone known":
self._state =\
self._data.welcomedata.someoneKnownSeen(self._home,
self._camera_name,
self._timeout*60)
elif self._sensor_name == "Someone unknown":
self._state =\
self._data.welcomedata.someoneUnknownSeen(self._home,
self._camera_name,
self._timeout*60)
elif self._sensor_name == "Motion":
self._state =\
self._data.welcomedata.motionDetected(self._home,
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

View File

@@ -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:

View File

@@ -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."""

View File

@@ -5,7 +5,6 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.rest/
"""
import logging
import json
import voluptuous as vol
from requests.auth import HTTPBasicAuth, HTTPDigestAuth
@@ -30,7 +29,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_RESOURCE): cv.url,
vol.Optional(CONF_AUTHENTICATION):
vol.In([HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION]),
vol.Optional(CONF_HEADERS): cv.string,
vol.Optional(CONF_HEADERS): {cv.string: cv.string},
vol.Optional(CONF_METHOD, default=DEFAULT_METHOD): vol.In(['POST', 'GET']),
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PASSWORD): cv.string,
@@ -42,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)
@@ -52,7 +50,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
verify_ssl = config.get(CONF_VERIFY_SSL)
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
headers = json.loads(config.get(CONF_HEADERS, '{}'))
headers = config.get(CONF_HEADERS)
sensor_class = config.get(CONF_SENSOR_CLASS)
value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
@@ -70,14 +68,13 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
rest.update()
if rest.data is None:
_LOGGER.error('Unable to fetch REST data')
_LOGGER.error("Unable to fetch REST data from %s", resource)
return False
add_devices([RestBinarySensor(
hass, rest, name, sensor_class, value_template)])
# pylint: disable=too-many-arguments
class RestBinarySensor(BinarySensorDevice):
"""Representation of a REST binary sensor."""
@@ -109,8 +106,8 @@ class RestBinarySensor(BinarySensorDevice):
return False
if self._value_template is not None:
response = self._value_template.render_with_possible_json_value(
self.rest.data, False)
response = self._value_template.\
async_render_with_possible_json_value(self.rest.data, False)
try:
return bool(int(response))

View File

@@ -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)

View File

@@ -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."""

View File

@@ -7,21 +7,20 @@ https://home-assistant.io/components/binary_sensor.tcp/
import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.sensor.tcp import Sensor, CONF_VALUE_ON
from homeassistant.components.sensor.tcp import (
TcpSensor, CONF_VALUE_ON, PLATFORM_SCHEMA)
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Create the binary sensor."""
if not BinarySensor.validate_config(config):
return False
add_entities((BinarySensor(hass, config),))
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({})
class BinarySensor(BinarySensorDevice, Sensor):
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the TCP binary sensor."""
add_devices([TcpBinarySensor(hass, config)])
class TcpBinarySensor(BinarySensorDevice, TcpSensor):
"""A binary sensor which is on when its state == CONF_VALUE_ON."""
required = (CONF_VALUE_ON,)

View File

@@ -17,8 +17,8 @@ from homeassistant.const import (
ATTR_FRIENDLY_NAME, ATTR_ENTITY_ID, CONF_VALUE_TEMPLATE,
CONF_SENSOR_CLASS, CONF_SENSORS)
from homeassistant.exceptions import TemplateError
from homeassistant.helpers.entity import generate_entity_id
from homeassistant.helpers.event import track_state_change
from homeassistant.helpers.entity import async_generate_entity_id
from homeassistant.helpers.event import async_track_state_change
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
@@ -35,7 +35,8 @@ 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 template binary sensors."""
sensors = []
@@ -61,34 +62,32 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
if not sensors:
_LOGGER.error('No sensors added')
return False
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."""
self.hass = hass
self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, device,
hass=hass)
self.entity_id = async_generate_entity_id(ENTITY_ID_FORMAT, device,
hass=hass)
self._name = friendly_name
self._sensor_class = sensor_class
self._template = value_template
self._state = None
self.update()
@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)
track_state_change(hass, entity_ids, template_bsensor_state_listener)
async_track_state_change(
hass, entity_ids, template_bsensor_state_listener)
@property
def name(self):
@@ -112,7 +111,7 @@ class BinarySensorTemplate(BinarySensorDevice):
@asyncio.coroutine
def async_update(self):
"""Get the latest data and update the state."""
"""Update the state from the template."""
try:
self._state = self._template.async_render().lower() == 'true'
except TemplateError as ex:

View File

@@ -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)

View File

@@ -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

View File

@@ -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'])

View File

@@ -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):

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()

View File

@@ -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

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]

View File

@@ -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']

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)

View File

@@ -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

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)

View File

@@ -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

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):

View File

@@ -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."""

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

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):

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

View File

@@ -5,12 +5,11 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.netatmo/
"""
import logging
from datetime import timedelta
import requests
import voluptuous as vol
from homeassistant.util import Throttle
from homeassistant.components.netatmo import WelcomeData
from homeassistant.components.camera import (Camera, PLATFORM_SCHEMA)
from homeassistant.loader import get_component
from homeassistant.helpers import config_validation as cv
@@ -22,8 +21,6 @@ _LOGGER = logging.getLogger(__name__)
CONF_HOME = 'home'
CONF_CAMERAS = 'cameras'
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=10)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_HOME): cv.string,
vol.Optional(CONF_CAMERAS, default=[]):
@@ -39,15 +36,15 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
import lnetatmo
try:
data = WelcomeData(netatmo.NETATMO_AUTH, home)
for camera_name in data.get_camera_names():
if CONF_CAMERAS in config:
if config[CONF_CAMERAS] != [] and \
camera_name not in config[CONF_CAMERAS]:
continue
add_devices([WelcomeCamera(data, camera_name, home)])
except lnetatmo.NoDevice:
return None
for camera_name in data.get_camera_names():
if config[CONF_CAMERAS] != []:
if camera_name not in config[CONF_CAMERAS]:
continue
add_devices([WelcomeCamera(data, camera_name, home)])
class WelcomeCamera(Camera):
"""Representation of the images published from Welcome camera."""
@@ -61,6 +58,10 @@ class WelcomeCamera(Camera):
self._name = home + ' / ' + camera_name
else:
self._name = camera_name
camera_id = data.welcomedata.cameraByName(camera=camera_name,
home=home)['id']
self._unique_id = "Welcome_camera {0} - {1}".format(self._name,
camera_id)
self._vpnurl, self._localurl = self._data.welcomedata.cameraUrls(
camera=camera_name
)
@@ -87,31 +88,7 @@ class WelcomeCamera(Camera):
"""Return the name of this Netatmo Welcome device."""
return self._name
class WelcomeData(object):
"""Get the latest data from NetAtmo."""
def __init__(self, auth, home=None):
"""Initialize the data object."""
self.auth = auth
self.welcomedata = None
self.camera_names = []
self.home = home
def get_camera_names(self):
"""Return all module available on the API as a list."""
self.update()
if not self.home:
for home in self.welcomedata.cameras:
for camera in self.welcomedata.cameras[home].values():
self.camera_names.append(camera['name'])
else:
for camera in self.welcomedata.cameras[self.home].values():
self.camera_names.append(camera['name'])
return self.camera_names
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Call the NetAtmo API to update the data."""
import lnetatmo
self.welcomedata = lnetatmo.WelcomeData(self.auth)
@property
def unique_id(self):
"""Return the unique ID for this sensor."""
return self._unique_id

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],

View File

@@ -0,0 +1,288 @@
"""
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 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_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__)
DEFAULT_NAME = 'Synology Camera'
DEFAULT_STREAM_ID = '0'
TIMEOUT = 5
CONF_CAMERA_NAME = 'camera_name'
CONF_STREAM_ID = 'stream_id'
QUERY_CGI = 'query.cgi'
QUERY_API = 'SYNO.API.Info'
AUTH_API = 'SYNO.API.Auth'
CAMERA_API = 'SYNO.SurveillanceStation.Camera'
STREAMING_API = 'SYNO.SurveillanceStation.VideoStream'
SESSION_ID = '0'
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}'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_URL): cv.string,
vol.Optional(CONF_WHITELIST, default=[]): cv.ensure_list,
vol.Optional(CONF_VERIFY_SSL, default=True): cv.boolean,
})
@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 = 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 = 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'
}
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']
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)
@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 = 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()
class SynologyCamera(Camera):
"""An implementation of a Synology NAS based IP camera."""
def __init__(self, hass, websession, config, camera_id,
camera_name, snapshot_path, streaming_path, camera_path,
auth_path):
"""Initialize a Synology Surveillance Station camera."""
super().__init__()
self.hass = hass
self._websession = websession
self._name = camera_name
self._synology_url = config.get(CONF_URL)
self._camera_name = config.get(CONF_CAMERA_NAME)
self._stream_id = config.get(CONF_STREAM_ID)
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
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
}
try:
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
image = yield from response.read()
yield from response.release()
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'
}
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):
"""Return the name of this device."""
return self._name

View File

@@ -0,0 +1,103 @@
"""
Camera that loads a picture from a local file.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.verisure/
"""
import errno
import logging
import os
from homeassistant.components.camera import Camera
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.components.verisure import HUB as hub
from homeassistant.components.verisure import CONF_SMARTCAM
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Camera."""
if not int(hub.config.get(CONF_SMARTCAM, 1)):
return False
directory_path = hass.config.config_dir
if not os.access(directory_path, os.R_OK):
_LOGGER.error("file path %s is not readable", directory_path)
return False
hub.update_smartcam()
smartcams = []
smartcams.extend([
VerisureSmartcam(hass, value.deviceLabel, directory_path)
for value in hub.smartcam_status.values()])
add_devices(smartcams)
class VerisureSmartcam(Camera):
"""Local camera."""
def __init__(self, hass, device_id, directory_path):
"""Initialize Verisure File Camera component."""
super().__init__()
self._device_id = device_id
self._directory_path = directory_path
self._image = None
self._image_id = None
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP,
self.delete_image)
def camera_image(self):
"""Return image response."""
self.check_imagelist()
if not self._image:
_LOGGER.debug('No image to display')
return
_LOGGER.debug('Trying to open %s', self._image)
with open(self._image, 'rb') as file:
return file.read()
def check_imagelist(self):
"""Check the contents of the image list."""
hub.update_smartcam_imagelist()
if (self._device_id not in hub.smartcam_dict or
not hub.smartcam_dict[self._device_id]):
return
images = hub.smartcam_dict[self._device_id]
new_image_id = images[0]
_LOGGER.debug('self._device_id=%s, self._images=%s, '
'self._new_image_id=%s', self._device_id,
images, new_image_id)
if (new_image_id == '-1' or
self._image_id == new_image_id):
_LOGGER.debug('The image is the same, or loading image_id')
return
_LOGGER.debug('Download new image %s', new_image_id)
hub.my_pages.smartcam.download_image(self._device_id,
new_image_id,
self._directory_path)
_LOGGER.debug('Old image_id=%s', self._image_id)
self.delete_image(self)
self._image_id = new_image_id
self._image = os.path.join(self._directory_path,
'{}{}'.format(
self._image_id,
'.jpg'))
def delete_image(self, event):
"""Delete an old image."""
remove_image = os.path.join(self._directory_path,
'{}{}'.format(
self._image_id,
'.jpg'))
try:
os.remove(remove_image)
_LOGGER.debug('Deleting old image %s', remove_image)
except OSError as error:
if error.errno != errno.ENOENT:
raise
@property
def name(self):
"""Return the name of this camera."""
return hub.smartcam_status[self._device_id].location

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)
@@ -253,7 +256,7 @@ def setup(hass, config):
kwargs[value] = convert_temperature(
temp,
hass.config.units.temperature_unit,
climate.unit_of_measurement
climate.temperature_unit
)
else:
kwargs[value] = temp
@@ -364,11 +367,22 @@ 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."""
return self.current_operation or STATE_UNKNOWN
if self.current_operation:
return self.current_operation
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):
@@ -398,17 +412,20 @@ class ClimateDevice(Entity):
fan_mode = self.current_fan_mode
if fan_mode is not None:
data[ATTR_FAN_MODE] = fan_mode
data[ATTR_FAN_LIST] = self.fan_list
if self.fan_list:
data[ATTR_FAN_LIST] = self.fan_list
operation_mode = self.current_operation
if operation_mode is not None:
data[ATTR_OPERATION_MODE] = operation_mode
data[ATTR_OPERATION_LIST] = self.operation_list
if self.operation_list:
data[ATTR_OPERATION_LIST] = self.operation_list
swing_mode = self.current_swing_mode
if swing_mode is not None:
data[ATTR_SWING_MODE] = swing_mode
data[ATTR_SWING_LIST] = self.swing_list
if self.swing_list:
data[ATTR_SWING_LIST] = self.swing_list
is_away = self.is_away_mode_on
if is_away is not None:
@@ -422,7 +439,12 @@ class ClimateDevice(Entity):
@property
def unit_of_measurement(self):
"""Return the unit of measurement."""
"""The unit of measurement to display."""
return self.hass.config.units.temperature_unit
@property
def temperature_unit(self):
"""The unit of measurement used by the platform."""
raise NotImplementedError
@property
@@ -534,12 +556,12 @@ class ClimateDevice(Entity):
@property
def min_temp(self):
"""Return the minimum temperature."""
return convert_temperature(7, TEMP_CELSIUS, self.unit_of_measurement)
return convert_temperature(7, TEMP_CELSIUS, self.temperature_unit)
@property
def max_temp(self):
"""Return the maximum temperature."""
return convert_temperature(35, TEMP_CELSIUS, self.unit_of_measurement)
return convert_temperature(35, TEMP_CELSIUS, self.temperature_unit)
@property
def min_humidity(self):
@@ -553,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.unit_of_measurement,
self.hass.config.units.temperature_unit)
value = convert_temperature(temp, self.temperature_unit,
self.unit_of_measurement)
if self.hass.config.units.temperature_unit 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)

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,
@@ -59,7 +57,7 @@ class DemoClimate(ClimateDevice):
return self._name
@property
def unit_of_measurement(self):
def temperature_unit(self):
"""Return the unit of measurement."""
return self._unit_of_measurement

View File

@@ -14,7 +14,7 @@ from homeassistant.components.climate import (
DOMAIN, STATE_COOL, STATE_HEAT, STATE_IDLE, ClimateDevice,
ATTR_TARGET_TEMP_LOW, ATTR_TARGET_TEMP_HIGH)
from homeassistant.const import (
ATTR_ENTITY_ID, STATE_OFF, STATE_ON, TEMP_FAHRENHEIT, TEMP_CELSIUS)
ATTR_ENTITY_ID, STATE_OFF, STATE_ON, TEMP_FAHRENHEIT)
from homeassistant.config import load_yaml_config_file
import homeassistant.helpers.config_validation as cv
@@ -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."""
@@ -105,12 +104,9 @@ class Thermostat(ClimateDevice):
return self.thermostat['name']
@property
def unit_of_measurement(self):
def temperature_unit(self):
"""Return the unit of measurement."""
if self.thermostat['settings']['useCelsius']:
return TEMP_CELSIUS
else:
return TEMP_FAHRENHEIT
return TEMP_FAHRENHEIT
@property
def current_temperature(self):
@@ -199,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

View File

@@ -1,24 +1,38 @@
"""
Support for eq3 Bluetooth Smart thermostats.
Support for eQ-3 Bluetooth Smart thermostats.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.eq3btsmart/
"""
import logging
from homeassistant.components.climate import ClimateDevice
from homeassistant.const import TEMP_CELSIUS, CONF_DEVICES, ATTR_TEMPERATURE
import voluptuous as vol
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
from homeassistant.const import (
CONF_MAC, TEMP_CELSIUS, CONF_DEVICES, ATTR_TEMPERATURE)
from homeassistant.util.temperature import convert
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['bluepy_devices==0.2.0']
CONF_MAC = 'mac'
_LOGGER = logging.getLogger(__name__)
ATTR_MODE = 'mode'
ATTR_MODE_READABLE = 'mode_readable'
DEVICE_SCHEMA = vol.Schema({
vol.Required(CONF_MAC): cv.string,
})
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_DEVICES):
vol.Schema({cv.string: DEVICE_SCHEMA}),
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the eq3 BLE thermostats."""
"""Setup the eQ-3 BLE thermostats."""
devices = []
for name, device_cfg in config[CONF_DEVICES].items():
@@ -28,16 +42,15 @@ 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 EQ3 Bluetooth Smart thermostat."""
"""Representation of a eQ-3 Bluetooth Smart thermostat."""
def __init__(self, _mac, _name):
"""Initialize the thermostat."""
from bluepy_devices.devices import eq3btsmart
self._name = _name
self._thermostat = eq3btsmart.EQ3BTSmartThermostat(_mac)
@property
@@ -46,7 +59,7 @@ class EQ3BTSmartThermostat(ClimateDevice):
return self._name
@property
def unit_of_measurement(self):
def temperature_unit(self):
"""Return the unit of measurement that is used."""
return TEMP_CELSIUS
@@ -70,8 +83,10 @@ class EQ3BTSmartThermostat(ClimateDevice):
@property
def device_state_attributes(self):
"""Return the device specific state attributes."""
return {"mode": self._thermostat.mode,
"mode_readable": self._thermostat.mode_readable}
return {
ATTR_MODE: self._thermostat.mode,
ATTR_MODE_READABLE: self._thermostat.mode_readable,
}
@property
def min_temp(self):

View File

@@ -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
@@ -100,7 +103,7 @@ class GenericThermostat(ClimateDevice):
return self._name
@property
def unit_of_measurement(self):
def temperature_unit(self):
"""Return the unit of measurement."""
return self._unit
@@ -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):

View File

@@ -1,56 +1,54 @@
"""
Support for the PRT Heatmiser themostats using the V3 protocol.
See https://github.com/andylockran/heatmiserV3 for more info on the
heatmiserV3 module dependency.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.heatmiser/
"""
import logging
from homeassistant.components.climate import ClimateDevice
from homeassistant.const import TEMP_CELSIUS, ATTR_TEMPERATURE
import voluptuous as vol
CONF_IPADDRESS = 'ipaddress'
CONF_PORT = 'port'
CONF_TSTATS = 'tstats'
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
from homeassistant.const import (
TEMP_CELSIUS, ATTR_TEMPERATURE, CONF_PORT, CONF_NAME, CONF_ID)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ["heatmiserV3==0.9.1"]
REQUIREMENTS = ['heatmiserV3==0.9.1']
_LOGGER = logging.getLogger(__name__)
CONF_IPADDRESS = 'ipaddress'
CONF_TSTATS = 'tstats'
TSTATS_SCHEMA = vol.Schema({
vol.Required(CONF_ID): cv.string,
vol.Required(CONF_NAME): cv.string,
})
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_IPADDRESS): cv.string,
vol.Required(CONF_PORT): cv.port,
vol.Required(CONF_TSTATS, default={}):
vol.Schema({cv.string: TSTATS_SCHEMA}),
})
# pylint: disable=unused-variable
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the heatmiser thermostat."""
from heatmiserV3 import heatmiser, connection
ipaddress = str(config[CONF_IPADDRESS])
port = str(config[CONF_PORT])
if ipaddress is None or port is None:
_LOGGER.error("Missing required configuration items %s or %s",
CONF_IPADDRESS, CONF_PORT)
return False
ipaddress = config.get(CONF_IPADDRESS)
port = str(config.get(CONF_PORT))
tstats = config.get(CONF_TSTATS)
serport = connection.connection(ipaddress, port)
serport.open()
tstats = []
if CONF_TSTATS in config:
tstats = config[CONF_TSTATS]
if tstats is None:
_LOGGER.error("No thermostats configured.")
return False
for tstat in tstats:
for thermostat, tstat in tstats.items():
add_devices([
HeatmiserV3Thermostat(
heatmiser,
tstat.get("id"),
tstat.get("name"),
serport)
heatmiser, tstat.get(CONF_ID), tstat.get(CONF_NAME), serport)
])
return
@@ -58,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
@@ -69,7 +66,7 @@ class HeatmiserV3Thermostat(ClimateDevice):
self._id = device
self.dcb = None
self.update()
self._target_temperature = int(self.dcb.get("roomset"))
self._target_temperature = int(self.dcb.get('roomset'))
@property
def name(self):
@@ -77,7 +74,7 @@ class HeatmiserV3Thermostat(ClimateDevice):
return self._name
@property
def unit_of_measurement(self):
def temperature_unit(self):
"""Return the unit of measurement which this thermostat uses."""
return TEMP_CELSIUS
@@ -85,9 +82,9 @@ class HeatmiserV3Thermostat(ClimateDevice):
def current_temperature(self):
"""Return the current temperature."""
if self.dcb is not None:
low = self.dcb.get("floortemplow ")
high = self.dcb.get("floortemphigh")
temp = (high*256 + low)/10.0
low = self.dcb.get('floortemplow ')
high = self.dcb.get('floortemphigh')
temp = (high * 256 + low) / 10.0
self._current_temperature = temp
else:
self._current_temperature = None

View File

@@ -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,19 +30,20 @@ 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
def unit_of_measurement(self):
def temperature_unit(self):
"""Return the unit of measurement that is used."""
return TEMP_CELSIUS
@@ -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):

View File

@@ -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
@@ -120,7 +119,7 @@ class RoundThermostat(ClimateDevice):
return self._name
@property
def unit_of_measurement(self):
def temperature_unit(self):
"""Return the unit of measurement."""
return TEMP_CELSIUS
@@ -197,7 +196,6 @@ class RoundThermostat(ClimateDevice):
self._is_dhw = False
# pylint: disable=abstract-method
class HoneywellUSThermostat(ClimateDevice):
"""Representation of a Honeywell US Thermostat."""
@@ -217,7 +215,7 @@ class HoneywellUSThermostat(ClimateDevice):
return self._device.name
@property
def unit_of_measurement(self):
def temperature_unit(self):
"""Return the unit of measurement."""
return (TEMP_CELSIUS if self._device.temperature_unit == 'C'
else TEMP_FAHRENHEIT)
@@ -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()

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):
@@ -63,23 +65,19 @@ class KNXThermostat(KNXMultiAddressDevice, ClimateDevice):
return True
@property
def unit_of_measurement(self):
def temperature_unit(self):
"""Return the unit of measurement."""
return self._unit_of_measurement
@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'))

View File

@@ -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."""
@@ -47,7 +51,7 @@ class MySensorsHVAC(mysensors.MySensorsDeviceEntity, ClimateDevice):
return self.gateway.optimistic
@property
def unit_of_measurement(self):
def temperature_unit(self):
"""Return the unit of measurement."""
return (TEMP_CELSIUS
if self.gateway.metric else TEMP_FAHRENHEIT)

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."""
@@ -40,97 +54,109 @@ class NestThermostat(ClimateDevice):
self.structure = structure
self.device = device
self._fan_list = [STATE_ON, STATE_AUTO]
self._operation_list = [STATE_HEAT, STATE_COOL, STATE_AUTO,
STATE_OFF]
# Not all nest devices support cooling and heating remove unused
self._operation_list = [STATE_OFF]
# Add supported nest thermostat features
if self.device.can_heat:
self._operation_list.append(STATE_HEAT)
if self.device.can_cool:
self._operation_list.append(STATE_COOL)
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 unit_of_measurement(self):
def temperature_unit(self):
"""Return the unit of measurement."""
return TEMP_CELSIUS
@property
def device_state_attributes(self):
"""Return the device specific state attributes."""
# Move these to Thermostat Device and make them global
return {
"humidity": self.device.humidity,
"target_humidity": self.device.target_humidity,
}
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)
@@ -139,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):
@@ -164,7 +187,12 @@ class NestThermostat(ClimateDevice):
@property
def current_fan_mode(self):
"""Return whether the fan is on."""
return STATE_ON if self.device.fan else STATE_AUTO
if self._has_fan:
# Return whether the fan is on
return STATE_ON if self._fan else STATE_AUTO
else:
# No Fan available so disable slider
return None
@property
def fan_list(self):
@@ -178,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

View File

@@ -0,0 +1,177 @@
"""
Support for Netatmo Smart Thermostat.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.netatmo/
"""
import logging
from datetime import timedelta
import voluptuous as vol
from homeassistant.const import TEMP_CELSIUS, ATTR_TEMPERATURE
from homeassistant.components.climate import (
STATE_HEAT, STATE_IDLE, ClimateDevice, PLATFORM_SCHEMA)
from homeassistant.util import Throttle
from homeassistant.loader import get_component
import homeassistant.helpers.config_validation as cv
DEPENDENCIES = ['netatmo']
_LOGGER = logging.getLogger(__name__)
CONF_RELAY = 'relay'
CONF_THERMOSTAT = 'thermostat'
DEFAULT_AWAY_TEMPERATURE = 14
# # The default offeset is 2 hours (when you use the thermostat itself)
DEFAULT_TIME_OFFSET = 7200
# # Return cached results if last scan was less then this time ago
# # NetAtmo Data is uploaded to server every hour
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=300)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_RELAY): cv.string,
vol.Optional(CONF_THERMOSTAT, default=[]):
vol.All(cv.ensure_list, [cv.string]),
})
def setup_platform(hass, config, add_callback_devices, discovery_info=None):
"""Setup the NetAtmo Thermostat."""
netatmo = get_component('netatmo')
device = config.get(CONF_RELAY)
import lnetatmo
try:
data = ThermostatData(netatmo.NETATMO_AUTH, device)
for module_name in data.get_module_names():
if CONF_THERMOSTAT in config:
if config[CONF_THERMOSTAT] != [] and \
module_name not in config[CONF_THERMOSTAT]:
continue
add_callback_devices([NetatmoThermostat(data, module_name)])
except lnetatmo.NoDevice:
return None
class NetatmoThermostat(ClimateDevice):
"""Representation a Netatmo thermostat."""
def __init__(self, data, module_name, away_temp=None):
"""Initialize the sensor."""
self._data = data
self._state = None
self._name = module_name
self._target_temperature = None
self._away = None
self.update()
@property
def name(self):
"""Return the name of the sensor."""
return self._name
@property
def state(self):
"""Return the state of the device."""
return self._target_temperature
@property
def temperature_unit(self):
"""Return the unit of measurement."""
return TEMP_CELSIUS
@property
def current_temperature(self):
"""Return the current temperature."""
return self._data.current_temperature
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
return self._target_temperature
@property
def current_operation(self):
"""Return the current state of the thermostat."""
state = self._data.thermostatdata.relay_cmd
if state == 0:
return STATE_IDLE
elif state == 100:
return STATE_HEAT
@property
def is_away_mode_on(self):
"""Return true if away mode is on."""
return self._away
def turn_away_mode_on(self):
"""Turn away on."""
mode = "away"
temp = None
self._data.thermostatdata.setthermpoint(mode, temp, endTimeOffset=None)
self._away = True
self.update_ha_state()
def turn_away_mode_off(self):
"""Turn away off."""
mode = "program"
temp = None
self._data.thermostatdata.setthermpoint(mode, temp, endTimeOffset=None)
self._away = False
self.update_ha_state()
def set_temperature(self, endTimeOffset=DEFAULT_TIME_OFFSET, **kwargs):
"""Set new target temperature for 2 hours."""
temperature = kwargs.get(ATTR_TEMPERATURE)
if temperature is None:
return
mode = "manual"
self._data.thermostatdata.setthermpoint(
mode, temperature, endTimeOffset)
self._target_temperature = temperature
self._away = False
self.update_ha_state()
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Get the latest data from NetAtmo API and updates the states."""
self._data.update()
self._target_temperature = self._data.thermostatdata.setpoint_temp
self._away = self._data.setpoint_mode == 'away'
class ThermostatData(object):
"""Get the latest data from Netatmo."""
def __init__(self, auth, device=None):
"""Initialize the data object."""
self.auth = auth
self.thermostatdata = None
self.module_names = []
self.device = device
self.current_temperature = None
self.target_temperature = None
self.setpoint_mode = None
# self.operation =
def get_module_names(self):
"""Return all module available on the API as a list."""
self.update()
if not self.device:
for device in self.thermostatdata.modules:
for module in self.thermostatdata.modules[device].values():
self.module_names.append(module['module_name'])
else:
for module in self.thermostatdata.modules[self.device].values():
self.module_names.append(module['module_name'])
return self.module_names
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Call the NetAtmo API to update the data."""
import lnetatmo
self.thermostatdata = lnetatmo.ThermostatData(self.auth)
self.target_temperature = self.thermostatdata.setpoint_temp
self.setpoint_mode = self.thermostatdata.setpoint_mode
self.current_temperature = self.thermostatdata.temp

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.3.1']
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."""
@@ -69,7 +78,7 @@ class ProliphixThermostat(ClimateDevice):
}
@property
def unit_of_measurement(self):
def temperature_unit(self):
"""Return the unit of measurement."""
return TEMP_FAHRENHEIT

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]
@@ -81,7 +97,7 @@ class RadioThermostat(ClimateDevice):
return self._name
@property
def unit_of_measurement(self):
def temperature_unit(self):
"""Return the unit of measurement."""
return TEMP_FAHRENHEIT
@@ -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)

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."""
@@ -93,9 +95,15 @@ class VeraThermostat(VeraDevice, ClimateDevice):
self._state = self.vera_device.get_hvac_mode()
@property
def unit_of_measurement(self):
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):

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

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
@@ -209,7 +192,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
return self._swing_list
@property
def unit_of_measurement(self):
def temperature_unit(self):
"""Return the unit of measurement."""
if self._unit == 'C':
return TEMP_CELSIUS
@@ -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

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({

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'

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'))

View File

@@ -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."""

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

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()

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

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

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 = {

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."""

View File

@@ -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."""

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