Compare commits

..

284 Commits
0.47 ... 0.49

Author SHA1 Message Date
Paulus Schoutsen
1d4a7f1160 Version bump to 0.49 2017-07-15 21:06:20 -07:00
Paulus Schoutsen
dc08852fc2 Merge pull request #8468 from home-assistant/release-0-49
0.49
2017-07-15 21:05:07 -07:00
Andrey
3377f30613 Make themes API work even when themes are not defined. (#8473) 2017-07-15 19:43:06 -07:00
Anders Melchiorsen
84ca4d2a21 Accept transition for light.toggle (#8466) 2017-07-15 19:43:06 -07:00
Bryce Edwards
1366c93c83 Radarr sensor fix for issue #8250 (#8456)
* Radarr sensor fix for issue #8250

* Radarr sensor fix for issue #8250
2017-07-15 19:43:06 -07:00
Sean Gollschewsky
e5e2a151aa Remove km from visibility, add visibility_distance (#8454)
* Remove km from visibility, add visibility_distance

* Fix line length

* Fix trailing space and line break indentation

* Indentation

* More whitespace
2017-07-15 19:43:06 -07:00
dersger
bd1e533409 Fix media_position for cast component (#8452)
* Make it available during state paused.

* Don't adjust for media_position_updated_at. I.e. do as vlc, sonos etc
  so that returned position is the position at the time of
  media_position_updated_at, not now.
2017-07-15 19:43:06 -07:00
Dougal Matthews
21e82bd037 Add RGB support to switch.flux (#8417) 2017-07-15 19:43:06 -07:00
Anders Melchiorsen
af9a0e8fea LIFX: support for multizone (#8399)
* Make aiolifx modules easily available

* Use aiolifx features_map for deciding bulb features

Also move the feature detection out of Light so it is available even
during the initial detection.

* Move each LIFX light type to a separate class

* Simplify AwaitAioLIFX

This has become possible with recent aiolifx that calls the callback even
when a message is lost.

Now the wrapper can be used also before a Light is added though the register
callback then has to become a coroutine.

* Refactor send_color

* Add support for multizone

This lets lifx_set_state work on individual zones.

Also update to aiolifx_effects 0.1.1 that restores the state for individual
zones.
2017-07-15 19:43:05 -07:00
Paulus Schoutsen
abc5c3e128 Fix iFrame panel test 2017-07-15 19:42:17 -07:00
Paulus Schoutsen
192db5bec3 Update frontend 2017-07-13 09:22:15 -07:00
Andrey
b8eaec565a Add kiosk-mode panel (#8457) 2017-07-13 09:11:49 -07:00
Daniel Perna
e0f35c0279 HomeMatic dependency upgrade + IP Wall Thermostat support (#8465)
* Upgrade dependency + added IP Wall Thermostat
2017-07-13 17:55:30 +02:00
Abílio Costa
2eeeb9075a Plex: Add exception handler when connection fails (#8179)
* add exception handler when connection fails

* plex: improve exception handling

* remove uneeded exception handler
2017-07-13 08:01:12 -07:00
Daniel Høyer Iversen
71ee290bfd upgrade rfxtrx lib (#8463) 2017-07-13 14:57:44 +02:00
Daniel Høyer Iversen
7aad93e90d upgrade broadlink (#8462) 2017-07-13 14:42:30 +02:00
Andrey
a65f22378e Backend support for themes (#8419)
* Backend support for themes

* Fix test

* Add theme_updated event

* Shorten name

* Add tests
2017-07-12 18:08:13 -07:00
Anders Melchiorsen
bb9db28c95 LIFX: make broadcast address configurable (#8453) 2017-07-12 23:08:18 +02:00
Anders Melchiorsen
d10f017441 LIFX: improve light availability (#8451)
The default aiolifx timers are tuned for a network with few lost packets.
This means that lights can become "unavailable" from just a two second
dropout. An unavailable light is completely useless for HA until it is
rediscovered so this is an undesirable state to be in.

These tweaks make aiolifx try harder to get its messages through to the
bulbs, at the cost of some latency in detecting lights that actually are
unavailable.
2017-07-12 19:24:24 +02:00
Open Home Automation
b6e0286d71 Implement a bridge between HASS event bus and KNX bus to send events (#8449)
* Implement a bridge between HASS event bus and KNX bus to send events as KNX messages

* Formatting
2017-07-12 12:21:15 +02:00
Fabian Affolter
4451d2e847 Upgrade youtube_dl to 2017.7.9 (#8450) 2017-07-12 10:38:08 +02:00
Niccolò Maggioni
229000b834 Support for Plex servers with enforced SSL (#8341)
* Support for Plex servers with enforced SSL

* Fixed HoundCI warnings

* Fixed HoundCI warnings (2nd)

* Configurator data validation

* Travis linting
2017-07-11 23:26:29 -07:00
viswa-swami
9704057959 Fix Arlo Q not working with 0.48.1 (#8446)
* This change will enable the functionality for Arlo Q cameras. When we added the code to enable/disable motion detection, we assumed that base station will be present for all arlo type of cameras. But found recently that Arlo Q cameras does not have base station. So, removed the base_station dependency in the init code. Also added code in enable/disable motion detection code to first check if base station is detected by library. If base station is detected then it will use it to enable the motion detection. If not detected, even if service was called, it will not do anything.  Enabling/disabling the motion detection for Arlo Q cameras have to added by someone who has that camera.  I don't have the Arlo Q cameras.

* Fixed a typo in the code.
2017-07-11 23:25:55 -07:00
Paulus Schoutsen
effb9e9d23 Hass.io: Disable timeout when updating OS/supervisor/hass (#8447) 2017-07-11 23:24:35 -07:00
Sean Dague
effbb3bd4c Add support for rain and moisture sensors (#8440) 2017-07-11 23:09:05 +02:00
gitmopp
471501d386 DHT support for humidity and temperature offset (#8238)
* Added support for temperature_offset and humidity_offset

Some DHT sensors require some offsets to work.

* Support for temperature and humidity offset

* Changed lines with 79 characters

* Moved const to dht.py from const.py

* Changed temperature_offset range

* Removed the const

const.py is at original state.

* Fixed continuation line under-indented

* Removed first round and added debug info
2017-07-11 23:04:00 +02:00
Vlad Korniev
ef94b5c77a Vizio SmartCast support (#8260)
* Vizio SmartCast support

* Requested changes
Added new config params

* Vizio SmartCast support

* Requested changes
Added new config params
2017-07-11 22:55:46 +02:00
Andrey
60dcc9a5c0 Switch pyW215 to pypi (#8445) 2017-07-11 22:44:01 +02:00
Fabian Affolter
5b4862cc3c Exclude 'TAXI' product (fixes #8401) (#8438) 2017-07-11 21:23:23 +02:00
Fabian Affolter
fbf945c18b Add effects (#8442) 2017-07-11 21:22:00 +02:00
Fabian Affolter
609c25691a Upgrade phue to 1.0 (fixes #7749) (#8444) 2017-07-11 21:21:17 +02:00
Paulus Schoutsen
6e77877743 Update frontend 2017-07-11 09:11:36 -07:00
Alan Fischer
7b105a2150 Updated pyvera (#8437) 2017-07-11 15:14:46 +02:00
Aliaksandr
ee57a823af Added media_helper service (#8369)
* Added media_helper service

* Fixed lint warning

* media_helper renamed to media_extractor
2017-07-11 10:16:34 +02:00
Andrew
04b1621b65 Fix radiothermostat -1 value issue (#8395)
* Fix -1 value issue

Fixed issue where thermostat will sometimes return a current temperature or set temperature value of -1

* Update radiotherm.py

* Update radiotherm.py

* Update radiotherm.py

Added retry limit

* Update radiotherm.py

* Update radiotherm.py
2017-07-11 10:12:51 +02:00
Marcelo Moreira de Mello
f5e24cb0bb Refactored Amcrest to use central hub component (#8184)
* Refactored Amcrest to use central hub component

* Set default streaming source to snapshot

* Simplified code by using discovery platforms

* Makes lint happy

* Update authentication method to basic

* Fixed lint issues

* Makes Amcrest hub async

*  Make Amcrest hub IO synchronous and disabled ffmpeg dependency on sensor/amcrest.

* Removed async to load component

* Organized import order

* Update amcrest.py
2017-07-11 10:10:10 +02:00
Russell Cloran
ac72dea09a Add support for Prometheus (#8211)
Prometheus (https://prometheus.io/) is an open source metric and alerting
system. This adds support for exporting some metrics to Prometheus, using
its Python client library.
2017-07-10 21:20:17 -07:00
Russell Cloran
2f474a0ed8 zha: Handle both input and output clusters (#8410)
bellows 0.3.0 changes the API to have both, renaming the attribute which used
to be for input clusters in the process.

This is in preparation for remotes.
2017-07-10 21:16:44 -07:00
thecynic
7a4cc8e082 Fix typo (sending USERNAME instead of PASSWORD) introduced in #7963 (#8433) 2017-07-10 20:52:37 -07:00
Mike Christianson
92dc76773a Allow Twitter notifications to include media (#8282)
* Allow notifications to include media, with Twitter as the first implementation. The Twitter notifier uses the Twitter-recommended async chunked media/upload approach and tries to convey the correct mime type of the media. Twitter implementation based on https://github.com/geduldig/TwitterAPI/blob/master/examples/upload_video.py.

* Changes based on balloob's review:
balloob: "Please remove this file. We fixed the issue in our tests that left this artifact."
balloob: "…prefer a guard clause"
balloob: "This is very inefficient. You are now generating up to 99 values."
balloob: "Since media_id is going to be None, why not just return None and you can remove the else."

* balloob: "Other notify platforms are using ATTR_DATA for it. That way if a platform requires extra metadata, we don't need to add extra fields. So please add it to Twitter via ATTR_DATA."
2017-07-10 19:58:01 -07:00
Open Home Automation
fe4abc8454 Integrate utility functions into restricted Python environment (#8427) 2017-07-10 18:42:42 -07:00
Eugenio Panadero
821d01f82c New service send_magic_packet with new component wake_on_lan (#8397)
* New service `send_magic_packet` in new component `wake_on_lan`

* fix

* Unit tests for new component wake_on_lan

* Add wakeonlan to tests requirements

* remove wakeonlan from tests requirements and remake the component tests

* remove wakeonlan from tests requirements

* link domain and service names to component

* fix mocking in test_setup_component

* send_magic_packet as coroutine, better use of mock_calls in tests

* fix imports

* review changes

* better async calls

* Update test_wake_on_lan.py
2017-07-10 18:37:51 -07:00
Teemu R
b453834b2f bump python-mirobo requirement to support newer firmwares and more (#8431) 2017-07-10 23:18:58 +02:00
Fabian Affolter
97f14015ea Use HA lat/long for the start (fixes #3971) (#8429) 2017-07-10 23:16:37 +02:00
Fabian Affolter
4fb25cf16d Fix KeyError (fixes #3721, fixes #7241) (#8428) 2017-07-10 23:15:59 +02:00
Julius Mittenzwei
e7b5c5812c Fixed link to documentation (#8424)
* Issue #8203: fixed link to documentation

* Revert URL change

* Minor changes to docstring
2017-07-10 12:11:16 +02:00
James Marsh
2ac423bd9d Do not overwrite a custom hyperion light name with the hostname of the server. (#8391)
Do not overwrite a custom name with the hostname of the hyperion server.
Correct comment in name() method.

Fixes #8390
2017-07-10 00:06:31 +02:00
Adam Mills
ec7ca9a560 Make gzips reproducible by excluding timestamp (#8420) 2017-07-09 16:21:17 -04:00
Matthew Treinish
cb298123d4 Add set_operation_mode support to generic_thermostat (#8392)
This commit adds support for the set_operation_mode system call to the
generic thermostat component. This enables users to set whether the
thermostat is enabled or not by either setting it to auto or off.
2017-07-08 09:21:10 -07:00
Andrey
c5bf4fe339 Properly handle the case when a group includes itself. (#8398)
* Properly handle the case when a group includes itself.

* Fix lint
2017-07-08 09:20:11 -07:00
Julius Mittenzwei
57c5ed33ee New component to connect to VELUX KLF 200 Interface (#8203)
* New component to connect to VELUX KLF 200 Interface

* Issue #8203: modifications as suggested by hound

* Issue #8203: added entries to .coveragerc

* moving velux/__init__p.y to velux.py

* Issue #8203: Using hass.data for storing global object, updated docstrings

* Issue #8203: validation of config, using standard approach for getting config values

* Issue #8203: Exception handling if connection to velux component fails

* Issue #8203: removed unused import

* Issue #8203: Some minor changes within docstrings

* Issue #8203: Some minor changes within docstrings

* Issue #8203: added dependency for pyvlx to requirements_all.txt

* Issue #8203: less broad exception

* Issue #8203: increased version

* Issue #8203: changed position of pyvlx within requirements_all.txt

* Issue #8203: bumped version of pyvlx to 0.1.3 (better handling of retries when token expires)

* reset pointer to home-assistant-polymer

* Issue #8203: modifications as suggested by fabaff

* hound *sigh*
2017-07-08 16:12:19 +02:00
Oliver
3be0103259 Marrantz SR5006 & SR5006 treated as AVR-X device | Fixed Mapping of Media Player and AUX input functions (#8409) 2017-07-08 16:02:59 +02:00
Andy Castille
614b5da170 Use upstream RachioPy, fix manual run switches (#8286)
* use upstream RachioPy, fix manual run switches

* Update requirements for PyPi version of rachiopy

* Use upstream RachioPy 0.1.2 (partial revert of 39c6484d89)

* Revert rachiopy downgrade
2017-07-08 11:34:34 +02:00
Fabian Affolter
acf6d4ab82 Upgrade Sphinx to 1.6.3 (#8405) 2017-07-08 11:57:24 +03:00
Adam Mills
d3acb25070 Fix CODEOWNERS z-wave team name (#8400) 2017-07-07 17:00:14 -07:00
Charles Blonde
222ad3ab6d Add new Dyson sensors (#8199)
* Add new Dyson sensors

* Add unit of measurement for dust and air quality

* Code review
2017-07-07 16:59:41 -07:00
Paulus Schoutsen
5ae2bcdbb7 Code owners (#8393)
* Add initial code owners file

* Add component author owners

* Add link to blog

* Sort

* Add setup.py

* Update CODEOWNERS
2017-07-07 11:40:04 -07:00
Anders Melchiorsen
6c9742afc4 Update aiolifx (#8396) 2017-07-07 19:57:14 +02:00
Andrey
cf924cd14d Update waqi sensor (#8385) 2017-07-07 07:55:58 -07:00
Paulus Schoutsen
f2267437df Update snips.py 2017-07-07 07:53:04 -07:00
Tom Matheussen
233920f22c Sets spotify media_type to music (#8387)
* Sets spotify media_type to music

* Removed whitespace

* Update spotify.py
2017-07-07 07:52:11 -07:00
Open Home Automation
7536e825fa LaMetric platform and notify module (#8230)
* First version of a LaMetrci platform with a Notify module

* Cleanup, fix formatting bugs

* More formatting

* Formatting

* Updated requirements

* formatting

* Formatting

* More formatting

* Dummy commit for new Travis CI run

* Refactoring class methods to instance methods

* Cleanup unused classed

* Removed Eddystone_weatherurl that had nothing to do with this component

* Cleanup class methods

* Cleanup requirements

* Removed broad excepts
Removed storage of LaMetric devices

* Removed unused import
2017-07-07 08:21:06 +02:00
Pascal Vizeli
e12a9eaadd Update avion.py (#8364)
* Update avion.py

* Update decora.py

* Update decora.py

* Update decora.py

* Update avion.py

* Update decora.py

* Update decora.py

* Update decora.py

* Update decora.py
2017-07-06 23:20:39 -07:00
Simao
fb184b4b6f Added support for upload of remote or local files to slack (#8278)
* Added support for upload of remote or local files to slack

* Checking local file with hass.config.is_allowed_path prior to posting it
2017-07-06 23:14:24 -07:00
Andy Castille
63ff173305 Use user-set device names for Linksys Smart Wi-Fi routers (3) (#8300)
* Use user-set device names for Linksys Smart Wi-Fi routers (3)

* Newline at end of linksys_smart.py

* Remove spaces in last line of linksys_smart.py

* Update linksys_smart.py
2017-07-06 23:07:12 -07:00
Diogo Gomes
903e6b5aee Upnp mapping notification (#8303)
* make port mapping optional

* dependencies + improvements

* Added bytes and packets sensors from IGD

* flake8 check

* new sensor with upnp counters

* checks

* whitespaces in blank line

* requirements update

* added sensor.upnp to .coveragerc

* downgrade miniupnpc

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

Fortunately it is enough

* revert to non async

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

* hof hof

forgot to remove import ot asyncio

* UPnP mapping overlap

Addressing: https://community.home-assistant.io/t/upnp-new-module/20839

* removed whitespaces
2017-07-06 23:05:09 -07:00
Russell Cloran
46ce26eb7a zha: Try multiple reads to get manufacturer/model (#8308)
Some devices don't seem to return the information properly when asked for
multiple attributes in one read. This separates out the reads if it didn't
work as expected the first time.

Because this data is cached in bellows, I don't expect any extra reads in
the "happy" case.
2017-07-06 23:02:22 -07:00
Russell Cloran
b1bba3675d zha light: Refresh at startup (#8310)
* zha light: Refresh at startup

* Add asyncio.coroutine annotation
2017-07-06 22:59:17 -07:00
Alex
ed5d10448e Presence detection for tp link eap225 (#8322)
* Added support for TP-Link EAP 225

* code style changes

* more code style changes

* more understandable variable name

* Added support for TP-Link EAP 225

* code style changes

* more code style changes

* more understandable variable name

* Fix pylint issue (#8325)

* Added support for TP-Link EAP 225

* code style changes

* more code style changes

* more understandable variable name

* Added support for TP-Link EAP 225

* code style changes

* Update snips.py
2017-07-06 22:52:40 -07:00
William Scanlon
652c006cbc Prevent errors on Octoprint sensors and binary_sensors when Octoprint and/or Printer are off (#8343)
* Added platformnotready

* Only log the first failure if octoprint/printer isn't up

Remove blank line
2017-07-06 22:39:11 -07:00
PhracturedBlue
b67c5df525 cover_template:i open/close/stop actions no longer required. Improve tests (#8344) 2017-07-06 22:35:59 -07:00
Matthew Schick
a7d5a8d93e Cleanup the asuswrt component (#8359)
* Get the list of wireless interfaces from nvram so tri-band routers
  work
* ARP checks don't work reliably for ap mode so remove it, associated
  list seems pretty solid
* Match on 'mac' instead of 'ip' since we can't use it for ap mode
* The `client_info_tmp` nvram reference, Asus removed it some time ago
* Update AsusWrtResult
2017-07-06 22:34:21 -07:00
Sabesto
c48c2b00a8 Modbus fixes to work with pymodbus 1.3.1 (#8365)
* Fixed a bug where changing fan speed was not possible

* Bump pymodbus version to 1.3.1 to fix issue #8285

* Changed all modbus components so that they use CONF_SLAVE from const.py

* Fix checking result from a modbus transaction

* Add missing decorator

* Added modbus write coil service and added descriptions

* Removed a hiding debug print
2017-07-06 22:30:23 -07:00
Charles Blonde
9bc5cd2d4b Add Soundtouch support for playing an HTTP url (#8370) 2017-07-06 22:28:09 -07:00
Open Home Automation
ecf3a9cb36 Implement KNX dimming functionality (#8371)
* Implement KNX dimming functionality

* Formatting

* Formatting

* Formatting
2017-07-06 22:24:25 -07:00
Marc Plano-Lesay
074e31bcf9 GTFS: check start/end date on services (#8373)
Fixes #8372
2017-07-06 22:22:31 -07:00
Open Home Automation
63cc658010 Add address-specific KNX listeners that fire events on the HASS bus (#8374)
* Add address-specific KNX listeners that fire events on the HASS bus

* Added docstring
1-byte messages will be converted from a list to the value

* Formating
2017-07-06 22:21:40 -07:00
Charles Blonde
8682f21fc5 Fix TTS options. #8375 (#8376) 2017-07-06 22:20:49 -07:00
Charles Blonde
aa28e6727d Fix Amazon Polly with non english voices. #8377 (#8378) 2017-07-06 22:19:05 -07:00
Sean
12129f0e6a Try catch around database updates in recorder. Resolves 6919 (#8349)
* Try catch around database updates in recorder. Resolves 6919

* Fixing failed test for line length

* Catch only OperationalError and retry connections before giving up

* Including alchemy exceptions in single function
2017-07-06 21:46:50 -07:00
Teemu R
8a7cfce67b Add component for xiaomi robot vacuum (switch.xiaomi_vacuum) (#7913)
* add component for xiaomi robot vacuum (switch.xiaomi_vacuum)

* enforce token length, update requirements_all.txt and .coveragerc

* bump version to avoid catching generic exception
2017-07-06 21:44:34 -07:00
mjj4791
5e71e9b826 buienradar==0.7, fix winddirection/azimuth, logging (#8281)
* buienradar==0.7, fix winddirection/azimuth, logging

* prevent multiple update cycles

* prevent multiple update cycles

* prevent multiple sensor updates for precipitation forecast

* prevent multiple sensor updates for precipitation forecast

* Update comments

* Adapted logging

* Adapted logging
2017-07-06 21:39:28 -07:00
Pierre Ståhl
db8bb53984 Add One-Time Password sensor (OTP) (#8332) 2017-07-06 21:25:54 -07:00
Craig J. Ward
692f4c293b update version (#8380) 2017-07-06 21:24:04 -07:00
John Mihalic
da37380410 Update pyHik to catch XML errors (#8384) 2017-07-06 21:23:47 -07:00
Paulus Schoutsen
fa4aa2244e Allow all panel urls (#8368)
* Allow all panel urls

* Lint
2017-07-06 20:58:21 -07:00
Michael Heinemann
c63bdd5afe Mqtt client_id fix for #8315 (#8366)
This applies what was the intended fix in #8336.

moves the fallback for setting client_id to the case when no mqtt config was provided at all. This should reflect the most common use case that fails.

This commit is a workaround and should be reverted when hbmqtt is fixed to allow empty client_id again.
2017-07-06 14:47:30 -07:00
clarkewd
20a9899354 Allow Pilight Binary Sensor to control reset_delay_sec through configuration (#8358)
* Allow Pilight Binary Sensor to control reset_delay_sec through configuration

* Define constant in platform

* Don't define constant twice
2017-07-06 21:09:31 +02:00
Johan Bloemberg
fe6a4b8ae5 Correct spelling of aliases, deprecate old config options. (#8348)
* correct spelling of aliases

* add deprecation

* Fix style.
2017-07-06 15:59:54 +02:00
Paulus Schoutsen
143044f8f1 Remove some more usage of run_in_executor (#8352)
* Remove usage of run_in_executor

* Lint
2017-07-06 14:08:32 +02:00
Fabian Affolter
d655c0e358 Upgrade aiohttp to 2.2.3 (#8363) 2017-07-06 13:56:50 +02:00
Paulus Schoutsen
46e030662d Fix pylint 1.7.2 no-else-return issues (#8361)
* Fix pylint 1.7.2 no-else-return issues

* Update tomato.py
2017-07-05 23:30:01 -07:00
Paulus Schoutsen
5779d64e98 Fix some issues for PyLint 1.7.2 (#8356)
* Fix some issues for PyLint 1.7.2

* More fixes

* Revert position change for cover
2017-07-05 20:02:16 -07:00
Lev Aronsky
83a5f932d1 Add citybikes platform (#8202)
* Initial commit - new CityBikes platform

* Several syntax fixes.

* Added imperial unit support.

* Added station list lenght validation.

* Style fixes.

* Updated requirements.

* Updated .coveragerc.

* Fixed style problems according to pylint output.

* Updated SCAN_INTERVAL value.

* Fixed station names.
Removed unnecessary calls to `slugify`.
Changed the base name to reflect the name of the bike sharing
network, instead of the more generic `citybikes`.

* Small style fix.

* Use async version of python-citybikes

* Made platform setup async.

* Made some more things async.

* Switched to constants.

* WIP: different approach to async.

* Removed python-citybikes depnedency to fix async issues.

* Removed unnecessary hidden property.

* Style fixes.

* Retry network detection.

* Style fixes, and base name usage.

* Fixes according to comments.

* Use cv.latitude instead of coercing to float.

* Updated requirements.

* Several fixes and improvements.

* Started using PlatformNotReady exception.
* Cached the networks list result to avoid unnecessary API requests.
* Switched the asyncio.timeout to use a constant.
* Refactored CityBikes API requests into a separate function

* Fixed linting errors.

* Removed unnecessary requirement.
2017-07-04 22:55:21 -07:00
Paulus Schoutsen
a12fa2e5bf Merge branch 'master' into dev 2017-07-04 21:53:35 -07:00
Paulus Schoutsen
ee37fc344b Merge pull request #8351 from home-assistant/release-0-48-1
0.48.1
2017-07-04 21:51:57 -07:00
Fabian Affolter
8cc0748db3 Temporary fix for the client_id generation (fixes #8315) (#8336)
* Temporary fix for the client_id generation (fixes #8315)

* Fix comment

* Move client id setting.

* Lint
2017-07-04 21:47:48 -07:00
Fabian Affolter
0ecceb601b Temporary fix for the client_id generation (fixes #8315) (#8336)
* Temporary fix for the client_id generation (fixes #8315)

* Fix comment

* Move client id setting.

* Lint
2017-07-04 21:47:30 -07:00
Fabian Affolter
2a1a5e53a1 Fix hass.data (fixes #8288) (#8290) 2017-07-04 21:43:15 -07:00
Paulus Schoutsen
c8b782189e Fix harmony (#8302)
* Fix harmony

* Fix 1 reference
2017-07-04 21:43:15 -07:00
Pascal Vizeli
74016c4179 Fix pathlib resolve (#8311)
* Fix pathlib resolve

* fix test
2017-07-04 21:43:15 -07:00
Adrien Ball
c30c8df449 Fix Snips json schema (#8317)
* Fix Snips json schema

* Fix test
2017-07-04 21:43:14 -07:00
Eugenio Panadero
58de661ad5 API POST has new state validation (fix for state = 0) (#8324)
* Fix validation of get new state

* Add unittest to check posting a state with zero value
2017-07-04 21:43:14 -07:00
bergemalm
f4a97db783 Fix arlo sensors. (#8333) 2017-07-04 21:43:14 -07:00
Paulus Schoutsen
b220ceec9c Update frontend 2017-07-04 21:41:22 -07:00
Paulus Schoutsen
fb796b5481 Version bump to 0.48.1 2017-07-04 21:38:48 -07:00
Pierre Ståhl
ea5bec3ef4 Add new feature to Apple TV platform (#8122)
* Lift Apple TV to pyatv 0.3.2

Update code to use basic new features.

* Support button presses in Apple TV

* Support device authentication

* Convert Apple TV to a component

A media_player platform and a remote platform will be loaded for each
manually configured or discovered device.

* Move device auth to apple_tv component

* Update requirements and coverage config

* Add scan support to apple_tv
2017-07-04 21:37:18 -07:00
Flavien Charlon
8185587100 Fix the "302" error in the UPC Connect component and remove the need to specify the router password (#8335)
* Remove the need to login on the UPC Connect component

* Remove unnecessary imports

* Update the unit tests for the UPC Connect component

* Fix the "302" error with the UPC Connect component

* Fix a flake8 error

* Update the unit tests for the UPC Connect component
2017-07-04 21:06:24 -07:00
Matthew Garrett
061a38cc3b Update Avion and Decora switches to match upstream changes (#7903)
The upstream Avion and Decora code has changed to punt retry logic out to
consumers, so update to match. As a bonus, Avion also gains support for
pulling switch configuration off the web rather than requiring manual
configuration.
2017-07-05 00:39:51 +02:00
Pascal Vizeli
23fc5e2c9f Bump dlib face_recognition to 0.2.0 (#8345)
* Update dlib_face_detect.py

* Update dlib_face_identify.py

* Update requirements_all.txt
2017-07-04 23:07:53 +02:00
Sabesto
6496c38ce6 Fix issue #8285 (#8340)
* Fixed a bug where changing fan speed was not possible

* Bump pymodbus version to 1.3.1 to fix issue #8285

* Changed all modbus components so that they use CONF_SLAVE from const.py
2017-07-04 17:01:35 +02:00
Fabian Affolter
3363b88a73 Only allow 'tls_insecure_set()' if cert is present (fixes #8329) (#8337)
Add an optional extended description…
2017-07-04 15:22:05 +02:00
Fabian Affolter
2e17d0926a Revert "Only allow 'tls_insecure_set()' if cert is present (fixes #8329)"
This reverts commit 85ac50cc77.
2017-07-04 13:46:38 +02:00
Fabian Affolter
85ac50cc77 Only allow 'tls_insecure_set()' if cert is present (fixes #8329) 2017-07-04 13:40:38 +02:00
Fabian Affolter
da61b18392 Partially revert #7931 (#8326) 2017-07-04 10:07:06 +02:00
bergemalm
8a88af20da Fix arlo sensors. (#8333) 2017-07-04 10:06:46 +02:00
John Mihalic
f8527e9773 Update pyEmby to fix media images (#8331) 2017-07-04 08:02:29 +02:00
Per Sandström
7977996c0d vsure 1.3.7 (#8321) 2017-07-04 06:26:55 +02:00
Eugenio Panadero
22681fbe08 API POST has new state validation (fix for state = 0) (#8324)
* Fix validation of get new state

* Add unittest to check posting a state with zero value
2017-07-03 23:35:57 +02:00
Fabian Affolter
1e655eea74 Fix pylint issue (#8325) 2017-07-03 22:02:43 +02:00
Adrien Ball
8d940fb585 Fix Snips json schema (#8317)
* Fix Snips json schema

* Fix test
2017-07-03 15:07:59 +02:00
Fabian Affolter
afe3dd8dbb Upgrade aiohttp to 2.2.2 (#8314)
* Upgrade aiohttp to 2.2.1

* Upgrade to aiohttp to 2.2.2
2017-07-03 13:48:53 +02:00
Fabian Affolter
bf96f28e95 Upgrade chardet to 3.0.4 (#8313) 2017-07-03 11:49:23 +02:00
Russell Cloran
5cba3085b4 zha: Strip whitespace from device names (#8306) 2017-07-03 08:31:16 +02:00
Fabian Affolter
407a419c83 Upgrade discord.py to 0.16.8 (#8304) 2017-07-03 08:30:54 +02:00
Robin
4ab778fd97 Fix doc link in header (#8305)
* Fix doc link in header

* Add missing `/`
2017-07-03 08:30:42 +02:00
Pascal Vizeli
ee7d4710c4 Fix pathlib resolve (#8311)
* Fix pathlib resolve

* fix test
2017-07-03 07:24:08 +02:00
Paulus Schoutsen
3a6434f566 Fix harmony (#8302)
* Fix harmony

* Fix 1 reference
2017-07-02 13:05:07 -07:00
Wolfgang Malgadey
a2f5b630d6 pytado moved to pypi (#8298) 2017-07-02 12:54:59 -07:00
Paulus Schoutsen
3f2fa0ed5a Disable Python 3.6-dev while it's broken 2017-07-02 11:52:24 -07:00
Robin
865865ca0f Add london_underground (#8272)
* Add tube_state

Add tube_state sensor

* Final cleanup

* Make corrections

Correct PLATFORM_SCHEMA

* Fix space

* Make test pass

* Correct format of test

Test still failing, don’t understand why

* correct description

* Make test pass

Preferred method below returns None

state = self.hass.states.get('sensor.london_overground')

* Format for hound

* indent

* Make requested changes to test, not working

Test fails with:

AssertionError: assert 0 > 0
where 0 = len([])

Surely I need tube_state.setup_platform ?

* Fixed test

Config was wrong

* Change component name to london_tube

* Update name to london_underground

Make consistent

* cleanup
2017-07-02 11:32:38 -07:00
Open Home Automation
05ced33648 Update knxip to 0.4 (better handling of reconnects) (#8289) 2017-07-02 12:59:45 +02:00
Fabian Affolter
b4165fe9f3 Fix hass.data (fixes #8288) (#8290) 2017-07-02 12:02:41 +02:00
Michaël Arnauts
47aa8c387a Update apcaccess to 0.0.13. Add "Percent Load Capacity" to INFERRED_UNITS. (#8277) 2017-07-02 11:24:07 +02:00
Paulus Schoutsen
2b94857ffd Merge branch 'master' into dev 2017-07-01 17:42:23 -07:00
Paulus Schoutsen
7461c57542 Merge pull request #8270 from home-assistant/release-0-48
0.48
2017-07-01 16:58:10 -07:00
Paulus Schoutsen
632f9a21b6 Update frontend 2017-07-01 16:53:50 -07:00
Mike Megally
da44f80b32 Create an index on the states table to help hass startup time (#8255) 2017-07-01 14:10:39 -07:00
Mike Megally
0bf5021c2c Create an index on the states table to help hass startup time (#8255) 2017-07-01 14:10:17 -07:00
Michael Fester
8fb49e8687 Snips ASR and NLU component (#8156)
* Snips ASR and NLU component

* Fix warning

* Fix warnings

* Fix lint issues

* Add tests

* Fix tabs

* Fix newline

* Fix quotes

* Fix docstrings

* Update tests

* Remove logs

* Fix lint warning

* Update API

* Fix Snips
2017-07-01 13:58:35 -07:00
Michael Fester
b82003ae08 Snips ASR and NLU component (#8156)
* Snips ASR and NLU component

* Fix warning

* Fix warnings

* Fix lint issues

* Add tests

* Fix tabs

* Fix newline

* Fix quotes

* Fix docstrings

* Update tests

* Remove logs

* Fix lint warning

* Update API

* Fix Snips
2017-07-01 13:58:12 -07:00
Will W
5f8dc8af20 components.knx - KNXMultiAddressDevice corrections (#8275)
1. The has_attributes was comparing names to addresses
2. Some errors outside of an except block were using
_LOGGER.except. This will cause an exception itself because there is no
trance context available to the logger
3. Added alias names for the address and state addresses so that they
can be accessed with the same
4. Added return values for the set_int_value and set_percentage methods
to allow error checking similar to the set_value method
5. Added the name of the configured object to the log messages to make
them more meaningful (otherwise multiple similar log messages are
received without any hint as to the target device)
2017-07-01 13:31:01 -07:00
Will W
c13fdd23c1 components.knx - KNXMultiAddressDevice corrections (#8275)
1. The has_attributes was comparing names to addresses
2. Some errors outside of an except block were using
_LOGGER.except. This will cause an exception itself because there is no
trance context available to the logger
3. Added alias names for the address and state addresses so that they
can be accessed with the same
4. Added return values for the set_int_value and set_percentage methods
to allow error checking similar to the set_value method
5. Added the name of the configured object to the log messages to make
them more meaningful (otherwise multiple similar log messages are
received without any hint as to the target device)
2017-07-01 13:30:39 -07:00
Fabian Affolter
e6e0e5263a Don't call update() in constructor (#8276) 2017-07-01 16:14:18 +02:00
Fabian Affolter
0981956caa Upgrade pyowm to 2.7.1 (#8274) 2017-07-01 12:53:29 +02:00
lrmate
d267fc608f Update modbus.py (#8256)
* Update modbus.py

Prevents Modbus binary sensors showing up as "unnamed_device".
Originally proposed here https://community.home-assistant.io/t/modbus-sensor/6751/11 by user Pjeter

* Update modbus.py
2017-06-30 23:56:46 -07:00
lrmate
e077998d38 Update modbus.py (#8256)
* Update modbus.py

Prevents Modbus binary sensors showing up as "unnamed_device".
Originally proposed here https://community.home-assistant.io/t/modbus-sensor/6751/11 by user Pjeter

* Update modbus.py
2017-06-30 23:32:10 -07:00
Paulus Schoutsen
d3bc8519c0 Update frontend 2017-06-30 22:34:55 -07:00
viswa-swami
d3adc6ddfb Camera services arm disarm including Netgear Arlo (#7961)
* Added camera service calls to arm/disarm the cameras. Entity id is optional so that with a single call we can arm all the cameras or specify a particular entity id to arm if applicable and possible in that camera type.

* Added camera service calls to arm/disarm the cameras. Entity id is optional so that with a single call we can arm all the cameras or specify a particular entity id to arm if applicable and possible in that camera type.

* Added camera service calls to arm/disarm the cameras. Entity id is optional so that with a single call we can arm all the cameras or specify a particular entity id to arm if applicable and possible in that camera type.

* Fixed the spaces and indentation related issues that houndci found

* Fixed the spaces and indentation related issues that houndci found

* Missed the const file which has the macros defined.

* Fixed the CI build error

* Fixed the CI build error because of unused variable in exception case

* Updating the arlo code based on comment from @balloob. Changed the arm and disarm to enable_motion_detection and disable_motion_detection respectively. Similarly fixed the AttributeError handling. Added dummy code to the demo camera also. Moved out the definitions in const.py into the camera __init__ file

* Fixed the comments posted by houndci-bot

* Fixed the comments posted by houndci-bot

* Fixed the comments posted by houndci-bot

* Fixed the comments posted by travis-ci integration bot

* Fixed the comments posted by travis-ci integration bot

* Fixed the comments posted by travis-ci integration bot for demo.py: expected 2 lines, found 1

* Updated code in camera __init__.py to use the get function instead of directly calling the member in the structure.

* Updated code in camera __init__.py

* Posting the updated code for PR based on @balloob's suggestions/recommendations

* Removed the arlo reference from demo code. Copy-paste error

* Removed the unused import found by hound bot

* Expected 2 lines before function, but found only 1.

* Based on @balloob's comments, moved these constants to the camera/arlo.py

* Added test_demo.py to test the motion enabled and motion disabled in camera component

* Fixing issues found by houndci-bot

* Fixing issues found by houndci-bot

* Fixing the code as per @balloob's suggestions

* Fixing the code as per @balloob's suggestions

* Fixing the test_demo failure. Tried to rewrite a base function to enable the motion in __init__.py and missed to add it to as a job.

* Fixing the hound bot comment

* Update arlo.py

* Update arlo.py
2017-06-30 22:24:36 -07:00
Josh
a3f586d097 Adding done_message to alert (#8116)
* Adding done_message to alert

Adding an optional entry to the config that will send a notification when an
alarm goes from on to off.

* Update test_alert.py

* Update test_alert.py
2017-06-30 22:24:36 -07:00
Paulus Schoutsen
f8c7fd212f Revert "Make Android app shortcut use 'Home Assistant' as name." (#8271)
* Revert "Version bump to 0.49.0.dev0 (#8266)"

This reverts commit 8e4394f173.

* Revert "Adding done_message to alert (#8116)"

This reverts commit 5e56bc7464.

* Revert "Camera services arm disarm including Netgear Arlo (#7961)"

This reverts commit ed20f7e359.

* Revert "Make Android app shortcut use 'Home Assistant' as name instead of just 'Assistant'. (#8261)"

This reverts commit 0bcb7839fb.
2017-06-30 22:24:36 -07:00
Paulus Schoutsen
b1f3492fd0 Notify.smtp: default to STARTTLS 2017-06-30 22:15:41 -07:00
Paulus Schoutsen
7123ec14be Revert "Make Android app shortcut use 'Home Assistant' as name." (#8271)
* Revert "Version bump to 0.49.0.dev0 (#8266)"

This reverts commit 8e4394f173.

* Revert "Adding done_message to alert (#8116)"

This reverts commit 5e56bc7464.

* Revert "Camera services arm disarm including Netgear Arlo (#7961)"

This reverts commit ed20f7e359.

* Revert "Make Android app shortcut use 'Home Assistant' as name instead of just 'Assistant'. (#8261)"

This reverts commit 0bcb7839fb.
2017-06-30 21:57:38 -07:00
Fabian Affolter
8e4394f173 Version bump to 0.49.0.dev0 (#8266) 2017-06-30 21:07:50 -07:00
Josh
5e56bc7464 Adding done_message to alert (#8116)
* Adding done_message to alert

Adding an optional entry to the config that will send a notification when an
alarm goes from on to off.

* Update test_alert.py

* Update test_alert.py
2017-06-30 21:07:12 -07:00
viswa-swami
ed20f7e359 Camera services arm disarm including Netgear Arlo (#7961)
* Added camera service calls to arm/disarm the cameras. Entity id is optional so that with a single call we can arm all the cameras or specify a particular entity id to arm if applicable and possible in that camera type.

* Added camera service calls to arm/disarm the cameras. Entity id is optional so that with a single call we can arm all the cameras or specify a particular entity id to arm if applicable and possible in that camera type.

* Added camera service calls to arm/disarm the cameras. Entity id is optional so that with a single call we can arm all the cameras or specify a particular entity id to arm if applicable and possible in that camera type.

* Fixed the spaces and indentation related issues that houndci found

* Fixed the spaces and indentation related issues that houndci found

* Missed the const file which has the macros defined.

* Fixed the CI build error

* Fixed the CI build error because of unused variable in exception case

* Updating the arlo code based on comment from @balloob. Changed the arm and disarm to enable_motion_detection and disable_motion_detection respectively. Similarly fixed the AttributeError handling. Added dummy code to the demo camera also. Moved out the definitions in const.py into the camera __init__ file

* Fixed the comments posted by houndci-bot

* Fixed the comments posted by houndci-bot

* Fixed the comments posted by houndci-bot

* Fixed the comments posted by travis-ci integration bot

* Fixed the comments posted by travis-ci integration bot

* Fixed the comments posted by travis-ci integration bot for demo.py: expected 2 lines, found 1

* Updated code in camera __init__.py to use the get function instead of directly calling the member in the structure.

* Updated code in camera __init__.py

* Posting the updated code for PR based on @balloob's suggestions/recommendations

* Removed the arlo reference from demo code. Copy-paste error

* Removed the unused import found by hound bot

* Expected 2 lines before function, but found only 1.

* Based on @balloob's comments, moved these constants to the camera/arlo.py

* Added test_demo.py to test the motion enabled and motion disabled in camera component

* Fixing issues found by houndci-bot

* Fixing issues found by houndci-bot

* Fixing the code as per @balloob's suggestions

* Fixing the code as per @balloob's suggestions

* Fixing the test_demo failure. Tried to rewrite a base function to enable the motion in __init__.py and missed to add it to as a job.

* Fixing the hound bot comment

* Update arlo.py

* Update arlo.py
2017-06-30 21:06:56 -07:00
Fabian Affolter
74acc5cf41 Merge branch 'master' into dev 2017-06-30 18:56:26 +02:00
Michaël Arnauts
0bcb7839fb Make Android app shortcut use 'Home Assistant' as name instead of just 'Assistant'. (#8261) 2017-06-30 18:51:07 +02:00
PhracturedBlue
17237e9d3f Implement templates for covers (#8100)
* Implement templates for covers

* Fix a few remaining pylint warnings

* Fix hound line-length warnings

* Fix one more hound line-length warning

* Fix quadruple-quotes an line length code-quality issues

* Irrelevant change to retrigger travis due to timeout

* Use volutuous Exclusive to check for mutex condition

* Fix incorrect state check
2017-06-30 08:24:29 -07:00
Michaël Arnauts
a663dbada0 Docker cleanup. (#8226) 2017-06-30 08:07:33 -07:00
Fabian Affolter
96e1d5524a Upgrade libnacl to 1.5.1 (#8259) 2017-06-30 11:12:21 +02:00
JudgeDredd
33fd2250fd further document add_node_secure (#8229)
added documentation to *attempt* explanation that add_node_secure will also function for adding unsecure nodes.
2017-06-30 10:00:38 +03:00
Per Sandström
31f17a91e6 verisure component names (#8251) 2017-06-30 08:53:14 +02:00
Andrey
d0720ac699 Add PlatformNotReady support for Sensibo (#8252) 2017-06-30 08:50:25 +02:00
Fabian Affolter
05acf1c10a Use constant and update ordering (#8246) 2017-06-30 08:46:22 +02:00
Fabian Affolter
27c92937f2 Use 'hass.data' instead of global (#8245) 2017-06-30 08:46:03 +02:00
Anders Melchiorsen
a328df6014 LIFX: Small code cleanups (#8228) 2017-06-30 02:10:28 +02:00
Eugenio Panadero
1fb4eefc2c better logging to debug when a message is not sent (#8248) 2017-06-29 21:13:46 +02:00
Fabian Affolter
0f12b4c955 Do not call update() in constructor (#8247)
Add an optional extended description…
2017-06-29 16:21:29 +02:00
Fabian Affolter
a9f14b67a8 Update docstrings (#8244) 2017-06-29 11:44:35 +02:00
Eugenio Panadero
445065700c update i2csense requirement (#8242) 2017-06-29 11:03:52 +02:00
Fabian Affolter
4bd96fd437 Upgrade python-digitalocean to 1.12 (#8241) 2017-06-29 10:52:12 +02:00
Michaël Arnauts
5dde0c2201 Comfoconnect fan component (#8073)
* Comfoconnect fan component.

* Fix linter. Don't store hass object when not needed.

* More code style.

* Rebase to dev and add to coverage ignore list.

* Use published package from pypi.
2017-06-28 18:04:54 +02:00
Open Home Automation
6846a76c46 KNX Cover tilt control (#8159)
* Added invert flag for position for actuators that uses 100% for fully closed position

* Implementation of tilt functionality

* Bugfix check tilt

* Formatting

* Formatting fixes

* Formatting

* Bugfix set_tilt

* Minor modifications in configuration section

* Formatting

* Update knx.py
2017-06-28 14:08:07 +02:00
Fabian Affolter
fa6e93f0c7 Do not call update() in constructor (#8148)
* Do not call update() in constructor

* Move handling to update and re-add throttle

* Fix indent

* Fix interval
2017-06-27 10:56:25 +02:00
Paulus Schoutsen
5ef274adce Cleanup automations yaml (#8223) 2017-06-27 10:36:26 +02:00
Eugenio Panadero
e39f7d3ef5 Fix homeassistant.start trigger (#8220)
* Fix homeassistant.start trigger

* ooops

* set sleep(0) just before changing to running state, revert async_block_till_done changes
2017-06-27 10:36:00 +02:00
Will W
88b9503962 add percentage (DPT_Scaling) KNX sensors (#8168)
* add percentage (DPT_Scaling) KNX sensors

1. moved basic functionality to KNXSensorBaseClass instead of
KNXSensorFloatClass
2. added "if" clause in setup for a "percentage" sensor type and added KNXSensorDPTScalingClass

* support-knx-percentage-sensor: lint correction

Updated convert method base sensor class to avoid lint warning
(R201 - Method could be a function)

* added PLATFORM_SCHEMA for configuration

1. added SCHEMA extension for defined keywords
2. moved fixed data for internal settings out of sensor logic
3. moved everything into standard KNXSensor object
4. added parsing of extra config parameters in __init__

* correct lint errors on support-knx-percentage-sensor
2017-06-26 22:25:54 -07:00
Eugenio Panadero
596093d564 telegram_bot platform to only send messages (#8186)
* add new telegram_bot platform to only send messages

* Fix async
2017-06-26 22:22:33 -07:00
natemason
23400c4b0a Fixed mqtt subscription filter on sys $ topics (#8166)
* Fixed mqtt subscription filter on sys $ topics

* fixed linting issue

* added unit tests for $ topics and changed fix to use re.escape

* merge upstream/dev mqtt unit tests

* Update test_init.py
2017-06-26 22:17:55 -07:00
Anders Melchiorsen
af54311718 LIFX: Move light effects to external library (#8222)
* LIFX: Move light effects to external library

This moves the LIFX light effects to the external library aiolifx_effects.

To get the light state synchronized between that library and HA, the LIFX
platform no longer maintains the light state itself. Instead, it uses the
cached state that aiolifx maintains.

The reorganization also includes the addition of a cleanup handler.

* Fix style
2017-06-26 22:05:32 -07:00
Pascal Vizeli
442dcd584b Improve executor pool size / speedup python 3.5 (#8215)
* Improve executor pool size / speedup python36

* fix style

* Add comment
2017-06-26 18:18:42 -07:00
Eugenio Panadero
1e4aec63ed guess the content_type in local_file cameras (#8217)
* guess the content_type in local_file cameras

* add unittest to check content_type of local_file cameras
2017-06-26 22:36:35 +02:00
Per Sandström
80c187f8ea WIP: Verisure app api (#7394)
update to verisure app api
2017-06-26 22:30:25 +02:00
Paulus Schoutsen
d73b695e73 EntityComponent to retry platforms that are not ready yet (#8209)
* Add PlatformNotReady Exception

* lint

* Remove cap, adjust algorithm
2017-06-26 09:41:48 -07:00
Adam Baxter
f02d169864 Fix Plex component to use port number in discovery. (#8197)
* Fix Plex component to use port number in discovery.

* Break line

* Correctly save port to config

* Handle port with fewer code changes

* This is stuck configuring and I'm not sure why

* Changes suggested by @dale3h
2017-06-25 18:06:15 -05:00
Pascal Vizeli
2dd7f0616e Add security layer to send file output things (#8189)
* Add security layer to send file output things

* Make telegram secure

* fix lint

* fix handling

* invert check

* resolve relative paths

* add test for relative paths

* fix lint

* fix tests

* Address paulus comments

* fix style

* fix tests

* Add more tests

* fix tests

* fix tests

* fix test p2

* fix lint

* fix tests

* Make it available for windows

* Change name / address comments

* fix set

* fix test

* fix tests

* fix test

* fix lint
2017-06-26 00:10:30 +02:00
Wim Haanstra
2f2952e0ec Openhardwaremonitor (#8056)
* Open Hardware Monitor sensor

Platform which is able to connect to the JSON API of Open Hardware Monitor and adds sensors for the devices.

* Remove copyright in header, not needed.

* - Removed old code
- Fixed typo’s in comments
- Removed log spamming
- Removed code that was unnecessary
- Use requests instead of urllib
- Moved sensor update functionality to data handler, to remove unwanted constructor parameters

* Fixed typo in comment
Added tests

* Added default fixture, to stabilize tests

* - Fix for values deeper than 4 levels, no longer relies on fixed level
- Fixed tests

* Removed timer in preference of helper methods

* Moved update functionality back to Entity….
Updated SCAN INTERVAL

* Added timeout to request
Removed retry when Open Hardware Monitor API is not reachable
Fixed naming of sensors
Flow optimalisations
Fixed tests to use states

* Remove unused import
2017-06-25 13:48:05 -07:00
Adam Mills
8358542ce0 Remove unnecessary thread_ident assignment (#8194)
* Remove mocking of _thread_ident

* Re-add run_loop thread_ident assignment
2017-06-25 16:39:05 -04:00
Eugenio Panadero
4ca5ed25bc add option to set content_type in camera.generic to support 'svg cameras' (#8188)
* add custom content_type to support 'generic svg cameras'

* add unittest to check content_type for svg generic camera

* Tweak tests
2017-06-25 12:25:14 -07:00
Paulus Schoutsen
7bf6ceafec Split mock_service (#8198) 2017-06-25 10:53:15 -07:00
Paulus Schoutsen
1cfed4f015 Fix plants calling async methods from sync context (#8200) 2017-06-25 10:07:28 -07:00
Paulus Schoutsen
a082ffca1d Fix MySensors climate (#8193) 2017-06-24 18:11:34 -07:00
Oliver
1b563b0640 Pushed to version 0.5.1 of the library (#8190) 2017-06-24 12:14:57 -07:00
Adam Mills
1fe189e9cb Switch to new zwave entity ids by default (#8192) 2017-06-24 15:01:57 -04:00
Marc Plano-Lesay
edeb92ea42 Add offset option to sensor.gtfs (#7980)
* Add offset option to sensor.gtfs

* Fix long lines in sensor.gtfs

* Expose GTFS offset as an attribute
2017-06-24 17:45:14 +02:00
Alex Mekkering
c1095665e9 added optional node_id to MQTT discovery (#8096) 2017-06-24 00:46:41 -07:00
Kane610
2a1f8af10a Axis service vapix call (#7794)
* Initial commit for an Axis service to do Vapix calls to device

* Added check to see if metadatastream initiated properly

* Make sure to configure the correct IP address when setting up registered devices on system start

* Manage reconnection when device is discovered with a different IP

* Cleaned up setting new IP

* Better naming of event for new IP

* New version of dependency axis

* Fix flake8 failing

* Break out service default strings to constants

* Use the dispatcher and not the core event bus for internal communication
2017-06-24 00:14:57 -07:00
Bas Schipper
6234f2d73f Added buienradar precipitation forecast average & total sensors (#8171)
* Added precipitation forecast average & total sensors

* Fixed some code style issues

* Fixed some code style issues

* Minor fix default timeframe

* Update buienradar.py

* Update buienradar.py
2017-06-24 00:12:52 -07:00
Paulus Schoutsen
b488663f2c Update Dockerfile 2017-06-23 23:13:38 -07:00
Sean Dague
a55d8776ff Throw exception if _convert_for_display called on non Number (#8178)
In trying to come up for some reason behind issue #6365 (which only
happens on some platforms) the best guess is that some components are
managing to get a string value all the way up to the Polymer UI for
temperature, which then an increment of +0.5 is treating as a string
concat operation instead of addition. So 20 + 0.5 becomes 200.5 hits
the max thermostat value.

This will throw an exception if the climate temp value isn't a
number. That's going to turn a soft fail into a hard fail on
potentially a number of platforms. Mysensors is one of the platforms
that was reported as having the issue. So put some explicit float
casts where that might be coming from as well.
2017-06-23 23:03:37 -07:00
Ryan Nowakowski
5ceb4c404d Fix radiotherm model CT50 (#8181)
Model CT50 has an "Auto" mode.  When mode is set to auto we need to ask
what the current state is: cool or heat.  Then we can query the
appropriate target temperature.

Without this fix, the target temperature shows up blank in the UI and
setting the mode fails.
2017-06-23 22:53:10 -07:00
lrmate
0061cece0c Update buienradar.py (#8173)
Swapped unit of measurement 'winddirection' vs 'windazimuth'
2017-06-23 22:51:45 -07:00
Morten Lied Johansen
0099168ff8 Add device tracker for Linksys Smart Wifi devices (#8144)
* Add device tracker for Linksys Smart Wifi devices

* Fixing code style
2017-06-23 22:36:04 -07:00
Paulus Schoutsen
87c89752ab Revert "Add libboost-python1.62-dev (fixes #7851)" (#8182)
* Revert "Uninstall enum34 in python3.6 docker image (#8103)"

This reverts commit 45f6f4443a.

* Revert "Add libboost-python1.62-dev (fixes #7851) (#7868)"

This reverts commit f1290d3135.
2017-06-23 22:33:33 -07:00
Jean Regisser
45f6f4443a Uninstall enum34 in python3.6 docker image (#8103)
* Uninstall enum34 in python3.6 docker image

This is a short term fix for #7733

What's happening is the following dependencies are pulling enum34:
- pygatt
- libsoundtouch
- yeelight
However, enum34 is not meant to be installed in Python versions 3.4+
and causing the `AttributeError: module 'enum' has no attribute 'IntFlag'``

I've submitted patches to these projects so we don't have to do this
manual uninstall in the future.

* Update Dockerfile
2017-06-23 22:29:39 -07:00
Fabian Affolter
f1290d3135 Add libboost-python1.62-dev (fixes #7851) (#7868) 2017-06-23 22:16:19 -07:00
Omar Usman
746aae51ec Add ClickSend notify service. (#8135)
* Add ClickSend notify service.

* PR #8135 changes.

- Some code spacing fixes.
- Add timeout to requests.
- Change doc url.
- Use const.py as much as possible.
- Check credentials to determine if setup fails or not.
- Add docstrings.
- Use string formatting.

* PR #8135 changes.

- Remove unused variables.
- Continuation line under-indented for visual indent.

* PR #8135 changes.

- Format code based on PEP8.

* PR #8135 changes.

- Remove unused base64 dependency.

* PR #8135 changes.

- Fix: D205: 1 blank line required between summary line and description (found 0)
- Fix: standard import "import json" comes before "import requests"

* PR #8135 changes.

- Add files to .coveragerc

* Remove obvious comments and set constant
2017-06-23 22:51:41 +02:00
Wolfgang Malgadey
da9430ed12 Tado climate device (#8041)
* added default parameter

* zone overlay can be set with or without a temperature and with or without a duration. Duration is not supported by hass

* Fixes issue #7059 with missing sensorDataPoints

* Fixes issue #6943 added ac_mode

* ac_mode cases
* added fan modes
* changed handling of device state OFF

* fixed an error initializing a dictionary (#6943)

* changed pytado version

* activated pytado debugging

* Changed pyTado version

* mytado.com changed authentication challenge

* Fixed linelength and whitespace issues

* requirements to pytado changed
2017-06-23 18:45:44 +02:00
Tim Wilde
bef22076ea Use version 1.3 of radiotherm (#8164)
Add an optional extended description…
2017-06-23 11:08:23 +02:00
Bas Schipper
fe93b51017 Fixed rfxtrx binary_sensor off command (#8160)
* Fixed applying rfxtrx binary off command

* Fixed some deprecation issues
2017-06-22 23:00:44 +02:00
Eugenio Panadero
07293e8d1e add telegram_bot service: delete_message (#8153)
* add telegram_bot service: delete_message

* better validating for `last` message_id option
2017-06-22 15:03:11 +02:00
Open Home Automation
ca71d34076 Added invert flag for position for actuators that uses 100% for fully closed position (#8147) 2017-06-22 13:42:13 +02:00
Anton Lundin
548417761e ubus: Refresh session on Access denied (#8111)
When a openwrt router reboots, all the session ids gets invalidated.
In that case we need to log in again and get a new session id.

Signed-off-by: Anton Lundin <glance@acc.umu.se>
2017-06-22 13:34:57 +02:00
Andrey
7b8ad1d365 Switch rachiopy to pypi (#8040)
* Switch rachiopy t pypi

* Update rachio.py

* Update requirements_all.txt
2017-06-22 13:34:00 +02:00
Fabian Affolter
61cb6ec3dc Upgrade libsoundtouch to 0.6.2 (#8149) 2017-06-22 13:27:42 +02:00
Fabian Affolter
349746f5f2 Upgrade python-telegram-bot to 6.1.0 (#8151) 2017-06-22 13:27:02 +02:00
Oliver
2e3b279873 Add support of Zone2 and Zone3 (#8025)
* Add support of Zone2 and Zone3

* Changes from balloobs feedback
2017-06-21 22:54:10 -07:00
Yannick POLLART
f26861976d Rfxtrx binary sensor (#6794)
* Added rfxtrx binary sensors to a new branch

* binary_sensor/rfxtrx: added support for automatic_add

* Fixed pylint warnings

* off_delay is set wit clearer time specifiers (cv.time_period)

* fire_event config attribute is now supported by rfxtrx binary sensors.

* Cosmetic ordering

* Fix lint errors for PR requirements.

* Fixed indents, line length and comment problems.

* Yet another line too long fix...

* Using existing attributes and config constants.

* Cosmetic fix (ATTR_DATABITS -> ATTR_DATA_BITS)

* Removed unused attribute

* FIX masked device id logging message

* FIX line too long

* FIX trailing white space

* FIX: rfxtrx binary_sensor manages its own devices only.

* Added a basic config helper for pt2262 devices

* Make pylint happy

* Fixed most houndci-bot-detected issues

* Fix TOX complaint about blank line after function docstring

* Fix data bit value calculation

* Fixed line too long

* Removed unnecessary code.

* remove trailing whitespace

* Added hass property to device object.
2017-06-21 22:48:45 -07:00
Anthony Hughes
6bfeac7f80 Harmony auto discovery via netdisco (#7741)
* Use netdisco to automatically discover harmony hubs.

* Allow some settings in configuration.yml to override even on discovered hubs.

* Global is not required as no assignment on variable

* Use `set` instead of list for perf

* Store cache of discovered devices against `hass.data` rather than in global

* Handle case if the device cache is empty

* Fix indentation issue
2017-06-21 22:43:35 -07:00
Miha Lunar
a95fe588ca LimitlessLED: Configurable fade-out behavior (#7369)
* Configurable fade-out behavior

Adds a per-group "fade" option with values of "out" (default) or "none".
By default, the lights are faded out when turned off, but this can cause usability issues when manually switching wall switches, since the bulbs turn back on at minimum brightness.

* Changed fade value from enum to boolean

* No need to fall back to default since voluptuous takes care of that.
2017-06-21 22:22:24 -07:00
Eugenio Panadero
e5d11dd1a5 Add new BH1750 light level sensor (#8050)
* new sensor platform
* requirements_all and .coveragerc update
2017-06-22 07:09:08 +02:00
Eugenio Panadero
435e5c8a91 Add I2c HTU21D temperature and humidity sensor for Raspberry Pi (#8049)
* Add new HTU21D temperature and humidity sensor

* new sensor platform
* requirements_all and .coveragerc update

* fix lint

* review changes: move sensor code to external module

* remove debug log msg

* add i2csense to COMMENT_REQUIREMENTS, require i2csense 0.0.3

* Add new HTU21D temperature and humidity sensor

* new sensor platform
* requirements_all and .coveragerc update

* fix lint

* review changes: move sensor code to external module

* remove debug log msg

* add i2csense to COMMENT_REQUIREMENTS, require i2csense 0.0.3

* change style for hass

* fix requirements
2017-06-22 07:05:58 +02:00
Jose Juan Montes
8d0553d9e6 Adds CPU temp monitoring, and allow startup when endpoint is not yet available. (#8093)
* Adds CPU temp monitoring, and allow startup when endpoint is not yet available.

* Added support for available() to glances sensor.
2017-06-21 22:45:15 +02:00
Charles Blonde
9a239d1afb Upgrade libsoundtouch to prevent Python3.6 errors with enum. #7733 #8103 (#8143) 2017-06-21 22:20:30 +02:00
Paulus Schoutsen
9252854f99 Merge pull request #8141 from home-assistant/release-0-47-1
0.47.1
2017-06-21 09:24:12 -07:00
Paulus Schoutsen
8d76e2679d Allow iteration in python_script (#8134)
* Allow iteration in python_script

* Add tests
2017-06-21 09:09:08 -07:00
Alan Fischer
4b1dcad7ae Fixed iTach command parsing with empty data (#8104)
* Fixed iTach command parsing with empty data

* Switched to using format
2017-06-21 09:09:08 -07:00
Phil Hawthorne
b45c386fd6 Update InfluxDB to handle datetime objects and multiple decimal points (#8080)
* Update InfluxDB to handle datetime objects

Updates the InfluxDB regex to ignore datetime objects being coverted
into float values.

Adds tests to the component to ensure datetime objects are corectly
handled.

* Fix Hound errors

Fixes errors from Hound bot

* Update InfluxDB to handle multiple decimal points

Changes the way InfluxDB handles values such as 1.2.3.4 to be 1.234 so
it stores in InfluxDB as a valid float value

* Fix lint issues

Reduce the size of a line for the linter

* Update InfluxDB to pass on unknown variable

If we get an error trying to convert a variable to a float, let's ignore
it completely

* Make InfluxDB Regex constants

Makes the Regex's used by InfluxDB constants so they don't need to be
compiled each time

* cleanup

* fix lint

* Update regex

* fix tests

* Fix JSON body missing new line character

* fix exceptions
2017-06-21 09:09:08 -07:00
Charles Blonde
cb5fa79835 Fix Dyson async_add_job (#8113) 2017-06-21 09:09:08 -07:00
Tsvi Mostovicz
b74217bec2 Fix lights issue #8098 (#8101)
* Fix lights issue #8098

* Don't check self._color to decide whether to calll get_color()

self._color is None on init, so get_color() will never be called.
2017-06-21 09:09:08 -07:00
Paulus Schoutsen
fcf60e740d Version bump to 0.47.1 2017-06-21 09:08:20 -07:00
Eugenio Panadero
bb05600010 Add I2c BME280 temperature, humidity and pressure sensor for Raspberry Pi (#7989)
* Add new BME280 temperature, humidity and pressure sensor

* Add BME280 sensor to optional requirements and .coveragerc

* move validation to sensor handler, async fix in setup

* fix Invalid attribute name

* review changes: move sensor code to external module

* async fix

* add i2csense to COMMENT_REQUIREMENTS, require i2csense 0.0.3, round prec to 1 dec

* change style for hass

* fix lint

* fix lint part 2
2017-06-21 17:24:39 +02:00
Thibault Cohen
d3bb6d3988 Decora light: Fix brightness level in UI (#8139) 2017-06-21 16:37:27 +02:00
Thibault Cohen
f3945147a4 Add current balance to hydroquebec sensor (#8138) 2017-06-21 16:36:20 +02:00
Paulus Schoutsen
6398e92836 Allow iteration in python_script (#8134)
* Allow iteration in python_script

* Add tests
2017-06-21 13:32:50 +02:00
Steven Conaway
4d2b79156d Change Error Message when Turning off ISY994 Light (#8131) 2017-06-21 09:38:12 +02:00
Fabian Affolter
b6d335f993 Do not call update() in constructor (#8120) 2017-06-21 09:35:44 +02:00
lunar-consultancy
4b82c34b8f Added RFXTRX UV badge (#8129) 2017-06-21 08:55:51 +02:00
Paulus Schoutsen
66fc852363 Update frontend 2017-06-20 21:10:53 -07:00
Paulus Schoutsen
87274879a8 Upgrade RestrictedPython dependency (#8132) 2017-06-20 19:30:01 -07:00
Fabian Affolter
e4dbf8033c Upgrade aiohttp to 2.2.0 (#8121) 2017-06-21 00:35:49 +02:00
Fabian Affolter
43db94d62d Upgrade sqlalchemy to 1.1.11 (#8124) 2017-06-21 00:32:49 +02:00
Fabian Affolter
6d5fca2db1 Upgrade paho-mqtt to 1.3.0 (#8125) 2017-06-21 00:32:04 +02:00
Fabian Affolter
d5e55448ef Upgrade mutagen to 1.38 (#8126) 2017-06-21 00:29:35 +02:00
Alan Fischer
4ad998378f Fixed iTach command parsing with empty data (#8104)
* Fixed iTach command parsing with empty data

* Switched to using format
2017-06-20 15:26:18 +02:00
Fabian Affolter
d46607c0d0 Add option to specify the location of the API (fixes #8115) (#8118) 2017-06-20 14:09:54 +02:00
Luar Roji
04920fa0bf Only mark active DHCP clients as present (#8110)
We only want to know which of the DHCP clients are indeed active.

For example: I've a table of static DHCP leases with most of the IPs of my network, so this module is always detecting them as present. With my patch only the active ones will be detected as present.

I already mentioned here: https://github.com/home-assistant/home-assistant/pull/7366#issuecomment-302950139
2017-06-20 12:16:56 +02:00
Fabian Affolter
1928da1fae Remove config details (see docs) (#8119) 2017-06-20 12:09:42 +02:00
Phil Hawthorne
06b051c53d Update InfluxDB to handle datetime objects and multiple decimal points (#8080)
* Update InfluxDB to handle datetime objects

Updates the InfluxDB regex to ignore datetime objects being coverted
into float values.

Adds tests to the component to ensure datetime objects are corectly
handled.

* Fix Hound errors

Fixes errors from Hound bot

* Update InfluxDB to handle multiple decimal points

Changes the way InfluxDB handles values such as 1.2.3.4 to be 1.234 so
it stores in InfluxDB as a valid float value

* Fix lint issues

Reduce the size of a line for the linter

* Update InfluxDB to pass on unknown variable

If we get an error trying to convert a variable to a float, let's ignore
it completely

* Make InfluxDB Regex constants

Makes the Regex's used by InfluxDB constants so they don't need to be
compiled each time

* cleanup

* fix lint

* Update regex

* fix tests

* Fix JSON body missing new line character

* fix exceptions
2017-06-20 07:53:13 +02:00
Charles Blonde
473d765bb9 Fix Dyson async_add_job (#8113) 2017-06-19 23:50:27 +02:00
sn0oz
8e34c27b63 Added SMTP SSL/TLS support (#7960)
* Added SMTP SSL/TLS support

* added new encryption option

* validation of encryption option

* Fix lint issues

* Rename var
2017-06-19 14:19:31 +02:00
Eugenio Panadero
77aa2e940d increase timeout for setWebhook to 10s (#8102)
Add an optional extended description…
2017-06-19 12:03:58 +02:00
Tsvi Mostovicz
3bbaf37193 Fix lights issue #8098 (#8101)
* Fix lights issue #8098

* Don't check self._color to decide whether to calll get_color()

self._color is None on init, so get_color() will never be called.
2017-06-19 09:54:13 +02:00
Andrey
b2d6ff9783 More updates to zwave services.yaml file (#8083) 2017-06-18 22:38:14 -07:00
Will W
4fdde4f0e2 add knx cover support (#7997)
* add knx cover

also corrected bugs in device config

1. overwriting of addresses in KNXMultiAddressDevice
2. setting and getting int values
3. added percentage scaling

* Update __init__.py
2017-06-18 22:30:39 -07:00
Jeff Wilson
756768e745 Add support for Insteon FanLinc fan (#6959)
* Add support for Insteon FanLinc fan

* Upgrade insteonlocal dependency to 0.49

* Lint/flake fixes

* Remove configurator

* Make Hound fixes

* Revert "Make Hound fixes" and "Remove configurator"

This reverts commit 04d1f7fdb162f59f8e740fd1fc17e87d0ca81fd3.
This reverts commit 7b8278d7cf8cb6b1e3606ee17bd4a9ebb6ca1e06.
2017-06-18 21:43:10 -07:00
Diogo Gomes
83b791489b Upnp properties (#8067)
* make port mapping optional

* dependencies + improvements

* Added bytes and packets sensors from IGD

* flake8 check

* new sensor with upnp counters

* checks

* whitespaces in blank line

* requirements update

* added sensor.upnp to .coveragerc

* downgrade miniupnpc

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

Fortunately it is enough

* revert to non async

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

* hof hof

forgot to remove import ot asyncio
2017-06-18 21:32:39 -07:00
Eugenio Panadero
35132f9836 media player Kodi: handle TransportError exceptions when calling JSONRPC API methods (#8047)
* handle TransportError exceptions when calling JSONRPC API

* use double quotes for log messages; show TransportErrors as in async_ws_connect

* fix spaces around keyword / parameter

* fix logging message

* review changes
2017-06-18 23:00:02 +02:00
Paulus Schoutsen
0e08785373 Update frontend 2017-06-18 11:37:15 -07:00
Per Osbäck
bf0dbdfd6a update pywebpush to 1.0.5 (#8084) 2017-06-18 10:51:37 -07:00
Michaël Arnauts
04407b8623 Cleanup .coveragerc (#8088) 2017-06-18 10:50:35 -07:00
Michael Auchter
fb0ee34f10 mpd: implement support for seek, shuffle, and clear playlist (#8090)
* mpd: add shuffle and clear_playlist support

* mpd: implement seek
2017-06-18 18:31:45 +02:00
Myles Eftos
ef63cfe8e4 Stopping the logfile spam by piping STDERR to /dev/null (#8081) 2017-06-18 11:44:41 +02:00
Paulus Schoutsen
e40f72e773 Merge branch 'master' into dev 2017-06-17 12:13:59 -07:00
Paulus Schoutsen
cec8ccb1a4 Version bump to 0.48.0.dev0 2017-06-17 12:13:46 -07:00
Caleb
6a017efc0e Update pyunifi component to use APIError passed from pyunifi 2.13. Better accommodate login failures with wrapper in pyunifi 2.13. (#7899)
* Pyunifi update

* Update pyunifi_test

* Import API Error

* Adjust test_unifi.py to import APIError

* Remove urllib import

* Remove urllib import from test

* Try fix mock

* Remove automations.yaml

* Lint
2017-06-17 11:09:27 -07:00
Paulus Schoutsen
18935440ed Fix EntityComponent handle entities without a name (#8065)
* Fix EntityComponent handle entities without a name

* Implement solution by Anders
2017-06-17 10:50:59 -07:00
Lev Aronsky
2ba6b3a2ab Added 'all_plants' group and support for plant groups state. (#8063)
* Added 'all_plants' group and support for plant groups state.

* Reversed the group states.
2017-06-17 10:22:23 -07:00
Pascal Vizeli
2438c6b7c2 Fix attribute entity (#8066)
* Bugfix entity attribute setter

* Fix tests

* Fix tests part 2

* Change filter only None

* Fix tests part 3

* Update entity.py

* Fix tests
2017-06-17 10:03:49 -07:00
Andrey
32a84f1466 Add to zwave services descriptions (#8072) 2017-06-17 10:02:37 -07:00
happyleavesaoc
0002a895ca bump usps version (#8074) 2017-06-17 18:42:56 +02:00
happyleavesaoc
d0b43b187a bump ups (#8075) 2017-06-17 18:42:12 +02:00
John Mihalic
33d381731f Bump pyEmby version to account for API changes (#8070) 2017-06-17 18:41:35 +02:00
Eugenio Panadero
18f81d7824 Add option to set language of openweathermap sensor, and handle updating errors (#8046)
* Add option to set language of openweathermap sensor messages

* handle error updating openweathermap sensor
2017-06-17 12:37:34 +02:00
Fabian Affolter
844c8149d7 Add initial support for Shiftr.io (#7974)
* Add initial support for Shiftr.io

* Fix lint issue

* Use paho-mqtt instead of internal MQTT object

* remove async flavor while paho is not async
2017-06-17 12:34:12 +02:00
pezinek
7617864ba5 Failed to parse response from WU API: 'record' (and 'recordyear') #7747 (#8058) 2017-06-17 12:32:22 +02:00
jshore1296
58c234466c Allow config of latitude and longitude (#8068)
This will allow for dynamically updating weather states, for instance if
you wanted to use the latitude and longitude of a phone or other device
to get the weather for your current location.
2017-06-17 10:41:11 +02:00
Phil Hawthorne
9071946e87 Remove % sign from Vera Battery Levels (#8069)
Vera devices are reporting battery levels as a sting by appending a
percentage sign (%) on the end.

To make the Vera component act like other Home Assistant components,
let's remove the percentage sign from the battery report levels so that
we only display the battery level.

This may be a "breaking change" if people are relying on the Vera
battery levels to be a string instead of an int. However, this will make
the battery level reports compatible with everything else.
2017-06-17 10:38:15 +02:00
Paulus Schoutsen
b24aa24f6a Always enable monkey patch (#8054) 2017-06-16 17:17:18 -07:00
Andrey
1fde234c78 Fix some warnings found by quantifiedcode (#8027)
* Cleanup of warnings by quantifiedcode

* Fix lint

* Fix test

* Delete insteon_hub component

* Also update .coveragerc
2017-06-16 22:44:14 +03:00
Adam Mills
afb9cba806 Use standard entity_ids for zwave entities (#7786)
* Use standard entity_ids for zwave entities

* Include temporary opt-in for new entity ids

* Update link to blog post

* Update tests

* Add old entity_id as state attribute

* Expose ZWave value details

* Update tests

* Also show new_entity_id

* Just can't win with this one
2017-06-16 13:07:17 -04:00
pezinek
1c2f4866e2 No update in MQTT Binary Sensor #7478 (#8057) 2017-06-16 14:55:59 +02:00
Pascal Vizeli
e90ae2fb75 Update numpy 1.13.0 (#8059) 2017-06-16 11:47:48 +02:00
412 changed files with 12384 additions and 5685 deletions

View File

@@ -11,9 +11,15 @@ omit =
homeassistant/components/alarmdecoder.py
homeassistant/components/*/alarmdecoder.py
homeassistant/components/amcrest.py
homeassistant/components/*/amcrest.py
homeassistant/components/apcupsd.py
homeassistant/components/*/apcupsd.py
homeassistant/components/apple_tv.py
homeassistant/components/*/apple_tv.py
homeassistant/components/arduino.py
homeassistant/components/*/arduino.py
@@ -35,23 +41,35 @@ omit =
homeassistant/components/bloomsky.py
homeassistant/components/*/bloomsky.py
homeassistant/components/comfoconnect.py
homeassistant/components/*/comfoconnect.py
homeassistant/components/digital_ocean.py
homeassistant/components/*/digital_ocean.py
homeassistant/components/dweet.py
homeassistant/components/*/dweet.py
homeassistant/components/eight_sleep.py
homeassistant/components/*/eight_sleep.py
homeassistant/components/ecobee.py
homeassistant/components/*/ecobee.py
homeassistant/components/enocean.py
homeassistant/components/*/enocean.py
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/hdmi_cec.py
homeassistant/components/*/hdmi_cec.py
homeassistant/components/homematic.py
homeassistant/components/*/homematic.py
homeassistant/components/insteon_local.py
homeassistant/components/*/insteon_local.py
@@ -65,12 +83,21 @@ omit =
homeassistant/components/isy994.py
homeassistant/components/*/isy994.py
homeassistant/components/joaoapps_join.py
homeassistant/components/*/joaoapps_join.py
homeassistant/components/juicenet.py
homeassistant/components/*/juicenet.py
homeassistant/components/kira.py
homeassistant/components/*/kira.py
homeassistant/components/knx.py
homeassistant/components/*/knx.py
homeassistant/components/lametric.py
homeassistant/components/*/lametric.py
homeassistant/components/lutron.py
homeassistant/components/*/lutron.py
@@ -80,15 +107,27 @@ omit =
homeassistant/components/mailgun.py
homeassistant/components/*/mailgun.py
homeassistant/components/maxcube.py
homeassistant/components/*/maxcube.py
homeassistant/components/mochad.py
homeassistant/components/*/mochad.py
homeassistant/components/modbus.py
homeassistant/components/*/modbus.py
homeassistant/components/mysensors.py
homeassistant/components/*/mysensors.py
homeassistant/components/neato.py
homeassistant/components/*/neato.py
homeassistant/components/nest.py
homeassistant/components/*/nest.py
homeassistant/components/netatmo.py
homeassistant/components/*/netatmo.py
homeassistant/components/octoprint.py
homeassistant/components/*/octoprint.py
@@ -116,6 +155,9 @@ omit =
homeassistant/components/scsgate.py
homeassistant/components/*/scsgate.py
homeassistant/components/tado.py
homeassistant/components/*/tado.py
homeassistant/components/tellduslive.py
homeassistant/components/*/tellduslive.py
@@ -131,6 +173,9 @@ omit =
homeassistant/components/notify/twilio_sms.py
homeassistant/components/notify/twilio_call.py
homeassistant/components/velux.py
homeassistant/components/*/velux.py
homeassistant/components/vera.py
homeassistant/components/*/vera.py
@@ -148,45 +193,18 @@ omit =
homeassistant/components/wink.py
homeassistant/components/*/wink.py
homeassistant/components/zigbee.py
homeassistant/components/*/zigbee.py
homeassistant/components/enocean.py
homeassistant/components/*/enocean.py
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/knx.py
homeassistant/components/*/knx.py
homeassistant/components/zoneminder.py
homeassistant/components/*/zoneminder.py
homeassistant/components/mochad.py
homeassistant/components/*/mochad.py
homeassistant/components/zabbix.py
homeassistant/components/*/zabbix.py
homeassistant/components/maxcube.py
homeassistant/components/*/maxcube.py
homeassistant/components/tado.py
homeassistant/components/*/tado.py
homeassistant/components/zha/__init__.py
homeassistant/components/zha/const.py
homeassistant/components/*/zha.py
homeassistant/components/eight_sleep.py
homeassistant/components/*/eight_sleep.py
homeassistant/components/zigbee.py
homeassistant/components/*/zigbee.py
homeassistant/components/zoneminder.py
homeassistant/components/*/zoneminder.py
homeassistant/components/alarm_control_panel/alarmdotcom.py
homeassistant/components/alarm_control_panel/concord232.py
@@ -205,7 +223,6 @@ omit =
homeassistant/components/binary_sensor/rest.py
homeassistant/components/binary_sensor/tapsaff.py
homeassistant/components/browser.py
homeassistant/components/camera/amcrest.py
homeassistant/components/camera/bloomsky.py
homeassistant/components/camera/ffmpeg.py
homeassistant/components/camera/foscam.py
@@ -224,11 +241,11 @@ omit =
homeassistant/components/climate/sensibo.py
homeassistant/components/cover/garadget.py
homeassistant/components/cover/homematic.py
homeassistant/components/cover/knx.py
homeassistant/components/cover/myq.py
homeassistant/components/cover/opengarage.py
homeassistant/components/cover/rpi_gpio.py
homeassistant/components/cover/scsgate.py
homeassistant/components/cover/wink.py
homeassistant/components/device_tracker/actiontec.py
homeassistant/components/device_tracker/aruba.py
homeassistant/components/device_tracker/asuswrt.py
@@ -242,6 +259,7 @@ omit =
homeassistant/components/device_tracker/gpslogger.py
homeassistant/components/device_tracker/icloud.py
homeassistant/components/device_tracker/linksys_ap.py
homeassistant/components/device_tracker/linksys_smart.py
homeassistant/components/device_tracker/luci.py
homeassistant/components/device_tracker/mikrotik.py
homeassistant/components/device_tracker/netgear.py
@@ -263,12 +281,10 @@ omit =
homeassistant/components/fan/mqtt.py
homeassistant/components/feedreader.py
homeassistant/components/foursquare.py
homeassistant/components/hdmi_cec.py
homeassistant/components/ifttt.py
homeassistant/components/image_processing/dlib_face_detect.py
homeassistant/components/image_processing/dlib_face_identify.py
homeassistant/components/image_processing/seven_segments.py
homeassistant/components/joaoapps_join.py
homeassistant/components/keyboard.py
homeassistant/components/keyboard_remote.py
homeassistant/components/light/avion.py
@@ -278,7 +294,7 @@ omit =
homeassistant/components/light/flux_led.py
homeassistant/components/light/hue.py
homeassistant/components/light/hyperion.py
homeassistant/components/light/lifx/*.py
homeassistant/components/light/lifx.py
homeassistant/components/light/lifx_legacy.py
homeassistant/components/light/limitlessled.py
homeassistant/components/light/mystrom.py
@@ -296,8 +312,8 @@ omit =
homeassistant/components/lock/nuki.py
homeassistant/components/lock/lockitron.py
homeassistant/components/lock/sesame.py
homeassistant/components/media_extractor.py
homeassistant/components/media_player/anthemav.py
homeassistant/components/media_player/apple_tv.py
homeassistant/components/media_player/aquostv.py
homeassistant/components/media_player/braviatv.py
homeassistant/components/media_player/cast.py
@@ -312,7 +328,6 @@ omit =
homeassistant/components/media_player/frontier_silicon.py
homeassistant/components/media_player/gpmdp.py
homeassistant/components/media_player/gstreamer.py
homeassistant/components/media_player/hdmi_cec.py
homeassistant/components/media_player/itunes.py
homeassistant/components/media_player/kodi.py
homeassistant/components/media_player/lg_netcast.py
@@ -335,6 +350,7 @@ omit =
homeassistant/components/media_player/sonos.py
homeassistant/components/media_player/spotify.py
homeassistant/components/media_player/squeezebox.py
homeassistant/components/media_player/vizio.py
homeassistant/components/media_player/vlc.py
homeassistant/components/media_player/volumio.py
homeassistant/components/media_player/yamaha.py
@@ -342,13 +358,13 @@ omit =
homeassistant/components/notify/aws_sns.py
homeassistant/components/notify/aws_sqs.py
homeassistant/components/notify/ciscospark.py
homeassistant/components/notify/clicksend.py
homeassistant/components/notify/discord.py
homeassistant/components/notify/facebook.py
homeassistant/components/notify/free_mobile.py
homeassistant/components/notify/gntp.py
homeassistant/components/notify/group.py
homeassistant/components/notify/instapush.py
homeassistant/components/notify/joaoapps_join.py
homeassistant/components/notify/kodi.py
homeassistant/components/notify/lannouncer.py
homeassistant/components/notify/llamalab_automate.py
@@ -371,20 +387,22 @@ omit =
homeassistant/components/notify/twitter.py
homeassistant/components/notify/xmpp.py
homeassistant/components/nuimo_controller.py
homeassistant/components/prometheus.py
homeassistant/components/remote/harmony.py
homeassistant/components/remote/itach.py
homeassistant/components/scene/hunterdouglas_powerview.py
homeassistant/components/scene/lifx_cloud.py
homeassistant/components/sensor/amcrest.py
homeassistant/components/sensor/arest.py
homeassistant/components/sensor/arwn.py
homeassistant/components/sensor/bbox.py
homeassistant/components/sensor/bh1750.py
homeassistant/components/sensor/bitcoin.py
homeassistant/components/sensor/blockchain.py
homeassistant/components/sensor/bme280.py
homeassistant/components/sensor/bom.py
homeassistant/components/sensor/broadlink.py
homeassistant/components/sensor/buienradar.py
homeassistant/components/sensor/dublin_bus_transport.py
homeassistant/components/sensor/citybikes.py
homeassistant/components/sensor/coinmarketcap.py
homeassistant/components/sensor/cert_expiry.py
homeassistant/components/sensor/comed_hourly_pricing.py
@@ -398,6 +416,7 @@ omit =
homeassistant/components/sensor/dnsip.py
homeassistant/components/sensor/dovado.py
homeassistant/components/sensor/dte_energy_bridge.py
homeassistant/components/sensor/dublin_bus_transport.py
homeassistant/components/sensor/ebox.py
homeassistant/components/sensor/eddystone_temperature.py
homeassistant/components/sensor/eliqonline.py
@@ -419,6 +438,7 @@ omit =
homeassistant/components/sensor/haveibeenpwned.py
homeassistant/components/sensor/hddtemp.py
homeassistant/components/sensor/hp_ilo.py
homeassistant/components/sensor/htu21d.py
homeassistant/components/sensor/hydroquebec.py
homeassistant/components/sensor/imap.py
homeassistant/components/sensor/imap_email_content.py
@@ -443,6 +463,7 @@ omit =
homeassistant/components/sensor/openexchangerates.py
homeassistant/components/sensor/opensky.py
homeassistant/components/sensor/openweathermap.py
homeassistant/components/sensor/otp.py
homeassistant/components/sensor/pi_hole.py
homeassistant/components/sensor/plex.py
homeassistant/components/sensor/pocketcasts.py
@@ -473,6 +494,7 @@ omit =
homeassistant/components/sensor/transmission.py
homeassistant/components/sensor/twitch.py
homeassistant/components/sensor/uber.py
homeassistant/components/sensor/upnp.py
homeassistant/components/sensor/ups.py
homeassistant/components/sensor/usps.py
homeassistant/components/sensor/vasttrafik.py
@@ -480,6 +502,7 @@ omit =
homeassistant/components/sensor/xbox_live.py
homeassistant/components/sensor/yweather.py
homeassistant/components/sensor/zamg.py
homeassistant/components/shiftr.py
homeassistant/components/spc.py
homeassistant/components/switch/acer_projector.py
homeassistant/components/switch/anel_pwrctrl.py
@@ -489,7 +512,6 @@ omit =
homeassistant/components/switch/dlink.py
homeassistant/components/switch/edimax.py
homeassistant/components/switch/fritzdect.py
homeassistant/components/switch/hdmi_cec.py
homeassistant/components/switch/hikvisioncam.py
homeassistant/components/switch/hook.py
homeassistant/components/switch/kankun.py
@@ -503,6 +525,7 @@ omit =
homeassistant/components/switch/tplink.py
homeassistant/components/switch/transmission.py
homeassistant/components/switch/wake_on_lan.py
homeassistant/components/switch/xiaomi_vacuum.py
homeassistant/components/telegram_bot/*
homeassistant/components/thingspeak.py
homeassistant/components/tts/amazon_polly.py

View File

@@ -1,2 +1,14 @@
.tox
# General files
.git
.github
config
# Test related files
.tox
# Other virtualization methods
venv
.vagrant
# Temporary files
**/__pycache__

View File

@@ -16,8 +16,8 @@ matrix:
env: TOXENV=py35
- python: "3.6"
env: TOXENV=py36
- python: "3.6-dev"
env: TOXENV=py36
# - python: "3.6-dev"
# env: TOXENV=py36
- python: "3.4.2"
env: TOXENV=requirements
# allow_failures:

41
CODEOWNERS Normal file
View File

@@ -0,0 +1,41 @@
# People marked here will be automatically requested for a review
# when the code that they own is touched.
# https://github.com/blog/2392-introducing-code-owners
setup.py @home-assistant/core
homeassistant/*.py @home-assistant/core
homeassistant/helpers/* @home-assistant/core
homeassistant/util/* @home-assistant/core
homeassistant/components/api.py @home-assistant/core
homeassistant/components/automation/* @home-assistant/core
homeassistant/components/configurator.py @home-assistant/core
homeassistant/components/group.py @home-assistant/core
homeassistant/components/history.py @home-assistant/core
homeassistant/components/http/* @home-assistant/core
homeassistant/components/input_*.py @home-assistant/core
homeassistant/components/introduction.py @home-assistant/core
homeassistant/components/logger.py @home-assistant/core
homeassistant/components/mqtt/* @home-assistant/core
homeassistant/components/panel_custom.py @home-assistant/core
homeassistant/components/panel_iframe.py @home-assistant/core
homeassistant/components/persistent_notification.py @home-assistant/core
homeassistant/components/scene/__init__.py @home-assistant/core
homeassistant/components/scene/hass.py @home-assistant/core
homeassistant/components/script.py @home-assistant/core
homeassistant/components/shell_command.py @home-assistant/core
homeassistant/components/sun.py @home-assistant/core
homeassistant/components/updater.py @home-assistant/core
homeassistant/components/weblink.py @home-assistant/core
homeassistant/components/websocket_api.py @home-assistant/core
homeassistant/components/zone.py @home-assistant/core
Dockerfile @home-assistant/docker
virtualization/Docker/* @home-assistant/docker
homeassistant/components/zwave/* @home-assistant/z-wave
homeassistant/components/*/zwave.py @home-assistant/z-wave
# Indiviudal components
homeassistant/components/cover/template.py @PhracturedBlue
homeassistant/components/device_tracker/automatic.py @armills
homeassistant/components/media_player/kodi.py @armills

View File

@@ -1,3 +1,7 @@
# Notice:
# When updating this file, please also update virtualization/Docker/Dockerfile.dev
# This way, the development image and the production image are kept in sync.
FROM python:3.6
MAINTAINER Paulus Schoutsen <Paulus@PaulusSchoutsen.nl>
@@ -21,8 +25,12 @@ RUN virtualization/Docker/setup_docker_prereqs
# Install hass component dependencies
COPY requirements_all.txt requirements_all.txt
# Uninstall enum34 because some depenndecies install it but breaks Python 3.4+.
# See PR #8103 for more info.
RUN pip3 install --no-cache-dir -r requirements_all.txt && \
pip3 install --no-cache-dir mysqlclient psycopg2 uvloop cchardet
pip3 install --no-cache-dir mysqlclient psycopg2 uvloop cchardet && \
pip3 uninstall -y enum34
# Copy source
COPY . .

View File

@@ -229,8 +229,8 @@ def cmdline() -> List[str]:
os.environ['PYTHONPATH'] = os.path.dirname(modulepath)
return [sys.executable] + [arg for arg in sys.argv if
arg != '--daemon']
else:
return [arg for arg in sys.argv if arg != '--daemon']
return [arg for arg in sys.argv if arg != '--daemon']
def setup_and_run_hass(config_dir: str,

View File

@@ -39,19 +39,19 @@ def is_on(hass, entity_id=None):
else:
entity_ids = hass.states.entity_ids()
for entity_id in entity_ids:
domain = ha.split_entity_id(entity_id)[0]
for ent_id in entity_ids:
domain = ha.split_entity_id(ent_id)[0]
module = get_component(domain)
try:
if module.is_on(hass, entity_id):
if module.is_on(hass, ent_id):
return True
except AttributeError:
# module is None or method is_on does not exist
_LOGGER.exception("Failed to call %s.is_on for %s",
module, entity_id)
module, ent_id)
return False

View File

@@ -92,8 +92,7 @@ class AlarmDotCom(alarm.AlarmControlPanel):
return STATE_ALARM_ARMED_HOME
elif self._alarm.state.lower() == 'armed away':
return STATE_ALARM_ARMED_AWAY
else:
return STATE_UNKNOWN
return STATE_UNKNOWN
@asyncio.coroutine
def async_alarm_disarm(self, code=None):

View File

@@ -113,8 +113,7 @@ class EnvisalinkAlarm(EnvisalinkDevice, alarm.AlarmControlPanel):
"""Regex for code format or None if no code is required."""
if self._code:
return None
else:
return '^\\d{4,6}$'
return '^\\d{4,6}$'
@property
def state(self):

View File

@@ -99,8 +99,7 @@ class ManualAlarm(alarm.AlarmControlPanel):
self._trigger_time) < dt_util.utcnow():
if self._disarm_after_trigger:
return STATE_ALARM_DISARMED
else:
return self._pre_trigger_state
return self._pre_trigger_state
return self._state

View File

@@ -80,8 +80,7 @@ class SimpliSafeAlarm(alarm.AlarmControlPanel):
"""Return the name of the device."""
if self._name is not None:
return self._name
else:
return 'Alarm {}'.format(self.simplisafe.location_id())
return 'Alarm {}'.format(self.simplisafe.location_id())
@property
def code_format(self):

View File

@@ -16,7 +16,7 @@ from homeassistant.const import (
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_UNKNOWN,
CONF_NAME)
REQUIREMENTS = ['total_connect_client==0.7']
REQUIREMENTS = ['total_connect_client==0.11']
_LOGGER = logging.getLogger(__name__)

View File

@@ -5,6 +5,7 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.verisure/
"""
import logging
from time import sleep
import homeassistant.components.alarm_control_panel as alarm
from homeassistant.components.verisure import HUB as hub
@@ -20,20 +21,29 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Verisure platform."""
alarms = []
if int(hub.config.get(CONF_ALARM, 1)):
hub.update_alarms()
alarms.extend([
VerisureAlarm(value.id)
for value in hub.alarm_status.values()
])
hub.update_overview()
alarms.append(VerisureAlarm())
add_devices(alarms)
def set_arm_state(state, code=None):
"""Send set arm state command."""
transaction_id = hub.session.set_arm_state(code, state)[
'armStateChangeTransactionId']
_LOGGER.info('verisure set arm state %s', state)
transaction = {}
while 'result' not in transaction:
sleep(0.5)
transaction = hub.session.get_arm_state_transaction(transaction_id)
# pylint: disable=unexpected-keyword-arg
hub.update_overview(no_throttle=True)
class VerisureAlarm(alarm.AlarmControlPanel):
"""Representation of a Verisure alarm status."""
def __init__(self, device_id):
"""Initialize the Verisure alarm panel."""
self._id = device_id
def __init__(self):
"""Initalize the Verisure alarm panel."""
self._state = STATE_UNKNOWN
self._digits = hub.config.get(CONF_CODE_DIGITS)
self._changed_by = None
@@ -41,18 +51,13 @@ class VerisureAlarm(alarm.AlarmControlPanel):
@property
def name(self):
"""Return the name of the device."""
return 'Alarm {}'.format(self._id)
return '{} alarm'.format(hub.session.installations[0]['alias'])
@property
def state(self):
"""Return the state of the device."""
return self._state
@property
def available(self):
"""Return True if entity is available."""
return hub.available
@property
def code_format(self):
"""Return the code format as regex."""
@@ -65,33 +70,26 @@ class VerisureAlarm(alarm.AlarmControlPanel):
def update(self):
"""Update alarm status."""
hub.update_alarms()
if hub.alarm_status[self._id].status == 'unarmed':
hub.update_overview()
status = hub.get_first("$.armState.statusType")
if status == 'DISARMED':
self._state = STATE_ALARM_DISARMED
elif hub.alarm_status[self._id].status == 'armedhome':
elif status == 'ARMED_HOME':
self._state = STATE_ALARM_ARMED_HOME
elif hub.alarm_status[self._id].status == 'armed':
elif status == 'ARMED_AWAY':
self._state = STATE_ALARM_ARMED_AWAY
elif hub.alarm_status[self._id].status != 'pending':
_LOGGER.error(
"Unknown alarm state %s", hub.alarm_status[self._id].status)
self._changed_by = hub.alarm_status[self._id].name
elif status != 'PENDING':
_LOGGER.error('Unknown alarm state %s', status)
self._changed_by = hub.get_first("$.armState.name")
def alarm_disarm(self, code=None):
"""Send disarm command."""
hub.my_pages.alarm.set(code, 'DISARMED')
_LOGGER.info("Verisure alarm disarming")
hub.my_pages.alarm.wait_while_pending()
set_arm_state('DISARMED', code)
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()
set_arm_state('ARMED_HOME', code)
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()
set_arm_state('ARMED_AWAY', code)

View File

@@ -39,10 +39,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class WinkCameraDevice(WinkDevice, alarm.AlarmControlPanel):
"""Representation a Wink camera alarm."""
def __init__(self, wink, hass):
"""Initialize the Wink alarm."""
super().__init__(wink, hass)
@asyncio.coroutine
def async_added_to_hass(self):
"""Callback when entity is added to hass."""

View File

@@ -25,6 +25,7 @@ _LOGGER = logging.getLogger(__name__)
DOMAIN = 'alert'
ENTITY_ID_FORMAT = DOMAIN + '.{}'
CONF_DONE_MESSAGE = 'done_message'
CONF_CAN_ACK = 'can_acknowledge'
CONF_NOTIFIERS = 'notifiers'
CONF_REPEAT = 'repeat'
@@ -35,6 +36,7 @@ DEFAULT_SKIP_FIRST = False
ALERT_SCHEMA = vol.Schema({
vol.Required(CONF_NAME): cv.string,
vol.Optional(CONF_DONE_MESSAGE, default=None): cv.string,
vol.Required(CONF_ENTITY_ID): cv.entity_id,
vol.Required(CONF_STATE, default=STATE_ON): cv.string,
vol.Required(CONF_REPEAT): vol.All(cv.ensure_list, [vol.Coerce(float)]),
@@ -121,10 +123,10 @@ def async_setup(hass, config):
# Setup alerts
for entity_id, alert in alerts.items():
entity = Alert(hass, entity_id,
alert[CONF_NAME], alert[CONF_ENTITY_ID],
alert[CONF_STATE], alert[CONF_REPEAT],
alert[CONF_SKIP_FIRST], alert[CONF_NOTIFIERS],
alert[CONF_CAN_ACK])
alert[CONF_NAME], alert[CONF_DONE_MESSAGE],
alert[CONF_ENTITY_ID], alert[CONF_STATE],
alert[CONF_REPEAT], alert[CONF_SKIP_FIRST],
alert[CONF_NOTIFIERS], alert[CONF_CAN_ACK])
all_alerts[entity.entity_id] = entity
# Read descriptions
@@ -154,8 +156,8 @@ def async_setup(hass, config):
class Alert(ToggleEntity):
"""Representation of an alert."""
def __init__(self, hass, entity_id, name, watched_entity_id, state,
repeat, skip_first, notifiers, can_ack):
def __init__(self, hass, entity_id, name, done_message, watched_entity_id,
state, repeat, skip_first, notifiers, can_ack):
"""Initialize the alert."""
self.hass = hass
self._name = name
@@ -163,6 +165,7 @@ class Alert(ToggleEntity):
self._skip_first = skip_first
self._notifiers = notifiers
self._can_ack = can_ack
self._done_message = done_message
self._delay = [timedelta(minutes=val) for val in repeat]
self._next_delay = 0
@@ -170,6 +173,7 @@ class Alert(ToggleEntity):
self._firing = False
self._ack = False
self._cancel = None
self._send_done_message = False
self.entity_id = ENTITY_ID_FORMAT.format(entity_id)
event.async_track_state_change(
@@ -230,6 +234,8 @@ class Alert(ToggleEntity):
self._cancel()
self._ack = False
self._firing = False
if self._done_message and self._send_done_message:
yield from self._notify_done_message()
self.hass.async_add_job(self.async_update_ha_state)
@asyncio.coroutine
@@ -249,20 +255,30 @@ class Alert(ToggleEntity):
if not self._ack:
_LOGGER.info("Alerting: %s", self._name)
self._send_done_message = True
for target in self._notifiers:
yield from self.hass.services.async_call(
'notify', target, {'message': self._name})
yield from self._schedule_notify()
@asyncio.coroutine
def async_turn_on(self):
def _notify_done_message(self, *args):
"""Send notification of complete alert."""
_LOGGER.info("Alerting: %s", self._done_message)
self._send_done_message = False
for target in self._notifiers:
yield from self.hass.services.async_call(
'notify', target, {'message': self._done_message})
@asyncio.coroutine
def async_turn_on(self, **kwargs):
"""Async Unacknowledge alert."""
_LOGGER.debug("Reset Alert: %s", self._name)
self._ack = False
yield from self.async_update_ha_state()
@asyncio.coroutine
def async_turn_off(self):
def async_turn_off(self, **kwargs):
"""Async Acknowledge alert."""
_LOGGER.debug("Acknowledged Alert: %s", self._name)
self._ack = True

View File

@@ -0,0 +1,149 @@
"""
This component provides basic support for Amcrest IP cameras.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/amcrest/
"""
import logging
from datetime import timedelta
import aiohttp
import voluptuous as vol
from requests.exceptions import HTTPError, ConnectTimeout
import homeassistant.loader as loader
from homeassistant.const import (
CONF_NAME, CONF_HOST, CONF_PORT, CONF_USERNAME, CONF_PASSWORD,
CONF_SENSORS, CONF_SCAN_INTERVAL, HTTP_BASIC_AUTHENTICATION)
from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['amcrest==1.2.0']
DEPENDENCIES = ['ffmpeg']
_LOGGER = logging.getLogger(__name__)
CONF_AUTHENTICATION = 'authentication'
CONF_RESOLUTION = 'resolution'
CONF_STREAM_SOURCE = 'stream_source'
CONF_FFMPEG_ARGUMENTS = 'ffmpeg_arguments'
DEFAULT_NAME = 'Amcrest Camera'
DEFAULT_PORT = 80
DEFAULT_RESOLUTION = 'high'
DEFAULT_STREAM_SOURCE = 'snapshot'
TIMEOUT = 10
DATA_AMCREST = 'amcrest'
DOMAIN = 'amcrest'
NOTIFICATION_ID = 'amcrest_notification'
NOTIFICATION_TITLE = 'Amcrest Camera Setup'
RESOLUTION_LIST = {
'high': 0,
'low': 1,
}
SCAN_INTERVAL = timedelta(seconds=10)
AUTHENTICATION_LIST = {
'basic': 'basic'
}
STREAM_SOURCE_LIST = {
'mjpeg': 0,
'snapshot': 1,
'rtsp': 2,
}
# Sensor types are defined like: Name, units, icon
SENSORS = {
'motion_detector': ['Motion Detected', None, 'mdi:run'],
'sdcard': ['SD Used', '%', 'mdi:sd'],
'ptz_preset': ['PTZ Preset', None, 'mdi:camera-iris'],
}
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.All(cv.ensure_list, [vol.Schema({
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_AUTHENTICATION, default=HTTP_BASIC_AUTHENTICATION):
vol.All(vol.In(AUTHENTICATION_LIST)),
vol.Optional(CONF_RESOLUTION, default=DEFAULT_RESOLUTION):
vol.All(vol.In(RESOLUTION_LIST)),
vol.Optional(CONF_STREAM_SOURCE, default=DEFAULT_STREAM_SOURCE):
vol.All(vol.In(STREAM_SOURCE_LIST)),
vol.Optional(CONF_FFMPEG_ARGUMENTS): cv.string,
vol.Optional(CONF_SCAN_INTERVAL, default=SCAN_INTERVAL):
cv.time_period,
vol.Optional(CONF_SENSORS, default=None):
vol.All(cv.ensure_list, [vol.In(SENSORS)]),
})])
}, extra=vol.ALLOW_EXTRA)
def setup(hass, config):
"""Set up the Amcrest IP Camera component."""
from amcrest import AmcrestCamera
amcrest_cams = config[DOMAIN]
persistent_notification = loader.get_component('persistent_notification')
for device in amcrest_cams:
camera = AmcrestCamera(device.get(CONF_HOST),
device.get(CONF_PORT),
device.get(CONF_USERNAME),
device.get(CONF_PASSWORD)).camera
try:
camera.current_time
except (ConnectTimeout, HTTPError) 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
ffmpeg_arguments = device.get(CONF_FFMPEG_ARGUMENTS)
name = device.get(CONF_NAME)
resolution = RESOLUTION_LIST[device.get(CONF_RESOLUTION)]
sensors = device.get(CONF_SENSORS)
stream_source = STREAM_SOURCE_LIST[device.get(CONF_STREAM_SOURCE)]
username = device.get(CONF_USERNAME)
password = device.get(CONF_PASSWORD)
# currently aiohttp only works with basic authentication
# only valid for mjpeg streaming
if username is not None and password is not None:
if device.get(CONF_AUTHENTICATION) == HTTP_BASIC_AUTHENTICATION:
authentication = aiohttp.BasicAuth(username, password)
else:
authentication = None
discovery.load_platform(
hass, 'camera', DOMAIN, {
'device': camera,
CONF_AUTHENTICATION: authentication,
CONF_FFMPEG_ARGUMENTS: ffmpeg_arguments,
CONF_NAME: name,
CONF_RESOLUTION: resolution,
CONF_STREAM_SOURCE: stream_source,
}, config)
if sensors:
discovery.load_platform(
hass, 'sensor', DOMAIN, {
'device': camera,
CONF_NAME: name,
CONF_SENSORS: sensors,
}, config)
return True

View File

@@ -13,7 +13,7 @@ from homeassistant.const import (CONF_HOST, CONF_PORT)
import homeassistant.helpers.config_validation as cv
from homeassistant.util import Throttle
REQUIREMENTS = ['apcaccess==0.0.10']
REQUIREMENTS = ['apcaccess==0.0.13']
_LOGGER = logging.getLogger(__name__)

View File

@@ -198,8 +198,7 @@ class APIEntityStateView(HomeAssistantView):
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)
return self.json_message('Entity not found', HTTP_NOT_FOUND)
@asyncio.coroutine
def post(self, request, entity_id):
@@ -213,7 +212,7 @@ class APIEntityStateView(HomeAssistantView):
new_state = data.get('state')
if not new_state:
if new_state is None:
return self.json_message('No state specified', HTTP_BAD_REQUEST)
attributes = data.get('attributes')
@@ -237,8 +236,7 @@ class APIEntityStateView(HomeAssistantView):
"""Remove entity."""
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)
return self.json_message('Entity not found', HTTP_NOT_FOUND)
class APIEventListenersView(HomeAssistantView):

View File

@@ -0,0 +1,259 @@
"""
Support for Apple TV.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/apple_tv/
"""
import os
import asyncio
import logging
import voluptuous as vol
from homeassistant.const import (CONF_HOST, CONF_NAME, ATTR_ENTITY_ID)
from homeassistant.config import load_yaml_config_file
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers import discovery
from homeassistant.components.discovery import SERVICE_APPLE_TV
from homeassistant.loader import get_component
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pyatv==0.3.2']
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'apple_tv'
SERVICE_SCAN = 'apple_tv_scan'
SERVICE_AUTHENTICATE = 'apple_tv_authenticate'
ATTR_ATV = 'atv'
ATTR_POWER = 'power'
CONF_LOGIN_ID = 'login_id'
CONF_START_OFF = 'start_off'
CONF_CREDENTIALS = 'credentials'
DEFAULT_NAME = 'Apple TV'
DATA_APPLE_TV = 'data_apple_tv'
DATA_ENTITIES = 'data_apple_tv_entities'
KEY_CONFIG = 'apple_tv_configuring'
NOTIFICATION_AUTH_ID = 'apple_tv_auth_notification'
NOTIFICATION_AUTH_TITLE = 'Apple TV Authentication'
NOTIFICATION_SCAN_ID = 'apple_tv_scan_notification'
NOTIFICATION_SCAN_TITLE = 'Apple TV Scan'
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.All(cv.ensure_list, [vol.Schema({
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_LOGIN_ID): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_CREDENTIALS, default=None): cv.string,
vol.Optional(CONF_START_OFF, default=False): cv.boolean
})])
}, extra=vol.ALLOW_EXTRA)
# Currently no attributes but it might change later
APPLE_TV_SCAN_SCHEMA = vol.Schema({})
APPLE_TV_AUTHENTICATE_SCHEMA = vol.Schema({
ATTR_ENTITY_ID: cv.entity_ids,
})
def request_configuration(hass, config, atv, credentials):
"""Request configuration steps from the user."""
configurator = get_component('configurator')
@asyncio.coroutine
def configuration_callback(callback_data):
"""Handle the submitted configuration."""
from pyatv import exceptions
pin = callback_data.get('pin')
notification = get_component('persistent_notification')
try:
yield from atv.airplay.finish_authentication(pin)
notification.async_create(
hass,
'Authentication succeeded!<br /><br />Add the following '
'to credentials: in your apple_tv configuration:<br /><br />'
'{0}'.format(credentials),
title=NOTIFICATION_AUTH_TITLE,
notification_id=NOTIFICATION_AUTH_ID)
except exceptions.DeviceAuthenticationError as ex:
notification.async_create(
hass,
'Authentication failed! Did you enter correct PIN?<br /><br />'
'Details: {0}'.format(ex),
title=NOTIFICATION_AUTH_TITLE,
notification_id=NOTIFICATION_AUTH_ID)
hass.async_add_job(configurator.request_done, instance)
instance = configurator.request_config(
hass, 'Apple TV Authentication', configuration_callback,
description='Please enter PIN code shown on screen.',
submit_caption='Confirm',
fields=[{'id': 'pin', 'name': 'PIN Code', 'type': 'password'}]
)
@asyncio.coroutine
def scan_for_apple_tvs(hass):
"""Scan for devices and present a notification of the ones found."""
import pyatv
atvs = yield from pyatv.scan_for_apple_tvs(hass.loop, timeout=3)
devices = []
for atv in atvs:
login_id = atv.login_id
if login_id is None:
login_id = 'Home Sharing disabled'
devices.append('Name: {0}<br />Host: {1}<br />Login ID: {2}'.format(
atv.name, atv.address, login_id))
if not devices:
devices = ['No device(s) found']
notification = get_component('persistent_notification')
notification.async_create(
hass,
'The following devices were found:<br /><br />' +
'<br /><br />'.join(devices),
title=NOTIFICATION_SCAN_TITLE,
notification_id=NOTIFICATION_SCAN_ID)
@asyncio.coroutine
def async_setup(hass, config):
"""Set up the Apple TV component."""
if DATA_APPLE_TV not in hass.data:
hass.data[DATA_APPLE_TV] = {}
@asyncio.coroutine
def async_service_handler(service):
"""Handler for service calls."""
entity_ids = service.data.get(ATTR_ENTITY_ID)
if entity_ids:
devices = [device for device in hass.data[DATA_ENTITIES]
if device.entity_id in entity_ids]
else:
devices = hass.data[DATA_ENTITIES]
for device in devices:
atv = device.atv
if service.service == SERVICE_AUTHENTICATE:
credentials = yield from atv.airplay.generate_credentials()
yield from atv.airplay.load_credentials(credentials)
_LOGGER.debug('Generated new credentials: %s', credentials)
yield from atv.airplay.start_authentication()
hass.async_add_job(request_configuration,
hass, config, atv, credentials)
elif service.service == SERVICE_SCAN:
hass.async_add_job(scan_for_apple_tvs, hass)
@asyncio.coroutine
def atv_discovered(service, info):
"""Setup an Apple TV that was auto discovered."""
yield from _setup_atv(hass, {
CONF_NAME: info['name'],
CONF_HOST: info['host'],
CONF_LOGIN_ID: info['properties']['hG'],
CONF_START_OFF: False
})
discovery.async_listen(hass, SERVICE_APPLE_TV, atv_discovered)
tasks = [_setup_atv(hass, conf) for conf in config.get(DOMAIN, [])]
if tasks:
yield from asyncio.wait(tasks, loop=hass.loop)
descriptions = yield from hass.async_add_job(
load_yaml_config_file, os.path.join(
os.path.dirname(__file__), 'services.yaml'))
hass.services.async_register(
DOMAIN, SERVICE_SCAN, async_service_handler,
descriptions.get(SERVICE_SCAN),
schema=APPLE_TV_SCAN_SCHEMA)
hass.services.async_register(
DOMAIN, SERVICE_AUTHENTICATE, async_service_handler,
descriptions.get(SERVICE_AUTHENTICATE),
schema=APPLE_TV_AUTHENTICATE_SCHEMA)
return True
@asyncio.coroutine
def _setup_atv(hass, atv_config):
"""Setup an Apple TV."""
import pyatv
name = atv_config.get(CONF_NAME)
host = atv_config.get(CONF_HOST)
login_id = atv_config.get(CONF_LOGIN_ID)
start_off = atv_config.get(CONF_START_OFF)
credentials = atv_config.get(CONF_CREDENTIALS)
if host in hass.data[DATA_APPLE_TV]:
return
details = pyatv.AppleTVDevice(name, host, login_id)
session = async_get_clientsession(hass)
atv = pyatv.connect_to_apple_tv(details, hass.loop, session=session)
if credentials:
yield from atv.airplay.load_credentials(credentials)
power = AppleTVPowerManager(hass, atv, start_off)
hass.data[DATA_APPLE_TV][host] = {
ATTR_ATV: atv,
ATTR_POWER: power
}
hass.async_add_job(discovery.async_load_platform(
hass, 'media_player', DOMAIN, atv_config))
hass.async_add_job(discovery.async_load_platform(
hass, 'remote', DOMAIN, atv_config))
class AppleTVPowerManager:
"""Manager for global power management of an Apple TV.
An instance is used per device to share the same power state between
several platforms.
"""
def __init__(self, hass, atv, is_off):
"""Initialize power manager."""
self.hass = hass
self.atv = atv
self.listeners = []
self._is_on = not is_off
def init(self):
"""Initialize power management."""
if self._is_on:
self.atv.push_updater.start()
@property
def turned_on(self):
"""If device is on or off."""
return self._is_on
def set_power_on(self, value):
"""Change if a device is on or off."""
if value != self._is_on:
self._is_on = value
if not self._is_on:
self.atv.push_updater.stop()
else:
self.atv.push_updater.start()
for listener in self.listeners:
self.hass.async_add_job(listener.async_update_ha_state())

View File

@@ -1,27 +1,27 @@
"""
This component provides basic support for Netgear Arlo IP cameras.
For more details about this platform, please refer to the documentation at
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/arlo/
"""
import logging
import voluptuous as vol
from homeassistant.helpers import config_validation as cv
from homeassistant.const import CONF_USERNAME, CONF_PASSWORD
import homeassistant.loader as loader
from requests.exceptions import HTTPError, ConnectTimeout
import homeassistant.loader as loader
from homeassistant.helpers import config_validation as cv
from homeassistant.const import CONF_USERNAME, CONF_PASSWORD
REQUIREMENTS = ['pyarlo==0.0.4']
_LOGGER = logging.getLogger(__name__)
CONF_ATTRIBUTION = 'Data provided by arlo.netgear.com'
DOMAIN = 'arlo'
CONF_ATTRIBUTION = "Data provided by arlo.netgear.com"
DATA_ARLO = 'data_arlo'
DEFAULT_BRAND = 'Netgear Arlo'
DOMAIN = 'arlo'
NOTIFICATION_ID = 'arlo_notification'
NOTIFICATION_TITLE = 'Arlo Camera Setup'
@@ -47,7 +47,7 @@ def setup(hass, config):
arlo = PyArlo(username, password, preload=False)
if not arlo.is_connected:
return False
hass.data['arlo'] = arlo
hass.data[DATA_ARLO] = arlo
except (ConnectTimeout, HTTPError) as ex:
_LOGGER.error("Unable to connect to Netgar Arlo: %s", str(ex))
persistent_notification.create(

View File

@@ -42,8 +42,6 @@ def async_trigger(hass, config, action):
},
})
# Do something to call action
if event == SUN_EVENT_SUNRISE:
return async_track_sunrise(hass, call_action, offset)
else:
return async_track_sunset(hass, call_action, offset)
return async_track_sunset(hass, call_action, offset)

View File

@@ -11,6 +11,7 @@ import os
import voluptuous as vol
from homeassistant.config import load_yaml_config_file
from homeassistant.const import (ATTR_LOCATION, ATTR_TRIPPED,
CONF_HOST, CONF_INCLUDE, CONF_NAME,
CONF_PASSWORD, CONF_TRIGGER_TIME,
@@ -18,11 +19,12 @@ from homeassistant.const import (ATTR_LOCATION, ATTR_TRIPPED,
from homeassistant.components.discovery import SERVICE_AXIS
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers import discovery
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.entity import Entity
from homeassistant.loader import get_component
REQUIREMENTS = ['axis==7']
REQUIREMENTS = ['axis==8']
_LOGGER = logging.getLogger(__name__)
@@ -59,6 +61,21 @@ CONFIG_SCHEMA = vol.Schema({
}),
}, extra=vol.ALLOW_EXTRA)
SERVICE_VAPIX_CALL = 'vapix_call'
SERVICE_VAPIX_CALL_RESPONSE = 'vapix_call_response'
SERVICE_CGI = 'cgi'
SERVICE_ACTION = 'action'
SERVICE_PARAM = 'param'
SERVICE_DEFAULT_CGI = 'param.cgi'
SERVICE_DEFAULT_ACTION = 'update'
SERVICE_SCHEMA = vol.Schema({
vol.Required(CONF_NAME): cv.string,
vol.Required(SERVICE_PARAM): cv.string,
vol.Optional(SERVICE_CGI, default=SERVICE_DEFAULT_CGI): cv.string,
vol.Optional(SERVICE_ACTION, default=SERVICE_DEFAULT_ACTION): cv.string,
})
def request_configuration(hass, name, host, serialnumber):
"""Request configuration steps from the user."""
@@ -135,23 +152,34 @@ def setup(hass, base_config):
def axis_device_discovered(service, discovery_info):
"""Called when axis devices has been found."""
host = discovery_info['host']
host = discovery_info[CONF_HOST]
name = discovery_info['hostname']
serialnumber = discovery_info['properties']['macaddress']
if serialnumber not in AXIS_DEVICES:
config_file = _read_config(hass)
if serialnumber in config_file:
# Device config saved to file
try:
config = DEVICE_SCHEMA(config_file[serialnumber])
config[CONF_HOST] = host
except vol.Invalid as err:
_LOGGER.error("Bad data from %s. %s", CONFIG_FILE, err)
return False
if not setup_device(hass, config):
_LOGGER.error("Couldn\'t set up %s", config['name'])
_LOGGER.error("Couldn\'t set up %s", config[CONF_NAME])
else:
# New device, create configuration request for UI
request_configuration(hass, name, host, serialnumber)
else:
# Device already registered, but on a different IP
device = AXIS_DEVICES[serialnumber]
device.url = host
async_dispatcher_send(hass,
DOMAIN + '_' + device.name + '_new_ip',
host)
# Register discovery service
discovery.listen(hass, SERVICE_AXIS, axis_device_discovered)
if DOMAIN in base_config:
@@ -160,7 +188,30 @@ def setup(hass, base_config):
if CONF_NAME not in config:
config[CONF_NAME] = device
if not setup_device(hass, config):
_LOGGER.error("Couldn\'t set up %s", config['name'])
_LOGGER.error("Couldn\'t set up %s", config[CONF_NAME])
# Services to communicate with device.
descriptions = load_yaml_config_file(
os.path.join(os.path.dirname(__file__), 'services.yaml'))
def vapix_service(call):
"""Service to send a message."""
for _, device in AXIS_DEVICES.items():
if device.name == call.data[CONF_NAME]:
response = device.do_request(call.data[SERVICE_CGI],
call.data[SERVICE_ACTION],
call.data[SERVICE_PARAM])
hass.bus.async_fire(SERVICE_VAPIX_CALL_RESPONSE, response)
return True
_LOGGER.info("Couldn\'t find device %s", call.data[CONF_NAME])
return False
# Register service with Home Assistant.
hass.services.register(DOMAIN,
SERVICE_VAPIX_CALL,
vapix_service,
descriptions[DOMAIN][SERVICE_VAPIX_CALL],
schema=SERVICE_SCHEMA)
return True
@@ -190,8 +241,16 @@ def setup_device(hass, config):
if enable_metadatastream:
device.initialize_new_event = event_initialized
device.initiate_metadatastream()
if not device.initiate_metadatastream():
notification = get_component('persistent_notification')
notification.create(hass,
'Dependency missing for sensors, '
'please check documentation',
title=DOMAIN,
notification_id='axis_notification')
AXIS_DEVICES[device.serial_number] = device
return True
@@ -311,4 +370,4 @@ REMAP = [{'type': 'motion',
'class': 'input',
'topic': 'tns1:Device/tnsaxis:IO/Port',
'subscribe': 'onvif:Device/axis:IO/Port',
'platform': 'sensor'}, ]
'platform': 'binary_sensor'}, ]

View File

@@ -51,7 +51,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
add_devices([ArestBinarySensor(
arest, resource, config.get(CONF_NAME, response[CONF_NAME]),
device_class, pin)])
device_class, pin)], True)
class ArestBinarySensor(BinarySensorDevice):
@@ -64,12 +64,11 @@ class ArestBinarySensor(BinarySensorDevice):
self._name = name
self._device_class = device_class
self._pin = pin
self.update()
if self._pin is not None:
request = requests.get(
'{}/mode/{}/i'.format(self._resource, self._pin), timeout=10)
if request.status_code is not 200:
if request.status_code != 200:
_LOGGER.error("Can't set mode of %s", self._resource)
@property

View File

@@ -8,19 +8,18 @@ import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA)
from homeassistant.components.digital_ocean import (
CONF_DROPLETS, ATTR_CREATED_AT, ATTR_DROPLET_ID, ATTR_DROPLET_NAME,
ATTR_FEATURES, ATTR_IPV4_ADDRESS, ATTR_IPV6_ADDRESS, ATTR_MEMORY,
ATTR_REGION, ATTR_VCPUS)
from homeassistant.loader import get_component
import homeassistant.helpers.config_validation as cv
ATTR_REGION, ATTR_VCPUS, DATA_DIGITAL_OCEAN)
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'Droplet'
DEFAULT_SENSOR_CLASS = 'motion'
DEFAULT_SENSOR_CLASS = 'moving'
DEPENDENCIES = ['digital_ocean']
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@@ -30,19 +29,21 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Digital Ocean droplet sensor."""
digital_ocean = get_component('digital_ocean')
digital = hass.data.get(DATA_DIGITAL_OCEAN)
if not digital:
return False
droplets = config.get(CONF_DROPLETS)
dev = []
for droplet in droplets:
droplet_id = digital_ocean.DIGITAL_OCEAN.get_droplet_id(droplet)
droplet_id = digital.get_droplet_id(droplet)
if droplet_id is None:
_LOGGER.error("Droplet %s is not available", droplet)
return False
dev.append(DigitalOceanBinarySensor(
digital_ocean.DIGITAL_OCEAN, droplet_id))
dev.append(DigitalOceanBinarySensor(digital, droplet_id))
add_devices(dev)
add_devices(dev, True)
class DigitalOceanBinarySensor(BinarySensorDevice):
@@ -53,7 +54,7 @@ class DigitalOceanBinarySensor(BinarySensorDevice):
self._digital_ocean = do
self._droplet_id = droplet_id
self._state = None
self.update()
self.data = None
@property
def name(self):

View File

@@ -199,11 +199,10 @@ class FlicButton(BinarySensorDevice):
"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
_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."""

View File

@@ -18,7 +18,7 @@ from homeassistant.const import (
CONF_SSL, EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_START,
ATTR_LAST_TRIP_TIME, CONF_CUSTOMIZE)
REQUIREMENTS = ['pyhik==0.1.2']
REQUIREMENTS = ['pyhik==0.1.3']
_LOGGER = logging.getLogger(__name__)
CONF_IGNORED = 'ignored'

View File

@@ -34,8 +34,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
return
devices = []
for config in discovery_info[ATTR_DISCOVER_DEVICES]:
new_device = HMBinarySensor(hass, config)
for conf in discovery_info[ATTR_DISCOVER_DEVICES]:
new_device = HMBinarySensor(hass, conf)
new_device.link_homematic()
devices.append(new_device)

View File

@@ -8,7 +8,7 @@ import logging
import voluptuous as vol
import homeassistant.components.modbus as modbus
from homeassistant.const import CONF_NAME
from homeassistant.const import CONF_NAME, CONF_SLAVE
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.helpers import config_validation as cv
from homeassistant.components.sensor import PLATFORM_SCHEMA
@@ -18,7 +18,6 @@ DEPENDENCIES = ['modbus']
CONF_COIL = 'coil'
CONF_COILS = 'coils'
CONF_SLAVE = 'slave'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_COILS): [{
@@ -50,6 +49,11 @@ class ModbusCoilSensor(BinarySensorDevice):
self._coil = int(coil)
self._value = None
@property
def name(self):
"""Return the name of the sensor."""
return self._name
@property
def is_on(self):
"""Return the state of the sensor."""
@@ -58,4 +62,10 @@ class ModbusCoilSensor(BinarySensorDevice):
def update(self):
"""Update the state of the sensor."""
result = modbus.HUB.read_coils(self._slave, self._coil, 1)
self._value = result.bits[0]
try:
self._value = result.bits[0]
except AttributeError:
_LOGGER.error(
'No response from modbus slave %s coil %s',
self._slave,
self._coil)

View File

@@ -156,8 +156,7 @@ class NetatmoBinarySensor(BinarySensorDevice):
return WELCOME_SENSOR_TYPES.get(self._sensor_name)
elif self._cameratype == 'NOC':
return PRESENCE_SENSOR_TYPES.get(self._sensor_name)
else:
return TAG_SENSOR_TYPES.get(self._sensor_name)
return TAG_SENSOR_TYPES.get(self._sensor_name)
@property
def is_on(self):

View File

@@ -12,13 +12,12 @@ import voluptuous as vol
from homeassistant.const import CONF_NAME, CONF_MONITORED_CONDITIONS
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA)
from homeassistant.loader import get_component
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['octoprint']
DOMAIN = "octoprint"
DEFAULT_NAME = 'OctoPrint'
SENSOR_TYPES = {
@@ -37,7 +36,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the available OctoPrint binary sensors."""
octoprint = get_component('octoprint')
octoprint_api = hass.data[DOMAIN]["api"]
name = config.get(CONF_NAME)
monitored_conditions = config.get(
CONF_MONITORED_CONDITIONS, SENSOR_TYPES.keys())
@@ -45,7 +44,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
devices = []
for octo_type in monitored_conditions:
new_sensor = OctoPrintBinarySensor(
octoprint.OCTOPRINT, octo_type, SENSOR_TYPES[octo_type][2],
octoprint_api, octo_type, SENSOR_TYPES[octo_type][2],
name, SENSOR_TYPES[octo_type][3], SENSOR_TYPES[octo_type][0],
SENSOR_TYPES[octo_type][1], 'flags')
devices.append(new_sensor)
@@ -98,6 +97,3 @@ class OctoPrintBinarySensor(BinarySensorDevice):
except requests.exceptions.ConnectionError:
# Error calling the api, already logged in api.update()
return
if self._state is None:
_LOGGER.warning("Unable to locate value for %s", self.sensor_type)

View File

@@ -28,6 +28,7 @@ from homeassistant.util import dt as dt_util
_LOGGER = logging.getLogger(__name__)
CONF_VARIABLE = 'variable'
CONF_RESET_DELAY_SEC = 'reset_delay_sec'
DEFAULT_NAME = 'Pilight Binary Sensor'
DEPENDENCIES = ['pilight']
@@ -38,7 +39,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PAYLOAD_ON, default='on'): cv.string,
vol.Optional(CONF_PAYLOAD_OFF, default='off'): cv.string,
vol.Optional(CONF_DISARM_AFTER_TRIGGER, default=False): cv.boolean
vol.Optional(CONF_DISARM_AFTER_TRIGGER, default=False): cv.boolean,
vol.Optional(CONF_RESET_DELAY_SEC, default=30): cv.positive_int
})
@@ -54,6 +56,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
payload=config.get(CONF_PAYLOAD),
on_value=config.get(CONF_PAYLOAD_ON),
off_value=config.get(CONF_PAYLOAD_OFF),
rst_dly_sec=config.get(CONF_RESET_DELAY_SEC),
)])
else:
add_devices([PilightBinarySensor(

View File

@@ -126,14 +126,14 @@ class PingData(object):
'avg': rtt_avg,
'max': rtt_max,
'mdev': ''}
else:
match = PING_MATCHER.search(str(out).split('\n')[-1])
rtt_min, rtt_avg, rtt_max, rtt_mdev = match.groups()
return {
'min': rtt_min,
'avg': rtt_avg,
'max': rtt_max,
'mdev': rtt_mdev}
match = PING_MATCHER.search(str(out).split('\n')[-1])
rtt_min, rtt_avg, rtt_max, rtt_mdev = match.groups()
return {
'min': rtt_min,
'avg': rtt_avg,
'max': rtt_max,
'mdev': rtt_mdev}
except (subprocess.CalledProcessError, AttributeError):
return False

View File

@@ -0,0 +1,233 @@
"""
Support for RFXtrx binary sensors.
Lighting4 devices (sensors based on PT2262 encoder) are supported and
tested. Other types may need some work.
"""
import logging
import voluptuous as vol
from homeassistant.components import rfxtrx
from homeassistant.util import slugify
from homeassistant.util import dt as dt_util
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers import event as evt
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.rfxtrx import (
ATTR_AUTOMATIC_ADD, ATTR_NAME, ATTR_OFF_DELAY, ATTR_FIREEVENT,
ATTR_DATA_BITS, CONF_DEVICES
)
from homeassistant.const import (
CONF_DEVICE_CLASS, CONF_COMMAND_ON, CONF_COMMAND_OFF
)
DEPENDENCIES = ["rfxtrx"]
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = vol.Schema({
vol.Required("platform"): rfxtrx.DOMAIN,
vol.Optional(CONF_DEVICES, default={}): vol.All(
dict, rfxtrx.valid_binary_sensor),
vol.Optional(ATTR_AUTOMATIC_ADD, default=False): cv.boolean,
}, extra=vol.ALLOW_EXTRA)
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Setup the Binary Sensor platform to rfxtrx."""
import RFXtrx as rfxtrxmod
sensors = []
for packet_id, entity in config['devices'].items():
event = rfxtrx.get_rfx_object(packet_id)
device_id = slugify(event.device.id_string.lower())
if device_id in rfxtrx.RFX_DEVICES:
continue
if entity[ATTR_DATA_BITS] is not None:
_LOGGER.info("Masked device id: %s",
rfxtrx.get_pt2262_deviceid(device_id,
entity[ATTR_DATA_BITS]))
_LOGGER.info("Add %s rfxtrx.binary_sensor (class %s)",
entity[ATTR_NAME], entity[CONF_DEVICE_CLASS])
device = RfxtrxBinarySensor(event, entity[ATTR_NAME],
entity[CONF_DEVICE_CLASS],
entity[ATTR_FIREEVENT],
entity[ATTR_OFF_DELAY],
entity[ATTR_DATA_BITS],
entity[CONF_COMMAND_ON],
entity[CONF_COMMAND_OFF])
device.hass = hass
sensors.append(device)
rfxtrx.RFX_DEVICES[device_id] = device
add_devices_callback(sensors)
# pylint: disable=too-many-branches
def binary_sensor_update(event):
"""Callback for control updates from the RFXtrx gateway."""
if not isinstance(event, rfxtrxmod.ControlEvent):
return
device_id = slugify(event.device.id_string.lower())
if device_id in rfxtrx.RFX_DEVICES:
sensor = rfxtrx.RFX_DEVICES[device_id]
else:
sensor = rfxtrx.get_pt2262_device(device_id)
if sensor is None:
# Add the entity if not exists and automatic_add is True
if not config[ATTR_AUTOMATIC_ADD]:
return
poss_dev = rfxtrx.find_possible_pt2262_device(device_id)
if poss_dev is not None:
poss_id = slugify(poss_dev.event.device.id_string.lower())
_LOGGER.info("Found possible matching deviceid %s.",
poss_id)
pkt_id = "".join("{0:02x}".format(x) for x in event.data)
sensor = RfxtrxBinarySensor(event, pkt_id)
rfxtrx.RFX_DEVICES[device_id] = sensor
add_devices_callback([sensor])
_LOGGER.info("Added binary sensor %s "
"(Device_id: %s Class: %s Sub: %s)",
pkt_id,
slugify(event.device.id_string.lower()),
event.device.__class__.__name__,
event.device.subtype)
elif not isinstance(sensor, RfxtrxBinarySensor):
return
else:
_LOGGER.info("Binary sensor update "
"(Device_id: %s Class: %s Sub: %s)",
slugify(event.device.id_string.lower()),
event.device.__class__.__name__,
event.device.subtype)
if sensor.is_pt2262:
cmd = rfxtrx.get_pt2262_cmd(device_id, sensor.data_bits)
_LOGGER.info("applying cmd %s to device_id: %s)",
cmd, sensor.masked_id)
sensor.apply_cmd(int(cmd, 16))
else:
rfxtrx.apply_received_command(event)
if (sensor.is_on and sensor.off_delay is not None and
sensor.delay_listener is None):
def off_delay_listener(now):
"""Switch device off after a delay."""
sensor.delay_listener = None
sensor.update_state(False)
sensor.delay_listener = evt.track_point_in_time(
hass, off_delay_listener, dt_util.utcnow() + sensor.off_delay
)
# Subscribe to main rfxtrx events
if binary_sensor_update not in rfxtrx.RECEIVED_EVT_SUBSCRIBERS:
rfxtrx.RECEIVED_EVT_SUBSCRIBERS.append(binary_sensor_update)
# pylint: disable=too-many-instance-attributes,too-many-arguments
class RfxtrxBinarySensor(BinarySensorDevice):
"""An Rfxtrx binary sensor."""
def __init__(self, event, name, device_class=None,
should_fire=False, off_delay=None, data_bits=None,
cmd_on=None, cmd_off=None):
"""Initialize the sensor."""
self.event = event
self._name = name
self._should_fire_event = should_fire
self._device_class = device_class
self._off_delay = off_delay
self._state = False
self.delay_listener = None
self._data_bits = data_bits
self._cmd_on = cmd_on
self._cmd_off = cmd_off
if data_bits is not None:
self._masked_id = rfxtrx.get_pt2262_deviceid(
event.device.id_string.lower(),
data_bits)
def __str__(self):
"""Return the name of the sensor."""
return self._name
@property
def name(self):
"""Return the device name."""
return self._name
@property
def is_pt2262(self):
"""Return true if the device is PT2262-based."""
return self._data_bits is not None
@property
def masked_id(self):
"""Return the masked device id (isolated address bits)."""
return self._masked_id
@property
def data_bits(self):
"""Return the number of data bits."""
return self._data_bits
@property
def cmd_on(self):
"""Return the value of the 'On' command."""
return self._cmd_on
@property
def cmd_off(self):
"""Return the value of the 'Off' command."""
return self._cmd_off
@property
def should_poll(self):
"""No polling needed."""
return False
@property
def should_fire_event(self):
"""Return is the device must fire event."""
return self._should_fire_event
@property
def device_class(self):
"""Return the sensor class."""
return self._device_class
@property
def off_delay(self):
"""Return the off_delay attribute value."""
return self._off_delay
@property
def is_on(self):
"""Return true if the sensor state is True."""
return self._state
def apply_cmd(self, cmd):
"""Apply a command for updating the state."""
if cmd == self.cmd_on:
self.update_state(True)
elif cmd == self.cmd_off:
self.update_state(False)
def update_state(self, state):
"""Update the state of the device."""
self._state = state
self.schedule_update_ha_state()

View File

@@ -0,0 +1,59 @@
"""
Interfaces with Verisure sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.verisure/
"""
import logging
from homeassistant.components.verisure import HUB as hub
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.verisure import CONF_DOOR_WINDOW
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup Verisure binary sensors."""
sensors = []
hub.update_overview()
if int(hub.config.get(CONF_DOOR_WINDOW, 1)):
sensors.extend([
VerisureDoorWindowSensor(device_label)
for device_label in hub.get(
"$.doorWindow.doorWindowDevice[*].deviceLabel")])
add_devices(sensors)
class VerisureDoorWindowSensor(BinarySensorDevice):
"""Verisure door window sensor."""
def __init__(self, device_label):
"""Initialize the modbus coil sensor."""
self._device_label = device_label
@property
def name(self):
"""Return the name of the binary sensor."""
return hub.get_first(
"$.doorWindow.doorWindowDevice[?(@.deviceLabel=='%s')].area",
self._device_label)
@property
def is_on(self):
"""Return the state of the sensor."""
return hub.get_first(
"$.doorWindow.doorWindowDevice[?(@.deviceLabel=='%s')].state",
self._device_label) == "OPEN"
@property
def available(self):
"""Return True if entity is available."""
return hub.get_first(
"$.doorWindow.doorWindowDevice[?(@.deviceLabel=='%s')]",
self._device_label) is not None
def update(self):
"""Update the state of the sensor."""
hub.update_overview()

View File

@@ -30,8 +30,7 @@ class VolvoSensor(VolvoEntity, BinarySensorDevice):
return bool(val)
elif self._attribute in ['doors', 'windows']:
return any([val[key] for key in val if 'Open' in key])
else:
return val != 'Normal'
return val != 'Normal'
@property
def device_class(self):

View File

@@ -121,10 +121,6 @@ class WinkBinarySensorDevice(WinkDevice, BinarySensorDevice, Entity):
class WinkSmokeDetector(WinkBinarySensorDevice):
"""Representation of a Wink Smoke detector."""
def __init__(self, wink, hass):
"""Initialize the Wink binary sensor."""
super().__init__(wink, hass)
@property
def device_state_attributes(self):
"""Return the state attributes."""
@@ -136,10 +132,6 @@ class WinkSmokeDetector(WinkBinarySensorDevice):
class WinkHub(WinkBinarySensorDevice):
"""Representation of a Wink Hub."""
def __init__(self, wink, hass):
"""Initialize the Wink binary sensor."""
super().__init__(wink, hass)
@property
def device_state_attributes(self):
"""Return the state attributes."""
@@ -152,10 +144,6 @@ class WinkHub(WinkBinarySensorDevice):
class WinkRemote(WinkBinarySensorDevice):
"""Representation of a Wink Lutron Connected bulb remote."""
def __init__(self, wink, hass):
"""Initialize the Wink binary sensor."""
super().__init__(wink, hass)
@property
def device_state_attributes(self):
"""Return the state attributes."""
@@ -175,10 +163,6 @@ class WinkRemote(WinkBinarySensorDevice):
class WinkButton(WinkBinarySensorDevice):
"""Representation of a Wink Relay button."""
def __init__(self, wink, hass):
"""Initialize the Wink binary sensor."""
super().__init__(wink, hass)
@property
def device_state_attributes(self):
"""Return the state attributes."""
@@ -191,10 +175,6 @@ class WinkButton(WinkBinarySensorDevice):
class WinkGang(WinkBinarySensorDevice):
"""Representation of a Wink Relay gang."""
def __init__(self, wink, hass):
"""Initialize the Wink binary sensor."""
super().__init__(wink, hass)
@property
def is_on(self):
"""Return true if the gang is connected."""

View File

@@ -34,10 +34,10 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
from bellows.zigbee.zcl.clusters.security import IasZone
clusters = discovery_info['clusters']
in_clusters = discovery_info['in_clusters']
device_class = None
cluster = [c for c in clusters if isinstance(c, IasZone)][0]
cluster = in_clusters[IasZone.cluster_id]
if discovery_info['new_join']:
yield from cluster.bind()
ieee = cluster.endpoint.device.application.ieee
@@ -64,7 +64,7 @@ class BinarySensor(zha.Entity, BinarySensorDevice):
super().__init__(**kwargs)
self._device_class = device_class
from bellows.zigbee.zcl.clusters.security import IasZone
self._ias_zone_cluster = self._clusters[IasZone.cluster_id]
self._ias_zone_cluster = self._in_clusters[IasZone.cluster_id]
@property
def is_on(self) -> bool:

View File

@@ -148,8 +148,7 @@ class CalendarEventDevice(Entity):
if 'date' in date:
return dt.start_of_local_day(dt.dt.datetime.combine(
dt.parse_date(date['date']), dt.dt.time.min))
else:
return dt.as_local(dt.parse_datetime(date['dateTime']))
return dt.as_local(dt.parse_datetime(date['dateTime']))
start = _get_date(self.data.event['start'])
end = _get_date(self.data.event['end'])

View File

@@ -30,7 +30,7 @@ def setup_platform(hass, config, add_devices, disc_info=None):
if disc_info is None:
return
if not any([data[CONF_TRACK] for data in disc_info[CONF_ENTITIES]]):
if not any(data[CONF_TRACK] for data in disc_info[CONF_ENTITIES]):
return
calendar_service = GoogleCalendarService(hass.config.path(TOKEN_FILE))

View File

@@ -12,13 +12,16 @@ from datetime import timedelta
import logging
import hashlib
from random import SystemRandom
import os
import aiohttp
from aiohttp import web
import async_timeout
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.const import ATTR_ENTITY_PICTURE
from homeassistant.const import (ATTR_ENTITY_ID, ATTR_ENTITY_PICTURE)
from homeassistant.config import load_yaml_config_file
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.entity import Entity
@@ -26,9 +29,12 @@ from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
from homeassistant.components.http import HomeAssistantView, KEY_AUTHENTICATED
from homeassistant.helpers.event import async_track_time_interval
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
SERVICE_EN_MOTION = 'enable_motion_detection'
SERVICE_DISEN_MOTION = 'disable_motion_detection'
DOMAIN = 'camera'
DEPENDENCIES = ['http']
SCAN_INTERVAL = timedelta(seconds=30)
@@ -38,11 +44,30 @@ STATE_RECORDING = 'recording'
STATE_STREAMING = 'streaming'
STATE_IDLE = 'idle'
DEFAULT_CONTENT_TYPE = 'image/jpeg'
ENTITY_IMAGE_URL = '/api/camera_proxy/{0}?token={1}'
TOKEN_CHANGE_INTERVAL = timedelta(minutes=5)
_RND = SystemRandom()
CAMERA_SERVICE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
})
def enable_motion_detection(hass, entity_id=None):
"""Enable Motion Detection."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
hass.async_add_job(hass.services.async_call(
DOMAIN, SERVICE_EN_MOTION, data))
def disable_motion_detection(hass, entity_id=None):
"""Disable Motion Detection."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
hass.async_add_job(hass.services.async_call(
DOMAIN, SERVICE_DISEN_MOTION, data))
@asyncio.coroutine
def async_get_image(hass, entity_id, timeout=10):
@@ -92,6 +117,44 @@ def async_setup(hass, config):
hass.async_add_job(entity.async_update_ha_state())
async_track_time_interval(hass, update_tokens, TOKEN_CHANGE_INTERVAL)
@asyncio.coroutine
def async_handle_camera_service(service):
"""Handle calls to the camera services."""
target_cameras = component.async_extract_from_service(service)
for camera in target_cameras:
if service.service == SERVICE_EN_MOTION:
yield from camera.async_enable_motion_detection()
elif service.service == SERVICE_DISEN_MOTION:
yield from camera.async_disable_motion_detection()
update_tasks = []
for camera in target_cameras:
if not camera.should_poll:
continue
update_coro = hass.async_add_job(
camera.async_update_ha_state(True))
if hasattr(camera, 'async_update'):
update_tasks.append(update_coro)
else:
yield from update_coro
if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)
descriptions = yield from hass.async_add_job(
load_yaml_config_file, os.path.join(
os.path.dirname(__file__), 'services.yaml'))
hass.services.async_register(
DOMAIN, SERVICE_EN_MOTION, async_handle_camera_service,
descriptions.get(SERVICE_EN_MOTION), schema=CAMERA_SERVICE_SCHEMA)
hass.services.async_register(
DOMAIN, SERVICE_DISEN_MOTION, async_handle_camera_service,
descriptions.get(SERVICE_DISEN_MOTION), schema=CAMERA_SERVICE_SCHEMA)
return True
@@ -101,6 +164,7 @@ class Camera(Entity):
def __init__(self):
"""Initialize a camera."""
self.is_streaming = False
self.content_type = DEFAULT_CONTENT_TYPE
self.access_tokens = collections.deque([], 2)
self.async_update_token()
@@ -124,6 +188,11 @@ class Camera(Entity):
"""Return the camera brand."""
return None
@property
def motion_detection_enabled(self):
"""Return the camera motion detection status."""
return None
@property
def model(self):
"""Return the camera model."""
@@ -149,16 +218,17 @@ class Camera(Entity):
response = web.StreamResponse()
response.content_type = ('multipart/x-mixed-replace; '
'boundary=--jpegboundary')
'boundary=--frameboundary')
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'
'--frameboundary\r\n'
'Content-Type: {}\r\n'
'Content-Length: {}\r\n\r\n'.format(
len(img_bytes)), 'utf-8') + img_bytes + b'\r\n')
self.content_type, len(img_bytes)),
'utf-8') + img_bytes + b'\r\n')
last_image = None
@@ -196,8 +266,23 @@ class Camera(Entity):
return STATE_RECORDING
elif self.is_streaming:
return STATE_STREAMING
else:
return STATE_IDLE
return STATE_IDLE
def enable_motion_detection(self):
"""Enable motion detection in the camera."""
raise NotImplementedError()
def async_enable_motion_detection(self):
"""Call the job and enable motion detection."""
return self.hass.async_add_job(self.enable_motion_detection)
def disable_motion_detection(self):
"""Disable motion detection in camera."""
raise NotImplementedError()
def async_disable_motion_detection(self):
"""Call the job and disable motion detection."""
return self.hass.async_add_job(self.disable_motion_detection)
@property
def state_attributes(self):
@@ -212,6 +297,9 @@ class Camera(Entity):
if self.brand:
attr['brand'] = self.brand
if self.motion_detection_enabled:
attr['motion_detection'] = self.motion_detection_enabled
return attr
@callback
@@ -269,7 +357,8 @@ class CameraImageView(CameraView):
image = yield from camera.async_camera_image()
if image:
return web.Response(body=image, content_type='image/jpeg')
return web.Response(body=image,
content_type=camera.content_type)
return web.Response(status=500)

View File

@@ -7,108 +7,59 @@ https://home-assistant.io/components/camera.amcrest/
import asyncio
import logging
import aiohttp
import voluptuous as vol
import homeassistant.loader as loader
from homeassistant.components.camera import (Camera, PLATFORM_SCHEMA)
from homeassistant.components.amcrest import (
STREAM_SOURCE_LIST, TIMEOUT)
from homeassistant.components.camera import Camera
from homeassistant.components.ffmpeg import DATA_FFMPEG
from homeassistant.const import (
CONF_HOST, CONF_NAME, CONF_USERNAME, CONF_PASSWORD, CONF_PORT)
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import (
async_get_clientsession, async_aiohttp_proxy_web,
async_aiohttp_proxy_stream)
REQUIREMENTS = ['amcrest==1.2.0']
DEPENDENCIES = ['ffmpeg']
DEPENDENCIES = ['amcrest', 'ffmpeg']
_LOGGER = logging.getLogger(__name__)
CONF_RESOLUTION = 'resolution'
CONF_STREAM_SOURCE = 'stream_source'
CONF_FFMPEG_ARGUMENTS = 'ffmpeg_arguments'
DEFAULT_NAME = 'Amcrest Camera'
DEFAULT_PORT = 80
DEFAULT_RESOLUTION = 'high'
DEFAULT_STREAM_SOURCE = 'mjpeg'
NOTIFICATION_ID = 'amcrest_notification'
NOTIFICATION_TITLE = 'Amcrest Camera Setup'
RESOLUTION_LIST = {
'high': 0,
'low': 1,
}
STREAM_SOURCE_LIST = {
'mjpeg': 0,
'snapshot': 1,
'rtsp': 2,
}
CONTENT_TYPE_HEADER = 'Content-Type'
TIMEOUT = 5
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,
vol.Optional(CONF_STREAM_SOURCE, default=DEFAULT_STREAM_SOURCE):
vol.All(vol.In(STREAM_SOURCE_LIST)),
vol.Optional(CONF_FFMPEG_ARGUMENTS): cv.string,
})
def setup_platform(hass, config, add_devices, discovery_info=None):
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up an Amcrest IP Camera."""
from amcrest import AmcrestCamera
camera = AmcrestCamera(
config.get(CONF_HOST), config.get(CONF_PORT),
config.get(CONF_USERNAME), config.get(CONF_PASSWORD)).camera
if discovery_info is None:
return
persistent_notification = loader.get_component('persistent_notification')
try:
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
device = discovery_info['device']
authentication = discovery_info['authentication']
ffmpeg_arguments = discovery_info['ffmpeg_arguments']
name = discovery_info['name']
resolution = discovery_info['resolution']
stream_source = discovery_info['stream_source']
async_add_devices([
AmcrestCam(hass,
name,
device,
authentication,
ffmpeg_arguments,
stream_source,
resolution)], True)
add_devices([AmcrestCam(hass, config, camera)])
return True
class AmcrestCam(Camera):
"""An implementation of an Amcrest IP camera."""
def __init__(self, hass, device_info, camera):
def __init__(self, hass, name, camera, authentication,
ffmpeg_arguments, stream_source, resolution):
"""Initialize an Amcrest camera."""
super(AmcrestCam, self).__init__()
self._name = name
self._camera = camera
self._base_url = self._camera.get_base_url()
self._name = device_info.get(CONF_NAME)
self._ffmpeg = hass.data[DATA_FFMPEG]
self._ffmpeg_arguments = device_info.get(CONF_FFMPEG_ARGUMENTS)
self._resolution = RESOLUTION_LIST[device_info.get(CONF_RESOLUTION)]
self._stream_source = STREAM_SOURCE_LIST[
device_info.get(CONF_STREAM_SOURCE)
]
self._token = self._auth = aiohttp.BasicAuth(
device_info.get(CONF_USERNAME),
password=device_info.get(CONF_PASSWORD)
)
self._ffmpeg_arguments = ffmpeg_arguments
self._stream_source = stream_source
self._resolution = resolution
self._token = self._auth = authentication
def camera_image(self):
"""Return a still image reponse from the camera."""

View File

@@ -6,32 +6,32 @@ https://home-assistant.io/components/camera.arlo/
"""
import asyncio
import logging
import voluptuous as vol
from homeassistant.helpers import config_validation as cv
from homeassistant.components.arlo import DEFAULT_BRAND
from homeassistant.components.camera import (Camera, PLATFORM_SCHEMA)
from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream
from homeassistant.components.arlo import DEFAULT_BRAND, DATA_ARLO
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
from homeassistant.components.ffmpeg import DATA_FFMPEG
from homeassistant.helpers.aiohttp_client import (
async_aiohttp_proxy_stream)
DEPENDENCIES = ['arlo', 'ffmpeg']
_LOGGER = logging.getLogger(__name__)
CONF_FFMPEG_ARGUMENTS = 'ffmpeg_arguments'
ARLO_MODE_ARMED = 'armed'
ARLO_MODE_DISARMED = 'disarmed'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_FFMPEG_ARGUMENTS):
cv.string,
vol.Optional(CONF_FFMPEG_ARGUMENTS): cv.string,
})
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up an Arlo IP Camera."""
arlo = hass.data.get('arlo')
arlo = hass.data.get(DATA_ARLO)
if not arlo:
return False
@@ -40,7 +40,6 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
cameras.append(ArloCam(hass, camera, config))
async_add_devices(cameras, True)
return True
class ArloCam(Camera):
@@ -49,14 +48,14 @@ class ArloCam(Camera):
def __init__(self, hass, camera, device_info):
"""Initialize an Arlo camera."""
super().__init__()
self._camera = camera
self._name = self._camera.name
self._motion_status = False
self._ffmpeg = hass.data[DATA_FFMPEG]
self._ffmpeg_arguments = device_info.get(CONF_FFMPEG_ARGUMENTS)
def camera_image(self):
"""Return a still image reponse from the camera."""
"""Return a still image response from the camera."""
return self._camera.last_image
@asyncio.coroutine
@@ -90,3 +89,36 @@ class ArloCam(Camera):
def brand(self):
"""Camera brand."""
return DEFAULT_BRAND
@property
def should_poll(self):
"""Camera should poll periodically."""
return True
@property
def motion_detection_enabled(self):
"""Camera Motion Detection Status."""
return self._motion_status
def set_base_station_mode(self, mode):
"""Set the mode in the base station."""
# Get the list of base stations identified by library
base_stations = self.hass.data[DATA_ARLO].base_stations
# Some Arlo cameras does not have basestation
# So check if there is base station detected first
# if yes, then choose the primary base station
# Set the mode on the chosen base station
if base_stations:
primary_base_station = base_stations[0]
primary_base_station.mode = mode
def enable_motion_detection(self):
"""Enable the Motion detection in base station (Arm)."""
self._motion_status = True
self.set_base_station_mode(ARLO_MODE_ARMED)
def disable_motion_detection(self):
"""Disable the motion detection in base station (Disarm)."""
self._motion_status = False
self.set_base_station_mode(ARLO_MODE_DISARMED)

View File

@@ -7,15 +7,16 @@ https://home-assistant.io/components/camera.axis/
import logging
from homeassistant.const import (
CONF_NAME, CONF_USERNAME, CONF_PASSWORD,
CONF_HOST, CONF_NAME, CONF_USERNAME, CONF_PASSWORD,
CONF_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION)
from homeassistant.components.camera.mjpeg import (
CONF_MJPEG_URL, CONF_STILL_IMAGE_URL, MjpegCamera)
from homeassistant.helpers.dispatcher import async_dispatcher_connect
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['axis']
DOMAIN = 'axis'
DEPENDENCIES = [DOMAIN]
def _get_image_url(host, mode):
@@ -27,12 +28,29 @@ def _get_image_url(host, mode):
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup Axis camera."""
device_info = {
CONF_NAME: discovery_info['name'],
CONF_USERNAME: discovery_info['username'],
CONF_PASSWORD: discovery_info['password'],
CONF_MJPEG_URL: _get_image_url(discovery_info['host'], 'mjpeg'),
CONF_STILL_IMAGE_URL: _get_image_url(discovery_info['host'], 'single'),
config = {
CONF_NAME: discovery_info[CONF_NAME],
CONF_USERNAME: discovery_info[CONF_USERNAME],
CONF_PASSWORD: discovery_info[CONF_PASSWORD],
CONF_MJPEG_URL: _get_image_url(discovery_info[CONF_HOST], 'mjpeg'),
CONF_STILL_IMAGE_URL: _get_image_url(discovery_info[CONF_HOST],
'single'),
CONF_AUTHENTICATION: HTTP_DIGEST_AUTHENTICATION,
}
add_devices([MjpegCamera(hass, device_info)])
add_devices([AxisCamera(hass, config)])
class AxisCamera(MjpegCamera):
"""AxisCamera class."""
def __init__(self, hass, config):
"""Initialize Axis Communications camera component."""
super().__init__(hass, config)
async_dispatcher_connect(hass,
DOMAIN + '_' + config[CONF_NAME] + '_new_ip',
self._new_ip)
def _new_ip(self, host):
"""Set new IP for video stream."""
self._mjpeg_url = _get_image_url(host, 'mjpeg')
self._still_image_url = _get_image_url(host, 'mjpeg')

View File

@@ -5,25 +5,29 @@ For more details about this platform, please refer to the documentation
https://home-assistant.io/components/demo/
"""
import os
import logging
import homeassistant.util.dt as dt_util
from homeassistant.components.camera import Camera
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Demo camera platform."""
add_devices([
DemoCamera('Demo camera')
DemoCamera(hass, config, 'Demo camera')
])
class DemoCamera(Camera):
"""The representation of a Demo camera."""
def __init__(self, name):
def __init__(self, hass, config, name):
"""Initialize demo camera component."""
super().__init__()
self._parent = hass
self._name = name
self._motion_status = False
def camera_image(self):
"""Return a faked still image response."""
@@ -38,3 +42,21 @@ class DemoCamera(Camera):
def name(self):
"""Return the name of this camera."""
return self._name
@property
def should_poll(self):
"""Camera should poll periodically."""
return True
@property
def motion_detection_enabled(self):
"""Camera Motion Detection Status."""
return self._motion_status
def enable_motion_detection(self):
"""Enable the Motion detection in base station (Arm)."""
self._motion_status = True
def disable_motion_detection(self):
"""Disable the motion detection in base station (Disarm)."""
self._motion_status = False

View File

@@ -17,13 +17,15 @@ from homeassistant.const import (
CONF_NAME, CONF_USERNAME, CONF_PASSWORD, CONF_AUTHENTICATION,
HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION)
from homeassistant.exceptions import TemplateError
from homeassistant.components.camera import (PLATFORM_SCHEMA, Camera)
from homeassistant.components.camera import (
PLATFORM_SCHEMA, DEFAULT_CONTENT_TYPE, 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__)
CONF_CONTENT_TYPE = 'content_type'
CONF_LIMIT_REFETCH_TO_URL_CHANGE = 'limit_refetch_to_url_change'
CONF_STILL_IMAGE_URL = 'still_image_url'
@@ -37,6 +39,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PASSWORD): cv.string,
vol.Optional(CONF_USERNAME): cv.string,
vol.Optional(CONF_CONTENT_TYPE, default=DEFAULT_CONTENT_TYPE): cv.string,
})
@@ -59,6 +62,7 @@ class GenericCamera(Camera):
self._still_image_url = device_info[CONF_STILL_IMAGE_URL]
self._still_image_url.hass = hass
self._limit_refetch = device_info[CONF_LIMIT_REFETCH_TO_URL_CHANGE]
self.content_type = device_info[CONF_CONTENT_TYPE]
username = device_info.get(CONF_USERNAME)
password = device_info.get(CONF_PASSWORD)

View File

@@ -5,6 +5,7 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.local_file/
"""
import logging
import mimetypes
import os
import voluptuous as vol
@@ -46,6 +47,10 @@ class LocalFile(Camera):
self._name = name
self._file_path = file_path
# Set content type of local file
content, _ = mimetypes.guess_type(file_path)
if content is not None:
self.content_type = content
def camera_image(self):
"""Return image response."""

View File

@@ -113,8 +113,7 @@ class NetatmoCamera(Camera):
return "Presence"
elif self._cameratype == "NACamera":
return "Welcome"
else:
return None
return None
@property
def unique_id(self):

View File

@@ -0,0 +1,17 @@
# Describes the format for available camera services
enable_motion_detection:
description: Enable the motion detection in a camera
fields:
entity_id:
description: Name(s) of entities to enable motion detection
example: 'camera.living_room_camera'
disable_motion_detection:
description: Disable the motion detection in a camera
fields:
entity_id:
description: Name(s) of entities to disable motion detection
example: 'camera.living_room_camera'

View File

@@ -54,7 +54,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
_LOGGER.error("Unable to connect to NVR: %s", str(ex))
return False
identifier = nvrconn.server_version >= (3, 2, 0) and 'id' or 'uuid'
identifier = 'id' if nvrconn.server_version >= (3, 2, 0) else 'uuid'
# Filter out airCam models, which are not supported in the latest
# version of UnifiVideo and which are EOL by Ubiquiti
cameras = [

View File

@@ -24,22 +24,23 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
if not os.access(directory_path, os.R_OK):
_LOGGER.error("file path %s is not readable", directory_path)
return False
hub.update_smartcam()
hub.update_overview()
smartcams = []
smartcams.extend([
VerisureSmartcam(hass, value.deviceLabel, directory_path)
for value in hub.smartcam_status.values()])
VerisureSmartcam(hass, device_label, directory_path)
for device_label in hub.get(
"$.customerImageCameras[*].deviceLabel")])
add_devices(smartcams)
class VerisureSmartcam(Camera):
"""Representation of a Verisure camera."""
def __init__(self, hass, device_id, directory_path):
def __init__(self, hass, device_label, directory_path):
"""Initialize Verisure File Camera component."""
super().__init__()
self._device_id = device_id
self._device_label = device_label
self._directory_path = directory_path
self._image = None
self._image_id = None
@@ -58,28 +59,27 @@ class VerisureSmartcam(Camera):
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]):
hub.update_smartcam_imageseries()
image_ids = hub.get_image_info(
"$.imageSeries[?(@.deviceLabel=='%s')].image[0].imageId",
self._device_label)
if not image_ids:
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)
new_image_id = image_ids[0]
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)
new_image_path = os.path.join(
self._directory_path, '{}{}'.format(new_image_id, '.jpg'))
hub.session.download_image(
self._device_label, new_image_id, new_image_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'))
self._image = new_image_path
def delete_image(self, event):
"""Delete an old image."""
@@ -95,4 +95,6 @@ class VerisureSmartcam(Camera):
@property
def name(self):
"""Return the name of this camera."""
return hub.smartcam_status[self._device_id].location
return hub.get_first(
"$.customerImageCameras[?(@.deviceLabel=='%s')].area",
self._device_label)

View File

@@ -398,16 +398,14 @@ class ClimateDevice(Entity):
"""Return the current state."""
if self.current_operation:
return self.current_operation
else:
return STATE_UNKNOWN
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
return PRECISION_WHOLE
@property
def state_attributes(self):
@@ -693,8 +691,14 @@ 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:
return temp
# if the temperature is not a number this can cause issues
# with polymer components, so bail early there.
if not isinstance(temp, Number):
raise TypeError("Temperature is not a number: %s" % temp)
if self.temperature_unit != self.unit_of_measurement:
temp = convert_temperature(
temp, self.temperature_unit, self.unit_of_measurement)
@@ -703,6 +707,5 @@ class ClimateDevice(Entity):
return round(temp * 2) / 2.0
elif self.precision == PRECISION_TENTHS:
return round(temp, 1)
else:
# PRECISION_WHOLE as a fall back
return round(temp)
# PRECISION_WHOLE as a fall back
return round(temp)

View File

@@ -151,16 +151,14 @@ class Thermostat(ClimateDevice):
"""Return the lower bound temperature we try to reach."""
if self.current_operation == STATE_AUTO:
return int(self.thermostat['runtime']['desiredHeat'] / 10)
else:
return None
return None
@property
def target_temperature_high(self):
"""Return the upper bound temperature we try to reach."""
if self.current_operation == STATE_AUTO:
return int(self.thermostat['runtime']['desiredCool'] / 10)
else:
return None
return None
@property
def target_temperature(self):
@@ -171,8 +169,7 @@ class Thermostat(ClimateDevice):
return int(self.thermostat['runtime']['desiredHeat'] / 10)
elif self.current_operation == STATE_COOL:
return int(self.thermostat['runtime']['desiredCool'] / 10)
else:
return None
return None
@property
def desired_fan_mode(self):
@@ -184,8 +181,7 @@ class Thermostat(ClimateDevice):
"""Return the current fan state."""
if 'fan' in self.thermostat['equipmentStatus']:
return STATE_ON
else:
return STATE_OFF
return STATE_OFF
@property
def current_hold_mode(self):
@@ -199,15 +195,13 @@ class Thermostat(ClimateDevice):
int(event['startDate'][0:4]) <= 1:
# A temporary hold from away climate is a hold
return 'away'
else:
# A permanent hold from away climate is away_mode
return None
# A permanent hold from away climate is away_mode
return None
elif event['holdClimateRef'] != "":
# Any other hold based on climate
return event['holdClimateRef']
else:
# Any hold not based on a climate is a temp hold
return TEMPERATURE_HOLD
# Any hold not based on a climate is a temp hold
return TEMPERATURE_HOLD
elif event['type'].startswith('auto'):
# All auto modes are treated as holds
return event['type'][4:].lower()
@@ -222,8 +216,7 @@ class Thermostat(ClimateDevice):
if self.operation_mode == 'auxHeatOnly' or \
self.operation_mode == 'heatPump':
return STATE_HEAT
else:
return self.operation_mode
return self.operation_mode
@property
def operation_list(self):
@@ -384,8 +377,7 @@ class Thermostat(ClimateDevice):
# add further conditions if other hold durations should be
# supported; note that this should not include 'indefinite'
# as an indefinite away hold is interpreted as away_mode
else:
return 'nextTransition'
return 'nextTransition'
@property
def climate_list(self):

View File

@@ -145,4 +145,4 @@ class Flexit(ClimateDevice):
def set_fan_mode(self, fan):
"""Set new fan mode."""
self.unit.set_fan_speed(fan)
self.unit.set_fan_speed(self._fan_list.index(fan))

View File

@@ -12,7 +12,8 @@ import voluptuous as vol
from homeassistant.core import callback
from homeassistant.components import switch
from homeassistant.components.climate import (
STATE_HEAT, STATE_COOL, STATE_IDLE, ClimateDevice, PLATFORM_SCHEMA)
STATE_HEAT, STATE_COOL, STATE_IDLE, ClimateDevice, PLATFORM_SCHEMA,
STATE_AUTO)
from homeassistant.const import (
ATTR_UNIT_OF_MEASUREMENT, STATE_ON, STATE_OFF, ATTR_TEMPERATURE,
CONF_NAME)
@@ -87,6 +88,7 @@ class GenericThermostat(ClimateDevice):
self.min_cycle_duration = min_cycle_duration
self._tolerance = tolerance
self._keep_alive = keep_alive
self._enabled = True
self._active = False
self._cur_temp = None
@@ -131,18 +133,39 @@ class GenericThermostat(ClimateDevice):
@property
def current_operation(self):
"""Return current operation ie. heat, cool, idle."""
if not self._enabled:
return STATE_OFF
if self.ac_mode:
cooling = self._active and self._is_device_active
return STATE_COOL if cooling else STATE_IDLE
else:
heating = self._active and self._is_device_active
return STATE_HEAT if heating else STATE_IDLE
heating = self._active and self._is_device_active
return STATE_HEAT if heating else STATE_IDLE
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
return self._target_temp
@property
def operation_list(self):
"""List of available operation modes."""
return [STATE_AUTO, STATE_OFF]
def set_operation_mode(self, operation_mode):
"""Set operation mode."""
if operation_mode == STATE_AUTO:
self._enabled = True
elif operation_mode == STATE_OFF:
self._enabled = False
if self._is_device_active:
switch.async_turn_off(self.hass, self.heater_entity_id)
else:
_LOGGER.error('Unrecognized operation mode: %s', operation_mode)
return
# Ensure we updae the current operation after changing the mode
self.schedule_update_ha_state()
@asyncio.coroutine
def async_set_temperature(self, **kwargs):
"""Set new target temperature."""
@@ -159,9 +182,9 @@ class GenericThermostat(ClimateDevice):
# pylint: disable=no-member
if self._min_temp:
return self._min_temp
else:
# get default temp from super class
return ClimateDevice.min_temp.fget(self)
# get default temp from super class
return ClimateDevice.min_temp.fget(self)
@property
def max_temp(self):
@@ -169,9 +192,9 @@ class GenericThermostat(ClimateDevice):
# pylint: disable=no-member
if self._max_temp:
return self._max_temp
else:
# Get default temp from super class
return ClimateDevice.max_temp.fget(self)
# Get default temp from super class
return ClimateDevice.max_temp.fget(self)
@asyncio.coroutine
def _async_sensor_changed(self, entity_id, old_state, new_state):
@@ -221,6 +244,9 @@ class GenericThermostat(ClimateDevice):
if not self._active:
return
if not self._enabled:
return
if self.min_cycle_duration:
if self._is_device_active:
current_state = STATE_ON

View File

@@ -46,8 +46,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
return
devices = []
for config in discovery_info[ATTR_DISCOVER_DEVICES]:
new_device = HMThermostat(hass, config)
for conf in discovery_info[ATTR_DISCOVER_DEVICES]:
new_device = HMThermostat(hass, conf)
new_device.link_homematic()
devices.append(new_device)

View File

@@ -60,8 +60,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
if region == 'us':
return _setup_us(username, password, config, add_devices)
else:
return _setup_round(username, password, config, add_devices)
return _setup_round(username, password, config, add_devices)
def _setup_round(username, password, config, add_devices):
@@ -251,8 +251,7 @@ class HoneywellUSThermostat(ClimateDevice):
"""Return the temperature we try to reach."""
if self._device.system_mode == 'cool':
return self._device.setpoint_cool
else:
return self._device.setpoint_heat
return self._device.setpoint_heat
@property
def current_operation(self: ClimateDevice) -> str:

View File

@@ -67,7 +67,12 @@ class MySensorsHVAC(mysensors.MySensorsDeviceEntity, ClimateDevice):
@property
def current_temperature(self):
"""Return the current temperature."""
return self._values.get(self.gateway.const.SetReq.V_TEMP)
value = self._values.get(self.gateway.const.SetReq.V_TEMP)
if value is not None:
value = float(value)
return value
@property
def target_temperature(self):
@@ -79,21 +84,21 @@ class MySensorsHVAC(mysensors.MySensorsDeviceEntity, ClimateDevice):
temp = self._values.get(set_req.V_HVAC_SETPOINT_COOL)
if temp is None:
temp = self._values.get(set_req.V_HVAC_SETPOINT_HEAT)
return temp
return float(temp)
@property
def target_temperature_high(self):
"""Return the highbound target temperature we try to reach."""
set_req = self.gateway.const.SetReq
if set_req.V_HVAC_SETPOINT_HEAT in self._values:
return self._values.get(set_req.V_HVAC_SETPOINT_COOL)
return float(self._values.get(set_req.V_HVAC_SETPOINT_COOL))
@property
def target_temperature_low(self):
"""Return the lowbound target temperature we try to reach."""
set_req = self.gateway.const.SetReq
if set_req.V_HVAC_SETPOINT_COOL in self._values:
return self._values.get(set_req.V_HVAC_SETPOINT_HEAT)
return float(self._values.get(set_req.V_HVAC_SETPOINT_HEAT))
@property
def current_operation(self):

View File

@@ -109,16 +109,14 @@ class NestThermostat(ClimateDevice):
return self._mode
elif self._mode == STATE_HEAT_COOL:
return STATE_AUTO
else:
return STATE_UNKNOWN
return STATE_UNKNOWN
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
if self._mode != STATE_HEAT_COOL and not self.is_away_mode_on:
return self._target_temperature
else:
return None
return None
@property
def target_temperature_low(self):
@@ -129,8 +127,7 @@ class NestThermostat(ClimateDevice):
return self._eco_temperature[0]
if self._mode == STATE_HEAT_COOL:
return self._target_temperature[0]
else:
return None
return None
@property
def target_temperature_high(self):
@@ -141,8 +138,7 @@ class NestThermostat(ClimateDevice):
return self._eco_temperature[1]
if self._mode == STATE_HEAT_COOL:
return self._target_temperature[1]
else:
return None
return None
@property
def is_away_mode_on(self):
@@ -188,9 +184,8 @@ class NestThermostat(ClimateDevice):
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
# No Fan available so disable slider
return None
@property
def fan_list(self):

View File

@@ -119,14 +119,14 @@ class NetatmoThermostat(ClimateDevice):
self._data.thermostatdata.setthermpoint(mode, temp, endTimeOffset=None)
self._away = False
def set_temperature(self, endTimeOffset=DEFAULT_TIME_OFFSET, **kwargs):
def set_temperature(self, **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)
mode, temperature, DEFAULT_TIME_OFFSET)
self._target_temperature = temperature
self._away = False

View File

@@ -92,8 +92,7 @@ class ThermostatDevice(ClimateDevice):
"""Return current operation i.e. heat, cool, idle."""
if self._state:
return STATE_HEAT
else:
return STATE_IDLE
return STATE_IDLE
@property
def current_temperature(self):

View File

@@ -15,7 +15,7 @@ from homeassistant.components.climate import (
from homeassistant.const import CONF_HOST, TEMP_FAHRENHEIT, ATTR_TEMPERATURE
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['radiotherm==1.2']
REQUIREMENTS = ['radiotherm==1.3']
_LOGGER = logging.getLogger(__name__)
@@ -84,6 +84,7 @@ class RadioThermostat(ClimateDevice):
self._name = None
self._fmode = None
self._tmode = None
self._tstate = None
self._hold_temp = hold_temp
self._away = False
self._away_temps = away_temps
@@ -135,18 +136,54 @@ class RadioThermostat(ClimateDevice):
return self._away
def update(self):
"""Update the data from the thermostat."""
self._current_temperature = self.device.temp['raw']
"""Update and validate the data from the thermostat."""
current_temp = self.device.temp['raw']
if current_temp == -1:
_LOGGER.error("Couldn't get valid temperature reading")
return
self._current_temperature = current_temp
self._name = self.device.name['raw']
self._fmode = self.device.fmode['human']
self._tmode = self.device.tmode['human']
try:
self._fmode = self.device.fmode['human']
except AttributeError:
_LOGGER.error("Couldn't get valid fan mode reading")
try:
self._tmode = self.device.tmode['human']
except AttributeError:
_LOGGER.error("Couldn't get valid thermostat mode reading")
try:
self._tstate = self.device.tstate['human']
except AttributeError:
_LOGGER.error("Couldn't get valid thermostat state reading")
if self._tmode == 'Cool':
self._target_temperature = self.device.t_cool['raw']
target_temp = self.device.t_cool['raw']
if target_temp == -1:
_LOGGER.error("Couldn't get target reading")
return
self._target_temperature = target_temp
self._current_operation = STATE_COOL
elif self._tmode == 'Heat':
self._target_temperature = self.device.t_heat['raw']
target_temp = self.device.t_heat['raw']
if target_temp == -1:
_LOGGER.error("Couldn't get valid target reading")
return
self._target_temperature = target_temp
self._current_operation = STATE_HEAT
elif self._tmode == 'Auto':
if self._tstate == 'Cool':
target_temp = self.device.t_cool['raw']
if target_temp == -1:
_LOGGER.error("Couldn't get valid target reading")
return
self._target_temperature = target_temp
elif self._tstate == 'Heat':
target_temp = self.device.t_heat['raw']
if target_temp == -1:
_LOGGER.error("Couldn't get valid target reading")
return
self._target_temperature = target_temp
self._current_operation = STATE_AUTO
else:
self._current_operation = STATE_IDLE
@@ -159,6 +196,12 @@ class RadioThermostat(ClimateDevice):
self.device.t_cool = round(temperature * 2.0) / 2.0
elif self._current_operation == STATE_HEAT:
self.device.t_heat = round(temperature * 2.0) / 2.0
elif self._current_operation == STATE_AUTO:
if self._tstate == 'Cool':
self.device.t_cool = round(temperature * 2.0) / 2.0
elif self._tstate == 'Heat':
self.device.t_heat = round(temperature * 2.0) / 2.0
if self._hold_temp or self._away:
self.device.hold = 1
else:

View File

@@ -16,6 +16,7 @@ from homeassistant.const import (
ATTR_TEMPERATURE, CONF_API_KEY, CONF_ID, TEMP_CELSIUS, TEMP_FAHRENHEIT)
from homeassistant.components.climate import (
ATTR_CURRENT_HUMIDITY, ClimateDevice, PLATFORM_SCHEMA)
from homeassistant.exceptions import PlatformNotReady
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.util.temperature import convert as convert_temperature
@@ -52,9 +53,10 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
yield from client.async_get_devices(_INITIAL_FETCH_FIELDS)):
if config[CONF_ID] == ALL or dev['id'] in config[CONF_ID]:
devices.append(SensiboClimate(client, dev))
except aiohttp.client_exceptions.ClientConnectorError:
except (aiohttp.client_exceptions.ClientConnectorError,
asyncio.TimeoutError):
_LOGGER.exception('Failed to connct to Sensibo servers.')
return False
raise PlatformNotReady
if devices:
async_add_devices(devices)
@@ -115,9 +117,8 @@ class SensiboClimate(ClimateDevice):
# We are working in same units as the a/c unit. Use whole degrees
# like the API supports.
return 1
else:
# Unit conversion is going on. No point to stick to specific steps.
return None
# Unit conversion is going on. No point to stick to specific steps.
return None
@property
def current_operation(self):

View File

@@ -24,6 +24,17 @@ CONST_OVERLAY_MANUAL = 'MANUAL'
# the temperature will be reset after a timespan
CONST_OVERLAY_TIMER = 'TIMER'
CONST_MODE_FAN_HIGH = 'HIGH'
CONST_MODE_FAN_MIDDLE = 'MIDDLE'
CONST_MODE_FAN_LOW = 'LOW'
FAN_MODES_LIST = {
CONST_MODE_FAN_HIGH: 'High',
CONST_MODE_FAN_MIDDLE: 'Middle',
CONST_MODE_FAN_LOW: 'Low',
CONST_MODE_OFF: 'Off',
}
OPERATION_LIST = {
CONST_OVERLAY_MANUAL: 'Manual',
CONST_OVERLAY_TIMER: 'Timer',
@@ -41,7 +52,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
zones = tado.get_zones()
except RuntimeError:
_LOGGER.error("Unable to get zone info from mytado")
return False
return
climate_devices = []
for zone in zones:
@@ -50,9 +61,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
if climate_devices:
add_devices(climate_devices, True)
return True
else:
return False
def create_climate_device(tado, hass, zone, name, zone_id):
@@ -60,9 +68,15 @@ def create_climate_device(tado, hass, zone, name, zone_id):
capabilities = tado.get_capabilities(zone_id)
unit = TEMP_CELSIUS
min_temp = float(capabilities['temperatures']['celsius']['min'])
max_temp = float(capabilities['temperatures']['celsius']['max'])
ac_mode = capabilities['type'] != 'HEATING'
ac_mode = capabilities['type'] == 'AIR_CONDITIONING'
if ac_mode:
temperatures = capabilities['HEAT']['temperatures']
else:
temperatures = capabilities['temperatures']
min_temp = float(temperatures['celsius']['min'])
max_temp = float(temperatures['celsius']['max'])
data_id = 'zone {} {}'.format(name, zone_id)
device = TadoClimate(tado,
@@ -107,7 +121,9 @@ class TadoClimate(ClimateDevice):
self._max_temp = max_temp
self._target_temp = None
self._tolerance = tolerance
self._cooling = False
self._current_fan = CONST_MODE_OFF
self._current_operation = CONST_MODE_SMART_SCHEDULE
self._overlay_mode = CONST_MODE_SMART_SCHEDULE
@@ -129,6 +145,8 @@ class TadoClimate(ClimateDevice):
@property
def current_operation(self):
"""Return current readable operation mode."""
if self._cooling:
return "Cooling"
return OPERATION_LIST.get(self._current_operation)
@property
@@ -136,6 +154,20 @@ class TadoClimate(ClimateDevice):
"""Return the list of available operation modes (readable)."""
return list(OPERATION_LIST.values())
@property
def current_fan_mode(self):
"""Return the fan setting."""
if self.ac_mode:
return FAN_MODES_LIST.get(self._current_fan)
return None
@property
def fan_list(self):
"""List of available fan modes."""
if self.ac_mode:
return list(FAN_MODES_LIST.values())
return None
@property
def temperature_unit(self):
"""Return the unit of measurement used by the platform."""
@@ -180,18 +212,16 @@ class TadoClimate(ClimateDevice):
"""Return the minimum temperature."""
if self._min_temp:
return self._min_temp
else:
# get default temp from super class
return super().min_temp
# get default temp from super class
return super().min_temp
@property
def max_temp(self):
"""Return the maximum temperature."""
if self._max_temp:
return self._max_temp
else:
# Get default temp from super class
return super().max_temp
# Get default temp from super class
return super().max_temp
def update(self):
"""Update the state of this climate device."""
@@ -205,27 +235,27 @@ class TadoClimate(ClimateDevice):
if 'sensorDataPoints' in data:
sensor_data = data['sensorDataPoints']
temperature = float(
sensor_data['insideTemperature']['celsius'])
humidity = float(
sensor_data['humidity']['percentage'])
setting = 0
unit = TEMP_CELSIUS
if 'insideTemperature' in sensor_data:
temperature = float(
sensor_data['insideTemperature']['celsius'])
self._cur_temp = self.hass.config.units.temperature(
temperature, unit)
if 'humidity' in sensor_data:
humidity = float(
sensor_data['humidity']['percentage'])
self._cur_humidity = humidity
# temperature setting will not exist when device is off
if 'temperature' in data['setting'] and \
data['setting']['temperature'] is not None:
setting = float(
data['setting']['temperature']['celsius'])
unit = TEMP_CELSIUS
self._cur_temp = self.hass.config.units.temperature(
temperature, unit)
self._target_temp = self.hass.config.units.temperature(
setting, unit)
self._cur_humidity = humidity
self._target_temp = self.hass.config.units.temperature(
setting, unit)
if 'tadoMode' in data:
mode = data['tadoMode']
@@ -235,29 +265,39 @@ class TadoClimate(ClimateDevice):
power = data['setting']['power']
if power == 'OFF':
self._current_operation = CONST_MODE_OFF
self._current_fan = CONST_MODE_OFF
# There is no overlay, the mode will always be
# "SMART_SCHEDULE"
self._overlay_mode = CONST_MODE_SMART_SCHEDULE
self._device_is_active = False
else:
self._device_is_active = True
if 'overlay' in data and data['overlay'] is not None:
overlay = True
termination = data['overlay']['termination']['type']
else:
if self._device_is_active:
overlay = False
termination = ""
overlay_data = None
termination = self._current_operation
cooling = False
fan_speed = CONST_MODE_OFF
# If you set mode manualy to off, there will be an overlay
# and a termination, but we want to see the mode "OFF"
if 'overlay' in data:
overlay_data = data['overlay']
overlay = overlay_data is not None
if overlay:
termination = overlay_data['termination']['type']
if 'setting' in overlay_data:
cooling = overlay_data['setting']['mode'] == 'COOL'
fan_speed = overlay_data['setting']['fanSpeed']
# If you set mode manualy to off, there will be an overlay
# and a termination, but we want to see the mode "OFF"
if overlay and self._device_is_active:
# There is an overlay the device is on
self._overlay_mode = termination
self._current_operation = termination
else:
# There is no overlay, the mode will always be
# "SMART_SCHEDULE"
self._overlay_mode = CONST_MODE_SMART_SCHEDULE
self._current_operation = CONST_MODE_SMART_SCHEDULE
self._cooling = cooling
self._current_fan = fan_speed
def _control_heating(self):
"""Send new target temperature to mytado."""

View File

@@ -111,8 +111,8 @@ class WinkThermostat(WinkDevice, ClimateDevice):
# This will address both possibilities
if self.wink.current_humidity() < 1:
return self.wink.current_humidity() * 100
else:
return self.wink.current_humidity()
return self.wink.current_humidity()
return None
@property
def external_temperature(self):
@@ -175,10 +175,7 @@ class WinkThermostat(WinkDevice, ClimateDevice):
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
return None
@property
def target_temperature_low(self):
@@ -206,8 +203,7 @@ class WinkThermostat(WinkDevice, ClimateDevice):
return True
elif self.wink.current_hvac_mode() == 'aux' and not self.wink.is_on():
return False
else:
return None
return None
def set_temperature(self, **kwargs):
"""Set new target temperature."""
@@ -270,9 +266,8 @@ class WinkThermostat(WinkDevice, ClimateDevice):
return STATE_ON
elif self.wink.current_fan_mode() == 'auto':
return STATE_AUTO
else:
# No Fan available so disable slider
return None
# No Fan available so disable slider
return None
@property
def fan_list(self):
@@ -451,8 +446,7 @@ class WinkAC(WinkDevice, ClimateDevice):
return SPEED_MEDIUM
elif speed <= 1.0 and speed > 0.8:
return SPEED_HIGH
else:
return STATE_UNKNOWN
return STATE_UNKNOWN
@property
def fan_list(self):

View File

@@ -154,8 +154,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
return TEMP_CELSIUS
elif self._unit == 'F':
return TEMP_FAHRENHEIT
else:
return self._unit
return self._unit
@property
def current_temperature(self):

View File

@@ -0,0 +1,134 @@
"""
Support to control a Zehnder ComfoAir Q350/450/600 ventilation unit.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/comfoconnect/
"""
import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.const import (
CONF_HOST, CONF_NAME, CONF_TOKEN, CONF_PIN, EVENT_HOMEASSISTANT_STOP)
from homeassistant.helpers import (discovery)
from homeassistant.helpers.dispatcher import (dispatcher_send)
REQUIREMENTS = ['pycomfoconnect==0.3']
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'comfoconnect'
SIGNAL_COMFOCONNECT_UPDATE_RECEIVED = 'comfoconnect_update_received'
ATTR_CURRENT_TEMPERATURE = 'current_temperature'
ATTR_CURRENT_HUMIDITY = 'current_humidity'
ATTR_OUTSIDE_TEMPERATURE = 'outside_temperature'
ATTR_OUTSIDE_HUMIDITY = 'outside_humidity'
ATTR_AIR_FLOW_SUPPLY = 'air_flow_supply'
ATTR_AIR_FLOW_EXHAUST = 'air_flow_exhaust'
CONF_USER_AGENT = 'user_agent'
DEFAULT_NAME = 'ComfoAirQ'
DEFAULT_PIN = 0
DEFAULT_TOKEN = '00000000000000000000000000000001'
DEFAULT_USER_AGENT = 'Home Assistant'
DEVICE = None
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_TOKEN, default=DEFAULT_TOKEN):
vol.Length(min=32, max=32, msg='invalid token'),
vol.Optional(CONF_USER_AGENT, default=DEFAULT_USER_AGENT): cv.string,
vol.Optional(CONF_PIN, default=DEFAULT_PIN): cv.positive_int,
}),
}, extra=vol.ALLOW_EXTRA)
def setup(hass, config):
"""Set up the ComfoConnect bridge."""
from pycomfoconnect import (Bridge)
conf = config[DOMAIN]
host = conf.get(CONF_HOST)
name = conf.get(CONF_NAME)
token = conf.get(CONF_TOKEN)
user_agent = conf.get(CONF_USER_AGENT)
pin = conf.get(CONF_PIN)
# Run discovery on the configured ip
bridges = Bridge.discover(host)
if not bridges:
_LOGGER.error("Could not connect to ComfoConnect bridge on %s", host)
return False
bridge = bridges[0]
_LOGGER.info("Bridge found: %s (%s)", bridge.uuid.hex(), bridge.host)
# Setup ComfoConnect Bridge
ccb = ComfoConnectBridge(hass, bridge, name, token, user_agent, pin)
hass.data[DOMAIN] = ccb
# Start connection with bridge
ccb.connect()
# Schedule disconnect on shutdown
def _shutdown(_event):
ccb.disconnect()
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, _shutdown)
# Load platforms
discovery.load_platform(hass, 'fan', DOMAIN, {}, config)
return True
class ComfoConnectBridge(object):
"""Representation of a ComfoConnect bridge."""
def __init__(self, hass, bridge, name, token, friendly_name, pin):
"""Initialize the ComfoConnect bridge."""
from pycomfoconnect import (ComfoConnect)
self.data = {}
self.name = name
self.hass = hass
self.comfoconnect = ComfoConnect(
bridge=bridge, local_uuid=bytes.fromhex(token),
local_devicename=friendly_name, pin=pin)
self.comfoconnect.callback_sensor = self.sensor_callback
def connect(self):
"""Connect with the bridge."""
_LOGGER.debug("Connecting with bridge")
self.comfoconnect.connect(True)
def disconnect(self):
"""Disconnect from the bridge."""
_LOGGER.debug("Disconnecting from bridge")
self.comfoconnect.disconnect()
def sensor_callback(self, var, value):
"""Callback function for sensor updates."""
_LOGGER.debug("Got value from bridge: %d = %d", var, value)
from pycomfoconnect import (
SENSOR_TEMPERATURE_EXTRACT, SENSOR_TEMPERATURE_OUTDOOR)
if var in [SENSOR_TEMPERATURE_EXTRACT, SENSOR_TEMPERATURE_OUTDOOR]:
self.data[var] = value / 10
else:
self.data[var] = value
# Notify listeners that we have received an update
dispatcher_send(self.hass, SIGNAL_COMFOCONNECT_UPDATE_RECEIVED, var)
def subscribe_sensor(self, sensor_id):
"""Subscribe for the specified sensor."""
self.comfoconnect.register_sensor(sensor_id)

View File

@@ -40,6 +40,8 @@ DEVICE_CLASSES = [
'garage', # Garage door control
]
DEVICE_CLASSES_SCHEMA = vol.All(vol.Lower, vol.In(DEVICE_CLASSES))
SUPPORT_OPEN = 1
SUPPORT_CLOSE = 2
SUPPORT_SET_POSITION = 4

View File

@@ -112,10 +112,7 @@ class CommandCover(CoverDevice):
def is_closed(self):
"""Return if the cover is closed."""
if self.current_cover_position is not None:
if self.current_cover_position > 0:
return False
else:
return True
return self.current_cover_position == 0
@property
def current_cover_position(self):

View File

@@ -79,8 +79,7 @@ class DemoCover(CoverDevice):
"""Flag supported features."""
if self._supported_features is not None:
return self._supported_features
else:
return super().supported_features
return super().supported_features
def close_cover(self, **kwargs):
"""Close the cover."""

View File

@@ -159,8 +159,7 @@ class GaradgetCover(CoverDevice):
"""Return if the cover is closed."""
if self._state == STATE_UNKNOWN:
return None
else:
return self._state == STATE_CLOSED
return self._state == STATE_CLOSED
@property
def device_class(self):

View File

@@ -20,8 +20,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
return
devices = []
for config in discovery_info[ATTR_DISCOVER_DEVICES]:
new_device = HMCover(hass, config)
for conf in discovery_info[ATTR_DISCOVER_DEVICES]:
new_device = HMCover(hass, conf)
new_device.link_homematic()
devices.append(new_device)
@@ -52,10 +52,7 @@ class HMCover(HMDevice, CoverDevice):
def is_closed(self):
"""Return if the cover is closed."""
if self.current_cover_position is not None:
if self.current_cover_position > 0:
return False
else:
return True
return self.current_cover_position == 0
def open_cover(self, **kwargs):
"""Open the cover."""

View File

@@ -0,0 +1,185 @@
"""
Support for KNX covers.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/cover.knx/
"""
import logging
import voluptuous as vol
from homeassistant.components.cover import (
CoverDevice, PLATFORM_SCHEMA, ATTR_POSITION, DEVICE_CLASSES_SCHEMA,
SUPPORT_OPEN, SUPPORT_CLOSE, SUPPORT_SET_POSITION, SUPPORT_STOP,
SUPPORT_SET_TILT_POSITION
)
from homeassistant.components.knx import (KNXConfig, KNXMultiAddressDevice)
from homeassistant.const import (CONF_NAME, CONF_DEVICE_CLASS)
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
CONF_GETPOSITION_ADDRESS = 'getposition_address'
CONF_SETPOSITION_ADDRESS = 'setposition_address'
CONF_GETANGLE_ADDRESS = 'getangle_address'
CONF_SETANGLE_ADDRESS = 'setangle_address'
CONF_STOP = 'stop_address'
CONF_UPDOWN = 'updown_address'
CONF_INVERT_POSITION = 'invert_position'
CONF_INVERT_ANGLE = 'invert_angle'
DEFAULT_NAME = 'KNX Cover'
DEPENDENCIES = ['knx']
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_UPDOWN): cv.string,
vol.Required(CONF_STOP): cv.string,
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_GETPOSITION_ADDRESS): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_SETPOSITION_ADDRESS): cv.string,
vol.Optional(CONF_INVERT_POSITION, default=False): cv.boolean,
vol.Inclusive(CONF_GETANGLE_ADDRESS, 'angle'): cv.string,
vol.Inclusive(CONF_SETANGLE_ADDRESS, 'angle'): cv.string,
vol.Optional(CONF_INVERT_ANGLE, default=False): cv.boolean,
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Create and add an entity based on the configuration."""
add_devices([KNXCover(hass, KNXConfig(config))])
class KNXCover(KNXMultiAddressDevice, CoverDevice):
"""Representation of a KNX cover. e.g. a rollershutter."""
def __init__(self, hass, config):
"""Initialize the cover."""
KNXMultiAddressDevice.__init__(
self, hass, config,
['updown', 'stop'], # required
optional=['setposition', 'getposition',
'getangle', 'setangle']
)
self._device_class = config.config.get(CONF_DEVICE_CLASS)
self._invert_position = config.config.get(CONF_INVERT_POSITION)
self._invert_angle = config.config.get(CONF_INVERT_ANGLE)
self._hass = hass
self._current_pos = None
self._target_pos = None
self._current_tilt = None
self._target_tilt = None
self._supported_features = SUPPORT_OPEN | SUPPORT_CLOSE | \
SUPPORT_SET_POSITION | SUPPORT_STOP
# Tilt is only supported, if there is a angle get and set address
if CONF_SETANGLE_ADDRESS in config.config:
_LOGGER.debug("%s: Tilt supported at addresses %s, %s",
self.name, config.config.get(CONF_SETANGLE_ADDRESS),
config.config.get(CONF_GETANGLE_ADDRESS))
self._supported_features = self._supported_features | \
SUPPORT_SET_TILT_POSITION
@property
def should_poll(self):
"""Polling is needed for the KNX cover."""
return True
@property
def supported_features(self):
"""Flag supported features."""
return self._supported_features
@property
def is_closed(self):
"""Return if the cover is closed."""
if self.current_cover_position is not None:
if self.current_cover_position > 0:
return False
else:
return True
@property
def current_cover_position(self):
"""Return current position of cover.
None is unknown, 0 is closed, 100 is fully open.
"""
return self._current_pos
@property
def target_position(self):
"""Return the position we are trying to reach: 0 - 100."""
return self._target_pos
@property
def current_cover_tilt_position(self):
"""Return current position of cover.
None is unknown, 0 is closed, 100 is fully open.
"""
return self._current_tilt
@property
def target_tilt(self):
"""Return the tilt angle (in %) we are trying to reach: 0 - 100."""
return self._target_tilt
def set_cover_position(self, **kwargs):
"""Set new target position."""
position = kwargs.get(ATTR_POSITION)
if position is None:
return
if self._invert_position:
position = 100-position
self._target_pos = position
self.set_percentage('setposition', position)
_LOGGER.debug("%s: Set target position to %d", self.name, position)
def update(self):
"""Update device state."""
super().update()
value = self.get_percentage('getposition')
if value is not None:
self._current_pos = value
if self._invert_position:
self._current_pos = 100-value
_LOGGER.debug("%s: position = %d", self.name, value)
if self._supported_features & SUPPORT_SET_TILT_POSITION:
value = self.get_percentage('getangle')
if value is not None:
self._current_tilt = value
if self._invert_angle:
self._current_tilt = 100-value
_LOGGER.debug("%s: tilt = %d", self.name, value)
def open_cover(self, **kwargs):
"""Open the cover."""
_LOGGER.debug("%s: open: updown = 0", self.name)
self.set_int_value('updown', 0)
def close_cover(self, **kwargs):
"""Close the cover."""
_LOGGER.debug("%s: open: updown = 1", self.name)
self.set_int_value('updown', 1)
def stop_cover(self, **kwargs):
"""Stop the cover movement."""
_LOGGER.debug("%s: stop: stop = 1", self.name)
self.set_int_value('stop', 1)
def set_cover_tilt_position(self, tilt_position, **kwargs):
"""Move the cover til to a specific position."""
if self._invert_angle:
tilt_position = 100-tilt_position
self._target_tilt = round(tilt_position, -1)
self.set_percentage('setangle', tilt_position)
@property
def device_class(self):
"""Return the class of this device, from component DEVICE_CLASSES."""
return self._device_class

View File

@@ -361,8 +361,7 @@ class MqttCover(CoverDevice):
position_percentage = float(offset_position) / tilt_range * 100.0
if self._tilt_invert:
return 100 - position_percentage
else:
return position_percentage
return position_percentage
def find_in_range_from_percent(self, percentage):
"""

View File

@@ -53,8 +53,7 @@ class MySensorsCover(mysensors.MySensorsDeviceEntity, CoverDevice):
set_req = self.gateway.const.SetReq
if set_req.V_DIMMER in self._values:
return self._values.get(set_req.V_DIMMER) == 0
else:
return self._values.get(set_req.V_LIGHT) == STATE_OFF
return self._values.get(set_req.V_LIGHT) == STATE_OFF
@property
def current_cover_position(self):

View File

@@ -117,8 +117,7 @@ class OpenGarageCover(CoverDevice):
"""Return if the cover is closed."""
if self._state == STATE_UNKNOWN:
return None
else:
return self._state in [STATE_CLOSED, STATE_OPENING]
return self._state in [STATE_CLOSED, STATE_OPENING]
def close_cover(self):
"""Close the cover."""

View File

@@ -0,0 +1,373 @@
"""
Support for covers which integrate with other components.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/cover.template/
"""
import asyncio
import logging
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.components.cover import (
ENTITY_ID_FORMAT, CoverDevice, PLATFORM_SCHEMA,
SUPPORT_OPEN_TILT, SUPPORT_CLOSE_TILT, SUPPORT_STOP_TILT,
SUPPORT_SET_TILT_POSITION, SUPPORT_OPEN, SUPPORT_CLOSE, SUPPORT_STOP,
SUPPORT_SET_POSITION, ATTR_POSITION, ATTR_TILT_POSITION)
from homeassistant.const import (
CONF_FRIENDLY_NAME, CONF_ENTITY_ID,
EVENT_HOMEASSISTANT_START, MATCH_ALL,
CONF_VALUE_TEMPLATE, CONF_ICON_TEMPLATE,
STATE_OPEN, STATE_CLOSED)
from homeassistant.exceptions import TemplateError
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import async_generate_entity_id
from homeassistant.helpers.event import async_track_state_change
from homeassistant.helpers.restore_state import async_get_last_state
from homeassistant.helpers.script import Script
_LOGGER = logging.getLogger(__name__)
_VALID_STATES = [STATE_OPEN, STATE_CLOSED, 'true', 'false']
CONF_COVERS = 'covers'
CONF_POSITION_TEMPLATE = 'position_template'
CONF_TILT_TEMPLATE = 'tilt_template'
OPEN_ACTION = 'open_cover'
CLOSE_ACTION = 'close_cover'
STOP_ACTION = 'stop_cover'
POSITION_ACTION = 'set_cover_position'
TILT_ACTION = 'set_cover_tilt_position'
CONF_VALUE_OR_POSITION_TEMPLATE = 'value_or_position'
CONF_OPEN_OR_CLOSE = 'open_or_close'
TILT_FEATURES = (SUPPORT_OPEN_TILT | SUPPORT_CLOSE_TILT | SUPPORT_STOP_TILT |
SUPPORT_SET_TILT_POSITION)
COVER_SCHEMA = vol.Schema({
vol.Inclusive(OPEN_ACTION, CONF_OPEN_OR_CLOSE): cv.SCRIPT_SCHEMA,
vol.Inclusive(CLOSE_ACTION, CONF_OPEN_OR_CLOSE): cv.SCRIPT_SCHEMA,
vol.Optional(STOP_ACTION): cv.SCRIPT_SCHEMA,
vol.Exclusive(CONF_POSITION_TEMPLATE,
CONF_VALUE_OR_POSITION_TEMPLATE): cv.template,
vol.Exclusive(CONF_VALUE_TEMPLATE,
CONF_VALUE_OR_POSITION_TEMPLATE): cv.template,
vol.Optional(CONF_POSITION_TEMPLATE): cv.template,
vol.Optional(CONF_TILT_TEMPLATE): cv.template,
vol.Optional(CONF_ICON_TEMPLATE): cv.template,
vol.Optional(POSITION_ACTION): cv.SCRIPT_SCHEMA,
vol.Optional(TILT_ACTION): cv.SCRIPT_SCHEMA,
vol.Optional(CONF_FRIENDLY_NAME, default=None): cv.string,
vol.Optional(CONF_ENTITY_ID): cv.entity_ids
})
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_COVERS): vol.Schema({cv.slug: COVER_SCHEMA}),
})
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up the Template cover."""
covers = []
for device, device_config in config[CONF_COVERS].items():
friendly_name = device_config.get(CONF_FRIENDLY_NAME, device)
state_template = device_config.get(CONF_VALUE_TEMPLATE)
position_template = device_config.get(CONF_POSITION_TEMPLATE)
tilt_template = device_config.get(CONF_TILT_TEMPLATE)
icon_template = device_config.get(CONF_ICON_TEMPLATE)
open_action = device_config.get(OPEN_ACTION)
close_action = device_config.get(CLOSE_ACTION)
stop_action = device_config.get(STOP_ACTION)
position_action = device_config.get(POSITION_ACTION)
tilt_action = device_config.get(TILT_ACTION)
if position_template is None and state_template is None:
_LOGGER.error('Must specify either %s' or '%s',
CONF_VALUE_TEMPLATE, CONF_VALUE_TEMPLATE)
continue
if position_action is None and open_action is None:
_LOGGER.error('Must specify at least one of %s' or '%s',
OPEN_ACTION, POSITION_ACTION)
continue
template_entity_ids = set()
if state_template is not None:
temp_ids = state_template.extract_entities()
if str(temp_ids) != MATCH_ALL:
template_entity_ids |= set(temp_ids)
if position_template is not None:
temp_ids = position_template.extract_entities()
if str(temp_ids) != MATCH_ALL:
template_entity_ids |= set(temp_ids)
if tilt_template is not None:
temp_ids = tilt_template.extract_entities()
if str(temp_ids) != MATCH_ALL:
template_entity_ids |= set(temp_ids)
if icon_template is not None:
temp_ids = icon_template.extract_entities()
if str(temp_ids) != MATCH_ALL:
template_entity_ids |= set(temp_ids)
if not template_entity_ids:
template_entity_ids = MATCH_ALL
entity_ids = device_config.get(CONF_ENTITY_ID, template_entity_ids)
covers.append(
CoverTemplate(
hass,
device, friendly_name, state_template,
position_template, tilt_template, icon_template,
open_action, close_action, stop_action,
position_action, tilt_action, entity_ids
)
)
if not covers:
_LOGGER.error("No covers added")
return False
async_add_devices(covers, True)
return True
class CoverTemplate(CoverDevice):
"""Representation of a Template cover."""
def __init__(self, hass, device_id, friendly_name, state_template,
position_template, tilt_template, icon_template,
open_action, close_action, stop_action,
position_action, tilt_action, entity_ids):
"""Initialize the Template cover."""
self.hass = hass
self.entity_id = async_generate_entity_id(
ENTITY_ID_FORMAT, device_id, hass=hass)
self._name = friendly_name
self._template = state_template
self._position_template = position_template
self._tilt_template = tilt_template
self._icon_template = icon_template
self._open_script = None
if open_action is not None:
self._open_script = Script(hass, open_action)
self._close_script = None
if close_action is not None:
self._close_script = Script(hass, close_action)
self._stop_script = None
if stop_action is not None:
self._stop_script = Script(hass, stop_action)
self._position_script = None
if position_action is not None:
self._position_script = Script(hass, position_action)
self._tilt_script = None
if tilt_action is not None:
self._tilt_script = Script(hass, tilt_action)
self._icon = None
self._position = None
self._tilt_value = None
self._entities = entity_ids
if self._template is not None:
self._template.hass = self.hass
if self._position_template is not None:
self._position_template.hass = self.hass
if self._tilt_template is not None:
self._tilt_template.hass = self.hass
if self._icon_template is not None:
self._icon_template.hass = self.hass
@asyncio.coroutine
def async_added_to_hass(self):
"""Register callbacks."""
state = yield from async_get_last_state(self.hass, self.entity_id)
if state:
self._position = 100 if state.state == STATE_OPEN else 0
@callback
def template_cover_state_listener(entity, old_state, new_state):
"""Handle target device state changes."""
self.hass.async_add_job(self.async_update_ha_state(True))
@callback
def template_cover_startup(event):
"""Update template on startup."""
async_track_state_change(
self.hass, self._entities, template_cover_state_listener)
self.hass.async_add_job(self.async_update_ha_state(True))
self.hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_START, template_cover_startup)
@property
def name(self):
"""Return the name of the cover."""
return self._name
@property
def is_closed(self):
"""Return if the cover is closed."""
return self._position == 0
@property
def current_cover_position(self):
"""Return current position of cover.
None is unknown, 0 is closed, 100 is fully open.
"""
return self._position
@property
def current_cover_tilt_position(self):
"""Return current position of cover tilt.
None is unknown, 0 is closed, 100 is fully open.
"""
return self._tilt_value
@property
def icon(self):
"""Return the icon to use in the frontend, if any."""
return self._icon
@property
def supported_features(self):
"""Flag supported features."""
supported_features = SUPPORT_OPEN | SUPPORT_CLOSE
if self._stop_script is not None:
supported_features |= SUPPORT_STOP
if self._position_script is not None:
supported_features |= SUPPORT_SET_POSITION
if self.current_cover_tilt_position is not None:
supported_features |= TILT_FEATURES
return supported_features
@property
def should_poll(self):
"""Return the polling state."""
return False
@asyncio.coroutine
def async_open_cover(self, **kwargs):
"""Move the cover up."""
if self._open_script:
self.hass.async_add_job(self._open_script.async_run())
elif self._position_script:
self.hass.async_add_job(self._position_script.async_run(
{"position": 100}))
@asyncio.coroutine
def async_close_cover(self, **kwargs):
"""Move the cover down."""
if self._close_script:
self.hass.async_add_job(self._close_script.async_run())
elif self._position_script:
self.hass.async_add_job(self._position_script.async_run(
{"position": 0}))
@asyncio.coroutine
def async_stop_cover(self, **kwargs):
"""Fire the stop action."""
if self._stop_script:
self.hass.async_add_job(self._stop_script.async_run())
@asyncio.coroutine
def async_set_cover_position(self, **kwargs):
"""Set cover position."""
self._position = kwargs[ATTR_POSITION]
self.hass.async_add_job(self._position_script.async_run(
{"position": self._position}))
@asyncio.coroutine
def async_open_cover_tilt(self, **kwargs):
"""Tilt the cover open."""
self._tilt_value = 100
self.hass.async_add_job(self._tilt_script.async_run(
{"tilt": self._tilt_value}))
@asyncio.coroutine
def async_close_cover_tilt(self, **kwargs):
"""Tilt the cover closed."""
self._tilt_value = 0
self.hass.async_add_job(self._tilt_script.async_run(
{"tilt": self._tilt_value}))
@asyncio.coroutine
def async_set_cover_tilt_position(self, **kwargs):
"""Move the cover tilt to a specific position."""
self._tilt_value = kwargs[ATTR_TILT_POSITION]
self.hass.async_add_job(self._tilt_script.async_run(
{"tilt": self._tilt_value}))
@asyncio.coroutine
def async_update(self):
"""Update the state from the template."""
if self._template is not None:
try:
state = self._template.async_render().lower()
if state in _VALID_STATES:
if state in ('true', STATE_OPEN):
self._position = 100
else:
self._position = 0
else:
_LOGGER.error(
'Received invalid cover is_on state: %s. Expected: %s',
state, ', '.join(_VALID_STATES))
self._position = None
except TemplateError as ex:
_LOGGER.error(ex)
self._position = None
if self._position_template is not None:
try:
state = float(self._position_template.async_render())
if state < 0 or state > 100:
self._position = None
_LOGGER.error("Cover position value must be"
" between 0 and 100."
" Value was: %.2f", state)
else:
self._position = state
except TemplateError as ex:
_LOGGER.error(ex)
self._position = None
except ValueError as ex:
_LOGGER.error(ex)
self._position = None
if self._tilt_template is not None:
try:
state = float(self._tilt_template.async_render())
if state < 0 or state > 100:
self._tilt_value = None
_LOGGER.error("Tilt value must be between 0 and 100."
" Value was: %.2f", state)
else:
self._tilt_value = state
except TemplateError as ex:
_LOGGER.error(ex)
self._tilt_value = None
except ValueError as ex:
_LOGGER.error(ex)
self._tilt_value = None
if self._icon_template is not None:
try:
self._icon = self._icon_template.async_render()
except TemplateError as ex:
if ex.args and ex.args[0].startswith(
"UndefinedError: 'None' has no attribute"):
# Common during HA startup - so just a warning
_LOGGER.warning('Could not render icon template %s,'
' the state is unknown.', self._name)
return
self._icon = super().icon
_LOGGER.error('Could not render icon template %s: %s',
self._name, ex)

View File

@@ -53,10 +53,7 @@ class VeraCover(VeraDevice, CoverDevice):
def is_closed(self):
"""Return if the cover is closed."""
if self.current_cover_position is not None:
if self.current_cover_position > 0:
return False
else:
return True
return self.current_cover_position == 0
def open_cover(self, **kwargs):
"""Open the cover."""

View File

@@ -29,20 +29,16 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class WinkCoverDevice(WinkDevice, CoverDevice):
"""Representation of a Wink cover device."""
def __init__(self, wink, hass):
"""Initialize the cover."""
super().__init__(wink, hass)
@asyncio.coroutine
def async_added_to_hass(self):
"""Callback when entity is added to hass."""
self.hass.data[DOMAIN]['entities']['cover'].append(self)
def close_cover(self):
def close_cover(self, **kwargs):
"""Close the shade."""
self.wink.set_state(0)
def open_cover(self):
def open_cover(self, **kwargs):
"""Open the shade."""
self.wink.set_state(1)

View File

@@ -73,8 +73,7 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice):
return None
if self.current_cover_position > 0:
return False
else:
return True
return True
@property
def current_cover_position(self):
@@ -86,8 +85,7 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice):
return 0
elif self._current_position >= 95:
return 100
else:
return self._current_position
return self._current_position
def open_cover(self, **kwargs):
"""Move the roller shutter up."""

View File

@@ -210,6 +210,7 @@ def async_setup(hass, config):
description=("Press the button on the bridge to register Philips "
"Hue with Home Assistant."),
description_image="/static/images/config_philips_hue.jpg",
fields=[{'id': 'username', 'name': 'Username'}],
submit_caption="I have pressed the button"
)
configurator_ids.append(request_id)

View File

@@ -60,20 +60,11 @@ _LEASES_REGEX = re.compile(
r'(?P<host>([^\s]+))')
# Command to get both 5GHz and 2.4GHz clients
_WL_CMD = '{ wl -i eth2 assoclist & wl -i eth1 assoclist ; }'
_WL_CMD = 'for dev in `nvram get wl_ifnames`; do wl -i $dev assoclist; done'
_WL_REGEX = re.compile(
r'\w+\s' +
r'(?P<mac>(([0-9A-F]{2}[:-]){5}([0-9A-F]{2})))')
_ARP_CMD = 'arp -n'
_ARP_REGEX = re.compile(
r'.+\s' +
r'\((?P<ip>([0-9]{1,3}[\.]){3}[0-9]{1,3})\)\s' +
r'.+\s' +
r'(?P<mac>(([0-9a-f]{2}[:-]){5}([0-9a-f]{2})))' +
r'\s' +
r'.*')
_IP_NEIGH_CMD = 'ip neigh'
_IP_NEIGH_REGEX = re.compile(
r'(?P<ip>([0-9]{1,3}[\.]){3}[0-9]{1,3}|'
@@ -84,15 +75,6 @@ _IP_NEIGH_REGEX = re.compile(
r'\s?(router)?'
r'(?P<status>(\w+))')
_NVRAM_CMD = 'nvram get client_info_tmp'
_NVRAM_REGEX = re.compile(
r'.*>.*>' +
r'(?P<ip>([0-9]{1,3}[\.]){3}[0-9]{1,3})' +
r'>' +
r'(?P<mac>(([0-9a-fA-F]{2}[:-]){5}([0-9a-fA-F]{2})))' +
r'>' +
r'.*')
# pylint: disable=unused-argument
def get_scanner(hass, config):
@@ -102,7 +84,7 @@ def get_scanner(hass, config):
return scanner if scanner.success_init else None
AsusWrtResult = namedtuple('AsusWrtResult', 'neighbors leases arp nvram')
AsusWrtResult = namedtuple('AsusWrtResult', 'neighbors leases')
class AsusWrtDeviceScanner(DeviceScanner):
@@ -173,7 +155,7 @@ class AsusWrtDeviceScanner(DeviceScanner):
return False
with self.lock:
_LOGGER.info('Checking ARP')
_LOGGER.info('Checking Devices')
data = self.get_asuswrt_data()
if not data:
return False
@@ -182,7 +164,7 @@ class AsusWrtDeviceScanner(DeviceScanner):
client['status'] == 'REACHABLE' or
client['status'] == 'DELAY' or
client['status'] == 'STALE' or
client['status'] == 'IN_NVRAM']
client['status'] == 'IN_ASSOCLIST']
self.last_results = active_clients
return True
@@ -204,41 +186,12 @@ class AsusWrtDeviceScanner(DeviceScanner):
host = ''
# match mac addresses to IP addresses in ARP table
for arp in result.arp:
if match.group('mac').lower() in \
arp.decode('utf-8').lower():
arp_match = _ARP_REGEX.search(
arp.decode('utf-8').lower())
if not arp_match:
_LOGGER.warning("Could not parse arp row: %s", arp)
continue
devices[arp_match.group('ip')] = {
'host': host,
'status': '',
'ip': arp_match.group('ip'),
'mac': match.group('mac').upper(),
}
# match mac addresses to IP addresses in NVRAM table
for nvr in result.nvram:
if match.group('mac').upper() in nvr.decode('utf-8'):
nvram_match = _NVRAM_REGEX.search(nvr.decode('utf-8'))
if not nvram_match:
_LOGGER.warning("Could not parse nvr row: %s", nvr)
continue
# skip current check if already in ARP table
if nvram_match.group('ip') in devices.keys():
continue
devices[nvram_match.group('ip')] = {
'host': host,
'status': 'IN_NVRAM',
'ip': nvram_match.group('ip'),
'mac': match.group('mac').upper(),
}
devices[match.group('mac').upper()] = {
'host': host,
'status': 'IN_ASSOCLIST',
'ip': '',
'mac': match.group('mac').upper(),
}
else:
for lease in result.leases:
@@ -256,20 +209,23 @@ class AsusWrtDeviceScanner(DeviceScanner):
if host == '*':
host = ''
devices[match.group('ip')] = {
devices[match.group('mac')] = {
'host': host,
'status': '',
'ip': match.group('ip'),
'mac': match.group('mac').upper(),
}
for neighbor in result.neighbors:
match = _IP_NEIGH_REGEX.search(neighbor.decode('utf-8'))
if not match:
_LOGGER.warning("Could not parse neighbor row: %s", neighbor)
continue
if match.group('ip') in devices:
devices[match.group('ip')]['status'] = match.group('status')
for neighbor in result.neighbors:
match = _IP_NEIGH_REGEX.search(neighbor.decode('utf-8'))
if not match:
_LOGGER.warning("Could not parse neighbor row: %s",
neighbor)
continue
if match.group('mac') in devices:
devices[match.group('mac')]['status'] = (
match.group('status'))
return devices
@@ -317,27 +273,19 @@ class SshConnection(_Connection):
try:
if not self.connected:
self.connect()
self._ssh.sendline(_IP_NEIGH_CMD)
self._ssh.prompt()
neighbors = self._ssh.before.split(b'\n')[1:-1]
if self._ap:
self._ssh.sendline(_ARP_CMD)
self._ssh.prompt()
arp_result = self._ssh.before.split(b'\n')[1:-1]
neighbors = ['']
self._ssh.sendline(_WL_CMD)
self._ssh.prompt()
leases_result = self._ssh.before.split(b'\n')[1:-1]
self._ssh.sendline(_NVRAM_CMD)
self._ssh.prompt()
nvram_result = self._ssh.before.split(b'\n')[1].split(b'<')[1:]
else:
arp_result = ['']
nvram_result = ['']
self._ssh.sendline(_IP_NEIGH_CMD)
self._ssh.prompt()
neighbors = self._ssh.before.split(b'\n')[1:-1]
self._ssh.sendline(_LEASES_CMD)
self._ssh.prompt()
leases_result = self._ssh.before.split(b'\n')[1:-1]
return AsusWrtResult(neighbors, leases_result, arp_result,
nvram_result)
return AsusWrtResult(neighbors, leases_result)
except exceptions.EOF as err:
_LOGGER.error("Connection refused. SSH enabled?")
self.disconnect()
@@ -407,23 +355,14 @@ class TelnetConnection(_Connection):
neighbors = (self._telnet.read_until(self._prompt_string).
split(b'\n')[1:-1])
if self._ap:
self._telnet.write('{}\n'.format(_ARP_CMD).encode('ascii'))
arp_result = (self._telnet.read_until(self._prompt_string).
split(b'\n')[1:-1])
self._telnet.write('{}\n'.format(_WL_CMD).encode('ascii'))
leases_result = (self._telnet.read_until(self._prompt_string).
split(b'\n')[1:-1])
self._telnet.write('{}\n'.format(_NVRAM_CMD).encode('ascii'))
nvram_result = (self._telnet.read_until(self._prompt_string).
split(b'\n')[1].split(b'<')[1:])
else:
arp_result = ['']
nvram_result = ['']
self._telnet.write('{}\n'.format(_LEASES_CMD).encode('ascii'))
leases_result = (self._telnet.read_until(self._prompt_string).
split(b'\n')[1:-1])
return AsusWrtResult(neighbors, leases_result, arp_result,
nvram_result)
return AsusWrtResult(neighbors, leases_result)
except EOFError:
_LOGGER.error("Unexpected response from router")
self.disconnect()

View File

@@ -52,8 +52,7 @@ class BboxDeviceScanner(DeviceScanner):
if filter_named:
return filter_named[0]
else:
return None
return None
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):

View File

@@ -87,21 +87,20 @@ class CiscoDeviceScanner(DeviceScanner):
lines_result = lines_result[2:]
for line in lines_result:
if len(line.split()) is 6:
parts = line.split()
if len(parts) != 6:
continue
parts = line.split()
if len(parts) != 6:
continue
# ['Internet', '10.10.11.1', '-', '0027.d32d.0123', 'ARPA',
# 'GigabitEthernet0']
age = parts[2]
hw_addr = parts[3]
# ['Internet', '10.10.11.1', '-', '0027.d32d.0123', 'ARPA',
# 'GigabitEthernet0']
age = parts[2]
hw_addr = parts[3]
if age != "-":
mac = _parse_cisco_mac_address(hw_addr)
age = int(age)
if age < 1:
last_results.append(mac)
if age != "-":
mac = _parse_cisco_mac_address(hw_addr)
age = int(age)
if age < 1:
last_results.append(mac)
self.last_results = last_results
return True

View File

@@ -0,0 +1,113 @@
"""Support for Linksys Smart Wifi routers."""
import logging
import threading
from datetime import timedelta
import requests
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST
from homeassistant.util import Throttle
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
DEFAULT_TIMEOUT = 10
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
})
def get_scanner(hass, config):
"""Validate the configuration and return a Linksys AP scanner."""
try:
return LinksysSmartWifiDeviceScanner(config[DOMAIN])
except ConnectionError:
return None
class LinksysSmartWifiDeviceScanner(DeviceScanner):
"""This class queries a Linksys Access Point."""
def __init__(self, config):
"""Initialize the scanner."""
self.host = config[CONF_HOST]
self.lock = threading.Lock()
self.last_results = {}
# Check if the access point is accessible
response = self._make_request()
if not response.status_code == 200:
raise ConnectionError("Cannot connect to Linksys Access Point")
def scan_devices(self):
"""Scan for new devices and return a list with device IDs (MACs)."""
self._update_info()
return self.last_results.keys()
def get_device_name(self, mac):
"""Return the name (if known) of the device."""
return self.last_results.get(mac)
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
"""Check for connected devices."""
with self.lock:
_LOGGER.info("Checking Linksys Smart Wifi")
self.last_results = {}
response = self._make_request()
if response.status_code != 200:
_LOGGER.error(
"Got HTTP status code %d when getting device list",
response.status_code)
return False
try:
data = response.json()
result = data["responses"][0]
devices = result["output"]["devices"]
for device in devices:
macs = device["knownMACAddresses"]
if not macs:
_LOGGER.warning(
"Skipping device without known MAC address")
continue
mac = macs[-1]
connections = device["connections"]
if not connections:
_LOGGER.debug("Device %s is not connected", mac)
continue
name = None
for prop in device["properties"]:
if prop["name"] == "userDeviceName":
name = prop["value"]
if not name:
name = device.get("friendlyName", device["deviceID"])
_LOGGER.debug("Device %s is connected", mac)
self.last_results[mac] = name
except (KeyError, IndexError):
_LOGGER.exception("Router returned unexpected response")
return False
return True
def _make_request(self):
# Weirdly enough, this doesn't seem to require authentication
data = [{
"request": {
"sinceRevision": 0
},
"action": "http://linksys.com/jnap/devicelist/GetDevices"
}]
headers = {"X-JNAP-Action": "http://linksys.com/jnap/core/Transaction"}
return requests.post('http://{}/JNAP/'.format(self.host),
timeout=DEFAULT_TIMEOUT,
headers=headers,
json=data)

View File

@@ -94,21 +94,20 @@ class LocativeView(HomeAssistantView):
partial(self.see, dev_id=device,
location_name=location_name, gps=gps_location))
return 'Setting location to not home'
else:
# Ignore the message if it is telling us to exit a zone that we
# aren't currently in. This occurs when a zone is entered
# before the previous zone was exited. The enter message will
# be sent first, then the exit message will be sent second.
return 'Ignoring exit from {} (already in {})'.format(
location_name, current_state)
# Ignore the message if it is telling us to exit a zone that we
# aren't currently in. This occurs when a zone is entered
# before the previous zone was exited. The enter message will
# be sent first, then the exit message will be sent second.
return 'Ignoring exit from {} (already in {})'.format(
location_name, current_state)
elif direction == 'test':
# In the app, a test message can be sent. Just return something to
# the user to let them know that it works.
return 'Received test message.'
else:
_LOGGER.error('Received unidentified message from Locative: %s',
direction)
return ('Received unidentified message: {}'.format(direction),
HTTP_UNPROCESSABLE_ENTITY)
_LOGGER.error('Received unidentified message from Locative: %s',
direction)
return ('Received unidentified message: {}'.format(direction),
HTTP_UNPROCESSABLE_ENTITY)

View File

@@ -158,6 +158,11 @@ class MikrotikScanner(DeviceScanner):
for device in devices
}
else:
self.last_results = mac_names
self.last_results = {
device.get('mac-address'):
mac_names.get(device.get('mac-address'))
for device in device_names
if device.get('active-address')
}
return True

View File

@@ -95,8 +95,7 @@ class NmapDeviceScanner(DeviceScanner):
if filter_named:
return filter_named[0]
else:
return None
return None
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):

View File

@@ -21,7 +21,7 @@ from homeassistant.components import zone as zone_comp
from homeassistant.components.device_tracker import PLATFORM_SCHEMA
DEPENDENCIES = ['mqtt']
REQUIREMENTS = ['libnacl==1.5.0']
REQUIREMENTS = ['libnacl==1.5.1']
_LOGGER = logging.getLogger(__name__)
@@ -131,8 +131,7 @@ def async_setup_scanner(hass, config, async_see, discovery_info=None):
plaintext_payload = decrypt_payload(topic, data['data'])
if plaintext_payload is None:
return None
else:
return validate_payload(topic, plaintext_payload, data_type)
return validate_payload(topic, plaintext_payload, data_type)
if not isinstance(data, dict) or data.get('_type') != data_type:
_LOGGER.debug("Skipping %s update for following data "

View File

@@ -90,8 +90,7 @@ class TadoDeviceScanner(DeviceScanner):
if filter_named:
return filter_named[0]
else:
return None
return None
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):

View File

@@ -73,8 +73,8 @@ class TomatoDeviceScanner(DeviceScanner):
if not filter_named or not filter_named[0]:
return None
else:
return filter_named[0]
return filter_named[0]
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_tomato_info(self):

View File

@@ -9,7 +9,7 @@ import hashlib
import logging
import re
import threading
from datetime import timedelta
from datetime import timedelta, datetime
import requests
import voluptuous as vol
@@ -33,8 +33,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
def get_scanner(hass, config):
"""Validate the configuration and return a TP-Link scanner."""
for cls in [Tplink4DeviceScanner, Tplink3DeviceScanner,
Tplink2DeviceScanner, TplinkDeviceScanner]:
for cls in [Tplink5DeviceScanner, Tplink4DeviceScanner,
Tplink3DeviceScanner, Tplink2DeviceScanner,
TplinkDeviceScanner]:
scanner = cls(config[DOMAIN])
if scanner.success_init:
return scanner
@@ -234,10 +235,9 @@ class Tplink3DeviceScanner(TplinkDeviceScanner):
self.stok = ''
self.sysauth = ''
return False
else:
_LOGGER.error(
"An unknown error happened while fetching data")
return False
_LOGGER.error(
"An unknown error happened while fetching data")
return False
except ValueError:
_LOGGER.error("Router didn't respond with JSON. "
"Check if credentials are correct")
@@ -350,3 +350,83 @@ class Tplink4DeviceScanner(TplinkDeviceScanner):
self.last_results = [mac.replace("-", ":") for mac in mac_results]
return True
class Tplink5DeviceScanner(TplinkDeviceScanner):
"""This class queries a TP-Link EAP-225 AP with newer TP-Link FW."""
def scan_devices(self):
"""Scan for new devices and return a list with found MAC IDs."""
self._update_info()
return self.last_results.keys()
# pylint: disable=no-self-use
def get_device_name(self, device):
"""Get firmware doesn't save the name of the wireless device."""
return None
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
"""Ensure the information from the TP-Link AP is up to date.
Return boolean if scanning successful.
"""
with self.lock:
_LOGGER.info("Loading wireless clients...")
base_url = 'http://{}'.format(self.host)
header = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12;"
" rv:53.0) Gecko/20100101 Firefox/53.0",
"Accept": "application/json, text/javascript, */*; q=0.01",
"Accept-Language": "Accept-Language: en-US,en;q=0.5",
"Accept-Encoding": "gzip, deflate",
"Content-Type": "application/x-www-form-urlencoded; "
"charset=UTF-8",
"X-Requested-With": "XMLHttpRequest",
"Referer": "http://" + self.host + "/",
"Connection": "keep-alive",
"Pragma": "no-cache",
"Cache-Control": "no-cache"
}
password_md5 = hashlib.md5(self.password).hexdigest().upper()
# create a session to handle cookie easier
session = requests.session()
session.get(base_url, headers=header)
login_data = {"username": self.username, "password": password_md5}
session.post(base_url, login_data, headers=header)
# a timestamp is required to be sent as get parameter
timestamp = int(datetime.now().timestamp() * 1e3)
client_list_url = '{}/data/monitor.client.client.json'.format(
base_url)
get_params = {
'operation': 'load',
'_': timestamp
}
response = session.get(client_list_url,
headers=header,
params=get_params)
session.close()
try:
list_of_devices = response.json()
except ValueError:
_LOGGER.error("AP didn't respond with JSON. "
"Check if credentials are correct.")
return False
if list_of_devices:
self.last_results = {
device['MAC'].replace('-', ':'): device['DeviceName']
for device in list_of_devices['data']
}
return True
return False

32
homeassistant/components/device_tracker/ubus.py Executable file → Normal file
View File

@@ -18,6 +18,7 @@ from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.util import Throttle
from homeassistant.exceptions import HomeAssistantError
# Return cached results if last scan was less then this time ago.
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
@@ -38,6 +39,23 @@ def get_scanner(hass, config):
return scanner if scanner.success_init else None
def _refresh_on_acccess_denied(func):
"""If remove rebooted, it lost our session so rebuld one and try again."""
def decorator(self, *args, **kwargs):
"""Wrapper function to refresh session_id on PermissionError."""
try:
return func(self, *args, **kwargs)
except PermissionError:
_LOGGER.warning("Invalid session detected." +
" Tryign to refresh session_id and re-run the rpc")
self.session_id = _get_session_id(self.url, self.username,
self.password)
return func(self, *args, **kwargs)
return decorator
class UbusDeviceScanner(DeviceScanner):
"""
This class queries a wireless router running OpenWrt firmware.
@@ -48,14 +66,16 @@ class UbusDeviceScanner(DeviceScanner):
def __init__(self, config):
"""Initialize the scanner."""
host = config[CONF_HOST]
username, password = config[CONF_USERNAME], config[CONF_PASSWORD]
self.username = config[CONF_USERNAME]
self.password = config[CONF_PASSWORD]
self.parse_api_pattern = re.compile(r"(?P<param>\w*) = (?P<value>.*);")
self.lock = threading.Lock()
self.last_results = {}
self.url = 'http://{}/ubus'.format(host)
self.session_id = _get_session_id(self.url, username, password)
self.session_id = _get_session_id(self.url, self.username,
self.password)
self.hostapd = []
self.leasefile = None
self.mac2name = None
@@ -66,6 +86,7 @@ class UbusDeviceScanner(DeviceScanner):
self._update_info()
return self.last_results
@_refresh_on_acccess_denied
def get_device_name(self, device):
"""Return the name of the given device or None if we don't know."""
with self.lock:
@@ -95,6 +116,7 @@ class UbusDeviceScanner(DeviceScanner):
return self.mac2name.get(device.upper(), None)
@Throttle(MIN_TIME_BETWEEN_SCANS)
@_refresh_on_acccess_denied
def _update_info(self):
"""Ensure the information from the Luci router is up to date.
@@ -142,6 +164,12 @@ def _req_json_rpc(url, session_id, rpcmethod, subsystem, method, **params):
if res.status_code == 200:
response = res.json()
if 'error' in response:
if 'message' in response['error'] and \
response['error']['message'] == "Access denied":
raise PermissionError(response['error']['message'])
else:
raise HomeAssistantError(response['error']['message'])
if rpcmethod == "call":
try:

View File

@@ -12,11 +12,10 @@ import aiohttp
import async_timeout
import voluptuous as vol
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST, CONF_PASSWORD
from homeassistant.const import CONF_HOST
from homeassistant.helpers.aiohttp_client import async_get_clientsession
@@ -25,12 +24,9 @@ _LOGGER = logging.getLogger(__name__)
DEFAULT_IP = '192.168.0.1'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_HOST, default=DEFAULT_IP): cv.string,
})
CMD_LOGIN = 15
CMD_LOGOUT = 16
CMD_DEVICES = 123
@@ -38,7 +34,7 @@ CMD_DEVICES = 123
def async_get_scanner(hass, config):
"""Return the UPC device scanner."""
scanner = UPCDeviceScanner(hass, config[DOMAIN])
success_init = yield from scanner.async_login()
success_init = yield from scanner.async_initialize_token()
return scanner if success_init else None
@@ -50,7 +46,6 @@ class UPCDeviceScanner(DeviceScanner):
"""Initialize the scanner."""
self.hass = hass
self.host = config[CONF_HOST]
self.password = config[CONF_PASSWORD]
self.data = {}
self.token = None
@@ -65,21 +60,12 @@ class UPCDeviceScanner(DeviceScanner):
self.websession = async_get_clientsession(hass)
@asyncio.coroutine
def async_logout(event):
"""Logout from upc connect box."""
yield from self._async_ws_function(CMD_LOGOUT)
self.token = None
hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_STOP, async_logout)
@asyncio.coroutine
def async_scan_devices(self):
"""Scan for new devices and return a list with found device IDs."""
if self.token is None:
reconnect = yield from self.async_login()
if not reconnect:
token_initialized = yield from self.async_initialize_token()
if not token_initialized:
_LOGGER.error("Not connected to %s", self.host)
return []
@@ -95,55 +81,42 @@ class UPCDeviceScanner(DeviceScanner):
@asyncio.coroutine
def async_get_device_name(self, device):
"""Ge the firmware doesn't save the name of the wireless device."""
"""The firmware doesn't save the name of the wireless device."""
return None
@asyncio.coroutine
def async_login(self):
"""Login into firmware and get first token."""
def async_initialize_token(self):
"""Get first token."""
try:
# get first token
with async_timeout.timeout(10, loop=self.hass.loop):
response = yield from self.websession.get(
"http://{}/common_page/login.html".format(self.host)
"http://{}/common_page/login.html".format(self.host),
headers=self.headers
)
yield from response.text()
self.token = response.cookies['sessionToken'].value
# login
data = yield from self._async_ws_function(CMD_LOGIN, {
'Username': 'NULL',
'Password': self.password,
})
# Successful?
return data is not None
return True
except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.error("Can not load login page from %s", self.host)
return False
@asyncio.coroutine
def _async_ws_function(self, function, additional_form=None):
def _async_ws_function(self, function):
"""Execute a command on UPC firmware webservice."""
form_data = {
'token': self.token,
'fun': function
}
if additional_form:
form_data.update(additional_form)
redirects = function != CMD_DEVICES
try:
with async_timeout.timeout(10, loop=self.hass.loop):
# The 'token' parameter has to be first, and 'fun' second
# or the UPC firmware will return an error
response = yield from self.websession.post(
"http://{}/xml/getter.xml".format(self.host),
data=form_data,
data="token={}&fun={}".format(self.token, function),
headers=self.headers,
allow_redirects=redirects
allow_redirects=False
)
# error?

View File

@@ -101,10 +101,10 @@ class XiaomiDeviceScanner(DeviceScanner):
result = _retrieve_list(self.host, self.token)
if result:
return result
else:
_LOGGER.info("Refreshing token and retrying device list refresh")
self.token = _get_token(self.host, self.username, self.password)
return _retrieve_list(self.host, self.token)
_LOGGER.info("Refreshing token and retrying device list refresh")
self.token = _get_token(self.host, self.username, self.password)
return _retrieve_list(self.host, self.token)
def _store_result(self, result):
"""Extract and store the device list in self.last_results."""

View File

@@ -13,7 +13,7 @@ from homeassistant.const import CONF_ACCESS_TOKEN
from homeassistant.util import Throttle
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['python-digitalocean==1.11']
REQUIREMENTS = ['python-digitalocean==1.12']
_LOGGER = logging.getLogger(__name__)
@@ -29,7 +29,7 @@ ATTR_VCPUS = 'vcpus'
CONF_DROPLETS = 'droplets'
DIGITAL_OCEAN = None
DATA_DIGITAL_OCEAN = 'data_do'
DIGITAL_OCEAN_PLATFORMS = ['switch', 'binary_sensor']
DOMAIN = 'digital_ocean'
@@ -47,13 +47,14 @@ def setup(hass, config):
conf = config[DOMAIN]
access_token = conf.get(CONF_ACCESS_TOKEN)
global DIGITAL_OCEAN
DIGITAL_OCEAN = DigitalOcean(access_token)
digital = DigitalOcean(access_token)
if not DIGITAL_OCEAN.manager.get_account():
if not digital.manager.get_account():
_LOGGER.error("No Digital Ocean account found for the given API Token")
return False
hass.data[DATA_DIGITAL_OCEAN] = digital
return True

View File

@@ -32,6 +32,7 @@ SERVICE_HASS_IOS_APP = 'hass_ios'
SERVICE_IKEA_TRADFRI = 'ikea_tradfri'
SERVICE_HASSIO = 'hassio'
SERVICE_AXIS = 'axis'
SERVICE_APPLE_TV = 'apple_tv'
SERVICE_HANDLERS = {
SERVICE_HASS_IOS_APP: ('ios', None),
@@ -40,6 +41,7 @@ SERVICE_HANDLERS = {
SERVICE_IKEA_TRADFRI: ('tradfri', None),
SERVICE_HASSIO: ('hassio', None),
SERVICE_AXIS: ('axis', None),
SERVICE_APPLE_TV: ('apple_tv', None),
'philips_hue': ('light', 'hue'),
'google_cast': ('media_player', 'cast'),
'panasonic_viera': ('media_player', 'panasonic_viera'),
@@ -52,9 +54,9 @@ SERVICE_HANDLERS = {
'denonavr': ('media_player', 'denonavr'),
'samsung_tv': ('media_player', 'samsungtv'),
'yeelight': ('light', 'yeelight'),
'apple_tv': ('media_player', 'apple_tv'),
'frontier_silicon': ('media_player', 'frontier_silicon'),
'openhome': ('media_player', 'openhome'),
'harmony': ('remote', 'harmony'),
'bose_soundtouch': ('media_player', 'soundtouch'),
}

View File

@@ -1,4 +1,8 @@
"""Parent component for Dyson Pure Cool Link devices."""
"""Parent component for Dyson Pure Cool Link devices.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/dyson/
"""
import logging
@@ -9,7 +13,7 @@ from homeassistant.helpers import discovery
from homeassistant.const import CONF_USERNAME, CONF_PASSWORD, CONF_TIMEOUT, \
CONF_DEVICES
REQUIREMENTS = ['libpurecoollink==0.1.5']
REQUIREMENTS = ['libpurecoollink==0.2.0']
_LOGGER = logging.getLogger(__name__)

View File

@@ -0,0 +1,118 @@
"""
Platform to control a Zehnder ComfoAir Q350/450/600 ventilation unit.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/fan.comfoconnect/
"""
import logging
from homeassistant.components.comfoconnect import (
DOMAIN, ComfoConnectBridge, SIGNAL_COMFOCONNECT_UPDATE_RECEIVED)
from homeassistant.components.fan import (
FanEntity, SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH,
SUPPORT_SET_SPEED)
from homeassistant.const import STATE_UNKNOWN
from homeassistant.helpers.dispatcher import (dispatcher_connect)
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['comfoconnect']
SPEED_MAPPING = {
0: SPEED_OFF,
1: SPEED_LOW,
2: SPEED_MEDIUM,
3: SPEED_HIGH
}
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the ComfoConnect fan platform."""
ccb = hass.data[DOMAIN]
add_devices([ComfoConnectFan(hass, name=ccb.name, ccb=ccb)], True)
return
class ComfoConnectFan(FanEntity):
"""Representation of the ComfoConnect fan platform."""
def __init__(self, hass, name, ccb: ComfoConnectBridge):
"""Initialize the ComfoConnect fan."""
from pycomfoconnect import SENSOR_FAN_SPEED_MODE
self._ccb = ccb
self._name = name
# Ask the bridge to keep us updated
self._ccb.comfoconnect.register_sensor(SENSOR_FAN_SPEED_MODE)
def _handle_update(var):
if var == SENSOR_FAN_SPEED_MODE:
_LOGGER.debug("Dispatcher update for %s", var)
self.schedule_update_ha_state()
# Register for dispatcher updates
dispatcher_connect(
hass, SIGNAL_COMFOCONNECT_UPDATE_RECEIVED, _handle_update)
@property
def name(self):
"""Return the name of the fan."""
return self._name
@property
def icon(self):
"""Return the icon to use in the frontend."""
return 'mdi:air-conditioner'
@property
def supported_features(self) -> int:
"""Flag supported features."""
return SUPPORT_SET_SPEED
@property
def speed(self):
"""Return the current fan mode."""
from pycomfoconnect import (SENSOR_FAN_SPEED_MODE)
try:
speed = self._ccb.data[SENSOR_FAN_SPEED_MODE]
return SPEED_MAPPING[speed]
except KeyError:
return STATE_UNKNOWN
@property
def speed_list(self):
"""List of available fan modes."""
return [SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]
def turn_on(self, speed: str=None, **kwargs) -> None:
"""Turn on the fan."""
if speed is None:
speed = SPEED_LOW
self.set_speed(speed)
def turn_off(self) -> None:
"""Turn off the fan (to away)."""
self.set_speed(SPEED_OFF)
def set_speed(self, mode):
"""Set fan speed."""
_LOGGER.debug('Changing fan mode to %s.', mode)
from pycomfoconnect import (
CMD_FAN_MODE_AWAY, CMD_FAN_MODE_LOW, CMD_FAN_MODE_MEDIUM,
CMD_FAN_MODE_HIGH)
if mode == SPEED_OFF:
self._ccb.comfoconnect.cmd_rmi_request(CMD_FAN_MODE_AWAY)
elif mode == SPEED_LOW:
self._ccb.comfoconnect.cmd_rmi_request(CMD_FAN_MODE_LOW)
elif mode == SPEED_MEDIUM:
self._ccb.comfoconnect.cmd_rmi_request(CMD_FAN_MODE_MEDIUM)
elif mode == SPEED_HIGH:
self._ccb.comfoconnect.cmd_rmi_request(CMD_FAN_MODE_HIGH)
# Update current mode
self.schedule_update_ha_state()

View File

@@ -1,4 +1,8 @@
"""Support for Dyson Pure Cool link fan."""
"""Support for Dyson Pure Cool link fan.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/fan.dyson/
"""
import logging
import asyncio
from os import path
@@ -75,13 +79,15 @@ class DysonPureCoolLinkDevice(FanEntity):
def async_added_to_hass(self):
"""Callback when entity is added to hass."""
self.hass.async_add_job(
self._device.add_message_listener(self.on_message))
self._device.add_message_listener, self.on_message)
def on_message(self, message):
"""Called when new messages received from the fan."""
_LOGGER.debug(
"Message received for fan device %s : %s", self.name, message)
self.schedule_update_ha_state()
from libpurecoollink.dyson import DysonState
if isinstance(message, DysonState):
_LOGGER.debug("Message received for fan device %s : %s", self.name,
message)
self.schedule_update_ha_state()
@property
def should_poll(self):
@@ -157,8 +163,7 @@ class DysonPureCoolLinkDevice(FanEntity):
from libpurecoollink.const import FanSpeed
if self._device.state.speed == FanSpeed.FAN_SPEED_AUTO.value:
return self._device.state.speed
else:
return int(self._device.state.speed)
return int(self._device.state.speed)
return None
@property

View File

@@ -0,0 +1,195 @@
"""
Support for Insteon fans via local hub control.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/fan.insteon_local/
"""
import json
import logging
import os
from datetime import timedelta
from homeassistant.components.fan import (
ATTR_SPEED, SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH,
SUPPORT_SET_SPEED, FanEntity)
from homeassistant.helpers.entity import ToggleEntity
from homeassistant.loader import get_component
import homeassistant.util as util
_CONFIGURING = {}
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['insteon_local']
DOMAIN = 'fan'
INSTEON_LOCAL_FANS_CONF = 'insteon_local_fans.conf'
MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(milliseconds=100)
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
SUPPORT_INSTEON_LOCAL = SUPPORT_SET_SPEED
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Insteon local fan platform."""
insteonhub = hass.data['insteon_local']
conf_fans = config_from_file(hass.config.path(INSTEON_LOCAL_FANS_CONF))
if conf_fans:
for device_id in conf_fans:
setup_fan(device_id, conf_fans[device_id], insteonhub, hass,
add_devices)
else:
linked = insteonhub.get_linked()
for device_id in linked:
if (linked[device_id]['cat_type'] == 'dimmer' and
linked[device_id]['sku'] == '2475F' and
device_id not in conf_fans):
request_configuration(device_id,
insteonhub,
linked[device_id]['model_name'] + ' ' +
linked[device_id]['sku'],
hass, add_devices)
def request_configuration(device_id, insteonhub, model, hass,
add_devices_callback):
"""Request configuration steps from the user."""
configurator = get_component('configurator')
# We got an error if this method is called while we are configuring
if device_id in _CONFIGURING:
configurator.notify_errors(
_CONFIGURING[device_id], 'Failed to register, please try again.')
return
def insteon_fan_config_callback(data):
"""The actions to do when our configuration callback is called."""
setup_fan(device_id, data.get('name'), insteonhub, hass,
add_devices_callback)
_CONFIGURING[device_id] = configurator.request_config(
hass, 'Insteon ' + model + ' addr: ' + device_id,
insteon_fan_config_callback,
description=('Enter a name for ' + model + ' Fan addr: ' + device_id),
entity_picture='/static/images/config_insteon.png',
submit_caption='Confirm',
fields=[{'id': 'name', 'name': 'Name', 'type': ''}]
)
def setup_fan(device_id, name, insteonhub, hass, add_devices_callback):
"""Set up the fan."""
if device_id in _CONFIGURING:
request_id = _CONFIGURING.pop(device_id)
configurator = get_component('configurator')
configurator.request_done(request_id)
_LOGGER.info("Device configuration done!")
conf_fans = config_from_file(hass.config.path(INSTEON_LOCAL_FANS_CONF))
if device_id not in conf_fans:
conf_fans[device_id] = name
if not config_from_file(
hass.config.path(INSTEON_LOCAL_FANS_CONF),
conf_fans):
_LOGGER.error("Failed to save configuration file")
device = insteonhub.fan(device_id)
add_devices_callback([InsteonLocalFanDevice(device, name)])
def config_from_file(filename, config=None):
"""Small configuration file management function."""
if config:
# We're writing configuration
try:
with open(filename, 'w') as fdesc:
fdesc.write(json.dumps(config))
except IOError as error:
_LOGGER.error('Saving config file failed: %s', error)
return False
return True
else:
# We're reading config
if os.path.isfile(filename):
try:
with open(filename, 'r') as fdesc:
return json.loads(fdesc.read())
except IOError as error:
_LOGGER.error("Reading configuration file failed: %s", error)
# This won't work yet
return False
else:
return {}
class InsteonLocalFanDevice(FanEntity):
"""An abstract Class for an Insteon node."""
def __init__(self, node, name):
"""Initialize the device."""
self.node = node
self.node.deviceName = name
self._speed = SPEED_OFF
@property
def name(self):
"""Return the the name of the node."""
return self.node.deviceName
@property
def unique_id(self):
"""Return the ID of this Insteon node."""
return 'insteon_local_{}_fan'.format(self.node.device_id)
@property
def speed(self) -> str:
"""Return the current speed."""
return self._speed
@property
def speed_list(self: ToggleEntity) -> list:
"""Get the list of available speeds."""
return [SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]
@util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS)
def update(self):
"""Update state of the fan."""
resp = self.node.status()
if 'cmd2' in resp:
if resp['cmd2'] == '00':
self._speed = SPEED_OFF
elif resp['cmd2'] == '55':
self._speed = SPEED_LOW
elif resp['cmd2'] == 'AA':
self._speed = SPEED_MEDIUM
elif resp['cmd2'] == 'FF':
self._speed = SPEED_HIGH
@property
def supported_features(self):
"""Flag supported features."""
return SUPPORT_INSTEON_LOCAL
def turn_on(self: ToggleEntity, speed: str=None, **kwargs) -> None:
"""Turn device on."""
if speed is None:
if ATTR_SPEED in kwargs:
speed = kwargs[ATTR_SPEED]
else:
speed = SPEED_MEDIUM
self.set_speed(speed)
def turn_off(self: ToggleEntity, **kwargs) -> None:
"""Turn device off."""
self.node.off()
def set_speed(self: ToggleEntity, speed: str) -> None:
"""Set the speed of the fan."""
if self.node.on(speed):
self._speed = speed

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