Compare commits

...

175 Commits
0.37 ... 0.38

Author SHA1 Message Date
Robbie Trencheny
e0f1c8ac67 Merge pull request #5833 from home-assistant/release-038
Release 0.38
2017-02-11 14:38:12 -08:00
Paulus Schoutsen
d8a34877d4 Version bump to 0.38 2017-02-10 20:55:52 -08:00
Marcelo Moreira de Mello
3fb70afb14 Avoid traceback for Amcrest cameras/firmware that does not have the software_information API call (#5865)
* Avoid traceback for Amcrest cameras/firmware that does not have the software_information API call

* Make lint happy
2017-02-10 20:55:34 -08:00
Johan Bloemberg
bb043c47f8 Rflink update and small refactor. (#5789)
* Use same pattern for device defaults in both platforms.

* Update Rflink that passes loop downstream.

* Update requirements.
2017-02-10 20:55:34 -08:00
Paulus Schoutsen
849ae9903c Recorder run can be None (#5854) 2017-02-10 20:55:34 -08:00
Johann Kellerman
66088377e1 [recorder] Run end model changed in session scope (#5858) 2017-02-10 20:55:34 -08:00
Paulus Schoutsen
0e6ab3ace6 Update frontend (#5855) 2017-02-10 20:55:34 -08:00
Pascal Vizeli
eaa6392535 Fix check_config script. (#5853) 2017-02-10 20:55:34 -08:00
Petr Vraník
a071cd21f2 version bump (#5846)
Add an optional extended description…
2017-02-10 20:55:33 -08:00
Teemu R
0f6aed16a2 bump python-yeelight version (#5850)
Add an optional extended description…
2017-02-10 20:55:33 -08:00
Christian Brædstrup
cee389f621 D-Link switch version bump of external library (#5843) 2017-02-10 20:55:33 -08:00
Andrey
2b62e9f008 Fix zwave helper getter not to fail on some None results. (#5845) 2017-02-10 20:55:33 -08:00
Adam Mills
3d7b79f523 [recorder] Add tests for full schema migration (#5831)
* [recorder] Add tests for full schema migration

* Remove leftover code

* Fix duplicate creation of sqlalchemy Index object

* It's that kind of day...

* Improve models_original docstring
2017-02-10 20:55:33 -08:00
Philipp Schmitt
7bf7c727d1 Refactoring and JSON decode error handling (#5826)
* Refactoring and JSON decode error handling

* Catch ValueError instead of simplejson.scanner.JSONDecodeError
2017-02-10 20:55:33 -08:00
Pascal Vizeli
3f87d28616 Update aiohttp 1.3.1 (#5838) 2017-02-10 20:55:33 -08:00
Pierre Ståhl
7259082de5 Reuse default aiohttp session (#5836) 2017-02-10 20:55:33 -08:00
Pierre Ståhl
60f85b1e09 Handle connection errors when connecting to Apple TVs (#5829)
* Handle connection errors when connecting to Apple TVs

Also bump pyatv to 0.1.2 which fixes a request leak.

* Fix pylint error

* Fix import order
2017-02-10 20:55:33 -08:00
Erik Eriksson
0fdf1391e2 Don't thow exception if connection to server is lost (#5775) 2017-02-10 20:55:33 -08:00
Paulus Schoutsen
e5256ccf1f Merge remote-tracking branch 'origin/master' into dev 2017-02-09 08:54:40 -08:00
John Arild Berentsen
298575f7cb Adding helper for get and set values (#5743)
* cleanup

* Update __init__.py

* Update __init__.py

* Update __init__.py
2017-02-09 13:40:35 +01:00
Paulus Schoutsen
c550a316a4 Make device sun light trigger async (#5823) 2017-02-09 00:10:53 -08:00
Paulus Schoutsen
4398b8b5c6 Deprecate event decorators (#5822) 2017-02-09 00:10:38 -08:00
Pascal Vizeli
2cbed9cd96 Move signal handling out of core to bootstrap (#5815)
* Move signal handling out of core to bootstrap

* Fix tests
2017-02-08 21:58:45 -08:00
Paulus Schoutsen
7eb4bdc37b Upgrade aiohttp to 1.3 (#5821) 2017-02-08 21:27:36 -08:00
Paulus Schoutsen
ebceca36ec Update frontend (#5820) 2017-02-08 20:55:15 -08:00
Paulus Schoutsen
03fe5b04b5 Remove non-working webfont preload (#5819) 2017-02-08 20:31:36 -08:00
Duoxilian
7fa08059dc Support away_mode as permanent hold and hold_mode as temporary hold. (#5725)
* Support away_mode as permanent hold and hold_mode as temporary hold.

* Add comments to explain code better. Remove indefinite hold preference
to be consistent with 'away_mode'.
2017-02-08 20:04:09 -08:00
John Arild Berentsen
fdcf5fe233 Bugfixes (#5740)
* wrong data for lock alarm_type

* missing whitespace

* Not possible to set codes starting with 0
2017-02-08 19:59:47 -08:00
Johann Kellerman
415500de23 [recorder] Protect against running in the event loop (#5812) 2017-02-08 19:58:43 -08:00
William Scanlon
628b169393 Fixed call to object_id() (#5814) 2017-02-08 19:57:58 -08:00
Sean Dague
49f2540730 Enhancements to ARWN platform (#5816)
* Fix arwn platform discover_sensors

The discover_sensors function can return either singletons or a list
of sensors. However the consumer was always expecting a list. This
fixes it to work in both cases.

* Add custom icons to arwn sensors.

This adds some custom icons for different kinds of weather sensors
that the arwn platform returns. Makes it a little easier to see what's
going on.
2017-02-08 19:56:44 -08:00
Teagan Glenn
76db4cc099 Change medium state for filtering (#5817) 2017-02-08 19:55:48 -08:00
Pascal Vizeli
d29b7f6910 Ffmpeg update 1.4 (#5813)
* Pump ffmpeg version

* update entity

* next 1.4
2017-02-08 23:18:23 +01:00
Johann Kellerman
612aa1cf21 Initial (#5811) 2017-02-08 13:16:39 -08:00
Andreas Cambitsis
4f20a2d3ea Upgrade Russound integration to v0.1.7 (#5756)
* Bumped up version to use 0.1.7 of Russound integration module.
Fixed bug arising from not supporting TURN_ON state (fixes issue https://github.com/home-assistant/home-assistant/issues/5012)
Implemented state support in 0.1.7 such that component state is returned from the actual AMP. (Still uses polling model though).
Tested it with home-assitant users @laf (original developer of the module) and @hofsta.  Works fine with their Russounds.

* Made styling / compliance changes and updated correct version of russound module on requirements_all.txt.

* Changed handling of properties to be compliant with https://github.com/home-assistant/home-assistant/issues/4210
(Specifcailly added member variables for state, volume and source to cache these values, and introduced Update() method to set their values).

Now returns None if the selected source index that is returned from russound is greater than the length of the specified source list in the yaml config.
Removed unnecesary comment.

* Removed blank line after docstring.

* Removed updated() in class init and added True paramter to add_devices in setup_platform.

* Dropped the no longer needed self.update()
2017-02-08 15:53:59 -05:00
Teemu R
061985bc65 Add available property and typing hints (#5593)
* light.demo: add available property, add typing hints

* light.demo: keep all lights available, fix init ordering

* Fix issues raised during review

* Update demo.py
2017-02-08 12:13:07 -08:00
Andrey
4b15946a9b Make sure workaround_component is not none (#5808) 2017-02-08 21:11:36 +02:00
Pascal Vizeli
881d53339b [Image_Processing][Breaking Change] Cleanup Base face class add support for microsoft face detect (#5802)
* [Image_Processing] Cleanup Base face class add support for microsoft face detect

* fix lint

* add unittest for micosoft detect

* fix test
2017-02-08 09:19:40 -08:00
Pascal Vizeli
3f82ef64a1 Move core service from core to components (#5787)
* Move core servcie from core to components

* add new handler for signals/exception

* Static persistent id

* Move unittest

* fix coro/callback

* Add more unittest for new services

* Address comments

* Update __init__.py
2017-02-08 09:17:52 -08:00
Daniel Høyer Iversen
08efe2bf6d Improve warning message in template rendering (#5806)
* improve warning message when template is none

* improve error message when template is none

* improve error message when template is none

* improve error message when template is none
2017-02-08 09:07:43 -08:00
Johann Kellerman
db6c166abe Update sma.py (#5807) 2017-02-08 09:07:03 -08:00
George.M
8951e1bdc0 The word router was misspelt (#5803)
fixed as this message is user facing
2017-02-08 12:28:45 +01:00
Pierre Ståhl
250523c1d8 Add discovery suppport to Apple TV (#5801)
Add an optional extended description…
2017-02-08 12:17:23 +01:00
Philipp Schmitt
2dab6cbb0e Mailgun notify service (#5782)
* Mailgun notify service

* Update dependency to version 1.3

- The provided credentials (including the domain) are now checked during
startup, as requested by @balloob
- The domain name is now optional
- There's a new config item "sandbox" which indicates whether to use the
sandboxed domain in case the domain is not set

* Fix a few lint issues

* Disable lint check no-value-for-parameter
2017-02-07 22:22:19 -08:00
Johan Bloemberg
0e6dd39c15 Add support for fluxled discovery. (#5784)
* Add support for fluxled discovery.

* Make use of device type/protocol auto detection.
2017-02-07 21:49:36 -08:00
Johann Kellerman
490ef6afad WIP: [component/recorder] Refactoring & better handling of SQLAlchemy Sessions (#5607)
* Refactor recorder and Sessions

* Cover #4352

* NO_reset_on_return

* contextmanager

* coverage
2017-02-07 21:47:41 -08:00
Paulus Schoutsen
bdebe5d53c Update frontend (#5800) 2017-02-07 21:30:24 -08:00
Adam Mills
ecfe8e0a9a Formalize supported_features as entity property (#5794)
* Formalize supported_features as entity property

* Remove extra emulated_hue conditions

* Generate log message in executor
2017-02-07 20:42:45 -08:00
Jeff Wilson
4fa4d7347f Fix climate.set_fan_mode yaml (#5799) 2017-02-07 20:38:16 -08:00
Andrey
1b54218d46 Create a file for zwave workarounds. (#5798)
* Create a file for zwave workarounds. Add sensor->binary_sensor for fgfs101 (#2)

* Don't use default None
2017-02-07 20:37:11 -08:00
Robbie Trencheny
b8a0792424 Bump netdisco to 0.8.2 2017-02-07 14:11:51 -08:00
Josh Wright
35f6dbc9dc Update python-nest dependency version (#5795)
The nest-cam changes have now been merged into the upstream library, so
there is no need to track a specific branch.

Updating to 3.1.0 also fixes a structure parsing bug I was experiencing.
2017-02-07 22:55:49 +01:00
Fabian Affolter
12bc7c7316 Upgrade pyasn1 to 0.2.2 (#5796) 2017-02-07 22:55:21 +01:00
Pascal Vizeli
acdda1f42b Revert last change on TTS cache load for more speed (#5797) 2017-02-07 22:54:52 +01:00
Martin Hjelmare
45a7c27280 Add mysensors device tracker and platform discovery (#5781)
* Add mysensors device_tracker platform

* Add discovery of device_tracker platforms

* Enable discovery of device_tracker platforms that are not
  DeviceScanner.
* Update signature of setup_scanner function in all affected platforms.
* Add test.
* Use discovery for mysensors device_tracker platform.

* Remove gps accuracy

* Small change to core like schema

* fix depency
2017-02-07 20:47:11 +01:00
Fabian Affolter
c7fd28c10f MQTT discovery (#5724)
* Change implementation

* Re-write

* Remove unused consts

* Update discovery.py

* Add tests

* fix other tests

* Fix check_config script test

* Lint

* Lint
2017-02-07 09:13:24 -08:00
Robbie Trencheny
45507cd9d1 TTS ID3 support (#5773)
* Add support for writing ID3 tags to the file for improved display in media players

* Lint and async fixes

* Use mutagen instead of taglib

* Fix tests

* Add fallback for album

* Requested changes

* move import

* Fix album name

* Change default options handling

* Move to member function / minor fix

* fix style

* fix lint

* change mutagen handling

* fix lint / add name to bytesio

* Update __init__.py

* Fix test, some cleanups

* Add mutagen exeption handling, fix tests

* fix mutagen taging
2017-02-07 12:07:11 +01:00
Trevor
063c0e8f44 Add icon_template to template sensor (#5766)
* Add icon_template to template sensor

* Update test_template.py

* Update test_template.py again

* Update template.py

* Update test_template.py

* Update test_template.py
2017-02-07 01:51:44 -08:00
Trevor
f0da576315 Fix Hue groups with same names (#5737)
* Revert "Fix hue lightgroups not syncing state (#5702)"

* Use light_id in unique_id for Hue groups

* Make sure HueLight unique_id is unique

* Update hue.py

* Update hue.py

* Update hue.py
2017-02-07 01:51:05 -08:00
andrey-git
f774538e66 Check config before restarting (#5609)
* Check config before restarting.

* Make check_config on restart async

* don't check if notification service exists

* Use .communicate()

* Reduce the number of notifications. Add tests.
2017-02-07 01:19:08 -08:00
Hermann Kraus
51810620fb Check for command topics when determinig the capabilities of an MQTT light. (#5770)
Previous code used the state topic which is obviously wrong:
- The state topic is already used to select optimistic mode.
- A light with only the state topic but no command topic would still announce the capability.
2017-02-07 01:13:00 -08:00
Pierre Ståhl
c7e282257a Initial support for Apple TV (#5698)
* Initial support for Apple TV

* hash_wip

* Add media_play support to Apple TV
2017-02-07 00:55:19 -08:00
Jesse Osiecki
9a2c84ee8a Added error checking to the MIMEImage encoding for smtp.py (#5753)
* Added error checking to the MIMEImage encoding for smtp.py

Added fallback to file attachment rather than inline image for images
without a known MIME

* PEP8 reqs to fix previous commit
2017-02-07 00:43:03 -08:00
Alessandro Mogavero
86da4f511d Improve Sky Hub error handling (#5762)
* Added error handling in function _get_skyhub_data

* Error line split for readability
2017-02-07 00:28:33 -08:00
Pascal Vizeli
48161697f8 Sonos fix favorite, coordinator, cleanup update (#5778)
* Sonos fix favorite, coordinator, cleanup update

* Bugfix snapshot restore
2017-02-07 00:27:55 -08:00
Stéphane Bidoul (ACSONE)
7927a6b588 add min and max jinja filters (#5765) 2017-02-07 00:25:47 -08:00
ray0711
1546ec7778 Fix brightness slider for mqtt template lights (#5780)
Add the missing supported_features declaration.
2017-02-07 09:52:07 +02:00
Marcelo Moreira de Mello
305d2612cf Fix attribute error for media_player/roku if roku device is unreachable and shows a persistent notification (#5785) (#5786) 2017-02-06 22:00:20 -08:00
Andrzej
34c7bac9b4 Update package.py (#5783) 2017-02-06 22:16:17 +01:00
Pascal Vizeli
2b124a008c Migrate lock component to async (#5748) 2017-02-06 21:25:34 +01:00
Alberto Arias Maestro
32dc276c53 Add support for position on wink cover (#5751)
Add support for position property for wink covers and fix state when stopped in the middle.
2017-02-06 15:43:36 +02:00
Adrián López
4cc711357a Allow to use data for enhanced messages (#5763)
Add notification data field to the message send to Facebook.
Allows to construct richer messages like cards, quick replies, attach
images, videos, etc
2017-02-06 14:01:41 +01:00
andrey-git
26a3ecc9d0 Merge pull request #5718 from benvm/modbus_write_register_array
Modbus write_register doesn't accept list
2017-02-06 13:55:48 +02:00
Johan Bloemberg
264bdc9d56 Make brightness display work for rgb devices. (#5675)
* Make brightness display work for rgb devices.

Non rgbw devices return 255 for getWarmWhite255. This is part 2 to make the brightness slider work for these devices.

https://github.com/Danielhiversen/flux_led/pull/25

* Query brightness property which return WW level or RGB brightness equivalent.

https://github.com/Danielhiversen/flux_led/pull/25
2017-02-06 11:32:51 +01:00
Daniel Høyer Iversen
150c8ac11c flux led o.13 (#5774) 2017-02-06 11:32:37 +01:00
Yannic-HAW
4a341ff55f - added send packet service to send ir packets directly from scripts (no need to define a switch for each command) (#5768)
- changed ip suffix of SERVICE_LEARN to use _ instead of . as ip seperator to avoid errors if used in yaml
2017-02-06 07:53:58 +01:00
Martin Hjelmare
0d89f2bc69 Update mysensors sensors (#5764)
* Add V_DIRECTION type for S_WIND.
* Add more predefined units of measurement.
2017-02-05 21:07:30 +01:00
Pascal Vizeli
2a139d6bc7 Add unittest for multible entities in one line (#5759) 2017-02-05 14:24:38 +01:00
Fabian Affolter
91bebca0b6 Upgrade pysnmp to 4.3.3 (#5757) 2017-02-05 11:22:32 +01:00
Philipp Schmitt
573fc651dc Store the key file in the config dir (#5732)
* webostv: Store the key file in the config dir

* Update the pylgtv source to use the repo by @TheRealLink

* Add missing config parameter
2017-02-05 10:39:04 +01:00
Fabian Affolter
d88c903537 Upgrade pyasn1 to 0.2.1 (#5755) 2017-02-05 10:16:57 +01:00
Martin Hjelmare
82c99f81fc Fix validation of serial port on windows (#5749)
* Fix validation of serial port on windows

* Use pyserial to check serial ports.
* Check that persistence file ends with either `.json` or `.pickle`.

* Change fix to not rely on pyserial

* Use generator expr instead of list comprehension
2017-02-04 21:54:20 -08:00
Fabian Affolter
02dfd9660e Upgrade zeroconf to 0.18.0 (#5746) 2017-02-04 20:52:11 +01:00
Fabian Affolter
6164b61e14 Upgrade psutil to 5.1.2 (#5745) 2017-02-04 20:51:49 +01:00
Fabian Affolter
4cb20ce6d9 Upgrade astral to 1.3.4 (#5744) 2017-02-04 20:51:22 +01:00
Joeboyc2
0ea81c1269 Change CONF_DEFAULT_COLOR CV type (#5700)
* Change CONF_DEFAULT_COLOR CV type

Changed  vol.Optional(CONF_DEFAULT_COLOR, default=DEFAULT_COLOR) from cv.string to cv.ensure_list

This allows the optional parameter default_color to be picked up correctly and to function

the option needs to be specifed as follows:

    default_color: [0,255,0]

Solution provided by @scossa2020 in issue #5338
https://github.com/home-assistant/home-assistant/issues/5338

* Update hyperion.py
2017-02-03 23:36:25 -08:00
Fabian Affolter
b29c167dde Upgrade psutil to 5.1.1 (#5736) 2017-02-03 09:09:14 -08:00
William Scanlon
25a68f3ce9 Update python-wink version (#5734) 2017-02-03 16:37:00 +01:00
Fabian Affolter
c0dcef6c3e Add wind bearing (#5730) 2017-02-03 09:44:07 +01:00
Fabian Affolter
6786f83c26 Upgrade pyowm to 2.6.1 (#5729) 2017-02-03 09:43:29 +01:00
Philipp Schmitt
7506569db9 Support for the Orange Livebox Play TV appliance (#5533)
* Support for the Orage Livebox Play TV appliance

* Add liveboxplaytv to coveragerc

* Minor refactoring

* Update requirements

* Adjust comments

* Fix alignment

* Fix some coding-style issues highlighted by Travis CI

* The livebox play TV does not support playing media

* Lint: shorten line

* Remove unused callback function

* Remove redundant backslash

* Implement changes requested by balloob

* Don't error out if channel name or media url could not be retrieved

* Support current program (media title property)

* Remove unnecessary check

* Clean up: Remove another unnecessary check, _CONFIGURING variable and _playing attribute

* Update liveboxplaytv dependency to version 1.4.4

* Fix liveboxplaytv requirement

* Improve media state (support for playing and pause state)

* Update liveboxplaytv.py
2017-02-02 23:52:52 -08:00
Fabian Affolter
f9ede73a55 Add moon sensor (#5726)
* Add moon sensor

* Update moon.py
2017-02-02 23:43:03 -08:00
Joseph Piron
4aa7f030e8 Adds average load to systemmonitor (#5686)
* Adds average load to systemmonitor

* split the values in 3 sensors

* hound ok
2017-02-02 23:33:15 -08:00
Wolf-Bastian Pöttner
e831a2705e Add support for FRITZ!DECT wireless switches based on fritzhome (#5541) 2017-02-02 23:29:18 -08:00
Ryan Kraus
537355924f Alert Component (#5201) 2017-02-03 07:20:51 +02:00
miniconfig
dbc2f6b9cd Merge pull request #5658 from miniconfig/openevse
Added new sensor component to monitor OpenEVSE chargers.
2017-02-02 23:52:11 -05:00
Adam Mills
6a64e79d7b [recorder] Index events time_fired to improve logbook performance (#5633)
* Index events time_fired to improve logbook perf.

* Updated implementation to track schema versions

* Added tests for schema migration support logic

* Rename check_schema to migrate_schema
2017-02-02 22:04:14 -05:00
miniconfig
4b62a0d924 Updated openvse sensor component to use new structure of openevsewifi library. 2017-02-02 21:46:35 -05:00
Valentin Alexeev
dfb991ce19 Bump pwaqi to 1.4 to fix a typo in the underlying library. (#5716) 2017-02-02 12:59:52 -08:00
Pascal Vizeli
f63874eb8c Migrate cover to async. (#5717) 2017-02-02 12:39:13 -08:00
Colin O'Dell
bc65452efb QNAP Sensor (#5666)
* Implement the QNAP sensor

* Add sensors immediately

* Remove unnecessary check

* Use CONF_SSL instead of CONF_PROTOCOL
2017-02-02 12:29:04 -08:00
Pascal Vizeli
2fc3dfff67 Migrate fan component to async. (#5723)
* Migrate fan component to async.

* Fix lint
2017-02-02 12:07:00 -08:00
Daniel Høyer Iversen
574384f446 update miflora lib and allow specification of bluetooth adapter (#5720) 2017-02-02 17:08:10 +01:00
Daniel Høyer Iversen
ee551e2a9c up rfxtrx lib (#5721) 2017-02-02 16:18:16 +01:00
Duoxilian
219337a574 Cleanup climate and ecobee (#5616)
* Remove redundant input validation which is already accomplished through
defined schemata.

* Rely on defined state attributes for hold mode.

* Remove misleading comment. This comment seems to assume that sleep
mode is a hold; it is a schedule instead. The code snippets in the
comment could never work.

* Remove use of constants for hold mode. Will be made irrelevant
by a planned change by nordlead2005.
2017-02-02 15:28:32 +01:00
Philipp Schmitt
ff65c2a114 Support for Nuki.io smart locks (#5715)
* Support for Nuki.io smart locks

* Update requirements and add lock.nuki to .coveragerc

* lint: Re-organize imports

* Schedule a state update instead of calling directly update_ha_state

* Remove update requests altogether

* Make sure there is no IO inside properties

* Fix: nuki lock are all initialized as "lock.unnamed_device"

* Update pynuki to 1.2 to avoid an extra REST API call for each lock init
2017-02-02 15:15:27 +01:00
Ben Van Mechelen
0ea9d935af Modbus write_register accept list 2017-02-02 10:23:13 +01:00
William Scanlon
80a794e587 Wink AC and addidtional sensor support (#5670)
* Added door bell sensors

* Initial support for AC units.

* Added new device service

* Quirky Aros AC unit support

* Use super() everywhere and error checking for token request.

* Ignore camera sensors during setup of alarms.

* Added manufacturer/device attributes to all wink devices.

* Fixed style errors

* Fixed remaining lint errors.
2017-02-01 22:43:12 -08:00
Trevor
b5f285a789 Fix OwnTracks state names (#5454)
* Fix OwnTracks state names (#5453)

* Update owntracks.py

* Update tests
2017-02-01 22:06:02 -08:00
Paulus Schoutsen
6e94f0d7cd Upgrade zeroconf dep" (#5706) 2017-02-01 21:48:30 -08:00
Pascal Vizeli
eefb603f17 Cleanup media_player universal platform / blocking platform. (#5671)
* Cleanup media_player universal platform / blocking platform.

* fix comments

* fix unittest

* fix lint

* fix unittest lint

* fix coro

* fix test

* fix tests part 2
2017-02-01 21:45:19 -08:00
Pascal Vizeli
10a104271e Cleanup climate platform for async update_ha_state / migrate generic thermostat (#5679)
* Cleanup climate from blocking stuff / migrate generic

* Migrate generic thermostat

* fix tests

* fix lint
2017-02-01 21:44:05 -08:00
Paulus Schoutsen
686c8466a0 Merge pull request #5707 from home-assistant/release-0-37-1
0.37.1
2017-02-01 21:34:52 -08:00
Paulus Schoutsen
181943e139 Version bump to 0.37.1 2017-02-01 21:19:45 -08:00
Trevor
e05c1bc160 Fix hue lightgroups not syncing state (#5702) 2017-02-01 21:19:28 -08:00
Johan Bloemberg
96745abf5d Prevent infinite loop in crossconfigured mqtt event streams (#5624)
* Prevent events about MQTT messages received to cause infinite loop when two HA instances are crossconfigured for mqtt_eventstream.

* Fix linting

* Publish all MQTT received events except incoming from eventstream. Also make it configurable.
2017-02-01 21:19:28 -08:00
John Arild Berentsen
17c4f4d391 [lock.zwave] Bugfix Zwave lock (#5619)
* Bugfix state

* remove debug
2017-02-01 21:19:28 -08:00
Pascal Vizeli
9ed8ee1261 Bugfix sonso source input (#5699) 2017-02-01 21:19:28 -08:00
Pascal Vizeli
df7ca22656 Fix bug for UNREACH devices / Variable handling and update. (#5689)
* Fix bug for UNREACH devices / Variable handling and update.

* fix track_time

* update after data after creation

* add message output

* change unreach

* change unreach code

* Revert "change unreach code"

This reverts commit f58430de3c.

* update pyhomematic
2017-02-01 21:19:28 -08:00
Hugo Dupras
9716cd3f48 Hotfix for netatmo cameras (#5644)
* Fix for missing netatmo tags in 0.37

Also fix issue with SSL certificate for vpn_url

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

* Netatmo welcome: vpn_url can be empty

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

* add config floag to disable SSL verification for vpn_url

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

* Import CONF_VERIFY_SSL from const
2017-02-01 21:19:28 -08:00
Pascal Vizeli
6ee7878236 Bugfix async blocking loop with xml parser. (#5694) 2017-02-01 21:19:28 -08:00
Pascal Vizeli
bc7fd5611e Bugfix sonos group coordinator (#5691)
* Bugfix sonos group coordinator

* Fix tests
2017-02-01 21:19:28 -08:00
Erik Eriksson
27a91b357e Upgraded tellduslive (#5664) 2017-02-01 21:19:28 -08:00
Marcelo Moreira de Mello
26d0fd772b Fixes issue #5627 by bumping external Amcrest module to version 1.1.4 (#5662)
Add an optional extended description…
2017-02-01 21:19:27 -08:00
Daniel Høyer Iversen
a168bf64b6 bug fix in hue (#5623) 2017-02-01 21:19:27 -08:00
David-Leon Pohl
db09ef0a6f Fixes: Pilight Switch rejects alphanumeric IDs #5119 (#5601) 2017-02-01 21:19:27 -08:00
Trevor
647a93801c Fix hue lightgroups not syncing state (#5702) 2017-02-01 21:06:11 -08:00
Johann Kellerman
2fcaf8bda6 [sensor/sma] handle units correctly (#5657) 2017-02-01 21:01:06 -08:00
Johan Bloemberg
ae1f59970d Prevent infinite loop in crossconfigured mqtt event streams (#5624)
* Prevent events about MQTT messages received to cause infinite loop when two HA instances are crossconfigured for mqtt_eventstream.

* Fix linting

* Publish all MQTT received events except incoming from eventstream. Also make it configurable.
2017-02-01 21:00:05 -08:00
John Arild Berentsen
68d6bcd3ed [lock.zwave] Bugfix Zwave lock (#5619)
* Bugfix state

* remove debug
2017-02-01 20:57:57 -08:00
Pascal Vizeli
b2180fba63 Bugfix sonso source input (#5699) 2017-02-02 00:02:24 +01:00
Pascal Vizeli
b5b1d72ab6 Fix bug for UNREACH devices / Variable handling and update. (#5689)
* Fix bug for UNREACH devices / Variable handling and update.

* fix track_time

* update after data after creation

* add message output

* change unreach

* change unreach code

* Revert "change unreach code"

This reverts commit f58430de3c.

* update pyhomematic
2017-02-01 23:55:16 +01:00
Daniel Høyer Iversen
2a5ccff82e up rfxtrx lib (#5687) 2017-02-01 19:47:18 +01:00
Paulus Schoutsen
bdc62730bd Update frontend (#5696) 2017-02-01 09:03:35 -08:00
Hugo Dupras
676519d0cb Hotfix for netatmo cameras (#5644)
* Fix for missing netatmo tags in 0.37

Also fix issue with SSL certificate for vpn_url

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

* Netatmo welcome: vpn_url can be empty

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

* add config floag to disable SSL verification for vpn_url

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

* Import CONF_VERIFY_SSL from const
2017-02-01 17:33:32 +01:00
Pascal Vizeli
a72d32b9af Bugfix async blocking loop with xml parser. (#5694) 2017-02-01 17:20:52 +01:00
Pascal Vizeli
32f8622bba Bugfix sonos group coordinator (#5691)
* Bugfix sonos group coordinator

* Fix tests
2017-02-01 16:53:02 +01:00
Johan Bloemberg
cabc4dff03 Use same pattern for device defaults in both platforms. (#5682) 2017-02-01 10:42:50 +01:00
Paulus Schoutsen
987f59e8d8 Update frontend (#5685) 2017-02-01 00:47:02 -08:00
Johan Bloemberg
7be3414785 Only set LANG=C.UTF8 during install. (#5648) 2017-01-31 19:30:53 -08:00
Paulus Schoutsen
8334bc908c Update frontend (#5683) 2017-01-31 19:29:07 -08:00
Michaël Arnauts
8247acb7b9 Move docker installation scripts to virtualization/Docker path. Splits out openalpr to seperate script. (#5676) 2017-01-31 19:02:06 -08:00
Erik Eriksson
89ec752064 Upgraded tellduslive (#5664) 2017-01-31 11:08:11 -08:00
Mathew Peterson
d65f07860c Refactors script/setup_docker_prereqs (#5506)
* Refactors script/setup_docker_prereqs

Refactors script/setup_docker_prereqs to allow toggling of packages
to being installed

* Adds support for openalpr to Docker

* Updates Dockerfile

Comments ENV directives in order to preserve cache.

* Fixes incorrect position of echo

* Fixes telldus installer by updating apt before pkg install
2017-01-31 18:11:51 +01:00
Johan Bloemberg
bbda2a72f4 Rflink 433Mhz gateway platform and components (#4547)
* Initial sketches of rflink component.

* Add requirement.

* Properly load configuration.

* Bump rflink for graceful parse errors and protocol callback.

* Cleanup, documentation and linting.

* More documentation, first sensor implementation (temp & hum).

* Add brightness/dim support for newkaku protocol.

* Use separate class for dimmables.

* Make sure non-dimmable newkaku devices are turned on.

* Move some code around, add switches. Support loading from config.

* Fix bug in ignoring devices.

* Fix initial state assumption.

* Improve reliability on invalid conditions.

* Allow configuration of group for new devices.

* Sensor icons.

* Fix parsing negative numbers.

* Correct icon.

* Allow sending commands serial.

* Pluralize.

* Allow adding sensors from config.

* Fix ignoring devices and bugs in previous commit.

* Share know devices so devices from configuration don't get added as lights.

* Lookup unit from value_key.

* Remove debug.

* Start implementing event protocol in place of packet protocol.

- Added first test suite for sensors.
- This currently breaks light and switch.

* Refactor switch component to fit new rflink changes. Add test suite.

* Fix style.

* Refactor and test lights. Bring coverage to 100%.

* Use non-broken and production tested rflink module.

* Update requirements.

* Bump for logging.

* Improve readability.

* Do not use global variable but keep known device state in intended place.

* Improve docs.

* Make icon support generic.

* Disable overriding icons in config, as it belongs in customization. Only keep custom icon for entities that are able to detect a icon based on the thing they represent (sensors in this case).

* Implement configuration schema, overall refactor of magic values.

* Fix bug in config/test wait_for_ack.

* Small refactors.

* Move command logic into separate class.

* Convert command sending logic to class based pattern instead of using the event bus.

* Start not using bus for rflink event propagation to platforms.

* Do not use event bus for all entity types.

* Fire an event on the bus for every switch incoming rflink command.

* Resolve lint errors, remove some old code.

* Known devices no longer need to be registered separately.

* Log bus events.

* Event callback is a..... callback.

* Use full entity id for events.

* Move event sending to entity.

* Log incoming events.

* Make firing events optional inline with rfxtrx.

* Add foundation for signal repetition.

* Add signal repetition config and tests.

* Make plain switchable type explicitly configurable.

* Enable default entity settings for automatically added entities as well.

* Prevent default configuration leaking accross entities.

* Make sure device defaults don't get overwritten by defaults further down.

* Don't let fast state switching and repetitions turn your house into a disco.

* Make repetitions more responsive.

* Disable on/off fallback on dimmables as it currently doesn't play nice with repetitions.

* Use rflink that allows send_command_ack to be safely cancelled.

* Reduce duplication and make repeat work for non-ack.

* Implement reconnection logic.

* Improve reconnection logic.

* Also cancel repetitions when entity state is changed due to external command.

* Update requirements.

* Fix linting.

* Fix spelling.

* Don't lie.

* Fix lint.

* Support for automatically creating protocol translation (fixes spaces in device names).

* Returned support for dimmable and on/off entity.

* Duplicate code to fix linting issues with inheritance.

* Allow overriding unit of measurement from config.
2017-01-31 08:11:52 -08:00
Johan Bloemberg
9925b2a8e0 Load mqtt_eventstream before any other component. (#5629)
This ensures that initial state changes for components are broadcasted on the MQTT bus.
2017-01-31 08:02:28 -08:00
Michaël Arnauts
b080ae154c Add devices detected by ping as SOURCE_TYPE_ROUTER instead of GPS (#5625) 2017-01-31 08:01:14 -08:00
Adrián López
14f8bc26d1 Voice command API.AI. First import (#5462)
* Voice command API.AI. First import

* Fixes suggested by hound

* Fixing comments

* Fix pylint and pydocstyle errors

* Change how speech is defined

Also clean some unused constants, remove card type (not used), define
a message when action is not defined and improve the message when
action is unknown.

* Change how speech is defined

Clean some constants.
Improve error messages.
Delete card type, not used.

* Tests for new Api.ai component

* Use async_add_job to python compatibility. New test to measure response time

* Add async_action option to choose between waiting or not for the action to execute

* Travis-ci needs more time

* Removed timeout tests

* Removed timeout tests

* Added apiai to .coveragerc as specified by PR doc
2017-01-31 07:54:54 -08:00
Pascal Vizeli
88d9d787a6 Add unittests for FFmpeg and spliting binary sensor (#5659)
* Spliting ffmpeg binary sensor and move service to component.

* unittests for component

* add unittest for binary_sensor

* exclude camera for tests
2017-01-31 07:48:03 -08:00
Paulus Schoutsen
9ae574c7d9 Update frontend (#5669) 2017-01-31 07:39:01 -08:00
Daniel Høyer Iversen
29816f3041 Vlc (#5665)
* vlc default name
2017-01-31 13:51:02 +01:00
Teemu R
5f0138f8e4 new yeelight backend lib, new features (#5296)
* initial yeelight based on python-yeelight

* adapt yeelight's discovery code & suppress exceptions on set_default

* Support flash & code cleanups

Adds simple pulse for flashing, needs to be refined.
This commit also includes changing transition from seconds to milliseconds,
and cleans up the code quite a bit.

* cleanup code, adjust default transition to 350

* bump required version to 0.0.13

* Cleaning up and marking todos, ready to be reviewed

* Renamed back to yeelight.
* Removed effect support for now until we have some sane effects available.
* Add "breath" notification for flash, currently hidden behind a False check due to unknown issue not accepting it.
* TODO/open points are marked as such.

* Fix a typo in rgb calculation

* yeelight_<bulbtype>_<mac> for autodetected bulbs

hostname from mdns seems to vary

* Lint fixes, add music mode, fix flash

* Flash transforms now to red and back
* Fix lint warnings
* Add initial music mode.

* remove unused mode logging, move set_mode to turn_on

* Add save_on_change configuration variable

* yeelight: check if music mode is on before enabling it.

* Fix linting, bump required python-yeelight version

* More linting fixes, use import when needed instead of saving the module handle

* Use OR instead of + for features assignment

* Fix color temperature support, convert non-rgb values to rgb values in rgb()

* Fix typo on duration, thanks @qzapwy for noticing

* yeelight: fix issues from review, behave when not available

* Implement available()
* Fix transition to take seconds instead of milliseconds
* Fix default configuration for detected bulbs
* Cache values fetched in update()
* Add return values for methods

* yeelight: kwarg-given transition overrides config, slight cleanups

* change settings back to optional, request update when calling add_devices

* As future version of python-yeelight will wrap exceptions, we can handle broken connections more nicely.

* bump yeelight library version

* Remove unused import

* set the default only when settings are changed and not, e.g., when turned on by automation

* update comment & fix linting
2017-01-31 10:01:11 +01:00
Teemu R
2c31e3ea8c Cleanup modes & available, bump version requirement (#5606)
* Cleanup modes & available, bump version requirement

* check for Noneness on available
2017-01-31 09:23:05 +01:00
Marcelo Moreira de Mello
976cd545fe Fixes issue #5627 by bumping external Amcrest module to version 1.1.4 (#5662)
Add an optional extended description…
2017-01-31 09:19:49 +01:00
Adam Mills
7f3ee8a83c [docker] cec install path fixed upstream (#5651) 2017-01-30 21:27:20 -05:00
miniconfig
495b0667e9 Added new sensor component to monitor OpenEVSE chargers equipped with the WiFi Kit. 2017-01-30 15:29:56 -05:00
Daniel Høyer Iversen
3b32afda01 bug fix in hue (#5623) 2017-01-30 09:20:35 -08:00
Daniel Høyer Iversen
3a1607500e Add set_options_service for input select (#5630) 2017-01-30 09:17:57 -08:00
Matt N
1bf3eba603 yarn setup_js_dev was deleted (#5639) 2017-01-30 09:15:26 -08:00
Fabian Affolter
ab019b9747 Upgrade slacker to 0.9.40 (#5650) 2017-01-30 09:14:00 -08:00
Paulus Schoutsen
55992468b0 Update frontend (#5652)
* Return empty result when history date is in future

* Update frontend
2017-01-30 09:12:07 -08:00
Fabian Affolter
87764a51ba Use device_state_attributes() for platforms (#5649)
* Use device_state_attributes() for platforms

* Update test

* Fix lint issue
2017-01-30 16:16:49 +01:00
Pascal Vizeli
ca558f6485 Add unittest for cleanup not validate ssl stuff. (#5643) 2017-01-30 13:09:36 +01:00
Paulus Schoutsen
f1d1f7d032 Update frontend (#5642) 2017-01-30 00:57:13 -08:00
Paulus Schoutsen
c8ff1094f8 Update frontend (#5640) 2017-01-30 00:20:51 -08:00
Paulus Schoutsen
318f3c9625 Update frontend (#5638)
* Ensure fonts get loaded quicker

* Update frontend
2017-01-29 23:26:35 -08:00
Paulus Schoutsen
da4f402ebe Update frontend (#5637) 2017-01-29 18:43:39 -08:00
Paulus Schoutsen
b5047bbaad Prioritize core.js (#5636)
* Prioritize loading app core script

* change app skeleton to be small
2017-01-29 18:36:48 -08:00
Pascal Vizeli
847a5a064d Aiohttp client unittest (#5635)
* add test for cleanup

* add test for mjpeg stream
2017-01-29 16:15:40 -08:00
Pascal Vizeli
261ffbbfea Move part of image_processing tests (#5634)
* Move part of image_processing tests

* fix lint
2017-01-29 14:40:37 -08:00
Martin Hjelmare
24f828d7eb Fix mysensors RGB and W light turn on (#5608)
* RGBW light needs a white value defined.
* Log error if V_RGB is not 6 characters and V_RGBW not 8 characters.
2017-01-29 12:59:13 +01:00
Kyle Hendricks
fddab7f2b4 Fixes the AsusWRT ip neigh regex to handle the possible IPv6 "router" flag (#5605)
See the last line here: http://linux-ip.net/gl/ip-cref/ip-cref-node62.html
2017-01-29 09:37:59 +01:00
David-Leon Pohl
a9325ea663 Fixes: Pilight Switch rejects alphanumeric IDs #5119 (#5601) 2017-01-28 17:49:16 -08:00
Fabian Affolter
5ae5d9b576 Upgrade rpi-rf to 0.9.6 (#5611) 2017-01-28 17:05:24 -08:00
Paulus Schoutsen
e32f933cb6 Version bump to 0.38.0.dev0 2017-01-28 15:13:59 -08:00
289 changed files with 10568 additions and 2641 deletions

View File

@@ -5,6 +5,7 @@ omit =
homeassistant/__main__.py
homeassistant/scripts/*.py
homeassistant/helpers/typing.py
homeassistant/helpers/signal.py
# omit pieces of code that rely on external devices being present
homeassistant/components/apcupsd.py
@@ -116,9 +117,6 @@ omit =
homeassistant/components/knx.py
homeassistant/components/*/knx.py
homeassistant/components/ffmpeg.py
homeassistant/components/*/ffmpeg.py
homeassistant/components/zoneminder.py
homeassistant/components/*/zoneminder.py
@@ -132,6 +130,7 @@ omit =
homeassistant/components/alarm_control_panel/concord232.py
homeassistant/components/alarm_control_panel/nx584.py
homeassistant/components/alarm_control_panel/simplisafe.py
homeassistant/components/apiai.py
homeassistant/components/binary_sensor/arest.py
homeassistant/components/binary_sensor/concord232.py
homeassistant/components/binary_sensor/flic.py
@@ -141,6 +140,7 @@ omit =
homeassistant/components/browser.py
homeassistant/components/camera/amcrest.py
homeassistant/components/camera/bloomsky.py
homeassistant/components/camera/ffmpeg.py
homeassistant/components/camera/foscam.py
homeassistant/components/camera/mjpeg.py
homeassistant/components/camera/rpi_camera.py
@@ -210,7 +210,9 @@ omit =
homeassistant/components/light/piglow.py
homeassistant/components/light/zengge.py
homeassistant/components/lirc.py
homeassistant/components/lock/nuki.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
@@ -226,6 +228,7 @@ omit =
homeassistant/components/media_player/itunes.py
homeassistant/components/media_player/kodi.py
homeassistant/components/media_player/lg_netcast.py
homeassistant/components/media_player/liveboxplaytv.py
homeassistant/components/media_player/mpchc.py
homeassistant/components/media_player/mpd.py
homeassistant/components/media_player/nad.py
@@ -256,6 +259,7 @@ omit =
homeassistant/components/notify/kodi.py
homeassistant/components/notify/lannouncer.py
homeassistant/components/notify/llamalab_automate.py
homeassistant/components/notify/mailgun.py
homeassistant/components/notify/matrix.py
homeassistant/components/notify/message_bird.py
homeassistant/components/notify/nfandroidtv.py
@@ -325,11 +329,13 @@ omit =
homeassistant/components/sensor/nzbget.py
homeassistant/components/sensor/ohmconnect.py
homeassistant/components/sensor/onewire.py
homeassistant/components/sensor/openevse.py
homeassistant/components/sensor/openexchangerates.py
homeassistant/components/sensor/openweathermap.py
homeassistant/components/sensor/pi_hole.py
homeassistant/components/sensor/plex.py
homeassistant/components/sensor/pvoutput.py
homeassistant/components/sensor/qnap.py
homeassistant/components/sensor/sabnzbd.py
homeassistant/components/sensor/scrape.py
homeassistant/components/sensor/sensehat.py
@@ -365,6 +371,7 @@ omit =
homeassistant/components/switch/digitalloggers.py
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

View File

@@ -1,14 +1,22 @@
FROM python:3.5
MAINTAINER Paulus Schoutsen <Paulus@PaulusSchoutsen.nl>
# Uncomment any of the following lines to disable the installation.
#ENV INSTALL_TELLSTICK no
#ENV INSTALL_OPENALPR no
#ENV INSTALL_FFMPEG no
#ENV INSTALL_OPENZWAVE no
#ENV INSTALL_LIBCEC no
#ENV INSTALL_PHANTOMJS no
VOLUME /config
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
# Copy build scripts
COPY script/setup_docker_prereqs script/build_python_openzwave script/build_libcec script/install_phantomjs script/
RUN script/setup_docker_prereqs
COPY virtualization/Docker/ virtualization/Docker/
RUN virtualization/Docker/setup_docker_prereqs
# Install hass component dependencies
COPY requirements_all.txt requirements_all.txt
@@ -18,4 +26,4 @@ RUN pip3 install --no-cache-dir -r requirements_all.txt && \
# Copy source
COPY . .
CMD [ "python", "-m", "homeassistant", "--config", "/config" ]
CMD [ "python", "-m", "homeassistant", "--config", "/config" ]

View File

@@ -26,6 +26,7 @@ from homeassistant.const import EVENT_COMPONENT_LOADED, PLATFORM_FORMAT
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import (
event_decorators, service, config_per_platform, extract_domain_configs)
from homeassistant.helpers.signal import async_register_signal_handling
_LOGGER = logging.getLogger(__name__)
@@ -435,6 +436,7 @@ def async_from_config_dict(config: Dict[str, Any],
yield from hass.async_stop_track_tasks()
async_register_signal_handling(hass)
return hass

View File

@@ -12,14 +12,19 @@ import itertools as it
import logging
import homeassistant.core as ha
import homeassistant.config as conf_util
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.service import extract_entity_ids
from homeassistant.loader import get_component
from homeassistant.const import (
ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE)
ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE,
SERVICE_HOMEASSISTANT_STOP, SERVICE_HOMEASSISTANT_RESTART,
RESTART_EXIT_CODE)
_LOGGER = logging.getLogger(__name__)
SERVICE_RELOAD_CORE_CONFIG = 'reload_core_config'
SERVICE_CHECK_CONFIG = 'check_config'
def is_on(hass, entity_id=None):
@@ -75,6 +80,21 @@ def toggle(hass, entity_id=None, **service_data):
hass.services.call(ha.DOMAIN, SERVICE_TOGGLE, service_data)
def stop(hass):
"""Stop Home Assistant."""
hass.services.call(ha.DOMAIN, SERVICE_HOMEASSISTANT_STOP)
def restart(hass):
"""Stop Home Assistant."""
hass.services.call(ha.DOMAIN, SERVICE_HOMEASSISTANT_RESTART)
def check_config(hass):
"""Check the config files."""
hass.services.call(ha.DOMAIN, SERVICE_CHECK_CONFIG)
def reload_core_config(hass):
"""Reload the core config."""
hass.services.call(ha.DOMAIN, SERVICE_RELOAD_CORE_CONFIG)
@@ -84,7 +104,7 @@ def reload_core_config(hass):
def async_setup(hass, config):
"""Setup general services related to Home Assistant."""
@asyncio.coroutine
def handle_turn_service(service):
def async_handle_turn_service(service):
"""Method to handle calls to homeassistant.turn_on/off."""
entity_ids = extract_entity_ids(hass, service)
@@ -122,18 +142,37 @@ def async_setup(hass, config):
yield from asyncio.wait(tasks, loop=hass.loop)
hass.services.async_register(
ha.DOMAIN, SERVICE_TURN_OFF, handle_turn_service)
ha.DOMAIN, SERVICE_TURN_OFF, async_handle_turn_service)
hass.services.async_register(
ha.DOMAIN, SERVICE_TURN_ON, handle_turn_service)
ha.DOMAIN, SERVICE_TURN_ON, async_handle_turn_service)
hass.services.async_register(
ha.DOMAIN, SERVICE_TOGGLE, handle_turn_service)
ha.DOMAIN, SERVICE_TOGGLE, async_handle_turn_service)
@asyncio.coroutine
def handle_reload_config(call):
"""Service handler for reloading core config."""
from homeassistant.exceptions import HomeAssistantError
from homeassistant import config as conf_util
def async_handle_core_service(call):
"""Service handler for handling core services."""
if call.service == SERVICE_HOMEASSISTANT_STOP:
hass.async_add_job(hass.async_stop())
return
try:
yield from conf_util.async_check_ha_config_file(hass)
except HomeAssistantError:
return
if call.service == SERVICE_HOMEASSISTANT_RESTART:
hass.async_add_job(hass.async_stop(RESTART_EXIT_CODE))
hass.services.async_register(
ha.DOMAIN, SERVICE_HOMEASSISTANT_STOP, async_handle_core_service)
hass.services.async_register(
ha.DOMAIN, SERVICE_HOMEASSISTANT_RESTART, async_handle_core_service)
hass.services.async_register(
ha.DOMAIN, SERVICE_CHECK_CONFIG, async_handle_core_service)
@asyncio.coroutine
def async_handle_reload_config(call):
"""Service handler for reloading core config."""
try:
conf = yield from conf_util.async_hass_config_yaml(hass)
except HomeAssistantError as err:
@@ -144,6 +183,6 @@ def async_setup(hass, config):
hass, conf.get(ha.DOMAIN) or {})
hass.services.async_register(
ha.DOMAIN, SERVICE_RELOAD_CORE_CONFIG, handle_reload_config)
ha.DOMAIN, SERVICE_RELOAD_CORE_CONFIG, async_handle_reload_config)
return True

View File

@@ -11,7 +11,7 @@ from homeassistant.const import (STATE_UNKNOWN,
STATE_ALARM_DISARMED,
STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_AWAY)
from homeassistant.components.wink import WinkDevice
from homeassistant.components.wink import WinkDevice, DOMAIN
_LOGGER = logging.getLogger(__name__)
@@ -24,7 +24,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
import pywink
for camera in pywink.get_cameras():
add_devices([WinkCameraDevice(camera, hass)])
# get_cameras returns multiple device types.
# Only add those that aren't sensors.
try:
camera.capability()
except AttributeError:
_id = camera.object_id() + camera.name()
if _id not in hass.data[DOMAIN]['unique_ids']:
add_devices([WinkCameraDevice(camera, hass)])
class WinkCameraDevice(WinkDevice, alarm.AlarmControlPanel):
@@ -32,7 +39,7 @@ class WinkCameraDevice(WinkDevice, alarm.AlarmControlPanel):
def __init__(self, wink, hass):
"""Initialize the Wink alarm."""
WinkDevice.__init__(self, wink, hass)
super().__init__(wink, hass)
@property
def state(self):

View File

@@ -0,0 +1,275 @@
"""
Support for repeating alerts when conditions are met.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/alert/
"""
import asyncio
from datetime import datetime, timedelta
import logging
import os
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.config import load_yaml_config_file
from homeassistant.const import (CONF_ENTITY_ID, STATE_IDLE, CONF_NAME,
CONF_STATE, STATE_ON, STATE_OFF,
SERVICE_TURN_ON, SERVICE_TURN_OFF,
SERVICE_TOGGLE, ATTR_ENTITY_ID)
from homeassistant.helpers.entity import ToggleEntity
from homeassistant.helpers import service, event
from homeassistant.util.async import run_callback_threadsafe
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'alert'
ENTITY_ID_FORMAT = DOMAIN + '.{}'
CONF_CAN_ACK = 'can_acknowledge'
CONF_NOTIFIERS = 'notifiers'
CONF_REPEAT = 'repeat'
CONF_SKIP_FIRST = 'skip_first'
ALERT_SCHEMA = vol.Schema({
vol.Required(CONF_NAME): 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)]),
vol.Required(CONF_CAN_ACK, default=True): cv.boolean,
vol.Required(CONF_SKIP_FIRST, default=False): cv.boolean,
vol.Required(CONF_NOTIFIERS): cv.ensure_list})
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
cv.slug: ALERT_SCHEMA,
}),
}, extra=vol.ALLOW_EXTRA)
ALERT_SERVICE_SCHEMA = vol.Schema({
vol.Required(ATTR_ENTITY_ID): cv.entity_ids,
})
def is_on(hass, entity_id):
"""Return if the alert is firing and not acknowledged."""
return hass.states.is_state(entity_id, STATE_ON)
def turn_on(hass, entity_id):
"""Reset the alert."""
run_callback_threadsafe(hass.loop, async_turn_on, hass, entity_id)
@callback
def async_turn_on(hass, entity_id):
"""Async reset the alert."""
data = {ATTR_ENTITY_ID: entity_id}
hass.async_add_job(
hass.services.async_call(DOMAIN, SERVICE_TURN_ON, data))
def turn_off(hass, entity_id):
"""Acknowledge alert."""
run_callback_threadsafe(hass.loop, async_turn_off, hass, entity_id)
@callback
def async_turn_off(hass, entity_id):
"""Async acknowledge the alert."""
data = {ATTR_ENTITY_ID: entity_id}
hass.async_add_job(
hass.services.async_call(DOMAIN, SERVICE_TURN_OFF, data))
def toggle(hass, entity_id):
"""Toggle acknowledgement of alert."""
run_callback_threadsafe(hass.loop, async_toggle, hass, entity_id)
@callback
def async_toggle(hass, entity_id):
"""Async toggle acknowledgement of alert."""
data = {ATTR_ENTITY_ID: entity_id}
hass.async_add_job(
hass.services.async_call(DOMAIN, SERVICE_TOGGLE, data))
@asyncio.coroutine
def async_setup(hass, config):
"""Setup alert component."""
alerts = config.get(DOMAIN)
all_alerts = {}
@asyncio.coroutine
def async_handle_alert_service(service_call):
"""Handle calls to alert services."""
alert_ids = service.extract_entity_ids(hass, service_call)
for alert_id in alert_ids:
alert = all_alerts[alert_id]
if service_call.service == SERVICE_TURN_ON:
yield from alert.async_turn_on()
elif service_call.service == SERVICE_TOGGLE:
yield from alert.async_toggle()
else:
yield from alert.async_turn_off()
# 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])
all_alerts[entity.entity_id] = entity
# read descriptions
descriptions = yield from hass.loop.run_in_executor(
None, load_yaml_config_file, os.path.join(
os.path.dirname(__file__), 'services.yaml'))
descriptions = descriptions.get(DOMAIN, {})
# setup service calls
hass.services.async_register(
DOMAIN, SERVICE_TURN_OFF, async_handle_alert_service,
descriptions.get(SERVICE_TURN_OFF), schema=ALERT_SERVICE_SCHEMA)
hass.services.async_register(
DOMAIN, SERVICE_TURN_ON, async_handle_alert_service,
descriptions.get(SERVICE_TURN_ON), schema=ALERT_SERVICE_SCHEMA)
hass.services.async_register(
DOMAIN, SERVICE_TOGGLE, async_handle_alert_service,
descriptions.get(SERVICE_TOGGLE), schema=ALERT_SERVICE_SCHEMA)
tasks = [alert.async_update_ha_state() for alert in all_alerts.values()]
if tasks:
yield from asyncio.wait(tasks, loop=hass.loop)
return True
class Alert(ToggleEntity):
"""Representation of an alert."""
def __init__(self, hass, entity_id, name, watched_entity_id, state,
repeat, skip_first, notifiers, can_ack):
"""Initialize the alert."""
self.hass = hass
self._name = name
self._alert_state = state
self._skip_first = skip_first
self._notifiers = notifiers
self._can_ack = can_ack
self._delay = [timedelta(minutes=val) for val in repeat]
self._next_delay = 0
self._firing = False
self._ack = False
self._cancel = None
self.entity_id = ENTITY_ID_FORMAT.format(entity_id)
event.async_track_state_change(hass, watched_entity_id,
self.watched_entity_change)
@property
def name(self):
"""Return the name of the alert."""
return self._name
@property
def should_poll(self):
"""HASS need not poll these entities."""
return False
@property
def state(self):
"""Return the alert status."""
if self._firing:
if self._ack:
return STATE_OFF
return STATE_ON
return STATE_IDLE
@property
def hidden(self):
"""Hide the alert when it is not firing."""
return not self._can_ack or not self._firing
@asyncio.coroutine
def watched_entity_change(self, entity, from_state, to_state):
"""Determine if the alert should start or stop."""
_LOGGER.debug('Watched entity (%s) has changed.', entity)
if to_state.state == self._alert_state and not self._firing:
yield from self.begin_alerting()
if to_state.state != self._alert_state and self._firing:
yield from self.end_alerting()
@asyncio.coroutine
def begin_alerting(self):
"""Begin the alert procedures."""
_LOGGER.debug('Beginning Alert: %s', self._name)
self._ack = False
self._firing = True
self._next_delay = 0
if not self._skip_first:
yield from self._notify()
else:
yield from self._schedule_notify()
self.hass.async_add_job(self.async_update_ha_state)
@asyncio.coroutine
def end_alerting(self):
"""End the alert procedures."""
_LOGGER.debug('Ending Alert: %s', self._name)
self._cancel()
self._ack = False
self._firing = False
self.hass.async_add_job(self.async_update_ha_state)
@asyncio.coroutine
def _schedule_notify(self):
"""Schedule a notification."""
delay = self._delay[self._next_delay]
next_msg = datetime.now() + delay
self._cancel = \
event.async_track_point_in_time(self.hass, self._notify, next_msg)
self._next_delay = min(self._next_delay + 1, len(self._delay) - 1)
@asyncio.coroutine
def _notify(self, *args):
"""Send the alert notification."""
if not self._firing:
return
if not self._ack:
_LOGGER.info('Alerting: %s', self._name)
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):
"""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):
"""Async Acknowledge alert."""
_LOGGER.debug('Acknowledged Alert: %s', self._name)
self._ack = True
yield from self.async_update_ha_state()
@asyncio.coroutine
def async_toggle(self):
"""Async toggle alert."""
if self._ack:
return self.async_turn_on()
return self.async_turn_off()

View File

@@ -0,0 +1,172 @@
"""
Support for API.AI webhook.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/apiai/
"""
import asyncio
import copy
import logging
import voluptuous as vol
from homeassistant.const import PROJECT_NAME, HTTP_BAD_REQUEST
from homeassistant.helpers import template, script, config_validation as cv
from homeassistant.components.http import HomeAssistantView
_LOGGER = logging.getLogger(__name__)
INTENTS_API_ENDPOINT = '/api/apiai'
CONF_INTENTS = 'intents'
CONF_SPEECH = 'speech'
CONF_ACTION = 'action'
CONF_ASYNC_ACTION = 'async_action'
DEFAULT_CONF_ASYNC_ACTION = False
DOMAIN = 'apiai'
DEPENDENCIES = ['http']
CONFIG_SCHEMA = vol.Schema({
DOMAIN: {
CONF_INTENTS: {
cv.string: {
vol.Optional(CONF_SPEECH): cv.template,
vol.Optional(CONF_ACTION): cv.SCRIPT_SCHEMA,
vol.Optional(CONF_ASYNC_ACTION,
default=DEFAULT_CONF_ASYNC_ACTION): cv.boolean
}
}
}
}, extra=vol.ALLOW_EXTRA)
def setup(hass, config):
"""Activate API.AI component."""
intents = config[DOMAIN].get(CONF_INTENTS, {})
hass.http.register_view(ApiaiIntentsView(hass, intents))
return True
class ApiaiIntentsView(HomeAssistantView):
"""Handle API.AI requests."""
url = INTENTS_API_ENDPOINT
name = 'api:apiai'
def __init__(self, hass, intents):
"""Initialize API.AI view."""
super().__init__()
self.hass = hass
intents = copy.deepcopy(intents)
template.attach(hass, intents)
for name, intent in intents.items():
if CONF_ACTION in intent:
intent[CONF_ACTION] = script.Script(
hass, intent[CONF_ACTION], "Apiai intent {}".format(name))
self.intents = intents
@asyncio.coroutine
def post(self, request):
"""Handle API.AI."""
data = yield from request.json()
_LOGGER.debug('Received Apiai request: %s', data)
req = data.get('result')
if req is None:
_LOGGER.error('Received invalid data from Apiai: %s', data)
return self.json_message('Expected result value not received',
HTTP_BAD_REQUEST)
action_incomplete = req['actionIncomplete']
if action_incomplete:
return None
# use intent to no mix HASS actions with this parameter
intent = req.get('action')
parameters = req.get('parameters')
# contexts = req.get('contexts')
response = ApiaiResponse(parameters)
# Default Welcome Intent
# Maybe is better to handle this in api.ai directly?
#
# if intent == 'input.welcome':
# response.add_speech(
# "Hello, and welcome to the future. How may I help?")
# return self.json(response)
if intent == "":
_LOGGER.warning('Received intent with empty action')
response.add_speech(
"You have not defined an action in your api.ai intent.")
return self.json(response)
config = self.intents.get(intent)
if config is None:
_LOGGER.warning('Received unknown intent %s', intent)
response.add_speech(
"Intent '%s' is not yet configured within Home Assistant." %
intent)
return self.json(response)
speech = config.get(CONF_SPEECH)
action = config.get(CONF_ACTION)
async_action = config.get(CONF_ASYNC_ACTION)
if action is not None:
# API.AI expects a response in less than 5s
if async_action:
# Do not wait for the action to be executed.
# Needed if the action will take longer than 5s to execute
self.hass.async_add_job(action.async_run(response.parameters))
else:
# Wait for the action to be executed so we can use results to
# render the answer
yield from action.async_run(response.parameters)
# pylint: disable=unsubscriptable-object
if speech is not None:
response.add_speech(speech)
return self.json(response)
class ApiaiResponse(object):
"""Help generating the response for API.AI."""
def __init__(self, parameters):
"""Initialize the response."""
self.speech = None
self.parameters = {}
# Parameter names replace '.' and '-' for '_'
for key, value in parameters.items():
underscored_key = key.replace('.', '_').replace('-', '_')
self.parameters[underscored_key] = value
def add_speech(self, text):
"""Add speech to the response."""
assert self.speech is None
if isinstance(text, template.Template):
text = text.async_render(self.parameters)
self.speech = text
def as_dict(self):
"""Return response in an API.AI valid dict."""
return {
'speech': self.speech,
'displayText': self.speech,
'source': PROJECT_NAME,
}

View File

@@ -1,279 +0,0 @@
"""
Provides a binary sensor which is a collection of ffmpeg tools.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.ffmpeg/
"""
import asyncio
import logging
import os
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA, DOMAIN)
from homeassistant.components.ffmpeg import (
DATA_FFMPEG, CONF_INPUT, CONF_OUTPUT, CONF_EXTRA_ARGUMENTS)
from homeassistant.config import load_yaml_config_file
from homeassistant.const import (
EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_START, CONF_NAME,
ATTR_ENTITY_ID)
DEPENDENCIES = ['ffmpeg']
_LOGGER = logging.getLogger(__name__)
SERVICE_START = 'ffmpeg_start'
SERVICE_STOP = 'ffmpeg_stop'
SERVICE_RESTART = 'ffmpeg_restart'
DATA_FFMPEG_DEVICE = 'ffmpeg_binary_sensor'
FFMPEG_SENSOR_NOISE = 'noise'
FFMPEG_SENSOR_MOTION = 'motion'
MAP_FFMPEG_BIN = [
FFMPEG_SENSOR_NOISE,
FFMPEG_SENSOR_MOTION
]
CONF_INITIAL_STATE = 'initial_state'
CONF_TOOL = 'tool'
CONF_PEAK = 'peak'
CONF_DURATION = 'duration'
CONF_RESET = 'reset'
CONF_CHANGES = 'changes'
CONF_REPEAT = 'repeat'
CONF_REPEAT_TIME = 'repeat_time'
DEFAULT_NAME = 'FFmpeg'
DEFAULT_INIT_STATE = True
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_TOOL): vol.In(MAP_FFMPEG_BIN),
vol.Required(CONF_INPUT): cv.string,
vol.Optional(CONF_INITIAL_STATE, default=DEFAULT_INIT_STATE): cv.boolean,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_EXTRA_ARGUMENTS): cv.string,
vol.Optional(CONF_OUTPUT): cv.string,
vol.Optional(CONF_PEAK, default=-30): vol.Coerce(int),
vol.Optional(CONF_DURATION, default=1):
vol.All(vol.Coerce(int), vol.Range(min=1)),
vol.Optional(CONF_RESET, default=10):
vol.All(vol.Coerce(int), vol.Range(min=1)),
vol.Optional(CONF_CHANGES, default=10):
vol.All(vol.Coerce(float), vol.Range(min=0, max=99)),
vol.Optional(CONF_REPEAT, default=0):
vol.All(vol.Coerce(int), vol.Range(min=0)),
vol.Optional(CONF_REPEAT_TIME, default=0):
vol.All(vol.Coerce(int), vol.Range(min=0)),
})
SERVICE_FFMPEG_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
})
def restart(hass, entity_id=None):
"""Restart a ffmpeg process on entity."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_RESTART, data)
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Create the binary sensor."""
from haffmpeg import SensorNoise, SensorMotion
# check source
if not hass.data[DATA_FFMPEG].async_run_test(config.get(CONF_INPUT)):
return
# generate sensor object
if config.get(CONF_TOOL) == FFMPEG_SENSOR_NOISE:
entity = FFmpegNoise(hass, SensorNoise, config)
else:
entity = FFmpegMotion(hass, SensorMotion, config)
@asyncio.coroutine
def async_shutdown(event):
"""Stop ffmpeg."""
yield from entity.async_shutdown_ffmpeg()
hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_STOP, async_shutdown)
# start on startup
if config.get(CONF_INITIAL_STATE):
@asyncio.coroutine
def async_start(event):
"""Start ffmpeg."""
yield from entity.async_start_ffmpeg()
yield from entity.async_update_ha_state()
hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_START, async_start)
# add to system
yield from async_add_devices([entity])
# exists service?
if hass.services.has_service(DOMAIN, SERVICE_RESTART):
hass.data[DATA_FFMPEG_DEVICE].append(entity)
return
hass.data[DATA_FFMPEG_DEVICE] = [entity]
descriptions = yield from hass.loop.run_in_executor(
None, load_yaml_config_file,
os.path.join(os.path.dirname(__file__), 'services.yaml'))
# register service
@asyncio.coroutine
def async_service_handle(service):
"""Handle service binary_sensor.ffmpeg_restart."""
entity_ids = service.data.get('entity_id')
if entity_ids:
_devices = [device for device in hass.data[DATA_FFMPEG_DEVICE]
if device.entity_id in entity_ids]
else:
_devices = hass.data[DATA_FFMPEG_DEVICE]
tasks = []
for device in _devices:
if service.service == SERVICE_START:
tasks.append(device.async_start_ffmpeg())
elif service.service == SERVICE_STOP:
tasks.append(device.async_shutdown_ffmpeg())
else:
tasks.append(device.async_restart_ffmpeg())
if tasks:
yield from asyncio.wait(tasks, loop=hass.loop)
hass.services.async_register(
DOMAIN, SERVICE_START, async_service_handle,
descriptions.get(SERVICE_START), schema=SERVICE_FFMPEG_SCHEMA)
hass.services.async_register(
DOMAIN, SERVICE_STOP, async_service_handle,
descriptions.get(SERVICE_STOP), schema=SERVICE_FFMPEG_SCHEMA)
hass.services.async_register(
DOMAIN, SERVICE_RESTART, async_service_handle,
descriptions.get(SERVICE_RESTART), schema=SERVICE_FFMPEG_SCHEMA)
class FFmpegBinarySensor(BinarySensorDevice):
"""A binary sensor which use ffmpeg for noise detection."""
def __init__(self, hass, ffobj, config):
"""Constructor for binary sensor noise detection."""
self._manager = hass.data[DATA_FFMPEG]
self._state = False
self._config = config
self._name = config.get(CONF_NAME)
self._ffmpeg = ffobj(
self._manager.binary, hass.loop, self._async_callback)
def _async_callback(self, state):
"""HA-FFmpeg callback for noise detection."""
self._state = state
self.hass.async_add_job(self.async_update_ha_state())
def async_start_ffmpeg(self):
"""Start a FFmpeg instance.
This method must be run in the event loop and returns a coroutine.
"""
raise NotImplementedError()
def async_shutdown_ffmpeg(self):
"""For STOP event to shutdown ffmpeg.
This method must be run in the event loop and returns a coroutine.
"""
return self._ffmpeg.close()
@asyncio.coroutine
def async_restart_ffmpeg(self):
"""Restart processing."""
yield from self.async_shutdown_ffmpeg()
yield from self.async_start_ffmpeg()
@property
def is_on(self):
"""True if the binary sensor is on."""
return self._state
@property
def should_poll(self):
"""Return True if entity has to be polled for state."""
return False
@property
def name(self):
"""Return the name of the entity."""
return self._name
@property
def available(self):
"""Return True if entity is available."""
return self._ffmpeg.is_running
class FFmpegNoise(FFmpegBinarySensor):
"""A binary sensor which use ffmpeg for noise detection."""
def async_start_ffmpeg(self):
"""Start a FFmpeg instance.
This method must be run in the event loop and returns a coroutine.
"""
# init config
self._ffmpeg.set_options(
time_duration=self._config.get(CONF_DURATION),
time_reset=self._config.get(CONF_RESET),
peak=self._config.get(CONF_PEAK),
)
# run
return self._ffmpeg.open_sensor(
input_source=self._config.get(CONF_INPUT),
output_dest=self._config.get(CONF_OUTPUT),
extra_cmd=self._config.get(CONF_EXTRA_ARGUMENTS),
)
@property
def sensor_class(self):
"""Return the class of this sensor, from SENSOR_CLASSES."""
return "sound"
class FFmpegMotion(FFmpegBinarySensor):
"""A binary sensor which use ffmpeg for noise detection."""
def async_start_ffmpeg(self):
"""Start a FFmpeg instance.
This method must be run in the event loop and returns a coroutine.
"""
# init config
self._ffmpeg.set_options(
time_reset=self._config.get(CONF_RESET),
time_repeat=self._config.get(CONF_REPEAT_TIME),
repeat=self._config.get(CONF_REPEAT),
changes=self._config.get(CONF_CHANGES),
)
# run
return self._ffmpeg.open_sensor(
input_source=self._config.get(CONF_INPUT),
extra_cmd=self._config.get(CONF_EXTRA_ARGUMENTS),
)
@property
def sensor_class(self):
"""Return the class of this sensor, from SENSOR_CLASSES."""
return "motion"

View File

@@ -0,0 +1,127 @@
"""
Provides a binary sensor which is a collection of ffmpeg tools.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.ffmpeg_motion/
"""
import asyncio
import logging
import voluptuous as vol
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA)
from homeassistant.components.ffmpeg import (
FFmpegBase, DATA_FFMPEG, CONF_INPUT, CONF_EXTRA_ARGUMENTS,
CONF_INITIAL_STATE)
from homeassistant.const import CONF_NAME
DEPENDENCIES = ['ffmpeg']
_LOGGER = logging.getLogger(__name__)
CONF_RESET = 'reset'
CONF_CHANGES = 'changes'
CONF_REPEAT = 'repeat'
CONF_REPEAT_TIME = 'repeat_time'
DEFAULT_NAME = 'FFmpeg Motion'
DEFAULT_INIT_STATE = True
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_INPUT): cv.string,
vol.Optional(CONF_INITIAL_STATE, default=DEFAULT_INIT_STATE): cv.boolean,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_EXTRA_ARGUMENTS): cv.string,
vol.Optional(CONF_RESET, default=10):
vol.All(vol.Coerce(int), vol.Range(min=1)),
vol.Optional(CONF_CHANGES, default=10):
vol.All(vol.Coerce(float), vol.Range(min=0, max=99)),
vol.Inclusive(CONF_REPEAT, 'repeat'):
vol.All(vol.Coerce(int), vol.Range(min=1)),
vol.Inclusive(CONF_REPEAT_TIME, 'repeat'):
vol.All(vol.Coerce(int), vol.Range(min=1)),
})
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Create the binary sensor."""
manager = hass.data[DATA_FFMPEG]
# check source
if not manager.async_run_test(config.get(CONF_INPUT)):
return
# generate sensor object
entity = FFmpegMotion(hass, manager, config)
# add to system
manager.async_register_device(entity)
yield from async_add_devices([entity])
class FFmpegBinarySensor(FFmpegBase, BinarySensorDevice):
"""A binary sensor which use ffmpeg for noise detection."""
def __init__(self, hass, config):
"""Constructor for binary sensor noise detection."""
super().__init__(config.get(CONF_INITIAL_STATE))
self._state = False
self._config = config
self._name = config.get(CONF_NAME)
@callback
def _async_callback(self, state):
"""HA-FFmpeg callback for noise detection."""
self._state = state
self.hass.async_add_job(self.async_update_ha_state())
@property
def is_on(self):
"""True if the binary sensor is on."""
return self._state
@property
def name(self):
"""Return the name of the entity."""
return self._name
class FFmpegMotion(FFmpegBinarySensor):
"""A binary sensor which use ffmpeg for noise detection."""
def __init__(self, hass, manager, config):
"""Initialize ffmpeg motion binary sensor."""
from haffmpeg import SensorMotion
super().__init__(hass, config)
self.ffmpeg = SensorMotion(
manager.binary, hass.loop, self._async_callback)
def async_start_ffmpeg(self):
"""Start a FFmpeg instance.
This method must be run in the event loop and returns a coroutine.
"""
# init config
self.ffmpeg.set_options(
time_reset=self._config.get(CONF_RESET),
time_repeat=self._config.get(CONF_REPEAT_TIME, 0),
repeat=self._config.get(CONF_REPEAT, 0),
changes=self._config.get(CONF_CHANGES),
)
# run
return self.ffmpeg.open_sensor(
input_source=self._config.get(CONF_INPUT),
extra_cmd=self._config.get(CONF_EXTRA_ARGUMENTS),
)
@property
def sensor_class(self):
"""Return the class of this sensor, from SENSOR_CLASSES."""
return "motion"

View File

@@ -0,0 +1,96 @@
"""
Provides a binary sensor which is a collection of ffmpeg tools.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.ffmpeg_noise/
"""
import asyncio
import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.binary_sensor import PLATFORM_SCHEMA
from homeassistant.components.binary_sensor.ffmpeg_motion import (
FFmpegBinarySensor)
from homeassistant.components.ffmpeg import (
DATA_FFMPEG, CONF_INPUT, CONF_OUTPUT, CONF_EXTRA_ARGUMENTS,
CONF_INITIAL_STATE)
from homeassistant.const import CONF_NAME
DEPENDENCIES = ['ffmpeg']
_LOGGER = logging.getLogger(__name__)
CONF_PEAK = 'peak'
CONF_DURATION = 'duration'
CONF_RESET = 'reset'
DEFAULT_NAME = 'FFmpeg Noise'
DEFAULT_INIT_STATE = True
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_INPUT): cv.string,
vol.Optional(CONF_INITIAL_STATE, default=DEFAULT_INIT_STATE): cv.boolean,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_EXTRA_ARGUMENTS): cv.string,
vol.Optional(CONF_OUTPUT): cv.string,
vol.Optional(CONF_PEAK, default=-30): vol.Coerce(int),
vol.Optional(CONF_DURATION, default=1):
vol.All(vol.Coerce(int), vol.Range(min=1)),
vol.Optional(CONF_RESET, default=10):
vol.All(vol.Coerce(int), vol.Range(min=1)),
})
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Create the binary sensor."""
manager = hass.data[DATA_FFMPEG]
# check source
if not manager.async_run_test(config.get(CONF_INPUT)):
return
# generate sensor object
entity = FFmpegNoise(hass, manager, config)
# add to system
manager.async_register_device(entity)
yield from async_add_devices([entity])
class FFmpegNoise(FFmpegBinarySensor):
"""A binary sensor which use ffmpeg for noise detection."""
def __init__(self, hass, manager, config):
"""Initialize ffmpeg noise binary sensor."""
from haffmpeg import SensorNoise
super().__init__(hass, config)
self.ffmpeg = SensorNoise(
manager.binary, hass.loop, self._async_callback)
def async_start_ffmpeg(self):
"""Start a FFmpeg instance.
This method must be run in the event loop and returns a coroutine.
"""
# init config
self.ffmpeg.set_options(
time_duration=self._config.get(CONF_DURATION),
time_reset=self._config.get(CONF_RESET),
peak=self._config.get(CONF_PEAK),
)
# run
return self.ffmpeg.open_sensor(
input_source=self._config.get(CONF_INPUT),
output_dest=self._config.get(CONF_OUTPUT),
extra_cmd=self._config.get(CONF_EXTRA_ARGUMENTS),
)
@property
def sensor_class(self):
"""Return the class of this sensor, from SENSOR_CLASSES."""
return "sound"

View File

@@ -36,7 +36,10 @@ PLATFORM_SCHEMA = mqtt.MQTT_RO_PLATFORM_SCHEMA.extend({
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the MQTT binary sensor."""
"""Set up the MQTT binary sensor."""
if discovery_info is not None:
config = PLATFORM_SCHEMA(discovery_info)
value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template.hass = hass

View File

@@ -26,8 +26,6 @@ WELCOME_SENSOR_TYPES = {
"Someone known": "motion",
"Someone unknown": "motion",
"Motion": "motion",
"Tag Vibration": 'vibration',
"Tag Open": 'opening'
}
PRESENCE_SENSOR_TYPES = {
"Outdoor motion": "motion",
@@ -35,11 +33,16 @@ PRESENCE_SENSOR_TYPES = {
"Outdoor animal": "motion",
"Outdoor vehicle": "motion"
}
TAG_SENSOR_TYPES = {
"Tag Vibration": 'vibration',
"Tag Open": 'opening'
}
CONF_HOME = 'home'
CONF_CAMERAS = 'cameras'
CONF_WELCOME_SENSORS = 'welcome_sensors'
CONF_PRESENCE_SENSORS = 'presence_sensors'
CONF_TAG_SENSORS = 'tag_sensors'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_HOME): cv.string,
@@ -78,6 +81,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
CONF_WELCOME_SENSORS, WELCOME_SENSOR_TYPES)
presence_sensors = config.get(
CONF_PRESENCE_SENSORS, PRESENCE_SENSOR_TYPES)
tag_sensors = config.get(CONF_TAG_SENSORS, TAG_SENSOR_TYPES)
for camera_name in data.get_camera_names():
camera_type = data.get_camera_type(camera=camera_name, home=home)
@@ -103,13 +107,13 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
variable)])
for module_name in data.get_module_names(camera_name):
for variable in welcome_sensors:
if variable in ('Tag Vibration', 'Tag Open'):
add_devices([NetatmoBinarySensor(data, camera_name,
module_name, home,
timeout, offset,
camera_type,
variable)])
for variable in tag_sensors:
camera_type = None
add_devices([NetatmoBinarySensor(data, camera_name,
module_name, home,
timeout, offset,
camera_type,
variable)])
class NetatmoBinarySensor(BinarySensorDevice):
@@ -157,7 +161,7 @@ class NetatmoBinarySensor(BinarySensorDevice):
elif self._cameratype == "NOC":
return PRESENCE_SENSOR_TYPES.get(self._sensor_name)
else:
return None
return TAG_SENSOR_TYPES.get(self._sensor_name)
@property
def is_on(self):
@@ -184,8 +188,6 @@ class NetatmoBinarySensor(BinarySensorDevice):
self._data.camera_data.motionDetected(self._home,
self._camera_name,
self._timeout*60)
else:
return None
elif self._cameratype == "NOC":
if self._sensor_name == "Outdoor motion":
self._state =\
@@ -206,9 +208,7 @@ class NetatmoBinarySensor(BinarySensorDevice):
self._data.camera_data.carDetected(self._home,
self._camera_name,
self._offset)
else:
return None
elif self._sensor_name == "Tag Vibration":
if self._sensor_name == "Tag Vibration":
self._state =\
self._data.camera_data.moduleMotionDetected(self._home,
self._module_name,

View File

@@ -1,25 +0,0 @@
# Describes the format for available binary_sensor services
ffmpeg_start:
description: Send a start command to a ffmpeg based sensor.
fields:
entity_id:
description: Name(s) of entites that will start. Platform dependent.
example: 'binary_sensor.ffmpeg_noise'
ffmpeg_stop:
description: Send a stop command to a ffmpeg based sensor.
fields:
entity_id:
description: Name(s) of entites that will stop. Platform dependent.
example: 'binary_sensor.ffmpeg_noise'
ffmpeg_restart:
description: Send a restart command to a ffmpeg based sensor.
fields:
entity_id:
description: Name(s) of entites that will restart. Platform dependent.
example: 'binary_sensor.ffmpeg_noise'

View File

@@ -118,7 +118,8 @@ class BinarySensorTemplate(BinarySensorDevice):
if ex.args and ex.args[0].startswith(
"UndefinedError: 'None' has no attribute"):
# Common during HA startup - so just a warning
_LOGGER.warning(ex)
_LOGGER.warning('Could not render template %s,'
' the state is unknown.', self._name)
return
_LOGGER.error(ex)
_LOGGER.error('Could not render template %s: %s', self._name, ex)
self._state = False

View File

@@ -4,11 +4,14 @@ Support for Wink binary sensors.
For more details about this platform, please refer to the documentation at
at https://home-assistant.io/components/binary_sensor.wink/
"""
import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.sensor.wink import WinkDevice
from homeassistant.components.wink import WinkDevice, DOMAIN
from homeassistant.helpers.entity import Entity
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['wink']
# These are the available sensors mapped to binary_sensor class
@@ -17,11 +20,14 @@ SENSOR_TYPES = {
"brightness": "light",
"vibration": "vibration",
"loudness": "sound",
"noise": "sound",
"capturing_audio": "sound",
"liquid_detected": "moisture",
"motion": "motion",
"presence": "occupancy",
"co_detected": "gas",
"smoke_detected": "smoke"
"smoke_detected": "smoke",
"capturing_video": None
}
@@ -30,26 +36,54 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
import pywink
for sensor in pywink.get_sensors():
if sensor.capability() in SENSOR_TYPES:
add_devices([WinkBinarySensorDevice(sensor, hass)])
_id = sensor.object_id() + sensor.name()
if _id not in hass.data[DOMAIN]['unique_ids']:
if sensor.capability() in SENSOR_TYPES:
add_devices([WinkBinarySensorDevice(sensor, hass)])
for key in pywink.get_keys():
add_devices([WinkBinarySensorDevice(key, hass)])
_id = key.object_id() + key.name()
if _id not in hass.data[DOMAIN]['unique_ids']:
add_devices([WinkBinarySensorDevice(key, hass)])
for sensor in pywink.get_smoke_and_co_detectors():
add_devices([WinkBinarySensorDevice(sensor, hass)])
_id = sensor.object_id() + sensor.name()
if _id not in hass.data[DOMAIN]['unique_ids']:
add_devices([WinkSmokeDetector(sensor, hass)])
for hub in pywink.get_hubs():
add_devices([WinkHub(hub, hass)])
_id = hub.object_id() + hub.name()
if _id not in hass.data[DOMAIN]['unique_ids']:
add_devices([WinkHub(hub, hass)])
for remote in pywink.get_remotes():
add_devices([WinkRemote(remote, hass)])
_id = remote.object_id() + remote.name()
if _id not in hass.data[DOMAIN]['unique_ids']:
add_devices([WinkRemote(remote, hass)])
for button in pywink.get_buttons():
add_devices([WinkButton(button, hass)])
_id = button.object_id() + button.name()
if _id not in hass.data[DOMAIN]['unique_ids']:
add_devices([WinkButton(button, hass)])
for gang in pywink.get_gangs():
add_devices([WinkGang(gang, hass)])
_id = gang.object_id() + gang.name()
if _id not in hass.data[DOMAIN]['unique_ids']:
add_devices([WinkGang(gang, hass)])
for door_bell_sensor in pywink.get_door_bells():
_id = door_bell_sensor.object_id() + door_bell_sensor.name()
if _id not in hass.data[DOMAIN]['unique_ids']:
add_devices([WinkBinarySensorDevice(door_bell_sensor, hass)])
for camera_sensor in pywink.get_cameras():
_id = camera_sensor.object_id() + camera_sensor.name()
if _id not in hass.data[DOMAIN]['unique_ids']:
try:
if camera_sensor.capability() in SENSOR_TYPES:
add_devices([WinkBinarySensorDevice(camera_sensor, hass)])
except AttributeError:
_LOGGER.info("Device isn't a sensor, skipping.")
class WinkBinarySensorDevice(WinkDevice, BinarySensorDevice, Entity):
@@ -58,8 +92,14 @@ class WinkBinarySensorDevice(WinkDevice, BinarySensorDevice, Entity):
def __init__(self, wink, hass):
"""Initialize the Wink binary sensor."""
super().__init__(wink, hass)
self._unit_of_measurement = self.wink.unit()
self.capability = self.wink.capability()
try:
self._unit_of_measurement = self.wink.unit()
except AttributeError:
self._unit_of_measurement = None
try:
self.capability = self.wink.capability()
except AttributeError:
self.capability = None
@property
def is_on(self):
@@ -72,17 +112,27 @@ class WinkBinarySensorDevice(WinkDevice, BinarySensorDevice, Entity):
return SENSOR_TYPES.get(self.capability)
class WinkHub(WinkDevice, BinarySensorDevice, Entity):
"""Representation of a Wink Hub."""
class WinkSmokeDetector(WinkBinarySensorDevice):
"""Representation of a Wink Smoke detector."""
def __init(self, wink, hass):
"""Initialize the hub sensor."""
WinkDevice.__init__(self, wink, hass)
def __init__(self, wink, hass):
"""Initialize the Wink binary sensor."""
super().__init__(wink, hass)
@property
def is_on(self):
"""Return true if the binary sensor is on."""
return self.wink.state()
def device_state_attributes(self):
"""Return the state attributes."""
return {
'test_activated': self.wink.test_activated()
}
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):
@@ -93,17 +143,12 @@ class WinkHub(WinkDevice, BinarySensorDevice, Entity):
}
class WinkRemote(WinkDevice, BinarySensorDevice, Entity):
class WinkRemote(WinkBinarySensorDevice):
"""Representation of a Wink Lutron Connected bulb remote."""
def __init(self, wink, hass):
"""Initialize the hub sensor."""
WinkDevice.__init__(self, wink, hass)
@property
def is_on(self):
"""Return true if the binary sensor is on."""
return self.wink.state()
def __init__(self, wink, hass):
"""Initialize the Wink binary sensor."""
super().__init__(wink, hass)
@property
def device_state_attributes(self):
@@ -115,18 +160,18 @@ class WinkRemote(WinkDevice, BinarySensorDevice, Entity):
'button_down_pressed': self.wink.button_down_pressed()
}
@property
def sensor_class(self):
"""Return the class of this sensor, from SENSOR_CLASSES."""
return None
class WinkButton(WinkDevice, BinarySensorDevice, Entity):
class WinkButton(WinkBinarySensorDevice):
"""Representation of a Wink Relay button."""
def __init(self, wink, hass):
"""Initialize the hub sensor."""
WinkDevice.__init__(self, wink, hass)
@property
def is_on(self):
"""Return true if the binary sensor is on."""
return self.wink.state()
def __init__(self, wink, hass):
"""Initialize the Wink binary sensor."""
super().__init__(wink, hass)
@property
def device_state_attributes(self):
@@ -137,12 +182,12 @@ class WinkButton(WinkDevice, BinarySensorDevice, Entity):
}
class WinkGang(WinkDevice, BinarySensorDevice, Entity):
class WinkGang(WinkBinarySensorDevice):
"""Representation of a Wink Relay gang."""
def __init(self, wink, hass):
"""Initialize the gang sensor."""
WinkDevice.__init__(self, wink, hass)
def __init__(self, wink, hass):
"""Initialize the Wink binary sensor."""
super().__init__(wink, hass)
@property
def is_on(self):

View File

@@ -9,6 +9,7 @@ import datetime
import homeassistant.util.dt as dt_util
from homeassistant.helpers.event import track_point_in_time
from homeassistant.components import zwave
from homeassistant.components.zwave import workaround
from homeassistant.components.binary_sensor import (
DOMAIN,
BinarySensorDevice)
@@ -16,22 +17,6 @@ from homeassistant.components.binary_sensor import (
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = []
PHILIO = 0x013c
PHILIO_SLIM_SENSOR = 0x0002
PHILIO_SLIM_SENSOR_MOTION = (PHILIO, PHILIO_SLIM_SENSOR, 0)
PHILIO_3_IN_1_SENSOR_GEN_4 = 0x000d
PHILIO_3_IN_1_SENSOR_GEN_4_MOTION = (PHILIO, PHILIO_3_IN_1_SENSOR_GEN_4, 0)
WENZHOU = 0x0118
WENZHOU_SLIM_SENSOR_MOTION = (WENZHOU, PHILIO_SLIM_SENSOR, 0)
WORKAROUND_NO_OFF_EVENT = 'trigger_no_off_event'
DEVICE_MAPPINGS = {
PHILIO_SLIM_SENSOR_MOTION: WORKAROUND_NO_OFF_EVENT,
PHILIO_3_IN_1_SENSOR_GEN_4_MOTION: WORKAROUND_NO_OFF_EVENT,
WENZHOU_SLIM_SENSOR_MOTION: WORKAROUND_NO_OFF_EVENT,
}
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Z-Wave platform for binary sensors."""
@@ -42,23 +27,19 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
value = node.values[discovery_info[zwave.const.ATTR_VALUE_ID]]
value.set_change_verified(False)
# Make sure that we have values for the key before converting to int
if (value.node.manufacturer_id.strip() and
value.node.product_id.strip()):
specific_sensor_key = (int(value.node.manufacturer_id, 16),
int(value.node.product_id, 16),
value.index)
device_mapping = workaround.get_device_mapping(value)
if device_mapping == workaround.WORKAROUND_NO_OFF_EVENT:
# Default the multiplier to 4
re_arm_multiplier = (zwave.get_config_value(value.node, 9) or 4)
add_devices([
ZWaveTriggerSensor(value, "motion",
hass, re_arm_multiplier * 8)
])
return
if specific_sensor_key in DEVICE_MAPPINGS:
if DEVICE_MAPPINGS[specific_sensor_key] == WORKAROUND_NO_OFF_EVENT:
# Default the multiplier to 4
re_arm_multiplier = (zwave.get_config_value(value.node,
9) or 4)
add_devices([
ZWaveTriggerSensor(value, "motion",
hass, re_arm_multiplier * 8)
])
return
if workaround.get_device_component_mapping(value) == DOMAIN:
add_devices([ZWaveBinarySensor(value, None)])
return
if value.command_class == zwave.const.COMMAND_CLASS_SENSOR_BINARY:
add_devices([ZWaveBinarySensor(value, None)])

View File

@@ -18,7 +18,7 @@ from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import (
async_get_clientsession, async_aiohttp_proxy_stream)
REQUIREMENTS = ['amcrest==1.1.3']
REQUIREMENTS = ['amcrest==1.1.4']
_LOGGER = logging.getLogger(__name__)

View File

@@ -9,6 +9,7 @@ import logging
import requests
import voluptuous as vol
from homeassistant.const import CONF_VERIFY_SSL
from homeassistant.components.netatmo import CameraData
from homeassistant.components.camera import (Camera, PLATFORM_SCHEMA)
from homeassistant.loader import get_component
@@ -22,6 +23,7 @@ CONF_HOME = 'home'
CONF_CAMERAS = 'cameras'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_VERIFY_SSL, default=True): cv.boolean,
vol.Optional(CONF_HOME): cv.string,
vol.Optional(CONF_CAMERAS, default=[]):
vol.All(cv.ensure_list, [cv.string]),
@@ -33,6 +35,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup access to Netatmo cameras."""
netatmo = get_component('netatmo')
home = config.get(CONF_HOME)
verify_ssl = config.get(CONF_VERIFY_SSL, True)
import lnetatmo
try:
data = CameraData(netatmo.NETATMO_AUTH, home)
@@ -42,7 +45,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
if config[CONF_CAMERAS] != [] and \
camera_name not in config[CONF_CAMERAS]:
continue
add_devices([NetatmoCamera(data, camera_name, home, camera_type)])
add_devices([NetatmoCamera(data, camera_name, home,
camera_type, verify_ssl)])
except lnetatmo.NoDevice:
return None
@@ -50,11 +54,12 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class NetatmoCamera(Camera):
"""Representation of the images published from a Netatmo camera."""
def __init__(self, data, camera_name, home, camera_type):
def __init__(self, data, camera_name, home, camera_type, verify_ssl):
"""Setup for access to the Netatmo camera images."""
super(NetatmoCamera, self).__init__()
self._data = data
self._camera_name = camera_name
self._verify_ssl = verify_ssl
if home:
self._name = home + ' / ' + camera_name
else:
@@ -74,11 +79,17 @@ class NetatmoCamera(Camera):
if self._localurl:
response = requests.get('{0}/live/snapshot_720.jpg'.format(
self._localurl), timeout=10)
else:
elif self._vpnurl:
response = requests.get('{0}/live/snapshot_720.jpg'.format(
self._vpnurl), timeout=10)
self._vpnurl), timeout=10, verify=self._verify_ssl)
else:
_LOGGER.error('Welcome VPN url is None')
self._data.update()
(self._vpnurl, self._localurl) = \
self._data.camera_data.cameraUrls(camera=self._camera_name)
return None
except requests.exceptions.RequestException as error:
_LOGGER.error('Welcome VPN url changed: %s', error)
_LOGGER.error('Welcome url changed: %s', error)
self._data.update()
(self._vpnurl, self._localurl) = \
self._data.camera_data.cameraUrls(camera=self._camera_name)

View File

@@ -122,8 +122,6 @@ def set_away_mode(hass, away_mode, entity_id=None):
if entity_id:
data[ATTR_ENTITY_ID] = entity_id
_LOGGER.warning(
'This service has been deprecated; use climate.set_hold_mode')
hass.services.call(DOMAIN, SERVICE_SET_AWAY_MODE, data)
@@ -243,14 +241,6 @@ def async_setup(hass, config):
away_mode = service.data.get(ATTR_AWAY_MODE)
if away_mode is None:
_LOGGER.error(
"Received call to %s without attribute %s",
SERVICE_SET_AWAY_MODE, ATTR_AWAY_MODE)
return
_LOGGER.warning(
'This service has been deprecated; use climate.set_hold_mode')
for climate in target_climate:
if away_mode:
yield from climate.async_turn_away_mode_on()
@@ -288,12 +278,6 @@ def async_setup(hass, config):
aux_heat = service.data.get(ATTR_AUX_HEAT)
if aux_heat is None:
_LOGGER.error(
"Received call to %s without attribute %s",
SERVICE_SET_AUX_HEAT, ATTR_AUX_HEAT)
return
for climate in target_climate:
if aux_heat:
yield from climate.async_turn_aux_heat_on()
@@ -340,12 +324,6 @@ def async_setup(hass, config):
humidity = service.data.get(ATTR_HUMIDITY)
if humidity is None:
_LOGGER.error(
"Received call to %s without attribute %s",
SERVICE_SET_HUMIDITY, ATTR_HUMIDITY)
return
for climate in target_climate:
yield from climate.async_set_humidity(humidity)
@@ -363,12 +341,6 @@ def async_setup(hass, config):
fan = service.data.get(ATTR_FAN_MODE)
if fan is None:
_LOGGER.error(
"Received call to %s without attribute %s",
SERVICE_SET_FAN_MODE, ATTR_FAN_MODE)
return
for climate in target_climate:
yield from climate.async_set_fan_mode(fan)
@@ -386,12 +358,6 @@ def async_setup(hass, config):
operation_mode = service.data.get(ATTR_OPERATION_MODE)
if operation_mode is None:
_LOGGER.error(
"Received call to %s without attribute %s",
SERVICE_SET_OPERATION_MODE, ATTR_OPERATION_MODE)
return
for climate in target_climate:
yield from climate.async_set_operation_mode(operation_mode)
@@ -409,12 +375,6 @@ def async_setup(hass, config):
swing_mode = service.data.get(ATTR_SWING_MODE)
if swing_mode is None:
_LOGGER.error(
"Received call to %s without attribute %s",
SERVICE_SET_SWING_MODE, ATTR_SWING_MODE)
return
for climate in target_climate:
yield from climate.async_set_swing_mode(swing_mode)

View File

@@ -135,27 +135,27 @@ class DemoClimate(ClimateDevice):
kwargs.get(ATTR_TARGET_TEMP_LOW) is not None:
self._target_temperature_high = kwargs.get(ATTR_TARGET_TEMP_HIGH)
self._target_temperature_low = kwargs.get(ATTR_TARGET_TEMP_LOW)
self.update_ha_state()
self.schedule_update_ha_state()
def set_humidity(self, humidity):
"""Set new target temperature."""
self._target_humidity = humidity
self.update_ha_state()
self.schedule_update_ha_state()
def set_swing_mode(self, swing_mode):
"""Set new target temperature."""
self._current_swing_mode = swing_mode
self.update_ha_state()
self.schedule_update_ha_state()
def set_fan_mode(self, fan):
"""Set new target temperature."""
self._current_fan_mode = fan
self.update_ha_state()
self.schedule_update_ha_state()
def set_operation_mode(self, operation_mode):
"""Set new target temperature."""
self._current_operation = operation_mode
self.update_ha_state()
self.schedule_update_ha_state()
@property
def current_swing_mode(self):
@@ -170,24 +170,24 @@ class DemoClimate(ClimateDevice):
def turn_away_mode_on(self):
"""Turn away mode on."""
self._away = True
self.update_ha_state()
self.schedule_update_ha_state()
def turn_away_mode_off(self):
"""Turn away mode off."""
self._away = False
self.update_ha_state()
self.schedule_update_ha_state()
def set_hold_mode(self, hold):
"""Update hold mode on."""
self._hold = hold
self.update_ha_state()
self.schedule_update_ha_state()
def turn_aux_heat_on(self):
"""Turn away auxillary heater on."""
self._aux = True
self.update_ha_state()
self.schedule_update_ha_state()
def turn_aux_heat_off(self):
"""Turn auxillary heater off."""
self._aux = False
self.update_ha_state()
self.schedule_update_ha_state()

View File

@@ -69,7 +69,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
for thermostat in target_thermostats:
thermostat.set_fan_min_on_time(str(fan_min_on_time))
thermostat.update_ha_state(True)
thermostat.schedule_update_ha_state(True)
def resume_program_set_service(service):
"""Resume the program on the target thermostats."""
@@ -85,7 +85,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
for thermostat in target_thermostats:
thermostat.resume_program(resume_all)
thermostat.update_ha_state(True)
thermostat.schedule_update_ha_state(True)
descriptions = load_yaml_config_file(
path.join(path.dirname(__file__), 'services.yaml'))
@@ -186,12 +186,27 @@ class Thermostat(ClimateDevice):
@property
def current_hold_mode(self):
"""Return current hold mode."""
if self.is_away_mode_on:
events = self.thermostat['events']
if any((event['holdClimateRef'] == 'away' and
int(event['endDate'][0:4])-int(event['startDate'][0:4]) <= 1)
or event['type'] == 'autoAway'
for event in events):
# away hold is auto away or a temporary hold from away climate
hold = 'away'
elif self.is_home_mode_on:
elif any(event['holdClimateRef'] == 'away' and
int(event['endDate'][0:4])-int(event['startDate'][0:4]) > 1
for event in events):
# a permanent away is not considered a hold, but away_mode
hold = None
elif any(event['holdClimateRef'] == 'home' or
event['type'] == 'autoHome'
for event in events):
# home mode is auto home or any home hold
hold = 'home'
elif self.is_temp_hold_on():
elif any(event['type'] == 'hold' and event['running']
for event in events):
hold = 'temp'
# temperature hold is any other hold not based on climate
else:
hold = None
return hold
@@ -255,42 +270,23 @@ class Thermostat(ClimateDevice):
return any(event['type'] == 'vacation' and event['running']
for event in events)
def is_temp_hold_on(self):
"""Return true if temperature hold is on."""
events = self.thermostat['events']
return any(event['type'] == 'hold' and event['running']
for event in events)
@property
def is_away_mode_on(self):
"""Return true if away mode is on."""
events = self.thermostat['events']
return any(event['holdClimateRef'] == 'away' or
event['type'] == 'autoAway'
return any(event['holdClimateRef'] == 'away' and
int(event['endDate'][0:4])-int(event['startDate'][0:4]) > 1
for event in events)
def turn_away_mode_on(self):
"""Turn away on."""
self.data.ecobee.set_climate_hold(self.thermostat_index,
"away", self.hold_preference())
"away", 'indefinite')
self.update_without_throttle = True
def turn_away_mode_off(self):
"""Turn away off."""
self.set_hold_mode(None)
@property
def is_home_mode_on(self):
"""Return true if home mode is on."""
events = self.thermostat['events']
return any(event['holdClimateRef'] == 'home' or
event['type'] == 'autoHome'
for event in events)
def turn_home_mode_on(self):
"""Turn home on."""
self.data.ecobee.set_climate_hold(self.thermostat_index,
"home", self.hold_preference())
self.data.ecobee.resume_program(self.thermostat_index)
self.update_without_throttle = True
def set_hold_mode(self, hold_mode):
@@ -298,11 +294,14 @@ class Thermostat(ClimateDevice):
hold = self.current_hold_mode
if hold == hold_mode:
# no change, so no action required
return
elif hold_mode == 'away':
self.turn_away_mode_on()
self.data.ecobee.set_climate_hold(self.thermostat_index,
"away", self.hold_preference())
elif hold_mode == 'home':
self.turn_home_mode_on()
self.data.ecobee.set_climate_hold(self.thermostat_index,
"home", self.hold_preference())
elif hold_mode == 'temp':
self.set_temp_hold(int(self.current_temperature))
else:
@@ -378,17 +377,8 @@ class Thermostat(ClimateDevice):
default = self.thermostat['settings']['holdAction']
if default == 'nextTransition':
return default
elif default == 'indefinite':
return default
# 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'
# Sleep mode isn't used in UI yet:
# def turn_sleep_mode_on(self):
# """ Turns sleep mode on. """
# self.data.ecobee.set_climate_hold(self.thermostat_index, "sleep")
# def turn_sleep_mode_off(self):
# """ Turns sleep mode off. """
# self.data.ecobee.resume_program(self.thermostat_index)

View File

@@ -10,14 +10,14 @@ import voluptuous as vol
from homeassistant.components.climate import (
ClimateDevice, PLATFORM_SCHEMA, PRECISION_HALVES,
STATE_UNKNOWN, STATE_AUTO, STATE_ON, STATE_OFF,
STATE_AUTO, STATE_ON, STATE_OFF,
)
from homeassistant.const import (
CONF_MAC, TEMP_CELSIUS, CONF_DEVICES, ATTR_TEMPERATURE)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['python-eq3bt==0.1.4']
REQUIREMENTS = ['python-eq3bt==0.1.5']
_LOGGER = logging.getLogger(__name__)
@@ -61,15 +61,12 @@ class EQ3BTSmartThermostat(ClimateDevice):
# we want to avoid name clash with this module..
import eq3bt as eq3
self.modes = {None: STATE_UNKNOWN, # When not yet connected.
eq3.Mode.Unknown: STATE_UNKNOWN,
eq3.Mode.Auto: STATE_AUTO,
# away handled separately, here just for reverse mapping
eq3.Mode.Away: STATE_AWAY,
self.modes = {eq3.Mode.Open: STATE_ON,
eq3.Mode.Closed: STATE_OFF,
eq3.Mode.Open: STATE_ON,
eq3.Mode.Auto: STATE_AUTO,
eq3.Mode.Manual: STATE_MANUAL,
eq3.Mode.Boost: STATE_BOOST}
eq3.Mode.Boost: STATE_BOOST,
eq3.Mode.Away: STATE_AWAY}
self.reverse_modes = {v: k for k, v in self.modes.items()}
@@ -79,7 +76,7 @@ class EQ3BTSmartThermostat(ClimateDevice):
@property
def available(self) -> bool:
"""Return if thermostat is available."""
return self.current_operation != STATE_UNKNOWN
return self.current_operation is not None
@property
def name(self):
@@ -116,6 +113,8 @@ class EQ3BTSmartThermostat(ClimateDevice):
@property
def current_operation(self):
"""Current mode."""
if self._thermostat.mode < 0:
return None
return self.modes[self._thermostat.mode]
@property

View File

@@ -4,17 +4,19 @@ Adds support for generic thermostat units.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.generic_thermostat/
"""
import asyncio
import logging
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)
from homeassistant.const import (
ATTR_UNIT_OF_MEASUREMENT, STATE_ON, STATE_OFF, ATTR_TEMPERATURE)
from homeassistant.helpers import condition
from homeassistant.helpers.event import track_state_change
from homeassistant.helpers.event import async_track_state_change
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
@@ -48,7 +50,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
def setup_platform(hass, config, add_devices, discovery_info=None):
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Setup the generic thermostat."""
name = config.get(CONF_NAME)
heater_entity_id = config.get(CONF_HEATER)
@@ -60,7 +63,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
min_cycle_duration = config.get(CONF_MIN_DUR)
tolerance = config.get(CONF_TOLERANCE)
add_devices([GenericThermostat(
yield from async_add_devices([GenericThermostat(
hass, name, heater_entity_id, sensor_entity_id, min_temp, max_temp,
target_temp, ac_mode, min_cycle_duration, tolerance)])
@@ -86,12 +89,14 @@ class GenericThermostat(ClimateDevice):
self._target_temp = target_temp
self._unit = hass.config.units.temperature_unit
track_state_change(hass, sensor_entity_id, self._sensor_changed)
track_state_change(hass, heater_entity_id, self._switch_changed)
async_track_state_change(
hass, sensor_entity_id, self._async_sensor_changed)
async_track_state_change(
hass, heater_entity_id, self._async_switch_changed)
sensor_state = hass.states.get(sensor_entity_id)
if sensor_state:
self._update_temp(sensor_state)
self._async_update_temp(sensor_state)
@property
def should_poll(self):
@@ -128,14 +133,15 @@ class GenericThermostat(ClimateDevice):
"""Return the temperature we try to reach."""
return self._target_temp
def set_temperature(self, **kwargs):
@asyncio.coroutine
def async_set_temperature(self, **kwargs):
"""Set new target temperature."""
temperature = kwargs.get(ATTR_TEMPERATURE)
if temperature is None:
return
self._target_temp = temperature
self._control_heating()
self.schedule_update_ha_state()
self._async_control_heating()
yield from self.async_update_ha_state()
@property
def min_temp(self):
@@ -157,22 +163,25 @@ class GenericThermostat(ClimateDevice):
# Get default temp from super class
return ClimateDevice.max_temp.fget(self)
def _sensor_changed(self, entity_id, old_state, new_state):
@asyncio.coroutine
def _async_sensor_changed(self, entity_id, old_state, new_state):
"""Called when temperature changes."""
if new_state is None:
return
self._update_temp(new_state)
self._control_heating()
self.schedule_update_ha_state()
self._async_update_temp(new_state)
self._async_control_heating()
yield from self.async_update_ha_state()
def _switch_changed(self, entity_id, old_state, new_state):
@callback
def _async_switch_changed(self, entity_id, old_state, new_state):
"""Called when heater switch changes state."""
if new_state is None:
return
self.schedule_update_ha_state()
self.hass.async_add_job(self.async_update_ha_state())
def _update_temp(self, state):
@callback
def _async_update_temp(self, state):
"""Update thermostat with latest state from sensor."""
unit = state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
@@ -182,7 +191,8 @@ class GenericThermostat(ClimateDevice):
except ValueError as ex:
_LOGGER.error('Unable to update from sensor: %s', ex)
def _control_heating(self):
@callback
def _async_control_heating(self):
"""Check if we need to turn heating on or off."""
if not self._active and None not in (self._cur_temp,
self._target_temp):
@@ -198,9 +208,9 @@ class GenericThermostat(ClimateDevice):
current_state = STATE_ON
else:
current_state = STATE_OFF
long_enough = condition.state(self.hass, self.heater_entity_id,
current_state,
self.min_cycle_duration)
long_enough = condition.state(
self.hass, self.heater_entity_id, current_state,
self.min_cycle_duration)
if not long_enough:
return
@@ -210,12 +220,12 @@ class GenericThermostat(ClimateDevice):
too_cold = self._target_temp - self._cur_temp > self._tolerance
if too_cold:
_LOGGER.info('Turning off AC %s', self.heater_entity_id)
switch.turn_off(self.hass, self.heater_entity_id)
switch.async_turn_off(self.hass, self.heater_entity_id)
else:
too_hot = self._cur_temp - self._target_temp > self._tolerance
if too_hot:
_LOGGER.info('Turning on AC %s', self.heater_entity_id)
switch.turn_on(self.hass, self.heater_entity_id)
switch.async_turn_on(self.hass, self.heater_entity_id)
else:
is_heating = self._is_device_active
if is_heating:
@@ -223,12 +233,12 @@ class GenericThermostat(ClimateDevice):
if too_hot:
_LOGGER.info('Turning off heater %s',
self.heater_entity_id)
switch.turn_off(self.hass, self.heater_entity_id)
switch.async_turn_off(self.hass, self.heater_entity_id)
else:
too_cold = self._target_temp - self._cur_temp > self._tolerance
if too_cold:
_LOGGER.info('Turning on heater %s', self.heater_entity_id)
switch.turn_on(self.hass, self.heater_entity_id)
switch.async_turn_on(self.hass, self.heater_entity_id)
@property
def _is_device_active(self):

View File

@@ -135,7 +135,7 @@ class MySensorsHVAC(mysensors.MySensorsDeviceEntity, ClimateDevice):
if self.gateway.optimistic:
# optimistically assume that switch has changed state
self._values[value_type] = value
self.update_ha_state()
self.schedule_update_ha_state()
def set_fan_mode(self, fan):
"""Set new target temperature."""
@@ -145,7 +145,7 @@ class MySensorsHVAC(mysensors.MySensorsDeviceEntity, ClimateDevice):
if self.gateway.optimistic:
# optimistically assume that switch has changed state
self._values[set_req.V_HVAC_SPEED] = fan
self.update_ha_state()
self.schedule_update_ha_state()
def set_operation_mode(self, operation_mode):
"""Set new target temperature."""
@@ -156,7 +156,7 @@ class MySensorsHVAC(mysensors.MySensorsDeviceEntity, ClimateDevice):
if self.gateway.optimistic:
# optimistically assume that switch has changed state
self._values[set_req.V_HVAC_FLOW_STATE] = operation_mode
self.update_ha_state()
self.schedule_update_ha_state()
def update(self):
"""Update the controller with the latest value from a sensor."""

View File

@@ -111,7 +111,6 @@ class NetatmoThermostat(ClimateDevice):
temp = None
self._data.thermostatdata.setthermpoint(mode, temp, endTimeOffset=None)
self._away = True
self.update_ha_state()
def turn_away_mode_off(self):
"""Turn away off."""
@@ -119,7 +118,6 @@ class NetatmoThermostat(ClimateDevice):
temp = None
self._data.thermostatdata.setthermpoint(mode, temp, endTimeOffset=None)
self._away = False
self.update_ha_state()
def set_temperature(self, endTimeOffset=DEFAULT_TIME_OFFSET, **kwargs):
"""Set new target temperature for 2 hours."""
@@ -131,7 +129,6 @@ class NetatmoThermostat(ClimateDevice):
mode, temperature, endTimeOffset)
self._target_temperature = temperature
self._away = False
self.update_ha_state()
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):

View File

@@ -78,7 +78,7 @@ set_fan_mode:
description: Name(s) of entities to change
example: 'climate.nest'
fan:
fan_mode:
description: New value of fan mode
example: On Low

View File

@@ -4,7 +4,7 @@ Support for Wink thermostats.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.wink/
"""
from homeassistant.components.wink import WinkDevice
from homeassistant.components.wink import WinkDevice, DOMAIN
from homeassistant.components.climate import (
STATE_AUTO, STATE_COOL, STATE_HEAT, ClimateDevice,
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
@@ -13,12 +13,16 @@ from homeassistant.components.climate import (
from homeassistant.const import (
TEMP_CELSIUS, STATE_ON,
STATE_OFF, STATE_UNKNOWN)
from homeassistant.loader import get_component
DEPENDENCIES = ['wink']
STATE_AUX = 'aux'
STATE_ECO = 'eco'
STATE_FAN = 'fan'
SPEED_LOWEST = 'lowest'
SPEED_LOW = 'low'
SPEED_MEDIUM = 'medium'
SPEED_HIGH = 'high'
ATTR_EXTERNAL_TEMPERATURE = "external_temperature"
ATTR_SMART_TEMPERATURE = "smart_temperature"
@@ -30,8 +34,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Wink thermostat."""
import pywink
temp_unit = hass.config.units.temperature_unit
add_devices(WinkThermostat(thermostat, hass, temp_unit)
for thermostat in pywink.get_thermostats())
for climate in pywink.get_thermostats():
_id = climate.object_id() + climate.name()
if _id not in hass.data[DOMAIN]['unique_ids']:
add_devices([WinkThermostat(climate, hass, temp_unit)])
for climate in pywink.get_air_conditioners():
_id = climate.object_id() + climate.name()
if _id not in hass.data[DOMAIN]['unique_ids']:
add_devices([WinkAC(climate, hass, temp_unit)])
# pylint: disable=abstract-method,too-many-public-methods, too-many-branches
@@ -41,7 +51,6 @@ class WinkThermostat(WinkDevice, ClimateDevice):
def __init__(self, wink, hass, temp_unit):
"""Initialize the Wink device."""
super().__init__(wink, hass)
wink = get_component('wink')
self._config_temp_unit = temp_unit
@property
@@ -329,3 +338,131 @@ class WinkThermostat(WinkDevice, ClimateDevice):
else:
return_value = maximum
return return_value
class WinkAC(WinkDevice, ClimateDevice):
"""Representation of a Wink air conditioner."""
def __init__(self, wink, hass, temp_unit):
"""Initialize the Wink device."""
super().__init__(wink, hass)
self._config_temp_unit = temp_unit
@property
def temperature_unit(self):
"""Return the unit of measurement."""
# The Wink API always returns temp in Celsius
return TEMP_CELSIUS
@property
def device_state_attributes(self):
"""Return the optional state attributes."""
data = {}
target_temp_high = self.target_temperature_high
target_temp_low = self.target_temperature_low
if target_temp_high is not None:
data[ATTR_TARGET_TEMP_HIGH] = self._convert_for_display(
self.target_temperature_high)
if target_temp_low is not None:
data[ATTR_TARGET_TEMP_LOW] = self._convert_for_display(
self.target_temperature_low)
data["total_consumption"] = self.wink.toatl_consumption()
data["schedule_enabled"] = self.wink.toatl_consumption()
return data
@property
def current_temperature(self):
"""Return the current temperature."""
return self.wink.current_temperature()
@property
def current_operation(self):
"""Return current operation ie. heat, cool, idle."""
if not self.wink.is_on():
current_op = STATE_OFF
elif self.wink.current_mode() == 'cool_only':
current_op = STATE_COOL
elif self.wink.current_mode() == 'auto_eco':
current_op = STATE_ECO
elif self.wink.current_mode() == 'fan_only':
current_op = STATE_FAN
else:
current_op = STATE_UNKNOWN
return current_op
@property
def operation_list(self):
"""List of available operation modes."""
op_list = ['off']
modes = self.wink.modes()
if 'cool_only' in modes:
op_list.append(STATE_COOL)
if 'auto_eco' in modes:
op_list.append(STATE_ECO)
if 'fan_eco' in modes:
op_list.append(STATE_FAN)
return op_list
def set_temperature(self, **kwargs):
"""Set new target temperature."""
target_temp = kwargs.get(ATTR_TEMPERATURE)
self.wink.set_temperature(target_temp)
def set_operation_mode(self, operation_mode):
"""Set operation mode."""
if operation_mode == STATE_COOL:
self.wink.set_operation_mode('cool_only')
elif operation_mode == STATE_ECO:
self.wink.set_operation_mode('auto_eco')
elif operation_mode == STATE_OFF:
self.wink.set_operation_mode('off')
elif operation_mode == STATE_FAN:
self.wink.set_operation_mode('fan_only')
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
return self.wink.current_max_set_point()
@property
def target_temperature_low(self):
"""Only supports cool."""
return None
@property
def target_temperature_high(self):
"""Only supports cool."""
return None
@property
def current_fan_mode(self):
"""Return the current fan mode."""
speed = self.wink.current_fan_speed()
if speed <= 0.3 and speed >= 0.0:
return SPEED_LOWEST
elif speed <= 0.5 and speed > 0.3:
return SPEED_LOW
elif speed <= 0.8 and speed > 0.5:
return SPEED_MEDIUM
elif speed <= 1.0 and speed > 0.8:
return SPEED_HIGH
else:
return STATE_UNKNOWN
@property
def fan_list(self):
"""List of available fan modes."""
return [SPEED_LOWEST, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]
def set_fan_mode(self, mode):
"""Set fan speed."""
if mode == SPEED_LOWEST:
speed = 0.3
elif mode == SPEED_LOW:
speed = 0.5
elif mode == SPEED_MEDIUM:
speed = 0.8
elif mode == SPEED_HIGH:
speed = 1.0
self.wink.set_ac_fan_speed(speed)

View File

@@ -83,45 +83,52 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
def update_properties(self):
"""Callback on data changes for node values."""
# Operation Mode
for value in self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_MODE).values():
self._current_operation = value.data
self._operation_list = list(value.data_items)
_LOGGER.debug("self._operation_list=%s", self._operation_list)
_LOGGER.debug("self._current_operation=%s",
self._current_operation)
self._current_operation = self.get_value(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_MODE, member='data')
operation_list = self.get_value(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_MODE,
member='data_items')
if operation_list:
self._operation_list = list(operation_list)
_LOGGER.debug("self._operation_list=%s", self._operation_list)
_LOGGER.debug("self._current_operation=%s", self._current_operation)
# Current Temp
for value in (
self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_SENSOR_MULTILEVEL)
.values()):
if value.label == 'Temperature':
self._current_temperature = round((float(value.data)), 1)
self._unit = value.units
self._current_temperature = self.get_value(
class_id=zwave.const.COMMAND_CLASS_SENSOR_MULTILEVEL,
label=['Temperature'], member='data')
self._unit = self.get_value(
class_id=zwave.const.COMMAND_CLASS_SENSOR_MULTILEVEL,
label=['Temperature'], member='units')
# Fan Mode
for value in (
self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_FAN_MODE)
.values()):
self._current_fan_mode = value.data
self._fan_list = list(value.data_items)
_LOGGER.debug("self._fan_list=%s", self._fan_list)
_LOGGER.debug("self._current_fan_mode=%s",
self._current_fan_mode)
self._current_fan_mode = self.get_value(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_FAN_MODE,
member='data')
fan_list = self.get_value(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_FAN_MODE,
member='data_items')
if fan_list:
self._fan_list = list(fan_list)
_LOGGER.debug("self._fan_list=%s", self._fan_list)
_LOGGER.debug("self._current_fan_mode=%s",
self._current_fan_mode)
# Swing mode
if self._zxt_120 == 1:
for value in (
self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_CONFIGURATION)
.values()):
if value.command_class == \
zwave.const.COMMAND_CLASS_CONFIGURATION and \
value.index == 33:
self._current_swing_mode = value.data
self._swing_list = list(value.data_items)
_LOGGER.debug("self._swing_list=%s", self._swing_list)
_LOGGER.debug("self._current_swing_mode=%s",
self._current_swing_mode)
self._current_swing_mode = (
self.get_value(
class_id=zwave.const.COMMAND_CLASS_CONFIGURATION,
index=33,
member='data'))
swing_list = self.get_value(class_id=zwave.const
.COMMAND_CLASS_CONFIGURATION,
index=33,
member='data_items')
if swing_list:
self._swing_list = list(swing_list)
_LOGGER.debug("self._swing_list=%s", self._swing_list)
_LOGGER.debug("self._current_swing_mode=%s",
self._current_swing_mode)
# Set point
temps = []
for value in (
@@ -139,19 +146,16 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
break
else:
self._target_temperature = round((float(value.data)), 1)
# Operating state
for value in (
self._node.get_values(
class_id=zwave.const
.COMMAND_CLASS_THERMOSTAT_OPERATING_STATE).values()):
self._operating_state = value.data
self._operating_state = self.get_value(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_OPERATING_STATE,
member='data')
# Fan operating state
for value in (
self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_FAN_STATE)
.values()):
self._fan_state = value.data
self._fan_state = self.get_value(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_FAN_STATE,
member='data')
@property
def should_poll(self):
@@ -215,50 +219,29 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
else:
return
for value in (self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_SETPOINT)
.values()):
if value.index == self._index:
if self._zxt_120:
# ZXT-120 responds only to whole int
value.data = round(temperature, 0)
self._target_temperature = temperature
self.update_ha_state()
else:
value.data = temperature
self.update_ha_state()
break
self.set_value(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_SETPOINT,
index=self._index, data=temperature)
self.update_ha_state()
def set_fan_mode(self, fan):
"""Set new target fan mode."""
for value in (self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_FAN_MODE).
values()):
if value.command_class == \
zwave.const.COMMAND_CLASS_THERMOSTAT_FAN_MODE and \
value.index == 0:
value.data = bytes(fan, 'utf-8')
break
self.set_value(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_FAN_MODE,
index=0, data=bytes(fan, 'utf-8'))
def set_operation_mode(self, operation_mode):
"""Set new target operation mode."""
for value in self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_MODE).values():
if value.command_class == \
zwave.const.COMMAND_CLASS_THERMOSTAT_MODE and value.index == 0:
value.data = bytes(operation_mode, 'utf-8')
break
self.set_value(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_MODE,
index=0, data=bytes(operation_mode, 'utf-8'))
def set_swing_mode(self, swing_mode):
"""Set new target swing mode."""
if self._zxt_120 == 1:
for value in self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_CONFIGURATION).values():
if value.command_class == \
zwave.const.COMMAND_CLASS_CONFIGURATION and \
value.index == 33:
value.data = bytes(swing_mode, 'utf-8')
break
self.set_value(
class_id=zwave.const.COMMAND_CLASS_CONFIGURATION,
index=33, data=bytes(swing_mode, 'utf-8'))
@property
def device_state_attributes(self):

View File

@@ -4,9 +4,11 @@ Support for Cover devices.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/cover/
"""
import os
import asyncio
from datetime import timedelta
import functools as ft
import logging
import os
import voluptuous as vol
@@ -53,17 +55,17 @@ COVER_SET_COVER_TILT_POSITION_SCHEMA = COVER_SERVICE_SCHEMA.extend({
})
SERVICE_TO_METHOD = {
SERVICE_OPEN_COVER: {'method': 'open_cover'},
SERVICE_CLOSE_COVER: {'method': 'close_cover'},
SERVICE_OPEN_COVER: {'method': 'async_open_cover'},
SERVICE_CLOSE_COVER: {'method': 'async_close_cover'},
SERVICE_SET_COVER_POSITION: {
'method': 'set_cover_position',
'method': 'async_set_cover_position',
'schema': COVER_SET_COVER_POSITION_SCHEMA},
SERVICE_STOP_COVER: {'method': 'stop_cover'},
SERVICE_OPEN_COVER_TILT: {'method': 'open_cover_tilt'},
SERVICE_CLOSE_COVER_TILT: {'method': 'close_cover_tilt'},
SERVICE_STOP_COVER_TILT: {'method': 'stop_cover_tilt'},
SERVICE_STOP_COVER: {'method': 'async_stop_cover'},
SERVICE_OPEN_COVER_TILT: {'method': 'async_open_cover_tilt'},
SERVICE_CLOSE_COVER_TILT: {'method': 'async_close_cover_tilt'},
SERVICE_STOP_COVER_TILT: {'method': 'async_stop_cover_tilt'},
SERVICE_SET_COVER_TILT_POSITION: {
'method': 'set_cover_tilt_position',
'method': 'async_set_cover_tilt_position',
'schema': COVER_SET_COVER_TILT_POSITION_SCHEMA},
}
@@ -124,40 +126,53 @@ def stop_cover_tilt(hass, entity_id=None):
hass.services.call(DOMAIN, SERVICE_STOP_COVER_TILT, data)
def setup(hass, config):
@asyncio.coroutine
def async_setup(hass, config):
"""Track states and offer events for covers."""
component = EntityComponent(
_LOGGER, DOMAIN, hass, SCAN_INTERVAL, GROUP_NAME_ALL_COVERS)
component.setup(config)
def handle_cover_service(service):
yield from component.async_setup(config)
@asyncio.coroutine
def async_handle_cover_service(service):
"""Handle calls to the cover services."""
covers = component.async_extract_from_service(service)
method = SERVICE_TO_METHOD.get(service.service)
params = service.data.copy()
params.pop(ATTR_ENTITY_ID, None)
if not method:
return
covers = component.extract_from_service(service)
# call method
for cover in covers:
getattr(cover, method['method'])(**params)
yield from getattr(cover, method['method'])(**params)
update_tasks = []
for cover in covers:
if not cover.should_poll:
continue
cover.update_ha_state(True)
update_coro = hass.loop.create_task(
cover.async_update_ha_state(True))
if hasattr(cover, 'async_update'):
update_tasks.append(update_coro)
else:
yield from update_coro
descriptions = load_yaml_config_file(
os.path.join(os.path.dirname(__file__), 'services.yaml'))
if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)
descriptions = yield from hass.loop.run_in_executor(
None, load_yaml_config_file, os.path.join(
os.path.dirname(__file__), 'services.yaml'))
for service_name in SERVICE_TO_METHOD:
schema = SERVICE_TO_METHOD[service_name].get(
'schema', COVER_SERVICE_SCHEMA)
hass.services.register(DOMAIN, service_name, handle_cover_service,
descriptions.get(service_name), schema=schema)
hass.services.async_register(
DOMAIN, service_name, async_handle_cover_service,
descriptions.get(service_name), schema=schema)
return True
@@ -215,30 +230,94 @@ class CoverDevice(Entity):
"""Open the cover."""
raise NotImplementedError()
def async_open_cover(self, **kwargs):
"""Open the cover.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.loop.run_in_executor(
None, ft.partial(self.open_cover, **kwargs))
def close_cover(self, **kwargs):
"""Close cover."""
raise NotImplementedError()
def async_close_cover(self, **kwargs):
"""Close cover.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.loop.run_in_executor(
None, ft.partial(self.close_cover, **kwargs))
def set_cover_position(self, **kwargs):
"""Move the cover to a specific position."""
pass
def async_set_cover_position(self, **kwargs):
"""Move the cover to a specific position.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.loop.run_in_executor(
None, ft.partial(self.set_cover_position, **kwargs))
def stop_cover(self, **kwargs):
"""Stop the cover."""
pass
def async_stop_cover(self, **kwargs):
"""Stop the cover.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.loop.run_in_executor(
None, ft.partial(self.stop_cover, **kwargs))
def open_cover_tilt(self, **kwargs):
"""Open the cover tilt."""
pass
def async_open_cover_tilt(self, **kwargs):
"""Open the cover tilt.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.loop.run_in_executor(
None, ft.partial(self.open_cover_tilt, **kwargs))
def close_cover_tilt(self, **kwargs):
"""Close the cover tilt."""
pass
def async_close_cover_tilt(self, **kwargs):
"""Close the cover tilt.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.loop.run_in_executor(
None, ft.partial(self.close_cover_tilt, **kwargs))
def set_cover_tilt_position(self, **kwargs):
"""Move the cover tilt to a specific position."""
pass
def async_set_cover_tilt_position(self, **kwargs):
"""Move the cover tilt to a specific position.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.loop.run_in_executor(
None, ft.partial(self.set_cover_tilt_position, **kwargs))
def stop_cover_tilt(self, **kwargs):
"""Stop the cover."""
pass
def async_stop_cover_tilt(self, **kwargs):
"""Stop the cover.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.loop.run_in_executor(
None, ft.partial(self.stop_cover_tilt, **kwargs))

View File

@@ -149,7 +149,7 @@ class DemoCover(CoverDevice):
if self._position in (100, 0, self._set_position):
self.stop_cover()
self.update_ha_state()
self.schedule_update_ha_state()
def _listen_cover_tilt(self):
"""Listen for changes in cover tilt."""
@@ -167,4 +167,4 @@ class DemoCover(CoverDevice):
if self._tilt_position in (100, 0, self._set_tilt_position):
self.stop_cover_tilt()
self.update_ha_state()
self.schedule_update_ha_state()

View File

@@ -199,8 +199,7 @@ class GaradgetCover(CoverDevice):
def _check_state(self, now):
"""Check the state of the service during an operation."""
self.update()
self.update_ha_state()
self.schedule_update_ha_state(True)
def close_cover(self):
"""Close the cover."""

View File

@@ -151,7 +151,7 @@ class MqttCover(CoverDevice):
if self._optimistic:
# Optimistically assume that cover has changed state.
self._state = False
self.update_ha_state()
self.schedule_update_ha_state()
def close_cover(self, **kwargs):
"""Move the cover down."""
@@ -160,7 +160,7 @@ class MqttCover(CoverDevice):
if self._optimistic:
# Optimistically assume that cover has changed state.
self._state = True
self.update_ha_state()
self.schedule_update_ha_state()
def stop_cover(self, **kwargs):
"""Stop the device."""

View File

@@ -75,7 +75,7 @@ class MySensorsCover(mysensors.MySensorsDeviceEntity, CoverDevice):
self._values[set_req.V_DIMMER] = 100
else:
self._values[set_req.V_LIGHT] = STATE_ON
self.update_ha_state()
self.schedule_update_ha_state()
def close_cover(self, **kwargs):
"""Move the cover down."""
@@ -88,7 +88,7 @@ class MySensorsCover(mysensors.MySensorsDeviceEntity, CoverDevice):
self._values[set_req.V_DIMMER] = 0
else:
self._values[set_req.V_LIGHT] = STATE_OFF
self.update_ha_state()
self.schedule_update_ha_state()
def set_cover_position(self, **kwargs):
"""Move the cover to a specific position."""
@@ -99,7 +99,7 @@ class MySensorsCover(mysensors.MySensorsDeviceEntity, CoverDevice):
if self.gateway.optimistic:
# Optimistically assume that cover has changed state.
self._values[set_req.V_DIMMER] = position
self.update_ha_state()
self.schedule_update_ha_state()
def stop_cover(self, **kwargs):
"""Stop the device."""

View File

@@ -6,7 +6,7 @@ https://home-assistant.io/components/cover.wink/
"""
from homeassistant.components.cover import CoverDevice
from homeassistant.components.wink import WinkDevice
from homeassistant.components.wink import WinkDevice, DOMAIN
DEPENDENCIES = ['wink']
@@ -15,10 +15,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Wink cover platform."""
import pywink
add_devices(WinkCoverDevice(shade, hass) for shade in
pywink.get_shades())
add_devices(WinkCoverDevice(door, hass) for door in
pywink.get_garage_doors())
for shade in pywink.get_shades():
_id = shade.object_id() + shade.name()
if _id not in hass.data[DOMAIN]['unique_ids']:
add_devices([WinkCoverDevice(shade, hass)])
for door in pywink.get_garage_doors():
_id = door.object_id() + door.name()
if _id not in hass.data[DOMAIN]['unique_ids']:
add_devices([WinkCoverDevice(door, hass)])
class WinkCoverDevice(WinkDevice, CoverDevice):
@@ -26,7 +30,7 @@ class WinkCoverDevice(WinkDevice, CoverDevice):
def __init__(self, wink, hass):
"""Initialize the cover."""
WinkDevice.__init__(self, wink, hass)
super().__init__(wink, hass)
def close_cover(self):
"""Close the shade."""
@@ -36,13 +40,17 @@ class WinkCoverDevice(WinkDevice, CoverDevice):
"""Open the shade."""
self.wink.set_state(1)
def set_cover_position(self, position, **kwargs):
"""Move the roller shutter to a specific position."""
self.wink.set_state(float(position)/100)
@property
def current_cover_position(self):
"""Return the current position of roller shutter."""
return int(self.wink.state()*100)
@property
def is_closed(self):
"""Return if the cover is closed."""
state = self.wink.state()
if state == 0:
return True
elif state == 1:
return False
else:
return None
return bool(state == 0)

View File

@@ -52,12 +52,11 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice):
def __init__(self, value):
"""Initialize the zwave rollershutter."""
import libopenzwave
ZWaveDeviceEntity.__init__(self, value, DOMAIN)
# pylint: disable=no-member
self._lozwmgr = libopenzwave.PyManager()
self._lozwmgr.create()
self._node = value.node
self._open_id = None
self._close_id = None
self._current_position = None
self._workaround = None
if (value.node.manufacturer_id.strip() and
@@ -73,12 +72,15 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice):
def update_properties(self):
"""Callback on data changes for node values."""
# Position value
for value in self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL).values():
if value.command_class == \
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL and \
value.label == 'Level':
self._current_position = value.data
self._current_position = self.get_value(
class_id=zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL,
label=['Level'], member='data')
self._open_id = self.get_value(
class_id=zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL,
label=['Open', 'Up'], member='value_id')
self._close_id = self.get_value(
class_id=zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL,
label=['Close', 'Down'], member='value_id')
@property
def is_closed(self):
@@ -104,27 +106,11 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice):
def open_cover(self, **kwargs):
"""Move the roller shutter up."""
for value in self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL).values():
if value.command_class == \
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL and value.label == \
'Open' or value.command_class == \
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL and value.label == \
'Up':
self._lozwmgr.pressButton(value.value_id)
break
zwave.NETWORK.manager.pressButton(self._open_id)
def close_cover(self, **kwargs):
"""Move the roller shutter down."""
for value in self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL).values():
if value.command_class == \
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL and value.label == \
'Down' or value.command_class == \
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL and value.label == \
'Close':
self._lozwmgr.pressButton(value.value_id)
break
zwave.NETWORK.manager.pressButton(self._close_id)
def set_cover_position(self, position, **kwargs):
"""Move the roller shutter to a specific position."""
@@ -132,15 +118,7 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice):
def stop_cover(self, **kwargs):
"""Stop the roller shutter."""
for value in self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL).values():
if value.command_class == \
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL and value.label == \
'Open' or value.command_class == \
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL and value.label == \
'Down':
self._lozwmgr.releaseButton(value.value_id)
break
zwave.NETWORK.manager.releaseButton(self._open_id)
class ZwaveGarageDoor(zwave.ZWaveDeviceEntity, CoverDevice):

View File

@@ -4,6 +4,7 @@ Provides functionality to turn on lights based on the states.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/device_sun_light_trigger/
"""
import asyncio
import logging
from datetime import timedelta
@@ -12,8 +13,8 @@ import voluptuous as vol
from homeassistant.core import callback
import homeassistant.util.dt as dt_util
from homeassistant.const import STATE_HOME, STATE_NOT_HOME
from homeassistant.helpers.event import track_point_in_time
from homeassistant.helpers.event_decorators import track_state_change
from homeassistant.helpers.event import (
async_track_point_in_time, async_track_state_change)
from homeassistant.loader import get_component
import homeassistant.helpers.config_validation as cv
@@ -42,20 +43,20 @@ CONFIG_SCHEMA = vol.Schema({
}, extra=vol.ALLOW_EXTRA)
def setup(hass, config):
@asyncio.coroutine
def async_setup(hass, config):
"""The triggers to turn lights on or off based on device presence."""
logger = logging.getLogger(__name__)
device_tracker = get_component('device_tracker')
group = get_component('group')
light = get_component('light')
sun = get_component('sun')
disable_turn_off = config[DOMAIN].get(CONF_DISABLE_TURN_OFF)
light_group = config[DOMAIN].get(CONF_LIGHT_GROUP,
light.ENTITY_ID_ALL_LIGHTS)
light_profile = config[DOMAIN].get(CONF_LIGHT_PROFILE)
device_group = config[DOMAIN].get(CONF_DEVICE_GROUP,
device_tracker.ENTITY_ID_ALL_DEVICES)
conf = config[DOMAIN]
disable_turn_off = conf.get(CONF_DISABLE_TURN_OFF)
light_group = conf.get(CONF_LIGHT_GROUP, light.ENTITY_ID_ALL_LIGHTS)
light_profile = conf.get(CONF_LIGHT_PROFILE)
device_group = conf.get(CONF_DEVICE_GROUP,
device_tracker.ENTITY_ID_ALL_DEVICES)
device_entity_ids = group.get_entity_ids(hass, device_group,
device_tracker.DOMAIN)
@@ -74,6 +75,8 @@ def setup(hass, config):
"""Calculate the time when to start fading lights in when sun sets.
Returns None if no next_setting data available.
Async friendly.
"""
next_setting = sun.next_setting(hass)
if not next_setting:
@@ -81,22 +84,26 @@ def setup(hass, config):
return next_setting - LIGHT_TRANSITION_TIME * len(light_ids)
def async_turn_on_before_sunset(light_id):
"""Helper function to turn on lights.
Speed is slow if there are devices home and the light is not on yet.
"""
"""Helper function to turn on lights."""
if not device_tracker.is_on(hass) or light.is_on(hass, light_id):
return
light.async_turn_on(hass, light_id,
transition=LIGHT_TRANSITION_TIME.seconds,
profile=light_profile)
def async_turn_on_factory(light_id):
"""Factory to generate turn on callbacks."""
@callback
def async_turn_on_light(now):
"""Turn on specific light."""
async_turn_on_before_sunset(light_id)
return async_turn_on_light
# Track every time sun rises so we can schedule a time-based
# pre-sun set event
@track_state_change(sun.ENTITY_ID, sun.STATE_BELOW_HORIZON,
sun.STATE_ABOVE_HORIZON)
@callback
def schedule_lights_at_sun_set(hass, entity, old_state, new_state):
def schedule_light_turn_on(entity, old_state, new_state):
"""The moment sun sets we want to have all the lights on.
We will schedule to have each light start after one another
@@ -106,35 +113,23 @@ def setup(hass, config):
if not start_point:
return
def async_turn_on_factory(light_id):
"""Lambda can keep track of function parameters.
No local parameters. If we put the lambda directly in the below
statement only the last light will be turned on.
"""
@callback
def async_turn_on_light(now):
"""Turn on specific light."""
async_turn_on_before_sunset(light_id)
return async_turn_on_light
for index, light_id in enumerate(light_ids):
track_point_in_time(hass, async_turn_on_factory(light_id),
start_point + index * LIGHT_TRANSITION_TIME)
async_track_point_in_time(
hass, async_turn_on_factory(light_id),
start_point + index * LIGHT_TRANSITION_TIME)
async_track_state_change(hass, sun.ENTITY_ID, schedule_light_turn_on,
sun.STATE_BELOW_HORIZON, sun.STATE_ABOVE_HORIZON)
# If the sun is already above horizon schedule the time-based pre-sun set
# event.
if sun.is_on(hass):
schedule_lights_at_sun_set(hass, None, None, None)
schedule_light_turn_on(None, None, None)
@track_state_change(device_entity_ids, STATE_NOT_HOME, STATE_HOME)
@callback
def check_light_on_dev_state_change(hass, entity, old_state, new_state):
def check_light_on_dev_state_change(entity, old_state, new_state):
"""Handle tracked device state changes."""
# pylint: disable=unused-variable
lights_are_on = group.is_on(hass, light_group)
light_needed = not (lights_are_on or sun.is_on(hass))
# These variables are needed for the elif check
@@ -164,17 +159,25 @@ def setup(hass, config):
# will all the following then, break.
break
if not disable_turn_off:
@track_state_change(device_group, STATE_HOME, STATE_NOT_HOME)
@callback
def turn_off_lights_when_all_leave(hass, entity, old_state, new_state):
"""Handle device group state change."""
# pylint: disable=unused-variable
if not group.is_on(hass, light_group):
return
async_track_state_change(
hass, device_entity_ids, check_light_on_dev_state_change,
STATE_NOT_HOME, STATE_HOME)
logger.info(
"Everyone has left but there are lights on. Turning them off")
light.async_turn_off(hass, light_ids)
if disable_turn_off:
return True
@callback
def turn_off_lights_when_all_leave(entity, old_state, new_state):
"""Handle device group state change."""
if not group.is_on(hass, light_group):
return
logger.info(
"Everyone has left but there are lights on. Turning them off")
light.async_turn_off(hass, light_ids)
async_track_state_change(
hass, device_group, turn_off_lights_when_all_leave,
STATE_HOME, STATE_NOT_HOME)
return True

View File

@@ -158,10 +158,11 @@ def async_setup(hass: HomeAssistantType, config: ConfigType):
None, platform.get_scanner, hass, {DOMAIN: p_config})
elif hasattr(platform, 'async_setup_scanner'):
setup = yield from platform.async_setup_scanner(
hass, p_config, tracker.async_see)
hass, p_config, tracker.async_see, disc_info)
elif hasattr(platform, 'setup_scanner'):
setup = yield from hass.loop.run_in_executor(
None, platform.setup_scanner, hass, p_config, tracker.see)
None, platform.setup_scanner, hass, p_config, tracker.see,
disc_info)
else:
raise HomeAssistantError("Invalid device_tracker platform.")
@@ -193,6 +194,13 @@ def async_setup(hass: HomeAssistantType, config: ConfigType):
discovery.async_listen(
hass, DISCOVERY_PLATFORMS.keys(), async_device_tracker_discovered)
@asyncio.coroutine
def async_platform_discovered(platform, info):
"""Callback to load a platform."""
yield from async_setup_platform(platform, {}, disc_info=info)
discovery.async_listen_platform(hass, DOMAIN, async_platform_discovered)
# Clean up stale devices
async_track_utc_time_change(
hass, tracker.async_update_stale, second=range(0, 60, 5))

View File

@@ -76,6 +76,7 @@ _IP_NEIGH_REGEX = re.compile(
r'\w+\s'
r'\w+\s'
r'(\w+\s(?P<mac>(([0-9a-f]{2}[:-]){5}([0-9a-f]{2}))))?\s'
r'\s?(router)?'
r'(?P<status>(\w+))')
_NVRAM_CMD = 'nvram get client_info_tmp'

View File

@@ -49,7 +49,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
def setup_scanner(hass, config: dict, see):
def setup_scanner(hass, config: dict, see, discovery_info=None):
"""Validate the configuration and return an Automatic scanner."""
try:
AutomaticDeviceScanner(hass, config, see)

View File

@@ -25,7 +25,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
def setup_scanner(hass, config, see):
def setup_scanner(hass, config, see, discovery_info=None):
"""Setup the Bluetooth LE Scanner."""
# pylint: disable=import-error
from gattlib import DiscoveryService

View File

@@ -21,7 +21,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
def setup_scanner(hass, config, see):
def setup_scanner(hass, config, see, discovery_info=None):
"""Setup the Bluetooth Scanner."""
# pylint: disable=import-error
import bluetooth

View File

@@ -4,7 +4,7 @@ import random
from homeassistant.components.device_tracker import DOMAIN
def setup_scanner(hass, config, see):
def setup_scanner(hass, config, see, discovery_info=None):
"""Setup the demo tracker."""
def offset():
"""Return random offset."""

View File

@@ -19,7 +19,7 @@ _LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['http']
def setup_scanner(hass, config, see):
def setup_scanner(hass, config, see, discovery_info=None):
"""Setup an endpoint for the GPSLogger application."""
hass.http.register_view(GPSLoggerView(see))

View File

@@ -71,7 +71,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
def setup_scanner(hass, config: dict, see):
def setup_scanner(hass, config: dict, see, discovery_info=None):
"""Set up the iCloud Scanner."""
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)

View File

@@ -20,7 +20,7 @@ _LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['http']
def setup_scanner(hass, config, see):
def setup_scanner(hass, config, see, discovery_info=None):
"""Setup an endpoint for the Locative application."""
hass.http.register_view(LocativeView(see))

View File

@@ -23,7 +23,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(mqtt.SCHEMA_BASE).extend({
})
def setup_scanner(hass, config, see):
def setup_scanner(hass, config, see, discovery_info=None):
"""Setup the MQTT tracker."""
devices = config[CONF_DEVICES]
qos = config[CONF_QOS]

View File

@@ -0,0 +1,60 @@
"""
Support for tracking MySensors devices.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.mysensors/
"""
import logging
from homeassistant.components import mysensors
from homeassistant.util import slugify
DEPENDENCIES = ['mysensors']
_LOGGER = logging.getLogger(__name__)
def setup_scanner(hass, config, see, discovery_info=None):
"""Setup the MySensors tracker."""
def mysensors_callback(gateway, node_id):
"""Callback for mysensors platform."""
node = gateway.sensors[node_id]
if node.sketch_name is None:
_LOGGER.info('No sketch_name: node %s', node_id)
return
pres = gateway.const.Presentation
set_req = gateway.const.SetReq
for child in node.children.values():
position = child.values.get(set_req.V_POSITION)
if child.type != pres.S_GPS or position is None:
continue
try:
latitude, longitude, _ = position.split(',')
except ValueError:
_LOGGER.error('Payload for V_POSITION %s is not of format '
'latitude,longitude,altitude', position)
continue
name = '{} {} {}'.format(
node.sketch_name, node_id, child.id)
attr = {
mysensors.ATTR_CHILD_ID: child.id,
mysensors.ATTR_DESCRIPTION: child.description,
mysensors.ATTR_DEVICE: gateway.device,
mysensors.ATTR_NODE_ID: node_id,
}
see(
dev_id=slugify(name),
host_name=name,
gps=(latitude, longitude),
battery=node.battery_level,
attributes=attr
)
gateways = hass.data.get(mysensors.MYSENSORS_GATEWAYS)
for gateway in gateways:
gateway.platform_callbacks.append(mysensors_callback)
return True

View File

@@ -71,7 +71,7 @@ def get_cipher():
return (KEYLEN, decrypt)
def setup_scanner(hass, config, see):
def setup_scanner(hass, config, see, discovery_info=None):
"""Set up an OwnTracks tracker."""
max_gps_accuracy = config.get(CONF_MAX_GPS_ACCURACY)
waypoint_import = config.get(CONF_WAYPOINT_IMPORT)
@@ -190,7 +190,7 @@ def setup_scanner(hass, config, see):
return
# OwnTracks uses - at the start of a beacon zone
# to switch on 'hold mode' - ignore this
location = slugify(data['desc'].lstrip("-"))
location = data['desc'].lstrip("-")
if location.lower() == 'home':
location = STATE_HOME
@@ -198,7 +198,7 @@ def setup_scanner(hass, config, see):
def enter_event():
"""Execute enter event."""
zone = hass.states.get("zone.{}".format(location))
zone = hass.states.get("zone.{}".format(slugify(location)))
with LOCK:
if zone is None and data.get('t') == 'b':
# Not a HA zone, and a beacon so assume mobile
@@ -227,7 +227,8 @@ def setup_scanner(hass, config, see):
if new_region:
# Exit to previous region
zone = hass.states.get("zone.{}".format(new_region))
zone = hass.states.get(
"zone.{}".format(slugify(new_region)))
_set_gps_from_zone(kwargs, new_region, zone)
_LOGGER.info("Exit to %s", new_region)
see(**kwargs)

View File

@@ -19,7 +19,7 @@ from datetime import timedelta
import voluptuous as vol
from homeassistant.components.device_tracker import (
PLATFORM_SCHEMA, DEFAULT_SCAN_INTERVAL)
PLATFORM_SCHEMA, DEFAULT_SCAN_INTERVAL, SOURCE_TYPE_ROUTER)
from homeassistant.helpers.event import track_point_in_utc_time
from homeassistant import util
from homeassistant import const
@@ -66,14 +66,14 @@ class Host:
failed = 0
while failed < self._count: # check more times if host in unreachable
if self.ping():
see(dev_id=self.dev_id)
see(dev_id=self.dev_id, source_type=SOURCE_TYPE_ROUTER)
return True
failed += 1
_LOGGER.debug("ping KO on ip=%s failed=%d", self.ip_address, failed)
def setup_scanner(hass, config, see):
def setup_scanner(hass, config, see, discovery_info=None):
"""Setup the Host objects and return the update function."""
hosts = [Host(ip, dev_id, hass, config) for (dev_id, ip) in
config[const.CONF_HOSTS].items()]

View File

@@ -30,7 +30,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
# pylint: disable=unused-argument
def get_scanner(hass, config):
"""Return a Sky Hub 5 scanner if successful."""
"""Return a Sky Hub scanner if successful."""
scanner = SkyHubDeviceScanner(config[DOMAIN])
return scanner if scanner.success_init else None
@@ -111,6 +111,9 @@ def _get_skyhub_data(url):
def _parse_skyhub_response(data_str):
"""Parse the Sky Hub data format."""
pattmatch = re.search('attach_dev = \'(.*)\'', data_str)
if pattmatch is None:
raise IOError('Error: Impossible to fetch data from' +
' Sky Hub. Try to reboot the router.')
patt = pattmatch.group(1)
dev = [patt1.split(',') for patt1 in patt.split('<lf>')]

View File

@@ -17,24 +17,24 @@ from homeassistant.components.device_tracker import (
from homeassistant.const import CONF_HOST
from homeassistant.util import Throttle
# Return cached results if last scan was less then this time ago.
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['pysnmp==4.3.2']
CONF_COMMUNITY = "community"
CONF_AUTHKEY = "authkey"
CONF_PRIVKEY = "privkey"
CONF_BASEOID = "baseoid"
REQUIREMENTS = ['pysnmp==4.3.3']
DEFAULT_COMMUNITY = "public"
CONF_COMMUNITY = 'community'
CONF_AUTHKEY = 'authkey'
CONF_PRIVKEY = 'privkey'
CONF_BASEOID = 'baseoid'
DEFAULT_COMMUNITY = 'public'
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_COMMUNITY, default=DEFAULT_COMMUNITY): cv.string,
vol.Inclusive(CONF_AUTHKEY, "keys"): cv.string,
vol.Inclusive(CONF_PRIVKEY, "keys"): cv.string,
vol.Inclusive(CONF_AUTHKEY, 'keys'): cv.string,
vol.Inclusive(CONF_PRIVKEY, 'keys'): cv.string,
vol.Required(CONF_BASEOID): cv.string
})
@@ -119,14 +119,14 @@ class SnmpScanner(DeviceScanner):
return
# pylint: disable=no-member
if errstatus:
_LOGGER.error('SNMP error: %s at %s', errstatus.prettyPrint(),
_LOGGER.error("SNMP error: %s at %s", errstatus.prettyPrint(),
errindex and restable[int(errindex) - 1][0] or '?')
return
for resrow in restable:
for _, val in resrow:
mac = binascii.hexlify(val.asOctets()).decode('utf-8')
_LOGGER.debug('Found mac %s', mac)
_LOGGER.debug("Found MAC %s", mac)
mac = ':'.join([mac[i:i+2] for i in range(0, len(mac), 2)])
devices.append({'mac': mac})
return devices

View File

@@ -23,7 +23,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
def setup_scanner(hass, config: dict, see):
def setup_scanner(hass, config: dict, see, discovery_info=None):
"""Validate the configuration and return a TrackR scanner."""
TrackRDeviceScanner(hass, config, see)
return True

View File

@@ -92,7 +92,8 @@ class UPCDeviceScanner(DeviceScanner):
raw = yield from self._async_ws_function(CMD_DEVICES)
try:
xml_root = ET.fromstring(raw)
xml_root = yield from self.hass.loop.run_in_executor(
None, ET.fromstring, raw)
return [mac.text for mac in xml_root.iter('MACAddr')]
except (ET.ParseError, TypeError):
_LOGGER.warning("Can't read device from %s", self.host)

View File

@@ -30,7 +30,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
def setup_scanner(hass, config, see):
def setup_scanner(hass, config, see, discovery_info=None):
"""Validate the configuration and return a scanner."""
from volvooncall import Connection
connection = Connection(

View File

@@ -14,7 +14,7 @@ import voluptuous as vol
from homeassistant.const import EVENT_HOMEASSISTANT_START
from homeassistant.helpers.discovery import load_platform, discover
REQUIREMENTS = ['netdisco==0.8.1']
REQUIREMENTS = ['netdisco==0.8.2']
DOMAIN = 'discovery'
@@ -39,6 +39,8 @@ SERVICE_HANDLERS = {
'denonavr': ('media_player', 'denonavr'),
'samsung_tv': ('media_player', 'samsungtv'),
'yeelight': ('light', 'yeelight'),
'flux_led': ('light', 'flux_led'),
'apple_tv': ('media_player', 'apple_tv'),
}
CONFIG_SCHEMA = vol.Schema({

View File

@@ -8,14 +8,13 @@ from homeassistant import core
from homeassistant.const import (
ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON, SERVICE_VOLUME_SET,
SERVICE_OPEN_COVER, SERVICE_CLOSE_COVER, STATE_ON, STATE_OFF,
HTTP_BAD_REQUEST, HTTP_NOT_FOUND,
HTTP_BAD_REQUEST, HTTP_NOT_FOUND, ATTR_SUPPORTED_FEATURES,
)
from homeassistant.components.light import (
ATTR_BRIGHTNESS, ATTR_SUPPORTED_FEATURES, SUPPORT_BRIGHTNESS
ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS
)
from homeassistant.components.media_player import (
ATTR_MEDIA_VOLUME_LEVEL, ATTR_SUPPORTED_MEDIA_COMMANDS,
SUPPORT_VOLUME_SET,
ATTR_MEDIA_VOLUME_LEVEL, SUPPORT_VOLUME_SET,
)
from homeassistant.components.fan import (
ATTR_SPEED, SUPPORT_SET_SPEED, SPEED_OFF, SPEED_LOW,
@@ -178,11 +177,10 @@ class HueOneLightChangeView(HomeAssistantView):
# Make sure the entity actually supports brightness
entity_features = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
if (entity_features &
SUPPORT_BRIGHTNESS &
(entity.domain == "light")) == SUPPORT_BRIGHTNESS:
if brightness is not None:
data[ATTR_BRIGHTNESS] = brightness
if entity.domain == "light":
if entity_features & SUPPORT_BRIGHTNESS:
if brightness is not None:
data[ATTR_BRIGHTNESS] = brightness
# If the requested entity is a script add some variables
elif entity.domain == "script":
@@ -195,9 +193,7 @@ class HueOneLightChangeView(HomeAssistantView):
# If the requested entity is a media player, convert to volume
elif entity.domain == "media_player":
media_commands = entity.attributes.get(
ATTR_SUPPORTED_MEDIA_COMMANDS, 0)
if media_commands & SUPPORT_VOLUME_SET == SUPPORT_VOLUME_SET:
if entity_features & SUPPORT_VOLUME_SET:
if brightness is not None:
turn_on_needed = True
domain = entity.domain
@@ -215,9 +211,7 @@ class HueOneLightChangeView(HomeAssistantView):
# If the requested entity is a fan, convert to speed
elif entity.domain == "fan":
functions = entity.attributes.get(
ATTR_SUPPORTED_FEATURES, 0)
if (functions & SUPPORT_SET_SPEED) == SUPPORT_SET_SPEED:
if entity_features & SUPPORT_SET_SPEED:
if brightness is not None:
domain = entity.domain
# Convert 0-100 to a fan speed
@@ -288,9 +282,10 @@ def parse_hue_api_put_light_body(request_json, entity):
# Make sure the entity actually supports brightness
entity_features = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
if (entity_features & SUPPORT_BRIGHTNESS) == SUPPORT_BRIGHTNESS:
report_brightness = True
result = (brightness > 0)
if entity.domain == "light":
if entity_features & SUPPORT_BRIGHTNESS:
report_brightness = True
result = (brightness > 0)
elif (entity.domain == "script" or
entity.domain == "media_player" or
@@ -316,8 +311,9 @@ def get_entity_state(config, entity):
# Make sure the entity actually supports brightness
entity_features = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
if (entity_features & SUPPORT_BRIGHTNESS) == SUPPORT_BRIGHTNESS:
pass
if entity.domain == "light":
if entity_features & SUPPORT_BRIGHTNESS:
pass
elif entity.domain == "media_player":
level = entity.attributes.get(

View File

@@ -4,7 +4,9 @@ Provides functionality to interact with fans.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/fan/
"""
import asyncio
from datetime import timedelta
import functools as ft
import logging
import os
@@ -24,13 +26,14 @@ import homeassistant.helpers.config_validation as cv
DOMAIN = 'fan'
SCAN_INTERVAL = timedelta(seconds=30)
_LOGGER = logging.getLogger(__name__)
GROUP_NAME_ALL_FANS = 'all fans'
ENTITY_ID_ALL_FANS = group.ENTITY_ID_FORMAT.format(GROUP_NAME_ALL_FANS)
ENTITY_ID_FORMAT = DOMAIN + '.{}'
# Bitfield of features supported by the fan entity
ATTR_SUPPORTED_FEATURES = 'supported_features'
SUPPORT_SET_SPEED = 1
SUPPORT_OSCILLATE = 2
SUPPORT_DIRECTION = 4
@@ -56,7 +59,6 @@ PROP_TO_ATTR = {
'speed': ATTR_SPEED,
'speed_list': ATTR_SPEED_LIST,
'oscillating': ATTR_OSCILLATING,
'supported_features': ATTR_SUPPORTED_FEATURES,
'direction': ATTR_DIRECTION,
} # type: dict
@@ -88,7 +90,32 @@ FAN_SET_DIRECTION_SCHEMA = vol.Schema({
vol.Optional(ATTR_DIRECTION): cv.string
}) # type: dict
_LOGGER = logging.getLogger(__name__)
SERVICE_TO_METHOD = {
SERVICE_TURN_ON: {
'method': 'async_turn_on',
'schema': FAN_TURN_ON_SCHEMA,
},
SERVICE_TURN_OFF: {
'method': 'async_turn_off',
'schema': FAN_TURN_OFF_SCHEMA,
},
SERVICE_TOGGLE: {
'method': 'async_toggle',
'schema': FAN_TOGGLE_SCHEMA,
},
SERVICE_SET_SPEED: {
'method': 'async_set_speed',
'schema': FAN_SET_SPEED_SCHEMA,
},
SERVICE_OSCILLATE: {
'method': 'async_oscillate',
'schema': FAN_OSCILLATE_SCHEMA,
},
SERVICE_SET_DIRECTION: {
'method': 'async_set_direction',
'schema': FAN_SET_DIRECTION_SCHEMA,
},
}
def is_on(hass, entity_id: str=None) -> bool:
@@ -164,60 +191,53 @@ def set_direction(hass, entity_id: str=None, direction: str=None) -> None:
hass.services.call(DOMAIN, SERVICE_SET_DIRECTION, data)
def setup(hass, config: dict) -> None:
@asyncio.coroutine
def async_setup(hass, config: dict):
"""Expose fan control via statemachine and services."""
component = EntityComponent(
_LOGGER, DOMAIN, hass, SCAN_INTERVAL, GROUP_NAME_ALL_FANS)
component.setup(config)
def handle_fan_service(service: str) -> None:
yield from component.async_setup(config)
@asyncio.coroutine
def async_handle_fan_service(service):
"""Hande service call for fans."""
# Get the validated data
method = SERVICE_TO_METHOD.get(service.service)
params = service.data.copy()
# Convert the entity ids to valid fan ids
target_fans = component.extract_from_service(service)
target_fans = component.async_extract_from_service(service)
params.pop(ATTR_ENTITY_ID, None)
service_fun = None
for service_def in [SERVICE_TURN_ON, SERVICE_TURN_OFF,
SERVICE_SET_SPEED, SERVICE_OSCILLATE,
SERVICE_SET_DIRECTION]:
if service_def == service.service:
service_fun = service_def
break
for fan in target_fans:
yield from getattr(fan, method['method'])(**params)
if service_fun:
for fan in target_fans:
getattr(fan, service_fun)(**params)
update_tasks = []
for fan in target_fans:
if fan.should_poll:
fan.update_ha_state(True)
return
for fan in target_fans:
if not fan.should_poll:
continue
update_coro = hass.loop.create_task(
fan.async_update_ha_state(True))
if hasattr(fan, 'async_update'):
update_tasks.append(update_coro)
else:
yield from update_coro
if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)
# Listen for fan service calls.
descriptions = load_yaml_config_file(
os.path.join(os.path.dirname(__file__), 'services.yaml'))
hass.services.register(DOMAIN, SERVICE_TURN_ON, handle_fan_service,
descriptions.get(SERVICE_TURN_ON),
schema=FAN_TURN_ON_SCHEMA)
descriptions = yield from hass.loop.run_in_executor(
None, load_yaml_config_file, os.path.join(
os.path.dirname(__file__), 'services.yaml'))
hass.services.register(DOMAIN, SERVICE_TURN_OFF, handle_fan_service,
descriptions.get(SERVICE_TURN_OFF),
schema=FAN_TURN_OFF_SCHEMA)
hass.services.register(DOMAIN, SERVICE_SET_SPEED, handle_fan_service,
descriptions.get(SERVICE_SET_SPEED),
schema=FAN_SET_SPEED_SCHEMA)
hass.services.register(DOMAIN, SERVICE_OSCILLATE, handle_fan_service,
descriptions.get(SERVICE_OSCILLATE),
schema=FAN_OSCILLATE_SCHEMA)
hass.services.register(DOMAIN, SERVICE_SET_DIRECTION, handle_fan_service,
descriptions.get(SERVICE_SET_DIRECTION),
schema=FAN_SET_DIRECTION_SCHEMA)
for service_name in SERVICE_TO_METHOD:
schema = SERVICE_TO_METHOD[service_name].get('schema')
hass.services.async_register(
DOMAIN, service_name, async_handle_fan_service,
descriptions.get(service_name), schema=schema)
return True
@@ -225,34 +245,57 @@ def setup(hass, config: dict) -> None:
class FanEntity(ToggleEntity):
"""Representation of a fan."""
# pylint: disable=no-self-use
def set_speed(self: ToggleEntity, speed: str) -> None:
"""Set the speed of the fan."""
if speed is SPEED_OFF:
self.turn_off()
return
raise NotImplementedError()
def async_set_speed(self: ToggleEntity, speed: str):
"""Set the speed of the fan.
This method must be run in the event loop and returns a coroutine.
"""
if speed is SPEED_OFF:
return self.async_turn_off()
return self.hass.loop.run_in_executor(None, self.set_speed, speed)
def set_direction(self: ToggleEntity, direction: str) -> None:
"""Set the direction of the fan."""
raise NotImplementedError()
def async_set_direction(self: ToggleEntity, direction: str):
"""Set the direction of the fan.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.loop.run_in_executor(
None, self.set_direction, direction)
def turn_on(self: ToggleEntity, speed: str=None, **kwargs) -> None:
"""Turn on the fan."""
if speed is SPEED_OFF:
self.turn_off()
return
raise NotImplementedError()
def turn_off(self: ToggleEntity, **kwargs) -> None:
"""Turn off the fan."""
raise NotImplementedError()
def async_turn_on(self: ToggleEntity, speed: str=None, **kwargs):
"""Turn on the fan.
This method must be run in the event loop and returns a coroutine.
"""
if speed is SPEED_OFF:
return self.async_turn_off()
return self.hass.loop.run_in_executor(
None, ft.partial(self.turn_on, speed, **kwargs))
def oscillate(self: ToggleEntity, oscillating: bool) -> None:
"""Oscillate the fan."""
pass
def async_oscillate(self: ToggleEntity, oscillating: bool):
"""Oscillate the fan.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.loop.run_in_executor(
None, self.oscillate, oscillating)
@property
def is_on(self):
"""Return true if the entity is on."""

View File

@@ -56,8 +56,10 @@ class DemoFan(FanEntity):
"""Get the list of available speeds."""
return [STATE_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]
def turn_on(self, speed: str=SPEED_MEDIUM) -> None:
def turn_on(self, speed: str=None) -> None:
"""Turn on the entity."""
if speed is None:
speed = SPEED_MEDIUM
self.set_speed(speed)
def turn_off(self) -> None:
@@ -68,17 +70,17 @@ class DemoFan(FanEntity):
def set_speed(self, speed: str) -> None:
"""Set the speed of the fan."""
self._speed = speed
self.update_ha_state()
self.schedule_update_ha_state()
def set_direction(self, direction: str) -> None:
"""Set the direction of the fan."""
self.direction = direction
self.update_ha_state()
self.schedule_update_ha_state()
def oscillate(self, oscillating: bool) -> None:
"""Set oscillation."""
self.oscillating = oscillating
self.update_ha_state()
self.schedule_update_ha_state()
@property
def current_direction(self) -> str:

View File

@@ -29,7 +29,7 @@ STATE_TO_VALUE = {}
for key in VALUE_TO_STATE:
STATE_TO_VALUE[VALUE_TO_STATE[key]] = key
STATES = [SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]
STATES = [SPEED_OFF, SPEED_LOW, 'med', SPEED_HIGH]
# pylint: disable=unused-argument

View File

@@ -150,7 +150,7 @@ class MqttFan(FanEntity):
elif payload == self._payload[STATE_OFF]:
self._state = False
self.update_ha_state()
self.schedule_update_ha_state()
if self._topic[CONF_STATE_TOPIC] is not None:
mqtt.subscribe(self._hass, self._topic[CONF_STATE_TOPIC],
@@ -165,7 +165,7 @@ class MqttFan(FanEntity):
self._speed = SPEED_MEDIUM
elif payload == self._payload[SPEED_HIGH]:
self._speed = SPEED_HIGH
self.update_ha_state()
self.schedule_update_ha_state()
if self._topic[CONF_SPEED_STATE_TOPIC] is not None:
mqtt.subscribe(self._hass, self._topic[CONF_SPEED_STATE_TOPIC],
@@ -183,7 +183,7 @@ class MqttFan(FanEntity):
self._oscillation = True
elif payload == self._payload[OSCILLATE_OFF_PAYLOAD]:
self._oscillation = False
self.update_ha_state()
self.schedule_update_ha_state()
if self._topic[CONF_OSCILLATION_STATE_TOPIC] is not None:
mqtt.subscribe(self._hass,
@@ -262,7 +262,7 @@ class MqttFan(FanEntity):
self._speed = speed
mqtt.publish(self._hass, self._topic[CONF_SPEED_COMMAND_TOPIC],
mqtt_payload, self._qos, self._retain)
self.update_ha_state()
self.schedule_update_ha_state()
def oscillate(self, oscillating: bool) -> None:
"""Set oscillation."""
@@ -274,4 +274,4 @@ class MqttFan(FanEntity):
mqtt.publish(self._hass,
self._topic[CONF_OSCILLATION_COMMAND_TOPIC],
payload, self._qos, self._retain)
self.update_ha_state()
self.schedule_update_ha_state()

View File

@@ -10,7 +10,7 @@ from homeassistant.components.fan import (FanEntity, SPEED_HIGH,
SPEED_LOW, SPEED_MEDIUM,
STATE_UNKNOWN)
from homeassistant.helpers.entity import ToggleEntity
from homeassistant.components.wink import WinkDevice
from homeassistant.components.wink import WinkDevice, DOMAIN
_LOGGER = logging.getLogger(__name__)
@@ -22,7 +22,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Wink platform."""
import pywink
add_devices(WinkFanDevice(fan, hass) for fan in pywink.get_fans())
for fan in pywink.get_fans():
if fan.object_id() + fan.name() not in hass.data[DOMAIN]['unique_ids']:
add_devices([WinkFanDevice(fan, hass)])
class WinkFanDevice(WinkDevice, FanEntity):
@@ -30,7 +32,7 @@ class WinkFanDevice(WinkDevice, FanEntity):
def __init__(self, wink, hass):
"""Initialize the fan."""
WinkDevice.__init__(self, wink, hass)
super().__init__(wink, hass)
def set_direction(self: ToggleEntity, direction: str) -> None:
"""Set the direction of the fan."""

View File

@@ -6,18 +6,29 @@ https://home-assistant.io/components/ffmpeg/
"""
import asyncio
import logging
import os
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.const import (
ATTR_ENTITY_ID, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP)
from homeassistant.config import load_yaml_config_file
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
DOMAIN = 'ffmpeg'
REQUIREMENTS = ["ha-ffmpeg==1.2"]
REQUIREMENTS = ["ha-ffmpeg==1.4"]
_LOGGER = logging.getLogger(__name__)
SERVICE_START = 'start'
SERVICE_STOP = 'stop'
SERVICE_RESTART = 'restart'
DATA_FFMPEG = 'ffmpeg'
CONF_INITIAL_STATE = 'initial_state'
CONF_INPUT = 'input'
CONF_FFMPEG_BIN = 'ffmpeg_bin'
CONF_EXTRA_ARGUMENTS = 'extra_arguments'
@@ -34,18 +45,89 @@ CONFIG_SCHEMA = vol.Schema({
}),
}, extra=vol.ALLOW_EXTRA)
SERVICE_FFMPEG_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
})
def start(hass, entity_id=None):
"""Start a ffmpeg process on entity."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_START, data)
def stop(hass, entity_id=None):
"""Stop a ffmpeg process on entity."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_STOP, data)
def restart(hass, entity_id=None):
"""Restart a ffmpeg process on entity."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_RESTART, data)
@asyncio.coroutine
def async_setup(hass, config):
"""Setup the FFmpeg component."""
conf = config.get(DOMAIN, {})
hass.data[DATA_FFMPEG] = FFmpegManager(
manager = FFmpegManager(
hass,
conf.get(CONF_FFMPEG_BIN, DEFAULT_BINARY),
conf.get(CONF_RUN_TEST, DEFAULT_RUN_TEST)
)
descriptions = yield from hass.loop.run_in_executor(
None, load_yaml_config_file,
os.path.join(os.path.dirname(__file__), 'services.yaml'))
# register service
@asyncio.coroutine
def async_service_handle(service):
"""Handle service ffmpeg process."""
entity_ids = service.data.get(ATTR_ENTITY_ID)
if entity_ids:
devices = [device for device in manager.entities
if device.entity_id in entity_ids]
else:
devices = manager.entities
tasks = []
for device in devices:
if service.service == SERVICE_START:
tasks.append(device.async_start_ffmpeg())
elif service.service == SERVICE_STOP:
tasks.append(device.async_stop_ffmpeg())
else:
tasks.append(device.async_restart_ffmpeg())
if tasks:
yield from asyncio.wait(tasks, loop=hass.loop)
tasks.clear()
for device in devices:
tasks.append(device.async_update_ha_state())
if tasks:
yield from asyncio.wait(tasks, loop=hass.loop)
hass.services.async_register(
DOMAIN, SERVICE_START, async_service_handle,
descriptions[DOMAIN].get(SERVICE_START), schema=SERVICE_FFMPEG_SCHEMA)
hass.services.async_register(
DOMAIN, SERVICE_STOP, async_service_handle,
descriptions[DOMAIN].get(SERVICE_STOP), schema=SERVICE_FFMPEG_SCHEMA)
hass.services.async_register(
DOMAIN, SERVICE_RESTART, async_service_handle,
descriptions[DOMAIN].get(SERVICE_RESTART),
schema=SERVICE_FFMPEG_SCHEMA)
hass.data[DATA_FFMPEG] = manager
return True
@@ -58,12 +140,42 @@ class FFmpegManager(object):
self._cache = {}
self._bin = ffmpeg_bin
self._run_test = run_test
self._entities = []
@property
def binary(self):
"""Return ffmpeg binary from config."""
return self._bin
@property
def entities(self):
"""Return ffmpeg entities for services."""
return self._entities
@callback
def async_register_device(self, device):
"""Register a ffmpeg process/device."""
self._entities.append(device)
@asyncio.coroutine
def async_shutdown(event):
"""Stop ffmpeg process."""
yield from device.async_stop_ffmpeg()
self.hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_STOP, async_shutdown)
# start on startup
if device.initial_state:
@asyncio.coroutine
def async_start(event):
"""Start ffmpeg process."""
yield from device.async_start_ffmpeg()
yield from device.async_update_ha_state()
self.hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_START, async_start)
@asyncio.coroutine
def async_run_test(self, input_source):
"""Run test on this input. TRUE is deactivate or run correct.
@@ -86,3 +198,42 @@ class FFmpegManager(object):
return False
self._cache[input_source] = True
return True
class FFmpegBase(Entity):
"""Interface object for ffmpeg."""
def __init__(self, initial_state=True):
"""Initialize ffmpeg base object."""
self.ffmpeg = None
self.initial_state = initial_state
@property
def available(self):
"""Return True if entity is available."""
return self.ffmpeg.is_running
@property
def should_poll(self):
"""Return True if entity has to be polled for state."""
return False
def async_start_ffmpeg(self):
"""Start a ffmpeg process.
This method must be run in the event loop and returns a coroutine.
"""
raise NotImplementedError()
def async_stop_ffmpeg(self):
"""Stop a ffmpeg process.
This method must be run in the event loop and returns a coroutine.
"""
return self.ffmpeg.close()
@asyncio.coroutine
def async_restart_ffmpeg(self):
"""Stop a ffmpeg process."""
yield from self.async_stop_ffmpeg()
yield from self.async_start_ffmpeg()

View File

@@ -9,6 +9,7 @@
<link rel='apple-touch-icon' sizes='180x180'
href='/static/icons/favicon-apple-180x180.png'>
<link rel="mask-icon" href="/static/icons/home-assistant-icon.svg" color="#3fbbf4">
<link rel='preload' href='{{ core_url }}' as='script'/>
{% for panel in panels.values() -%}
<link rel='prefetch' href='{{ panel.url }}'>
{% endfor -%}
@@ -24,35 +25,28 @@
<style>
body {
font-family: 'Roboto', 'Noto', sans-serif;
font-weight: 300;
font-weight: 400;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
margin: 0;
padding: 0;
}
#ha-init-skeleton {
display: -webkit-flex;
display: flex;
-webkit-flex-direction: column;
-webkit-justify-content: center;
-webkit-align-items: center;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin-bottom: 83px;
font-family: Roboto, sans-serif;
font-size: 0pt;
#ha-init-skeleton::before {
display: block;
content: "";
height: 48px;
background-color: #03A9F4;
}
#ha-init-skeleton .message {
transition: font-size 2s;
font-size: 0;
padding: 24px;
}
#ha-init-skeleton paper-spinner {
height: 28px;
margin-top: 16px;
#ha-init-skeleton.error .message {
font-size: 16px;
}
#ha-init-skeleton a {
@@ -60,31 +54,27 @@
text-decoration: none;
font-weight: bold;
}
#ha-init-skeleton.error {
font-size: 16px;
}
#ha-init-skeleton.error img,
#ha-init-skeleton.error paper-spinner {
display: none;
}
</style>
<script>
function initError() {
document
.getElementById('ha-init-skeleton')
.classList.add('error');
document.getElementById('ha-init-skeleton').classList.add('error');
};
window.noAuth = {{ no_auth }};
window.Polymer = {lazyRegister: true, useNativeCSSProperties: true, dom: 'shady'};
window.Polymer = {
lazyRegister: true,
useNativeCSSProperties: true,
dom: 'shady',
suppressTemplateNotifications: true,
suppressBindingNotifications: true,
};
</script>
</head>
<body>
<div id='ha-init-skeleton'>
<img src='/static/icons/favicon-192x192.png' height='192'>
<paper-spinner active></paper-spinner>
Home Assistant had trouble<br>connecting to the server.<br><br><a href='/'>TRY AGAIN</a>
<div class='message'>
Home Assistant had trouble<br>connecting to the server.<br><br>
<a href='/'>TRY AGAIN</a>
</div>
</div>
<home-assistant icons='{{ icons }}'></home-assistant>
{# <script src='/static/home-assistant-polymer/build/_demo_data_compiled.js'></script> #}

View File

@@ -1,18 +1,18 @@
"""DO NOT MODIFY. Auto-generated by script/fingerprint_frontend."""
FINGERPRINTS = {
"core.js": "769f3fdd4e04b34bd66c7415743cf7b5",
"frontend.html": "d48d9a13f7d677e59b1d22c6db051207",
"mdi.html": "7a0f14bbf3822449f9060b9c53bd7376",
"core.js": "adfeb513cf650acf763e284d76a48d6b",
"frontend.html": "43340b2369646b779e04a9925c225ab4",
"mdi.html": "c1dde43ccf5667f687c418fc8daf9668",
"micromarkdown-js.html": "93b5ec4016f0bba585521cf4d18dec1a",
"panels/ha-panel-dev-event.html": "f19840b9a6a46f57cb064b384e1353f5",
"panels/ha-panel-dev-info.html": "3765a371478cc66d677cf6dcc35267c6",
"panels/ha-panel-dev-service.html": "1d223225c1c75083738033895ea3e4b5",
"panels/ha-panel-dev-state.html": "8257d99a38358a150eafdb23fa6727e0",
"panels/ha-panel-dev-template.html": "cbb251acabd5e7431058ed507b70522b",
"panels/ha-panel-history.html": "9f2c72574fb6135beb1b381a4b8b7703",
"panels/ha-panel-dev-event.html": "5c82300b3cf543a92cf4297506e450e7",
"panels/ha-panel-dev-info.html": "0469024d94d6270a8680df2be44ba916",
"panels/ha-panel-dev-service.html": "9f749635e518a4ca7991975bdefdb10a",
"panels/ha-panel-dev-state.html": "7d069ba8fd5379fa8f59858b8c0a7473",
"panels/ha-panel-dev-template.html": "2b618508510afa5281c9ecae0c3a3dbd",
"panels/ha-panel-history.html": "8955c1d093a2c417c89ed90dd627c7d3",
"panels/ha-panel-iframe.html": "d920f0aa3c903680f2f8795e2255daab",
"panels/ha-panel-logbook.html": "313f2ac57aaa5ad55933c9bbf8d8a1e5",
"panels/ha-panel-map.html": "13f120066c0b5faa2ce1db2c3f3cc486",
"panels/ha-panel-logbook.html": "f36297a894524518fa70883f264492b0",
"panels/ha-panel-map.html": "9c8c7924ba8f731560c9f4093835cc26",
"websocket_test.html": "575de64b431fe11c3785bf96d7813450"
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -26,7 +26,7 @@ from homeassistant.const import (EVENT_HOMEASSISTANT_START, STATE_UNKNOWN,
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity import Entity
REQUIREMENTS = ['pyCEC==0.4.12']
REQUIREMENTS = ['pyCEC==0.4.13']
DOMAIN = 'hdmi_cec'

View File

@@ -64,7 +64,7 @@ def get_significant_states(start_time, end_time=None, entity_id=None,
"""
entity_ids = (entity_id.lower(), ) if entity_id is not None else None
states = recorder.get_model('States')
query = recorder.query('States').filter(
query = recorder.query(states).filter(
(states.domain.in_(SIGNIFICANT_DOMAINS) |
(states.last_changed == states.last_updated)) &
(states.last_updated > start_time))
@@ -221,12 +221,17 @@ class HistoryPeriodView(HomeAssistantView):
if datetime is None:
return self.json_message('Invalid datetime', HTTP_BAD_REQUEST)
now = dt_util.utcnow()
one_day = timedelta(days=1)
if datetime:
start_time = dt_util.as_utc(datetime)
else:
start_time = dt_util.utcnow() - one_day
start_time = now - one_day
if start_time > now:
return self.json([])
end_time = start_time + one_day
entity_id = request.GET.get('filter_entity_id')

View File

@@ -16,17 +16,16 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.const import (
EVENT_HOMEASSISTANT_STOP, STATE_UNKNOWN, CONF_USERNAME, CONF_PASSWORD,
CONF_PLATFORM, CONF_HOSTS, CONF_NAME, ATTR_ENTITY_ID)
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers import discovery
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import track_time_interval
from homeassistant.config import load_yaml_config_file
from homeassistant.util import Throttle
DOMAIN = 'homematic'
REQUIREMENTS = ["pyhomematic==0.1.20"]
REQUIREMENTS = ["pyhomematic==0.1.21"]
MIN_TIME_BETWEEN_UPDATE_HUB = timedelta(seconds=300)
SCAN_INTERVAL = timedelta(seconds=30)
SCAN_INTERVAL_HUB = timedelta(seconds=300)
SCAN_INTERVAL_VARIABLES = timedelta(seconds=30)
DISCOVER_SWITCHES = 'homematic.switch'
DISCOVER_LIGHTS = 'homematic.light'
@@ -176,8 +175,9 @@ SCHEMA_SERVICE_VIRTUALKEY = vol.Schema({
})
SCHEMA_SERVICE_SET_VAR_VALUE = vol.Schema({
vol.Required(ATTR_ENTITY_ID): cv.entity_ids,
vol.Required(ATTR_NAME): cv.string,
vol.Required(ATTR_VALUE): cv.match_all,
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
})
SCHEMA_SERVICE_SET_DEV_VALUE = vol.Schema({
@@ -236,8 +236,6 @@ def setup(hass, config):
"""Setup the Homematic component."""
from pyhomematic import HMConnection
component = EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL)
hass.data[DATA_DELAY] = config[DOMAIN].get(CONF_DELAY)
hass.data[DATA_DEVINIT] = {}
hass.data[DATA_STORE] = []
@@ -281,11 +279,10 @@ def setup(hass, config):
hass.config.components.append(DOMAIN)
# init homematic hubs
hub_entities = []
entity_hubs = []
for _, hub_data in hosts.items():
hub_entities.append(HMHub(hass, component, hub_data[CONF_NAME],
hub_data[CONF_VARIABLES]))
component.add_entities(hub_entities)
entity_hubs.append(HMHub(
hass, hub_data[CONF_NAME], hub_data[CONF_VARIABLES]))
# regeister homematic services
descriptions = load_yaml_config_file(
@@ -323,14 +320,23 @@ def setup(hass, config):
schema=SCHEMA_SERVICE_VIRTUALKEY)
def _service_handle_value(service):
"""Set value on homematic variable object."""
variable_list = component.extract_from_service(service)
"""Set value on homematic variable."""
entity_ids = service.data.get(ATTR_ENTITY_ID)
name = service.data[ATTR_NAME]
value = service.data[ATTR_VALUE]
for hm_variable in variable_list:
if isinstance(hm_variable, HMVariable):
hm_variable.hm_set(value)
if entity_ids:
entities = [entity for entity in entity_hubs if
entity.entity_id in entity_ids]
else:
entities = entity_hubs
if not entities:
_LOGGER.error("Homematic controller not found!")
return
for hub in entities:
hub.hm_set_variable(name, value)
hass.services.register(
DOMAIN, SERVICE_SET_VAR_VALUE, _service_handle_value,
@@ -579,132 +585,87 @@ def _device_from_servicecall(hass, service):
class HMHub(Entity):
"""The Homematic hub. I.e. CCU2/HomeGear."""
def __init__(self, hass, component, name, use_variables):
def __init__(self, hass, name, use_variables):
"""Initialize Homematic hub."""
self.hass = hass
self.entity_id = "{}.{}".format(DOMAIN, name.lower())
self._homematic = hass.data[DATA_HOMEMATIC]
self._component = component
self._variables = {}
self._name = name
self._state = STATE_UNKNOWN
self._store = {}
self._use_variables = use_variables
# load data
self._update_hub_state()
self._init_variables()
track_time_interval(hass, self._update_hub, SCAN_INTERVAL_HUB)
self._update_hub(None)
if self._use_variables:
track_time_interval(
hass, self._update_variables, SCAN_INTERVAL_VARIABLES)
self._update_variables(None)
@property
def name(self):
"""Return the name of the device."""
return self._name
@property
def state(self):
"""Return the state of the entity."""
return self._state
@property
def device_state_attributes(self):
"""Return device specific state attributes."""
return {}
@property
def icon(self):
"""Return the icon to use in the frontend, if any."""
return "mdi:gradient"
def update(self):
"""Update Hub data and all HM variables."""
self._update_hub_state()
self._update_variables_state()
@Throttle(MIN_TIME_BETWEEN_UPDATE_HUB)
def _update_hub_state(self):
"""Retrieve latest state."""
state = self._homematic.getServiceMessages(self._name)
self._state = STATE_UNKNOWN if state is None else len(state)
def _update_variables_state(self):
"""Retrive all variable data and update hmvariable states."""
if not self._use_variables:
return
variables = self._homematic.getAllSystemVariables(self._name)
if variables is None:
return
for key, value in variables.items():
if key in self._store:
self._store.get(key).hm_update(value)
def _init_variables(self):
"""Load variables from hub."""
if not self._use_variables:
return
variables = self._homematic.getAllSystemVariables(self._name)
if variables is None:
return
entities = []
for key, value in variables.items():
entities.append(HMVariable(self.hass, self._name, key, value))
self._component.add_entities(entities)
class HMVariable(Entity):
"""The Homematic system variable."""
def __init__(self, hass, hub_name, name, state):
"""Initialize Homematic hub."""
self.hass = hass
self._homematic = hass.data[DATA_HOMEMATIC]
self._state = state
self._name = name
self._hub_name = hub_name
@property
def name(self):
"""Return the name of the device."""
return self._name
@property
def state(self):
"""Return the state of the entity."""
return self._state
@property
def icon(self):
"""Return the icon to use in the frontend, if any."""
return "mdi:code-string"
@property
def should_poll(self):
"""Return false. Homematic Hub object update variable."""
return False
@property
def device_state_attributes(self):
"""Return device specific state attributes."""
attr = {
'hub': self._hub_name,
}
def state(self):
"""Return the state of the entity."""
return self._state
@property
def state_attributes(self):
"""Return the state attributes."""
attr = self._variables.copy()
return attr
def hm_update(self, value):
"""Update variable over Hub object."""
if value != self._state:
self._state = value
@property
def icon(self):
"""Return the icon to use in the frontend, if any."""
return "mdi:gradient"
def _update_hub(self, now):
"""Retrieve latest state."""
state = self._homematic.getServiceMessages(self._name)
self._state = STATE_UNKNOWN if state is None else len(state)
self.schedule_update_ha_state()
def _update_variables(self, now):
"""Retrive all variable data and update hmvariable states."""
variables = self._homematic.getAllSystemVariables(self._name)
if variables is None:
return
state_change = False
for key, value in variables.items():
if key in self._variables and value == self._variables[key]:
continue
state_change = True
self._variables.update({key: value})
if state_change:
self.schedule_update_ha_state()
def hm_set(self, value):
def hm_set_variable(self, name, value):
"""Set variable on homematic controller."""
if isinstance(self._state, bool):
if name not in self._variables:
_LOGGER.error("Variable %s not found on %s", name, self.name)
return
old_value = self._variables.get(name)
if isinstance(old_value, bool):
value = cv.boolean(value)
else:
value = float(value)
self._homematic.setSystemVariable(self._hub_name, self._name, value)
self._state = value
self._homematic.setSystemVariable(self.name, name, value)
self._variables.update({name: value})
self.schedule_update_ha_state()
@@ -817,7 +778,7 @@ class HMDevice(Entity):
have_change = True
# If available it has changed
if attribute is 'UNREACH':
if attribute == 'UNREACH':
self._available = bool(value)
have_change = True
@@ -829,7 +790,7 @@ class HMDevice(Entity):
def _subscribe_homematic_events(self):
"""Subscribe all required events to handle job."""
channels_to_sub = {}
channels_to_sub = {0: True} # add channel 0 for UNREACH
# Push data to channels_to_sub from hmdevice metadata
for metadata in (self._hmdevice.SENSORNODE, self._hmdevice.BINARYNODE,
@@ -856,7 +817,7 @@ class HMDevice(Entity):
# Set callbacks
for channel in channels_to_sub:
_LOGGER.debug(
"Subscribe channel %s from %s", str(channel), self._name)
"Subscribe channel %d from %s", channel, self._name)
self._hmdevice.setEventCallback(
callback=self._hm_event_callback, bequeath=False,
channel=channel)

View File

@@ -4,53 +4,22 @@ Support for the demo image processing.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/demo/
"""
from homeassistant.components.image_processing import ImageProcessingEntity
from homeassistant.components.image_processing import ATTR_CONFIDENCE
from homeassistant.components.image_processing.openalpr_local import (
ImageProcessingAlprEntity)
from homeassistant.components.image_processing.microsoft_face_identify import (
ImageProcessingFaceIdentifyEntity)
ImageProcessingFaceEntity, ATTR_NAME, ATTR_AGE, ATTR_GENDER)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the demo image_processing platform."""
add_devices([
DemoImageProcessing('camera.demo_camera', "Demo"),
DemoImageProcessingAlpr('camera.demo_camera', "Demo Alpr"),
DemoImageProcessingFaceIdentify(
'camera.demo_camera', "Demo Face Identify")
DemoImageProcessingFace(
'camera.demo_camera', "Demo Face")
])
class DemoImageProcessing(ImageProcessingEntity):
"""Demo alpr image processing entity."""
def __init__(self, camera_entity, name):
"""Initialize demo alpr."""
self._name = name
self._camera = camera_entity
self._count = 0
@property
def camera_entity(self):
"""Return camera entity id from process pictures."""
return self._camera
@property
def name(self):
"""Return the name of the entity."""
return self._name
@property
def state(self):
"""Return the state of the entity."""
return self._count
def process_image(self, image):
"""Process image."""
self._count += 1
class DemoImageProcessingAlpr(ImageProcessingAlprEntity):
"""Demo alpr image processing entity."""
@@ -88,7 +57,7 @@ class DemoImageProcessingAlpr(ImageProcessingAlprEntity):
self.process_plates(demo_data, 1)
class DemoImageProcessingFaceIdentify(ImageProcessingFaceIdentifyEntity):
class DemoImageProcessingFace(ImageProcessingFaceEntity):
"""Demo face identify image processing entity."""
def __init__(self, camera_entity, name):
@@ -115,10 +84,22 @@ class DemoImageProcessingFaceIdentify(ImageProcessingFaceIdentifyEntity):
def process_image(self, image):
"""Process image."""
demo_data = {
'Hans': 98.34,
'Helena': 82.53,
'Luna': 62.53,
}
demo_data = [
{
ATTR_CONFIDENCE: 98.34,
ATTR_NAME: 'Hans',
ATTR_AGE: 16.0,
ATTR_GENDER: 'male',
},
{
ATTR_NAME: 'Helena',
ATTR_AGE: 28.0,
ATTR_GENDER: 'female',
},
{
ATTR_CONFIDENCE: 62.53,
ATTR_NAME: 'Luna',
},
]
self.process_faces(demo_data, 4)

View File

@@ -0,0 +1,122 @@
"""
Component that will help set the microsoft face detect processing.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/image_processing.microsoft_face_detect/
"""
import asyncio
import logging
import voluptuous as vol
from homeassistant.core import split_entity_id
from homeassistant.exceptions import HomeAssistantError
from homeassistant.components.microsoft_face import DATA_MICROSOFT_FACE
from homeassistant.components.image_processing import (
PLATFORM_SCHEMA, CONF_SOURCE, CONF_ENTITY_ID, CONF_NAME)
from homeassistant.components.image_processing.microsoft_face_identify import (
ImageProcessingFaceEntity, ATTR_GENDER, ATTR_AGE, ATTR_GLASSES)
import homeassistant.helpers.config_validation as cv
DEPENDENCIES = ['microsoft_face']
_LOGGER = logging.getLogger(__name__)
EVENT_IDENTIFY_FACE = 'detect_face'
SUPPORTED_ATTRIBUTES = [
ATTR_AGE,
ATTR_GENDER,
ATTR_GLASSES
]
CONF_ATTRIBUTES = 'attributes'
DEFAULT_ATTRIBUTES = [ATTR_AGE, ATTR_GENDER]
def validate_attributes(list_attributes):
"""Validate face attributes."""
for attr in list_attributes:
if attr not in SUPPORTED_ATTRIBUTES:
raise vol.Invalid("Invalid attribtue {0}".format(attr))
return list_attributes
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_ATTRIBUTES, default=DEFAULT_ATTRIBUTES):
vol.All(cv.ensure_list, validate_attributes),
})
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up the microsoft face detection platform."""
api = hass.data[DATA_MICROSOFT_FACE]
attributes = config[CONF_ATTRIBUTES]
entities = []
for camera in config[CONF_SOURCE]:
entities.append(MicrosoftFaceDetectEntity(
camera[CONF_ENTITY_ID], api, attributes, camera.get(CONF_NAME)
))
yield from async_add_devices(entities)
class MicrosoftFaceDetectEntity(ImageProcessingFaceEntity):
"""Microsoft face api entity for identify."""
def __init__(self, camera_entity, api, attributes, name=None):
"""Initialize openalpr local api."""
super().__init__()
self._api = api
self._camera = camera_entity
self._attributes = attributes
if name:
self._name = name
else:
self._name = "MicrosoftFace {0}".format(
split_entity_id(camera_entity)[1])
@property
def camera_entity(self):
"""Return camera entity id from process pictures."""
return self._camera
@property
def name(self):
"""Return the name of the entity."""
return self._name
@asyncio.coroutine
def async_process_image(self, image):
"""Process image.
This method is a coroutine.
"""
face_data = None
try:
face_data = yield from self._api.call_api(
'post', 'detect', image, binary=True,
params={'returnFaceAttributes': ",".join(self._attributes)})
except HomeAssistantError as err:
_LOGGER.error("Can't process image on microsoft face: %s", err)
return
if face_data is None or len(face_data) < 1:
return
faces = []
for face in face_data:
face_attr = {}
for attr in self._attributes:
if attr in face['faceAttributes']:
face_attr[attr] = face['faceAttributes'][attr]
if face_attr:
faces.append(face_attr)
self.async_process_faces(faces, len(face_data))

View File

@@ -23,11 +23,16 @@ DEPENDENCIES = ['microsoft_face']
_LOGGER = logging.getLogger(__name__)
EVENT_IDENTIFY_FACE = 'identify_face'
EVENT_DETECT_FACE = 'image_processing.detect_face'
ATTR_NAME = 'name'
ATTR_TOTAL_FACES = 'total_faces'
ATTR_KNOWN_FACES = 'known_faces'
ATTR_AGE = 'age'
ATTR_GENDER = 'gender'
ATTR_MOTION = 'motion'
ATTR_GLASSES = 'glasses'
ATTR_FACES = 'faces'
CONF_GROUP = 'group'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@@ -52,71 +57,90 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
yield from async_add_devices(entities)
class ImageProcessingFaceIdentifyEntity(ImageProcessingEntity):
"""Base entity class for face identify/verify image processing."""
class ImageProcessingFaceEntity(ImageProcessingEntity):
"""Base entity class for face image processing."""
def __init__(self):
"""Initialize base face identify/verify entity."""
self.known_faces = {} # last scan data
self.faces = [] # last scan data
self.total_faces = 0 # face count
@property
def state(self):
"""Return the state of the entity."""
confidence = 0
face_name = STATE_UNKNOWN
state = STATE_UNKNOWN
# search high verify face
for i_name, i_co in self.known_faces.items():
if i_co > confidence:
confidence = i_co
face_name = i_name
return face_name
# no confidence support
if not self.confidence:
return self.total_faces
# search high confidence
for face in self.faces:
if ATTR_CONFIDENCE not in face:
continue
f_co = face[ATTR_CONFIDENCE]
if f_co > confidence:
confidence = f_co
for attr in [ATTR_NAME, ATTR_MOTION]:
if attr in face:
state = face[attr]
break
return state
@property
def state_attributes(self):
"""Return device specific state attributes."""
attr = {
ATTR_KNOWN_FACES: self.known_faces,
ATTR_FACES: self.faces,
ATTR_TOTAL_FACES: self.total_faces,
}
return attr
def process_faces(self, known, total):
def process_faces(self, faces, total):
"""Send event with detected faces and store data."""
run_callback_threadsafe(
self.hass.loop, self.async_process_faces, known, total
self.hass.loop, self.async_process_faces, faces, total
).result()
@callback
def async_process_faces(self, known, total):
def async_process_faces(self, faces, total):
"""Send event with detected faces and store data.
known are a dict in follow format:
{ 'name': confidence }
[
{
ATTR_CONFIDENCE: 80,
ATTR_NAME: 'Name',
ATTR_AGE: 12.0,
ATTR_GENDER: 'man',
ATTR_MOTION: 'smile',
ATTR_GLASSES: 'sunglasses'
},
]
This method must be run in the event loop.
"""
detect = {name: confidence for name, confidence in known.items()
if confidence >= self.confidence}
# send events
for name, confidence in detect.items():
for face in faces:
if ATTR_CONFIDENCE in face and self.confidence:
if face[ATTR_CONFIDENCE] < self.confidence:
continue
face.update({ATTR_ENTITY_ID: self.entity_id})
self.hass.async_add_job(
self.hass.bus.async_fire, EVENT_IDENTIFY_FACE, {
ATTR_NAME: name,
ATTR_ENTITY_ID: self.entity_id,
ATTR_CONFIDENCE: confidence,
}
self.hass.bus.async_fire, EVENT_DETECT_FACE, face
)
# update entity store
self.known_faces = detect
self.faces = faces
self.total_faces = total
class MicrosoftFaceIdentifyEntity(ImageProcessingFaceIdentifyEntity):
class MicrosoftFaceIdentifyEntity(ImageProcessingFaceEntity):
"""Microsoft face api entity for identify."""
def __init__(self, camera_entity, api, face_group, confidence, name=None):
@@ -173,7 +197,7 @@ class MicrosoftFaceIdentifyEntity(ImageProcessingFaceIdentifyEntity):
return
# parse data
knwon_faces = {}
knwon_faces = []
total = 0
for face in detect:
total += 1
@@ -187,7 +211,10 @@ class MicrosoftFaceIdentifyEntity(ImageProcessingFaceIdentifyEntity):
name = s_name
break
knwon_faces[name] = data['confidence'] * 100
knwon_faces.append({
ATTR_NAME: name,
ATTR_CONFIDENCE: data['confidence'] * 100,
})
# process data
self.async_process_faces(knwon_faces, total)

View File

@@ -24,7 +24,7 @@ _LOGGER = logging.getLogger(__name__)
RE_ALPR_PLATE = re.compile(r"^plate\d*:")
RE_ALPR_RESULT = re.compile(r"- (\w*)\s*confidence: (\d*.\d*)")
EVENT_FOUND_PLATE = 'found_plate'
EVENT_FOUND_PLATE = 'image_processing.found_plate'
ATTR_PLATE = 'plate'
ATTR_PLATES = 'plates'

View File

@@ -45,6 +45,15 @@ SERVICE_SELECT_PREVIOUS_SCHEMA = vol.Schema({
})
SERVICE_SET_OPTIONS = 'set_options'
SERVICE_SET_OPTIONS_SCHEMA = vol.Schema({
vol.Required(ATTR_ENTITY_ID): cv.entity_ids,
vol.Required(ATTR_OPTIONS):
vol.All(cv.ensure_list, vol.Length(min=1), [cv.string]),
})
def _cv_input_select(cfg):
"""Config validation helper for input select (Voluptuous)."""
options = cfg[CONF_OPTIONS]
@@ -89,6 +98,14 @@ def select_previous(hass, entity_id):
})
def set_options(hass, entity_id, options):
"""Set options of input_select."""
hass.services.call(DOMAIN, SERVICE_SET_OPTIONS, {
ATTR_ENTITY_ID: entity_id,
ATTR_OPTIONS: options,
})
@asyncio.coroutine
def async_setup(hass, config):
"""Setup input select."""
@@ -148,6 +165,20 @@ def async_setup(hass, config):
DOMAIN, SERVICE_SELECT_PREVIOUS, async_select_previous_service,
schema=SERVICE_SELECT_PREVIOUS_SCHEMA)
@asyncio.coroutine
def async_set_options_service(call):
"""Handle a calls to the set options service."""
target_inputs = component.async_extract_from_service(call)
tasks = [input_select.async_set_options(call.data[ATTR_OPTIONS])
for input_select in target_inputs]
if tasks:
yield from asyncio.wait(tasks, loop=hass.loop)
hass.services.async_register(
DOMAIN, SERVICE_SET_OPTIONS, async_set_options_service,
schema=SERVICE_SET_OPTIONS_SCHEMA)
yield from component.async_add_entities(entities)
return True
@@ -207,3 +238,10 @@ class InputSelect(Entity):
new_index = (current_index + offset) % len(self._options)
self._current_option = self._options[new_index]
yield from self.async_update_ha_state()
@asyncio.coroutine
def async_set_options(self, options):
"""Set options."""
self._current_option = options[0]
self._options = options
yield from self.async_update_ha_state()

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