Compare commits

..

167 Commits

Author SHA1 Message Date
Paulus Schoutsen
3f97944f6b Bumped version to 0.78.0b0 2018-09-10 13:44:56 +02:00
Paulus Schoutsen
1d6609e386 Update translations 2018-09-10 13:44:27 +02:00
Paulus Schoutsen
fb15dbf3ba Bump frontend to 20180910.0 2018-09-10 13:44:11 +02:00
Fabian Affolter
c20f147949 Upgrade keyring to 15.0.0 (#16536) 2018-09-10 13:35:47 +02:00
Fabian Affolter
8ed4d732ac Upgrade youtube_dl to 2018.09.10 (#16534) 2018-09-10 13:35:24 +02:00
Louis-Dominique Dubeau
a1578e3c6e Add a base_url configuration setting to tts. (#16478)
* Add a base_url configuration setting to tts.

* Remove the empty string as default value for base_urls

As requested in

https://github.com/home-assistant/home-assistant/pull/16478#pullrequestreview-153526144
2018-09-10 11:50:25 +02:00
Jason Hu
253e787a1b Upgrade aiohttp to 3.4.4 (#16486) 2018-09-10 10:39:51 +02:00
Fabian Affolter
0d7ee9b93b Order imports (#16515) 2018-09-09 14:26:06 +02:00
tadly
d6d4ff6888 adds listener for OnAVStart and OnAVChange (#16495)
With Kodi 18, OnPlay is called before an item actually started playing.
For this OnAVStart and OnAVChange have been introduced.
2018-09-09 09:55:57 +02:00
Jason Hu
4291bdc6b2 Move voluptuous-serialize to core requirement (#16507) 2018-09-09 09:49:51 +02:00
Anders Melchiorsen
7d590a6b93 Update pyHS100 to 0.3.3 (#16502) 2018-09-09 00:34:22 +02:00
Ville Skyttä
e3e3ed42ec Fix Netgear LTESensor docstring (#16501) 2018-09-08 23:22:41 +02:00
Paulus Schoutsen
e7b8d2e6df Update name legacy api password (#16455) 2018-09-08 22:10:42 +02:00
Fabian Affolter
9944c60311 Check if API key is valid and users available (#16494) 2018-09-08 18:33:41 +02:00
Varga Tamas
93143384a8 Restore status attribute for xiaomi_vacuum (#16366)
* Added new states and exposed state/state code received from xiaomi vacuum

* Restore status attribute for xiaomi_vacuum
2018-09-08 18:13:24 +02:00
Florian Werner
8a2bc99f63 Add rate of change to statistics sensor (#15632)
* always export max_age/min_age

* downgrade errors of missing data
on start with empty recorder database these errors are logged multiple times:
ERROR (MainThread) [homeassistant.components.sensor.statistics] mean requires at least one data point
ERROR (MainThread) [homeassistant.components.sensor.statistics] variance requires at least two data points

downgrade them to debug as they are not meaningful to end users

* add change_rate attribute
this calculates the average change rate of all data points

* simplify count, reorder attribute calculation

* reorder initialization

* reorder attribute names

* don't use min/max for min_age/max_age

* add test case

* style

* style

* sort constants

* init variables with None

* add precision config setting

* round to precision

* test round
2018-09-08 01:10:08 +02:00
Andreas Oberritter
50266e9b91 Support SNMPv3 and asyncio in snmp sensor (#14753)
* snmp sensor: Add asyncio support and reuse SnmpEngine object

* snmp sensor: Support protocol v3

* Fix lint issue
2018-09-07 20:51:18 +02:00
mvn23
7ad094b0a7 Add OpenTherm Gateway climate platform (#16299)
* Initial OpenTherm Gateway support.

* Fix coveragerc and requirements_all*.txt
Overall cleanup and polishing

* Make hound/flake/travis happy

* Basic improvements to comply with Home Assistant's style guidelines
Changed wording from "component" to more appropriate terms where necessary
Fixed small mistakes that snuck in during testing and/or due to my own ignorance ;)

* Fixed overwriting state property

* Fixed a bug with ROOM_SETPOINT_OVRD
Updated dependency pyotgw to latest version

* Remove unit_of_measurement from OpenThermGateway class

* Cleanup after previous commits

* Moved initialisation and configuration from async_setup_platform to async_added_to_hass

* Make travis happy

* Disable polling for this platform
Improve update flow

* Small improvements/optimisations
2018-09-07 18:16:19 +02:00
Fabian Affolter
a5715c48a4 Fix GitHub change to resolve conflicts (#16477) 2018-09-07 14:41:31 +02:00
Andreas Oberritter
d0f9d125a7 Support SNMPv3 and asyncio in snmp switch (#14754)
* snmp switch: Add asyncio support and reuse SnmpEngine object

* snmp switch: Support protocol v3
2018-09-07 13:28:42 +02:00
cpw
80c77b8696 Update radiotherm (#15031)
the two device_state_attributes don't return the current actual state (fan on/off, heat/cool), but rather the mode of the fan or thermostat (generally auto). As such this change makes the actual current state visible to the system, so you can do stuff when the fan turns on, for example. I suspect this is just a bug, but maybe it was intended NOT to be able to see the actual fan state?
2018-09-07 13:21:32 +02:00
Dom
1f73840aab Add Yale Smart Alarm component (#16377)
* Add Smart Alarm Component

* Lint fixes

* Update coverage

* Update coverage with the correct file name

* PR Fixes. Update client version (to include timeouts). Cleaned up clien tcreation and improved authentication failure error. Added state map to replace nasty if blocks.

* PR Fixes. Update client version (to include timeouts). Cleaned up clien tcreation and improved authentication failure error. Added state map to replace nasty if blocks.

* Remove test file added in error.

* PR Fixes
2018-09-07 12:22:10 +02:00
Greg Laabs
fbaa489533 Update license to official GitHub template (#16470)
* Delete LICENSE.md

* Create LICENSE.md
2018-09-07 10:11:51 +02:00
Tsvi Mostovicz
cff9b1bf7e Fix waze_travel_time component startup (#16465)
* Fix waze_travel_time component startup

* Remove @Throttle decorator as per pvizelli's request

* Make the linting gods happy again
2018-09-07 09:55:53 +02:00
Ioan Loosley
ce06229c42 Added Twitch v5 support to the twitch platform (#16428)
* code not working

* did it all all at once

* removing debug stuff

* fixing lint issues

* fixing lint issues

* I am blind

* I felt small(80 by somthing) was to small, especily with lovelace

* fixing silly mistake

* this shouldnt be here

* made some changes

* corrrected client_id

* corrrected client_id

* less returns needed

* Fixing Small bugs and making stuff snake case

* Tweaking
2018-09-06 14:08:39 +02:00
Daniel Høyer Iversen
3acb3a86cf update rfxtrx lib (#16463) 2018-09-06 12:50:55 +02:00
Fabian Affolter
db1bda2975 Upgrade Sphinx to 1.7.8 (#16459) 2018-09-06 12:31:30 +02:00
Fabian Affolter
2640db1522 Upgrade shodan to 1.10.1 (#16460) 2018-09-06 12:31:08 +02:00
PhracturedBlue
cf4b72e00e Fix camera proxy to not require api_password to function (#16450) 2018-09-06 10:51:16 +02:00
Daniel Høyer Iversen
5bd9be6252 switchbot (#16396)
* switchbot

* style

* use switchbot lib

* unnecessary import

* switchbot, rename variable update requirements_all

* add line
2018-09-06 10:02:37 +02:00
kbickar
e1084e3953 Upgrade sense library to 0.4.2 (#16429)
* Added error handling for sense API timeouts

* Moved imports in function

* Moved imports to more appropriate function

* Change exception to custom package version

* Updated sense_energy library to 0.4.2
2018-09-05 08:01:36 +02:00
Jason Hu
3afc983c05 Fix openuv.config_flow unit test (#16419) 2018-09-04 21:18:35 +02:00
Paulus Schoutsen
746f4ac158 Add context to scripts and automations (#16415)
* Add context to script helper

* Update script component

* Add context to automations

* Lint
2018-09-04 21:16:24 +02:00
Paul Annekov
e1501c83f8 Fix Mi Flora median calculation (#16085)
* fixed median was based on 1.5 minute interval, not 1 hour

* ignore median and set state from first value, when previous state was None

* update before add, removed unused 'retries' and 'ble_timeout', check if platform ready

* added missing blank line

* fixed too long line

* using modern python 3.5 features, changed comment to be less verbose

* continuation line fix

* removed DEFAULT_SCAN_INTERVAL in favor of existing SCAN_INTERVAL
2018-09-04 21:03:30 +02:00
Toon Willems
3bd12fcef6 Implement correct state for RFlink cover (#16304)
* implement correct state for rflink cover

* Fix linting error

* invert logic as local testing pointed out it should be reversed

* add period at the end to satisfy the linter
2018-09-04 11:15:02 +02:00
Daniel Høyer Iversen
85658b6dd1 Clean up dlink and some bug fix (#16346)
* Update dlink.py

* style

* style
2018-09-04 10:50:12 +02:00
Rohan Kapoor
e61ac1a4a1 Delegate mqtt topic match validation to the paho mqtt client (#16403)
* Delegate mqtt match topics to the paho mqtt client

* Fixing linting error with importing MQTTMatcher
2018-09-04 10:31:45 +02:00
Robert Svensson
8fa9992589 Service to load new deCONZ devices without restart (#16308)
* New service to load new devices from deCONZ without restarting HASS

* Do not use len to check if list is empty

* Add support for scenes to be updated as well

* Rework refresh devices method

* Fix test
2018-09-04 09:24:42 +02:00
Aaron Bach
f96aee2832 Add config flow for OpenUV (#16159)
* OpenUV config flow in place

* Test folder in place

* Owner-requested comments

* Tests

* More tests

* Owner-requested changes (part 1 of 2)

* Updated requirements

* Owner-requested changes (2 of 2)

* Removed unnecessary import

* Bumping Travis

* Updated requirements

* More requirements

* Updated tests

* Owner-requested changes

* Hound

* Updated docstring
2018-09-04 09:22:44 +02:00
Robert Svensson
7a6facc875 Device and entity registry remove config entry on unload (#16247)
* Test

* Ability to remove device

* Don't remove devices, instead remove config entry from device and entity registries

* Remove print

* Remove is not the same as unload

* Add tests

* Fix hound comment
2018-09-04 09:00:14 +02:00
9R
7ea482cb1d add ExpressBus icon key to sensor.mvg (#16387) 2018-09-04 08:48:03 +02:00
Rene Nulsch
a4aa30fc73 Fix SystemMonitor IP address sensor (#16394) 2018-09-04 08:46:04 +02:00
Russell Cloran
ba63a6abc0 zha: Bump to zigpy 0.2.0/bellows 0.7.0 (#16404) 2018-09-03 22:46:27 -07:00
Daniel Høyer Iversen
2252f4a257 Bug fix for Tibber (#16397) 2018-09-04 01:11:40 +02:00
Pawel
00cba29ae1 Support for playing radio preset by Onkyo media_player (#16258)
* Added support to play radio preset by play media

* Switch radio station by preset for Onkyo

* added SUPPORT_PLAY_MEDIA
2018-09-03 21:40:04 +02:00
Paulus Schoutsen
cd473643fe Merge branch 'master' into dev 2018-09-03 14:17:00 +02:00
Paulus Schoutsen
4063b24ddb Merge pull request #16390 from home-assistant/rc
0.77.3
2018-09-03 14:15:54 +02:00
Paulus Schoutsen
46734b8409 Bumped version to 0.77.3 2018-09-03 13:22:49 +02:00
Paulus Schoutsen
b9c80a6bb3 Update translations 2018-09-03 13:21:47 +02:00
Paulus Schoutsen
a2a447b466 Update translations 2018-09-03 13:21:37 +02:00
Paulus Schoutsen
669b3458b9 Update frontend to 20180903.0 2018-09-03 13:21:16 +02:00
Paulus Schoutsen
bf29cbd381 Update frontend to 20180903.0 2018-09-03 13:20:57 +02:00
Daniel Høyer Iversen
1966597d5e add_entities for switchmate (#16368) 2018-09-02 23:05:48 +02:00
Jason Hu
4685a2cd97 Update server.py (#16375) 2018-09-02 22:17:29 +02:00
Fabian Affolter
78fcea25bb Upgrade attrs to 18.2.0 (#16372) 2018-09-02 19:01:43 +02:00
Fabian Affolter
ac3700d1c4 Upgrade python-telegram-bot to 11.0.0 (#16373) 2018-09-02 19:01:25 +02:00
Totoo
03480dc779 Update discord.py (#16248)
* Update discord.py

* Update discord.py

Fixed ATTR_IMAGES checking, line length, and ATTR_DATA imported. Also fixed missing spaces.

* Update discord.py

Fix E302...
2018-09-02 18:58:31 +02:00
Martin Fuchs
a5cff9877e Add support for Tahoma Lighting Receiver on/off io (#15925)
* Add support for Tahoma light switch

* Clean up attributes and add available method

* Remove else statement
2018-09-02 17:02:51 +02:00
Lev Aronsky
b29c296ced Generic Thermostat: add support for climate.turn_on/climate.turn_off (#16080)
* Added async_turn_on and async_turn_off implementations.

* Added turning on/off tests to generic thermostat

* style

* style

* style
2018-09-02 16:42:08 +02:00
MarcSN311
357e5eadb8 Added 'nomapnt', 'outcurnt', 'loadapnt' fields (#16176)
* Added 'nomapnt', 'outcurnt', 'loadapnt' fields

Also added Ampere and Volt-Ampere to INFERRED_UNITS

* Fix lint issue
2018-09-02 15:51:15 +02:00
Fabian Affolter
52e922171d Upgrade to youtube_dl to 2018.09.01 (#16365) 2018-09-02 14:33:20 +02:00
Fabian Affolter
97695a30f5 Upgrade shodan to 1.10.0 (#16363) 2018-09-02 12:51:56 +02:00
Fabian Affolter
1d12c7b0e7 Upgrade mutagen to 1.41.1 (#16361) 2018-09-02 12:51:36 +02:00
Fabian Affolter
15ad82b9bd Upgrade qnapstats to 0.2.7 (#16360) 2018-09-02 12:51:25 +02:00
Fabian Affolter
03fb2b32a6 Upgrade Sphinx to 1.7.7 (#16359) 2018-09-02 12:51:07 +02:00
Jason Hu
87eb6cd25a Upgrade hbmqtt to 0.9.4 (#16356)
* Upgrade to hbmqtt 0.9.4

* Lint

* Typo
2018-09-02 12:50:30 +02:00
Tod Schmidt
3797b6b012 Snips: Added special slot values, session_id and slotname_raw (#16185)
* Added special slot values, site_id, session_id, and slotname_raw

* Update snips.py
2018-09-02 00:01:11 +02:00
Jesse Rizzo
2b0b431a2a Update to EnvoyReader 0.2, support for more hardware (#16212)
* Add support for older Envoy models

* Stop requiring envoy model name in config

* Update to envoy_reader0.2

* Minor formatting fixes

* run script/gen_requirements_all.py

* Minor formatting fixes

* Change some strings to constants, use getattr to call function
2018-09-01 23:45:47 +02:00
Maciej Bieniek
e75a1690d1 Add unique_id to MQTT Light (#16303)
* Add unique_id

* Delete whitespaces
2018-09-01 23:37:03 +02:00
Joshi
444df5b09a Add support for sound_mode for Yamaha rxv media_player (#16352) 2018-09-01 23:34:38 +02:00
Daniel Høyer Iversen
b31890c4cb Handle netatmo exception (#16344) 2018-09-01 23:30:34 +02:00
Phil Bruckner
a5d95dfbdc Make last_seen attribute a timezone aware datetime in UTC (#16348)
The last_seen attribute was a datetime in the local timezone but with
no tzinfo (i.e., a "naive" datetime.) When state changes occurred it
would be printed incorrectly in homeassistant.log because
homeassistant.util.dt.as_local assumes any datetime without tzinfo is
UTC. Also most, if not all, datetime attributes are timezone aware in
UTC. So use homeassistant.util.dt.as_utc (which assumes a naive
datetime is local) to convert last_seen to a timezone aware datetime
in UTC.
2018-09-01 18:49:03 +02:00
Philipp Temminghoff
901cfef78e Support Sonos Beam HDMI input (#16340) 2018-09-01 16:02:38 +02:00
Daniel Perna
2c7d6ee6b5 Fix missing humidity sensor (#16337) 2018-09-01 10:40:16 +02:00
Tom Harris
d3791fa45d Add Cover to the Insteon component (#16215)
* Create cover platform

* Create insteon cover platform

* Bump insteonplm to 0.13.0

* Change async_add_devices to async_add_entities

* Missing doc string

* Simplify open and set_position

* Flake8

* Bump insteonplm to 0.13.1

* Code review changes

* Flake8 updates
2018-08-31 23:56:26 +02:00
Matt Schmitt
fa81385b5c Add unique ID (#16323) 2018-08-31 16:47:37 +02:00
thomaslian
7d852a985c Upgrade Adafruit-DHT to 1.3.4 (#16327)
* Update dht.py

* Update requirements_all.txt
2018-08-31 16:47:10 +02:00
Paulus Schoutsen
976626d0ab Merge pull request #16324 from home-assistant/rc
0.77.2
2018-08-31 13:59:48 +02:00
Paulus Schoutsen
d705375a9a Update translations 2018-08-31 13:23:57 +02:00
Paulus Schoutsen
5e8a1496d7 Update translations 2018-08-31 13:23:22 +02:00
Paulus Schoutsen
bc618af193 Bumped version to 0.77.2 2018-08-31 13:17:24 +02:00
Malte Franken
0e076fb9e7 avoid error in debug log mode and rss entry without title (#16316) 2018-08-31 13:12:17 +02:00
Anders Melchiorsen
16a58bd1cf Fix LIFX effects (#16309) 2018-08-31 13:12:16 +02:00
lamiskin
8be7a0a9b9 Correct wemo static device discovery issue. (#16292)
A recent change caused an issue if a single static wemo device is offline and could not be reached, then the whole component would not initialize (and therefore all other wemo devices are not added).
2018-08-31 13:12:16 +02:00
Paulus Schoutsen
232076b41d Update frontend to 20180831.0 2018-08-31 12:59:57 +02:00
Paulus Schoutsen
efa9c82c38 Update frontend to 20180831.0 2018-08-31 12:59:39 +02:00
lamiskin
93f45779c6 Correct wemo static device discovery issue. (#16292)
A recent change caused an issue if a single static wemo device is offline and could not be reached, then the whole component would not initialize (and therefore all other wemo devices are not added).
2018-08-31 12:57:07 +02:00
Malte Franken
26d39d39ea avoid error in debug log mode and rss entry without title (#16316) 2018-08-31 12:54:25 +02:00
Anders Melchiorsen
b43c47cb17 Fix LIFX effects (#16309) 2018-08-31 10:17:11 +02:00
PhracturedBlue
67d8db2c9f Use asterisk_mbox 0.5.0 client (#16296) 2018-08-30 18:44:37 +02:00
Teemu R
3cbf8e4f87 Bump songpal dependency (#16297)
Fixes #14936
2018-08-30 18:21:37 +02:00
Malte Franken
f20a3313b0 Geo Location component (#15953)
* initial working version of a geo location component and georss platform

* ensure that custom attributes don't override built-in ones

* bugfixes and tests

* fixing tests because of introduction of new component using same fixture

* improving test cases

* removing potentially unavailable attribute from debug message output

* completing test suite

* cleaning up debug messages; sorting entries in group view by distance

* ability to define the desired state attribute and corresponding unit of measurement; sort devices in group by configured state; find centroid for map if event is defined by polygon; updated tests

* sort entries in group; code clean-ups

* fixing indentation

* added requirements of new component and platform

* fixed various lint issues

* fixed more lint issues

* introducing demo geo location platform; refactored geo location component and geo rss platform to fit

* removing geo rss events platform; added unit tests for geo location platform and demo platform

* reverting change in debug message for feedreader to avoid confusion with new geo location component

* updated requirements after removing georss platform

* removed unused imports

* fixing a lint issue and a test case

* simplifying component code; moving code into demo platform; fixing tests

* removed grouping from demo platform; small refactorings

* automating the entity id generation (the use of an entity namespace achieves the same thing)

* undoing changes made for the georss platform

* simplified test cases

* small tweaks to test case

* rounding all state attribute values

* fixing lint; removing distance from state attributes

* fixed test

* renamed add_devices to add_entities; tweaked test to gain more control over the timed update in the demo platform

* reusing utcnow variable instead of patched method

* fixed test by avoiding to make assumptions about order of list of entity ids

* adding test for the geo location event class
2018-08-30 13:58:23 +02:00
Robbie Trencheny
54c3f4f001 Fix spelling mistake in recorder migration [ci skip] 2018-08-29 14:59:48 -07:00
Paulus Schoutsen
7289d5b656 Merge branch 'master' into dev 2018-08-29 23:37:53 +02:00
Paulus Schoutsen
e77344f029 Merge pull request #16284 from home-assistant/rc
0.77.1
2018-08-29 23:33:53 +02:00
Paulus Schoutsen
60438067f8 Bumped version to 0.77.1 2018-08-29 23:22:56 +02:00
Conrad Juhl Andersen
9062de0704 Fix error when vacuum is idling (#16282) 2018-08-29 23:22:44 +02:00
Sebastian Muszynski
64453638bb Fix data_key override by parent class (#16278) 2018-08-29 23:22:43 +02:00
Paulus Schoutsen
5f1282a4ab Bump frontend to 20180829.1 2018-08-29 23:22:21 +02:00
Robert Svensson
645c3a67d8 Fix so that entities are properly unloaded with config entry (#16281) 2018-08-29 23:18:20 +02:00
Conrad Juhl Andersen
88f72a654a Fix error when vacuum is idling (#16282) 2018-08-29 23:17:18 +02:00
Paulus Schoutsen
87df102772 Bump frontend to 20180829.1 2018-08-29 22:59:55 +02:00
Sebastian Muszynski
25ee8e551c Fix data_key override by parent class (#16278) 2018-08-29 22:29:34 +02:00
Pavel Pletenev
99d48795b9 Add support for Habitica (#15744)
* Added support for Habitica

Second refactoring
Moved all config to component.
Sensors are autodiscovered.
Signed-off-by: delphi <cpp.create@gmail.com>

* Apply requested changes

Signed-off-by: delphi <cpp.create@gmail.com>

* Made event fire async. Made `sensors` config implicit and opt-out-style.

Signed-off-by: delphi <cpp.create@gmail.com>

* Removed unneeded check and await.

Signed-off-by: delphi <cpp.create@gmail.com>

* Moved into separate component package and added service.yaml

Signed-off-by: delphi <cpp.create@gmail.com>

* Fix coveralls

Signed-off-by: delphi <cpp.create@gmail.com>
2018-08-29 21:13:01 +02:00
Jason Hu
5681fa8f07 Nest Thermostat has software version (#16275) 2018-08-29 13:00:40 -06:00
Paulus Schoutsen
867d17b03d Add Hue device info (#16267)
* Add Hue device info

* Set with tuples

* Fix tests
2018-08-29 17:04:04 +02:00
Paulus Schoutsen
7751dd7535 Add device info Nest (#16265)
* Add device info Nest

* Sets
2018-08-29 16:44:10 +02:00
Paulus Schoutsen
16a885824d Add device info for sonos (#16263)
* Add device info for sonos

* Sets
2018-08-29 16:27:08 +02:00
Paulus Schoutsen
3934f7bf3a Add device info to Chromecast (#16261) 2018-08-29 15:46:09 +02:00
cgtobi
96cf6d59a3 Replace Authorization by Authentication (#16259) 2018-08-29 15:43:01 +02:00
Diogo Gomes
d46a1a266d bump version (#16262) 2018-08-29 15:32:47 +02:00
Matt Schmitt
aaa1ebeed5 Add support for discrete states to MyQ cover (#16251)
* Add discrete states and update dependency

* Add translation dict
2018-08-29 14:33:09 +02:00
Daniel Høyer Iversen
18ba50bc2d Switchmate (#15535)
* Switchmate

* switchmate

* swithcmate

* switchmate

* switchmate

* fix comments

* Update switchmate.py

* change error log
2018-08-29 12:56:15 +02:00
Paulus Schoutsen
3df8840fee Version bump to 0.78.0.dev0 2018-08-29 12:20:05 +02:00
Paulus Schoutsen
630b5df59a Merge branch 'master' into dev 2018-08-29 12:19:30 +02:00
Paulus Schoutsen
e8801ee22f Update translations 2018-08-29 10:28:34 +02:00
Paulus Schoutsen
74c0429437 Bump frontend to 20180829.0 2018-08-29 10:27:34 +02:00
Jason Hu
563588651c Tweak MFA login flow (#16254)
* Tweak MFA login flow

* Fix typo
2018-08-29 10:16:54 +02:00
Robert Svensson
63614a477a def device shouldnt call it self but self._device (#16255) 2018-08-29 10:07:32 +02:00
Paulus Schoutsen
f891d0f5be Update translations 2018-08-28 20:55:58 +02:00
Jason Hu
257b8b9b80 Blow up startup if init auth providers or modules failed (#16240)
* Blow up startup if init auth providers or modules failed

* Delete core.entity_registry
2018-08-28 20:54:01 +02:00
Paulus Schoutsen
9a786e449b Fix hangouts (#16232) 2018-08-28 15:44:06 +02:00
Paulus Schoutsen
09dc4d663d Improve package loadable (#16237)
* Add caching to package loadable

* Fix tests

* Improve package loadable

* Lint

* Typing
2018-08-28 12:52:18 +02:00
Paulus Schoutsen
12709ceaa3 Avoid insecure pycryptodome (#16238) 2018-08-28 12:49:50 +02:00
Jason Hu
67df162bcc Change log level to error when auth provider failed loading (#16235) 2018-08-28 11:23:58 +02:00
Paulus Schoutsen
a14980716d Package loadable: compare case insensitive (#16234) 2018-08-28 10:53:12 +02:00
Paulus Schoutsen
376d4e4fa0 Warning missed a space (#16233) 2018-08-28 09:32:50 +02:00
Paulus Schoutsen
5397c0d73a Update trusted networks flow (#16227)
* Update the trusted networks flow

* Fix tests

* Remove errors
2018-08-28 00:37:15 +02:00
Robert Svensson
8ab31fe139 Store devices as dict instead of list (#16229)
* Store devices as dict instead of list

* Use OrderedDict
2018-08-28 00:37:04 +02:00
Marcel Hoppe
45649824ca rewrite hangouts to use intents instead of commands (#16220)
* rewrite hangouts to use intents instead of commands

* small fixes

* remove configured_hangouts check and CONFIG_SCHEMA

* Lint

* add import from .config_flow
2018-08-28 00:20:12 +02:00
Paulus Schoutsen
6f0c30ff84 Bump frontend to 20180827.0 2018-08-27 22:28:17 +02:00
Fabian Affolter
943260fcd6 Upgrade alpha_vantage to 2.1.0 (#16217) 2018-08-27 22:00:20 +02:00
Paulus Schoutsen
24aa580b63 Fix device telldus (#16224) 2018-08-27 21:56:28 +02:00
Daniel Bowman
8435d2f53d openalpr flag WITH_TEST should be WITH_TESTS (#16218)
Removes warning from openalpr build and saves a few seconds from build
time as tests weren't being bypassed as intended
2018-08-27 17:17:43 +02:00
Fabian Affolter
c51170ef6d Add Volkszaehler sensor (#16188)
* Add Volkszaehler sensor

* Update icons

* Improve code
2018-08-27 15:05:36 +02:00
Paulus Schoutsen
9d491f5322 Change auth warning (#16216) 2018-08-27 10:37:03 +02:00
Paulus Schoutsen
94662620e2 Update translations 2018-08-27 10:16:59 +02:00
Paulus Schoutsen
f1e378bff8 Add new translations 2018-08-27 09:48:17 +02:00
Julian Kahnert
2e9db1f5c4 Fix geizhals price parsing (#15990)
* fix geizhals price parsing

* Fix lint issue

* switch to the geizhals pypi package

* throttle updates

* update geizhals version

* initialize empty device

* minor changes to trigger another TravisCI test

* device => _device

* bump geizhals version
2018-08-27 09:39:11 +02:00
Hunter Horsman
dec2d8d5b0 Add device_tracker.bluetooth_update service (#15252)
* Add device_tracker.bluetooth_update service

Will immediately scan for Bluetooth devices outside of the interval timer. Allows for less frequent scanning, with scanning on demand via automation.

* remove excess whitespace per bot comments

* Refactored update_bluetooth to call new function update_bluetooth_once

* Change service name to bluetooth_tracker_update to reflect platform name

* Reformat for line length

* Linting fix, pydoc, first line should end with a period

* Fixed a method call, and removed some more unsused parameters
2018-08-27 09:08:23 +02:00
Jonas Karlsson
a439690bd7 Rewrite of Trafikverket weather - Multiple sensor types supported (#15935)
* Added precipitation type from API

Enables users to see type of precipitation.
Value returned from API is a string in swedish.

* Corrected tox verification errors

Correction of tox findings

* Missed in tox - fixed

* Hound witespace fix

* Updated comment to trigger travis rebuild

Travis tox failed due to problem with tox build process. 
Correcting in a comment to trigger retry in travis..

* Try to retrigger travis/tox successful rebuild

* Cleaning

* Cleaning more

* Trafikverket rebuilt for library

Extended pytrafikverket with weather sensor collction
Changed behaviour of sensor component to use pytrafikverket.
Added more sensors.

User need to change config to use new version.
 [] Documentation needs to be updated

* Cleaned up based on Martins input

Appreciate the feedback
2018-08-27 06:19:51 +02:00
Maikel Punie
5d7a2f92df Add temperature sensors to the velbus component (#16203)
* Added support for velbus temperature sensors

* Bumped the required version

* updated requirements_all.txt

* Auto review comments fixed

* Updated after comments

* Updated after comments

* Fix travis

* Fix travis
2018-08-27 06:06:46 +02:00
Paulus Schoutsen
4da719f43c Update translations 2018-08-26 22:52:21 +02:00
Matt Hamilton
bacecb4249 Replace pbkdf2 with bcrypt (#16071)
* Replace pbkdf2 with bcrypt

bcrypt isn't inherently better than pbkdf2, but everything "just works"
out of the box.

  * the hash verification routine now only computes one hash per call
  * a per-user salt is built into the hash as opposed to the current
  global salt
  * bcrypt.checkpw() is immune to timing attacks regardless of input
  * hash strength is a function of real time benchmarks and a
  "difficulty" level, meaning we won't have to ever update the iteration
  count

* WIP: add hash upgrade mechanism

* WIP: clarify decode issue

* remove stale testing code

* Fix test

* Ensure incorrect legacy passwords fail

* Add better invalid legacy password test

* Lint

* Run tests in async scope
2018-08-26 22:50:31 +02:00
Jason Hu
47755fb1e9 Add Time-based Onetime Password Multi-factor Authentication Module (#16129)
* Add Time-based Onetime Password Multi-factor Auth

Add TOTP setup flow, generate QR code

* Resolve rebase issue

* Use svg instead png for QR code

* Lint and typing

* Fix translation

* Load totp auth module by default

* use <svg> tag instead markdown image

* Update strings

* Cleanup
2018-08-26 22:38:52 +02:00
Penny Wood
69d104bcb6 Update aiohttp to version 3.4.0. (#16198) 2018-08-26 21:35:06 +02:00
Paulus Schoutsen
b043ac0f7f Update frontend to 20180826.0 2018-08-26 21:30:14 +02:00
PhracturedBlue
499bb3f4a2 Handle exception from pillow (#16190) 2018-08-26 21:29:15 +02:00
Marcel Hoppe
d166f2da80 remove hangouts.users state, simplifies hangouts.conversations (#16191) 2018-08-26 21:28:42 +02:00
Antoine GRÉA
3032de1dc1 Inconsistent entity_id when multiple sensors (#16205)
* Inconsistent entity_id when multiple sensors

I am submitting a change to fix a [bug](https://github.com/home-assistant/home-assistant/issues/16204) for when there are several sensors for the same hostname. For example I want to track my IPv4 and IPv6 address. It creates two entities that regularly switch ids based on the order they get initialized.

To fix this I comform to the way other componnents have addressed the issue by adding an optional `name` attribute.

* Line too long

* Removing trailing whitespace
2018-08-26 21:27:03 +02:00
Robert Svensson
5341785aae Revert changes to platforms using self.device (#16209)
* Revert tank_utility

* Fix Soundtouch

* Fix Plex

* Fix Emby

* Fix Radiotherm

* Fix Juicenet

* Fix Qwikswitch

* Fix Xiaomi miio

* Fix Nest

* Fix Tellduslive

* Fix KNX
2018-08-26 21:25:39 +02:00
Martin Fuchs
289b1802fd Add battery warning, rssi level and check for availability (#16193) 2018-08-26 21:20:34 +02:00
Fabian Affolter
0da3e73765 Upgrade sqlalchemy to 1.2.11 (#16192) 2018-08-26 12:28:44 +02:00
Dan Klaffenbach
0a7055d475 homematic: Make device avilable again when UNREACH becomes False (#16202) 2018-08-26 12:00:20 +02:00
Matthias Urlichs
a1ce14e70f MQTT: Log transmitted as well as received messages (#16195) 2018-08-26 10:04:51 +02:00
Thomas Delaet
2f2bcf0058 update python-velbus library version (#16194) 2018-08-25 20:42:26 +02:00
djm300
f929c38e98 Zoneminder SSL fix (#16157)
* Update zoneminder.py

Added a verify_ssl parameter for zoneminder

* PEP8 fixup

* PEP8 indenting fix

* Fix lint issue

* Remove whitespace
2018-08-25 11:21:57 +02:00
Paulus Schoutsen
617802653f Bump frontend to 20180825.0 2018-08-25 11:15:01 +02:00
Jason Hu
26a485d43c Default load trusted_network auth provider if configured trusted networks (#16184) 2018-08-25 11:09:48 +02:00
Paulus Schoutsen
456aa5a2b2 Fix hangouts (#16180) 2018-08-25 11:01:32 +02:00
Robert Svensson
97173f495c Device registry store config entry (#16152)
* Allow device registry to optionally store config entries

* Connections and identifiers are now sets with tupels

* Make config entries mandatory

* Fix duplicate keys in test

* Rename device to device_info

* Entity platform should only create device entries if config_entry_id exists

* Fix Soundtouch tests

* Revert soundtouch to use self.device

* Fix baloobs comments

* Correct type in test
2018-08-25 10:59:28 +02:00
Jason Hu
24a8d60566 Tweak log level for bearer token warning (#16182) 2018-08-25 07:57:36 +02:00
Fabian Affolter
69cea6001f Add 'moon_phase' to Dark Sky sensor (#16179) 2018-08-24 17:05:53 -06:00
Nate Clark
647b3ff0fe Decouple Konnected entity setup from discovery (#16146)
* decouple entity setup from discovery

* validate that device_id is a full MAC address
2018-08-24 23:29:25 +02:00
Nate Clark
84365cde07 fix error message for cv.matches_regex (#16175) 2018-08-24 23:27:12 +02:00
Robert Svensson
e91a1529e4 deCONZ - Support device registry (#16115)
Add support for device registry in deCONZ component
2018-08-24 19:37:22 +02:00
Jason Hu
e8775ba2b4 Add multi-factor auth module setup flow (#16141)
* Add mfa setup flow

* Lint

* Address code review comment

* Fix unit test

* Add assertion for WS response ordering

* Missed a return

* Remove setup_schema from MFA base class

* Move auth.util.validate_current_user -> webscoket_api.ws_require_user
2018-08-24 10:17:43 -07:00
271 changed files with 5124 additions and 2070 deletions

View File

@@ -116,11 +116,14 @@ omit =
homeassistant/components/google.py
homeassistant/components/*/google.py
homeassistant/components/habitica/*
homeassistant/components/*/habitica.py
homeassistant/components/hangouts/__init__.py
homeassistant/components/hangouts/const.py
homeassistant/components/hangouts/hangouts_bot.py
homeassistant/components/hangouts/hangups_utils.py
homeassistant/components/*/hangouts.py
homeassistant/components/*/hangouts.py
homeassistant/components/hdmi_cec.py
homeassistant/components/*/hdmi_cec.py
@@ -142,12 +145,12 @@ omit =
homeassistant/components/ihc/*
homeassistant/components/*/ihc.py
homeassistant/components/insteon/*
homeassistant/components/*/insteon.py
homeassistant/components/insteon_local.py
homeassistant/components/insteon_plm.py
homeassistant/components/ios.py
@@ -225,7 +228,7 @@ omit =
homeassistant/components/opencv.py
homeassistant/components/*/opencv.py
homeassistant/components/openuv.py
homeassistant/components/openuv/__init__.py
homeassistant/components/*/openuv.py
homeassistant/components/pilight.py
@@ -374,6 +377,7 @@ omit =
homeassistant/components/alarm_control_panel/nx584.py
homeassistant/components/alarm_control_panel/simplisafe.py
homeassistant/components/alarm_control_panel/totalconnect.py
homeassistant/components/alarm_control_panel/yale_smart_alarm.py
homeassistant/components/apiai.py
homeassistant/components/binary_sensor/arest.py
homeassistant/components/binary_sensor/concord232.py
@@ -411,6 +415,7 @@ omit =
homeassistant/components/climate/honeywell.py
homeassistant/components/climate/knx.py
homeassistant/components/climate/oem.py
homeassistant/components/climate/opentherm_gw.py
homeassistant/components/climate/proliphix.py
homeassistant/components/climate/radiotherm.py
homeassistant/components/climate/sensibo.py
@@ -759,6 +764,7 @@ omit =
homeassistant/components/sensor/uscis.py
homeassistant/components/sensor/vasttrafik.py
homeassistant/components/sensor/viaggiatreno.py
homeassistant/components/sensor/volkszaehler.py
homeassistant/components/sensor/waqi.py
homeassistant/components/sensor/waze_travel_time.py
homeassistant/components/sensor/whois.py
@@ -789,6 +795,8 @@ omit =
homeassistant/components/switch/rest.py
homeassistant/components/switch/rpi_rf.py
homeassistant/components/switch/snmp.py
homeassistant/components/switch/switchbot.py
homeassistant/components/switch/switchmate.py
homeassistant/components/switch/telnet.py
homeassistant/components/switch/tplink.py
homeassistant/components/switch/transmission.py

View File

@@ -52,6 +52,8 @@ homeassistant/components/cover/template.py @PhracturedBlue
homeassistant/components/device_tracker/automatic.py @armills
homeassistant/components/device_tracker/tile.py @bachya
homeassistant/components/history_graph.py @andrey-git
homeassistant/components/light/lifx.py @amelchio
homeassistant/components/light/lifx_legacy.py @amelchio
homeassistant/components/light/tplink.py @rytilahti
homeassistant/components/light/yeelight.py @rytilahti
homeassistant/components/lock/nello.py @pschmitt
@@ -65,6 +67,7 @@ homeassistant/components/media_player/sonos.py @amelchio
homeassistant/components/media_player/xiaomi_tv.py @fattdev
homeassistant/components/media_player/yamaha_musiccast.py @jalmeroth
homeassistant/components/plant.py @ChristianKuehnel
homeassistant/components/scene/lifx_cloud.py @amelchio
homeassistant/components/sensor/airvisual.py @bachya
homeassistant/components/sensor/filter.py @dgomes
homeassistant/components/sensor/gearbest.py @HerrHofrat

View File

@@ -1,194 +1,201 @@
Apache License
==============
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
_Version 2.0, January 2004_
_&lt;<http://www.apache.org/licenses/>&gt;_
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
### Terms and Conditions for use, reproduction, and distribution
1. Definitions.
#### 1. Definitions
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
License” shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
“Licensor” shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
“Legal Entity” shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity.
For the purposes of this definition, “control” means **(i)** the power, direct or
indirect, to cause the direction or management of such entity, whether by
contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the
outstanding shares, or **(iii)** beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
“You” (or “Your”) shall mean an individual or Legal Entity exercising
permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
“Source” form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
“Object” form shall mean any form resulting from mechanical transformation or
translation of a Source form, including but not limited to compiled object code,
generated documentation, and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
Work shall mean the work of authorship, whether in Source or Object form, made
available under the License, as indicated by a copyright notice that is included
in or attached to the work (an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
“Derivative Works” shall mean any work, whether in Source or Object form, that
is based on (or derived from) the Work and for which the editorial revisions,
annotations, elaborations, or other modifications represent, as a whole, an
original work of authorship. For the purposes of this License, Derivative Works
shall not include works that remain separable from, or merely link (or bind by
name) to the interfaces of, the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
Contribution” shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative Works
thereof, that is intentionally submitted to Licensor for inclusion in the Work
by the copyright owner or by an individual or Legal Entity authorized to submit
on behalf of the copyright owner. For the purposes of this definition,
“submitted” means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor for
the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as “Not a Contribution.”
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
“Contributor” shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
#### 2. Grant of Copyright License
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
#### 3. Grant of Patent License
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable (except as stated in this section) patent license to make, have
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
such license applies only to those patent claims licensable by such Contributor
that are necessarily infringed by their Contribution(s) alone or by combination
of their Contribution(s) with the Work to which such Contribution(s) was
submitted. If You institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
Contribution incorporated within the Work constitutes direct or contributory
patent infringement, then any patent licenses granted to You under this License
for that Work shall terminate as of the date such litigation is filed.
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
#### 4. Redistribution
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
You may reproduce and distribute copies of the Work or Derivative Works thereof
in any medium, with or without modifications, and in Source or Object form,
provided that You meet the following conditions:
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
* **(a)** You must give any other recipients of the Work or Derivative Works a copy of
this License; and
* **(b)** You must cause any modified files to carry prominent notices stating that You
changed the files; and
* **(c)** You must retain, in the Source form of any Derivative Works that You distribute,
all copyright, patent, trademark, and attribution notices from the Source form
of the Work, excluding those notices that do not pertain to any part of the
Derivative Works; and
* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any
Derivative Works that You distribute must include a readable copy of the
attribution notices contained within such NOTICE file, excluding those notices
that do not pertain to any part of the Derivative Works, in at least one of the
following places: within a NOTICE text file distributed as part of the
Derivative Works; within the Source form or documentation, if provided along
with the Derivative Works; or, within a display generated by the Derivative
Works, if and wherever such third-party notices normally appear. The contents of
the NOTICE file are for informational purposes only and do not modify the
License. You may add Your own attribution notices within Derivative Works that
You distribute, alongside or as an addendum to the NOTICE text from the Work,
provided that such additional attribution notices cannot be construed as
modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works as a whole,
provided Your use, reproduction, and distribution of the Work otherwise complies
with the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
#### 5. Submission of Contributions
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
Unless You explicitly state otherwise, any Contribution intentionally submitted
for inclusion in the Work by You to the Licensor shall be under the terms and
conditions of this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify the terms of
any separate license agreement you may have executed with Licensor regarding
such Contributions.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
#### 6. Trademarks
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
This License does not grant permission to use the trade names, trademarks,
service marks, or product names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
#### 7. Disclaimer of Warranty
END OF TERMS AND CONDITIONS
Unless required by applicable law or agreed to in writing, Licensor provides the
Work (and each Contributor provides its Contributions) on an “AS IS” BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
including, without limitation, any warranties or conditions of TITLE,
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
solely responsible for determining the appropriateness of using or
redistributing the Work and assume any risks associated with Your exercise of
permissions under this License.
APPENDIX: How to apply the Apache License to your work.
#### 8. Limitation of Liability
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
In no event and under no legal theory, whether in tort (including negligence),
contract, or otherwise, unless required by applicable law (such as deliberate
and grossly negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special, incidental,
or consequential damages of any character arising as a result of this License or
out of the use or inability to use the Work (including but not limited to
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
any and all other commercial damages or losses), even if such Contributor has
been advised of the possibility of such damages.
Copyright [yyyy] [name of copyright owner]
#### 9. Accepting Warranty or Additional Liability
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
While redistributing the Work or Derivative Works thereof, You may choose to
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
other liability obligations and/or rights consistent with this License. However,
in accepting such obligations, You may act only on Your own behalf and on Your
sole responsibility, not on behalf of any other Contributor, and only if You
agree to indemnify, defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason of your
accepting any such warranty or additional liability.
http://www.apache.org/licenses/LICENSE-2.0
_END OF TERMS AND CONDITIONS_
### APPENDIX: How to apply the Apache License to your work
To apply the Apache License to your work, attach the following boilerplate
notice, with the fields enclosed by brackets `[]` replaced with your own
identifying information. (Don't include the brackets!) The text should be
enclosed in the appropriate comment syntax for the file format. We also
recommend that a file or class name and description of purpose be included on
the same “printed page” as the copyright notice for easier identification within
third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -24,7 +24,7 @@ USER_SCHEMA = vol.Schema({
CONFIG_SCHEMA = AUTH_PROVIDER_SCHEMA.extend({
}, extra=vol.PREVENT_EXTRA)
LEGACY_USER = 'homeassistant'
LEGACY_USER_NAME = 'Legacy API password user'
class InvalidAuthError(HomeAssistantError):
@@ -52,23 +52,21 @@ class LegacyApiPasswordAuthProvider(AuthProvider):
async def async_get_or_create_credentials(
self, flow_result: Dict[str, str]) -> Credentials:
"""Return LEGACY_USER always."""
for credential in await self.async_credentials():
if credential.data['username'] == LEGACY_USER:
return credential
"""Return credentials for this login."""
credentials = await self.async_credentials()
if credentials:
return credentials[0]
return self.async_create_credentials({
'username': LEGACY_USER
})
return self.async_create_credentials({})
async def async_user_meta_for_credentials(
self, credentials: Credentials) -> UserMeta:
"""
Set name as LEGACY_USER always.
Return info for the user.
Will be used to populate info when creating a new user.
"""
return UserMeta(name=LEGACY_USER, is_active=True)
return UserMeta(name=LEGACY_USER_NAME, is_active=True)
class LegacyLoginFlow(LoginFlow):

View File

@@ -0,0 +1,98 @@
"""
Yale Smart Alarm client for interacting with the Yale Smart Alarm System API.
For more details about this platform, please refer to the documentation at
https://www.home-assistant.io/components/alarm_control_panel.yale_smart_alarm
"""
import logging
import voluptuous as vol
from homeassistant.components.alarm_control_panel import (
AlarmControlPanel, PLATFORM_SCHEMA)
from homeassistant.const import (
CONF_PASSWORD, CONF_USERNAME, CONF_NAME,
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['yalesmartalarmclient==0.1.4']
CONF_AREA_ID = 'area_id'
DEFAULT_NAME = 'Yale Smart Alarm'
DEFAULT_AREA_ID = '1'
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_AREA_ID, default=DEFAULT_AREA_ID): cv.string,
})
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the alarm platform."""
name = config[CONF_NAME]
username = config[CONF_USERNAME]
password = config[CONF_PASSWORD]
area_id = config[CONF_AREA_ID]
from yalesmartalarmclient.client import (
YaleSmartAlarmClient, AuthenticationError)
try:
client = YaleSmartAlarmClient(username, password, area_id)
except AuthenticationError:
_LOGGER.error("Authentication failed. Check credentials")
return
add_entities([YaleAlarmDevice(name, client)], True)
class YaleAlarmDevice(AlarmControlPanel):
"""Represent a Yale Smart Alarm."""
def __init__(self, name, client):
"""Initialize the Yale Alarm Device."""
self._name = name
self._client = client
self._state = None
from yalesmartalarmclient.client import (YALE_STATE_DISARM,
YALE_STATE_ARM_PARTIAL,
YALE_STATE_ARM_FULL)
self._state_map = {
YALE_STATE_DISARM: STATE_ALARM_DISARMED,
YALE_STATE_ARM_PARTIAL: STATE_ALARM_ARMED_HOME,
YALE_STATE_ARM_FULL: STATE_ALARM_ARMED_AWAY
}
@property
def name(self):
"""Return the name of the device."""
return self._name
@property
def state(self):
"""Return the state of the device."""
return self._state
def update(self):
"""Return the state of the device."""
armed_status = self._client.get_armed_status()
self._state = self._state_map.get(armed_status)
def alarm_disarm(self, code=None):
"""Send disarm command."""
self._client.disarm()
def alarm_arm_home(self, code=None):
"""Send arm home command."""
self._client.arm_partial()
def alarm_arm_away(self, code=None):
"""Send arm away command."""
self._client.arm_full()

View File

@@ -15,7 +15,7 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import (
async_dispatcher_connect, async_dispatcher_send)
REQUIREMENTS = ['asterisk_mbox==0.4.0']
REQUIREMENTS = ['asterisk_mbox==0.5.0']
_LOGGER = logging.getLogger(__name__)

View File

@@ -0,0 +1,7 @@
{
"mfa_setup": {
"totp": {
"title": "TOTP"
}
}
}

View File

@@ -0,0 +1,16 @@
{
"mfa_setup": {
"totp": {
"error": {
"invalid_code": "Ung\u00fcltiger Code, bitte versuche es erneut. Wenn Sie diesen Fehler regelm\u00e4\u00dfig erhalten, stelle sicher, dass die Uhr deines Home Assistant-Systems korrekt ist."
},
"step": {
"init": {
"description": "Um die Zwei-Faktor-Authentifizierung mit zeitbasierten Einmalpassw\u00f6rtern zu aktivieren, scanne den QR-Code mit Ihrer Authentifizierungs-App. Wenn du keine hast, empfehlen wir entweder [Google Authenticator] (https://support.google.com/accounts/answer/1066447) oder [Authy] (https://authy.com/). \n\n {qr_code} \n \nNachdem du den Code gescannt hast, gebe den sechsstelligen Code aus der App ein, um das Setup zu \u00fcberpr\u00fcfen. Wenn es Probleme beim Scannen des QR-Codes gibt, f\u00fchre ein manuelles Setup mit dem Code ** ` {code} ` ** durch.",
"title": "Richte die Zwei-Faktor-Authentifizierung mit TOTP ein"
}
},
"title": "TOTP"
}
}
}

View File

@@ -0,0 +1,12 @@
{
"mfa_setup": {
"totp": {
"step": {
"init": {
"title": "Configurar la autenticaci\u00f3n de dos factores mediante TOTP"
}
},
"title": "TOTP"
}
}
}

View File

@@ -0,0 +1,16 @@
{
"mfa_setup": {
"totp": {
"error": {
"invalid_code": "Code invalide. S'il vous pla\u00eet essayez \u00e0 nouveau. Si cette erreur persiste, assurez-vous que l'horloge de votre syst\u00e8me Home Assistant est correcte."
},
"step": {
"init": {
"description": "Pour activer l'authentification \u00e0 deux facteurs \u00e0 l'aide de mots de passe \u00e0 utilisation unique bas\u00e9s sur l'heure, num\u00e9risez le code QR avec votre application d'authentification. Si vous n'en avez pas, nous vous recommandons d'utiliser [Google Authenticator] (https://support.google.com/accounts/answer/1066447) ou [Authy] (https://authy.com/). \n\n {qr_code} \n \n Apr\u00e8s avoir num\u00e9ris\u00e9 le code, entrez le code \u00e0 six chiffres de votre application pour v\u00e9rifier la configuration. Si vous rencontrez des probl\u00e8mes lors de l\u2019analyse du code QR, effectuez une configuration manuelle avec le code ** ` {code} ` **.",
"title": "Configurer une authentification \u00e0 deux facteurs \u00e0 l'aide de TOTP"
}
},
"title": "TOTP (Mot de passe \u00e0 utilisation unique bas\u00e9 sur le temps)"
}
}
}

View File

@@ -0,0 +1,16 @@
{
"mfa_setup": {
"totp": {
"error": {
"invalid_code": "\u00c9rv\u00e9nytelen k\u00f3d, pr\u00f3b\u00e1ld \u00fajra. Ha ez a hiba folyamatosan el\u0151fordul, akkor gy\u0151z\u0151dj meg r\u00f3la, hogy a Home Assistant rendszered \u00f3r\u00e1ja pontosan j\u00e1r."
},
"step": {
"init": {
"description": "Ahhoz, hogy haszn\u00e1lhasd a k\u00e9tfaktoros hiteles\u00edt\u00e9st id\u0151alap\u00fa egyszeri jelszavakkal, szkenneld be a QR k\u00f3dot a hiteles\u00edt\u00e9si applik\u00e1ci\u00f3ddal. Ha m\u00e9g nincsen, akkor a [Google Hiteles\u00edt\u0151](https://support.google.com/accounts/answer/1066447)t vagy az [Authy](https://authy.com/)-t aj\u00e1nljuk.\n\n{qr_code}\n\nA k\u00f3d beolvas\u00e1sa ut\u00e1n add meg a hat sz\u00e1mjegy\u0171 k\u00f3dot az applik\u00e1ci\u00f3b\u00f3l a telep\u00edt\u00e9s ellen\u0151rz\u00e9s\u00e9hez. Ha probl\u00e9m\u00e1ba \u00fctk\u00f6z\u00f6l a QR k\u00f3d beolvas\u00e1s\u00e1n\u00e1l, akkor ind\u00edts egy k\u00e9zi be\u00e1ll\u00edt\u00e1st a **`{code}`** k\u00f3ddal.",
"title": "K\u00e9tfaktoros hiteles\u00edt\u00e9s be\u00e1ll\u00edt\u00e1sa TOTP haszn\u00e1lat\u00e1val"
}
},
"title": "TOTP"
}
}
}

View File

@@ -0,0 +1,13 @@
{
"mfa_setup": {
"totp": {
"step": {
"init": {
"description": "Per attivare l'autenticazione a due fattori utilizzando password monouso basate sul tempo, eseguire la scansione del codice QR con l'app di autenticazione. Se non ne hai uno, ti consigliamo [Google Authenticator] (https://support.google.com/accounts/answer/1066447) o [Authy] (https://authy.com/). \n\n {qr_code} \n \n Dopo aver scansionato il codice, inserisci il codice a sei cifre dalla tua app per verificare la configurazione. Se riscontri problemi con la scansione del codice QR, esegui una configurazione manuale con codice ** ` {code} ` **.",
"title": "Imposta l'autenticazione a due fattori usando TOTP"
}
},
"title": "TOTP"
}
}
}

View File

@@ -2,11 +2,11 @@
"mfa_setup": {
"totp": {
"error": {
"invalid_code": "\uc798\ubabb\ub41c \ucf54\ub4dc \uc785\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694. \uc774 \uc624\ub958\uac00 \uc9c0\uc18d\uc801\uc73c\ub85c \ubc1c\uc0dd\ud55c\ub2e4\uba74 Home Assistant \uc758 \uc2dc\uacc4\uac00 \uc815\ud655\ud55c\uc9c0 \ud655\uc778\ud574\ubcf4\uc138\uc694."
"invalid_code": "\uc798\ubabb\ub41c \ucf54\ub4dc \uc785\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694. \uc774 \uc624\ub958\uac00 \uc9c0\uc18d\uc801\uc73c\ub85c \ubc1c\uc0dd\ud55c\ub2e4\uba74 Home Assistant \uc758 \uc2dc\uac04\uc124\uc815\uc774 \uc62c\ubc14\ub978\uc9c0 \ud655\uc778\ud574\ubcf4\uc138\uc694."
},
"step": {
"init": {
"description": "\uc2dc\uac04 \uae30\ubc18\uc758 \uc77c\ud68c\uc6a9 \ube44\ubc00\ubc88\ud638\ub97c \uc0ac\uc6a9\ud558\ub294 2\ub2e8\uacc4 \uc778\uc99d\uc744 \ud558\ub824\uba74 \uc778\uc99d\uc6a9 \uc571\uc744 \uc774\uc6a9\ud574\uc11c QR \ucf54\ub4dc\ub97c \uc2a4\uce94\ud574 \uc8fc\uc138\uc694. \uc778\uc99d\uc6a9 \uc571\uc740 [Google Authenticator](https://support.google.com/accounts/answer/1066447) \ub098 [Authy](https://authy.com/) \ub97c \ucd94\ucc9c\ub4dc\ub9bd\ub2c8\ub2e4.\n\n{qr_code}\n\n\uc2a4\uce94 \ud6c4\uc5d0 \uc0dd\uc131\ub41c 6\uc790\ub9ac \ucf54\ub4dc\ub97c \uc785\ub825\ud574\uc11c \uc124\uc815\uc744 \ud655\uc778\ud558\uc138\uc694. QR \ucf54\ub4dc \uc2a4\uce94\uc5d0 \ubb38\uc81c\uac00 \uc788\ub2e4\uba74, **`{code}`** \ucf54\ub4dc\ub85c \uc9c1\uc811 \uc124\uc815\ud574\ubcf4\uc138\uc694.",
"description": "\uc2dc\uac04 \uae30\ubc18\uc758 \uc77c\ud68c\uc6a9 \ube44\ubc00\ubc88\ud638\ub97c \uc0ac\uc6a9\ud558\ub294 2\ub2e8\uacc4 \uc778\uc99d\uc744 \ud558\ub824\uba74 \uc778\uc99d\uc6a9 \uc571\uc744 \uc774\uc6a9\ud574\uc11c QR \ucf54\ub4dc\ub97c \uc2a4\uce94\ud574 \uc8fc\uc138\uc694. \uc778\uc99d\uc6a9 \uc571\uc740 [Google OTP](https://support.google.com/accounts/answer/1066447) \ub610\ub294 [Authy](https://authy.com/) \ub97c \ucd94\ucc9c\ub4dc\ub9bd\ub2c8\ub2e4.\n\n{qr_code}\n\n\uc2a4\uce94 \ud6c4\uc5d0 \uc0dd\uc131\ub41c 6\uc790\ub9ac \ucf54\ub4dc\ub97c \uc785\ub825\ud574\uc11c \uc124\uc815\uc744 \ud655\uc778\ud558\uc138\uc694. QR \ucf54\ub4dc \uc2a4\uce94\uc5d0 \ubb38\uc81c\uac00 \uc788\ub2e4\uba74, **`{code}`** \ucf54\ub4dc\ub85c \uc9c1\uc811 \uc124\uc815\ud574\ubcf4\uc138\uc694.",
"title": "TOTP \ub97c \uc0ac\uc6a9\ud558\uc5ec 2 \ub2e8\uacc4 \uc778\uc99d \uad6c\uc131"
}
},

View File

@@ -0,0 +1,16 @@
{
"mfa_setup": {
"totp": {
"error": {
"invalid_code": "Ongeldige code, probeer het opnieuw. Als u deze fout blijft krijgen, controleer dan of de klok van uw Home Assistant systeem correct is ingesteld."
},
"step": {
"init": {
"description": "Voor het activeren van twee-factor-authenticatie via tijdgebonden eenmalige wachtwoorden: scan de QR code met uw authenticatie-app. Als u nog geen app heeft, adviseren we [Google Authenticator (https://support.google.com/accounts/answer/1066447) of [Authy](https://authy.com/).\n\n{qr_code}\n\nNa het scannen van de code voert u de zescijferige code uit uw app in om de instelling te controleren. Als u problemen heeft met het scannen van de QR-code, voert u een handmatige configuratie uit met code **`{code}`**.",
"title": "Configureer twee-factor-authenticatie via TOTP"
}
},
"title": "TOTP"
}
}
}

View File

@@ -0,0 +1,16 @@
{
"mfa_setup": {
"totp": {
"error": {
"invalid_code": "Ugyldig kode, pr\u00f8v igjen. Hvis du f\u00e5r denne feilen konsekvent, m\u00e5 du s\u00f8rge for at klokken p\u00e5 Home Assistant systemet er riktig."
},
"step": {
"init": {
"description": "For \u00e5 aktivere tofaktorautentisering ved hjelp av tidsbaserte engangspassord, skann QR-koden med autentiseringsappen din. Hvis du ikke har en, kan vi anbefale enten [Google Authenticator](https://support.google.com/accounts/answer/1066447) eller [Authy](https://authy.com/). \n\n {qr_code} \n \nEtter at du har skannet koden, skriver du inn den seks-sifrede koden fra appen din for \u00e5 kontrollere oppsettet. Dersom du har problemer med \u00e5 skanne QR-koden kan du taste inn f\u00f8lgende kode manuelt: **`{code}`**.",
"title": "Konfigurer tofaktorautentisering ved hjelp av TOTP"
}
},
"title": "TOTP"
}
}
}

View File

@@ -0,0 +1,16 @@
{
"mfa_setup": {
"totp": {
"error": {
"invalid_code": "Nieprawid\u0142owy kod, spr\u00f3buj ponownie. Je\u015bli b\u0142\u0105d b\u0119dzie si\u0119 powtarza\u0142, upewnij si\u0119, \u017ce czas zegara systemu Home Assistant jest prawid\u0142owy."
},
"step": {
"init": {
"description": "Aby aktywowa\u0107 uwierzytelnianie dwusk\u0142adnikowe przy u\u017cyciu jednorazowych hase\u0142 opartych na czasie, zeskanuj kod QR za pomoc\u0105 aplikacji uwierzytelniaj\u0105cej. Je\u015bli jej nie masz, polecamy [Google Authenticator](https://support.google.com/accounts/answer/1066447) lub [Authy](https://authy.com/).\n\n{qr_code} \n \nPo zeskanowaniu kodu wprowad\u017a sze\u015bciocyfrowy kod z aplikacji, aby zweryfikowa\u0107 konfiguracj\u0119. Je\u015bli masz problemy z zeskanowaniem kodu QR, wykonaj r\u0119czn\u0105 konfiguracj\u0119 z kodem **`{code}`**.",
"title": "Skonfiguruj uwierzytelnianie dwusk\u0142adnikowe za pomoc\u0105 hase\u0142 jednorazowych opartych na czasie"
}
},
"title": "Has\u0142a jednorazowe oparte na czasie"
}
}
}

View File

@@ -0,0 +1,16 @@
{
"mfa_setup": {
"totp": {
"error": {
"invalid_code": "C\u00f3digo inv\u00e1lido, por favor, tente novamente. Se receber este erro constantemente, por favor, certifique-se de que o rel\u00f3gio do sistema que hospeda o Home Assistent \u00e9 preciso."
},
"step": {
"init": {
"description": "Para ativar a autentica\u00e7\u00e3o com dois fatores utilizando passwords unicas temporais (OTP), ler o c\u00f3digo QR com a sua aplica\u00e7\u00e3o de autentica\u00e7\u00e3o. Se voc\u00ea n\u00e3o tiver uma, recomendamos [Google Authenticator](https://support.google.com/accounts/answer/1066447) ou [Authy](https://authy.com/).\n\n{qr_code}\n\nDepois de ler o c\u00f3digo, introduza o c\u00f3digo de seis d\u00edgitos fornecido pela sua aplica\u00e7\u00e3o para verificar a configura\u00e7\u00e3o. Se tiver problemas a ler o c\u00f3digo QR, fa\u00e7a uma configura\u00e7\u00e3o manual com o c\u00f3digo **`{c\u00f3digo}`**.",
"title": "Configurar autentica\u00e7\u00e3o com dois fatores usando TOTP"
}
},
"title": "TOTP"
}
}
}

View File

@@ -6,7 +6,7 @@
},
"step": {
"init": {
"description": "\u0427\u0442\u043e\u0431\u044b \u0430\u043a\u0442\u0438\u0432\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0434\u0432\u0443\u0445\u0444\u0430\u043a\u0442\u043e\u0440\u043d\u0443\u044e \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044e \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u043e\u0434\u043d\u043e\u0440\u0430\u0437\u043e\u0432\u044b\u0445 \u043f\u0430\u0440\u043e\u043b\u0435\u0439, \u043e\u0441\u043d\u043e\u0432\u0430\u043d\u043d\u044b\u0445 \u043d\u0430 \u0432\u0440\u0435\u043c\u0435\u043d\u0438, \u043e\u0442\u0441\u043a\u0430\u043d\u0438\u0440\u0443\u0439\u0442\u0435 QR-\u043a\u043e\u0434 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043f\u043e\u0434\u043b\u0438\u043d\u043d\u043e\u0441\u0442\u0438. \u0415\u0441\u043b\u0438 \u0443 \u0432\u0430\u0441 \u0435\u0433\u043e \u043d\u0435\u0442, \u043c\u044b \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043b\u0438\u0431\u043e [Google Authenticator] (https://support.google.com/accounts/answer/1066447), \u043b\u0438\u0431\u043e [Authy] (https://authy.com/). \n\n {qr_code} \n \n \u041f\u043e\u0441\u043b\u0435 \u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f QR-\u043a\u043e\u0434\u0430 \u0432\u0432\u0435\u0434\u0438\u0442\u0435 \u0448\u0435\u0441\u0442\u0438\u0437\u043d\u0430\u0447\u043d\u044b\u0439 \u043a\u043e\u0434 \u0438\u0437 \u0432\u0430\u0448\u0435\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443. \u0415\u0441\u043b\u0438 \u0443 \u0432\u0430\u0441 \u0435\u0441\u0442\u044c \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b \u0441\u043e \u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435\u043c QR-\u043a\u043e\u0434\u0430, \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u0435 \u0440\u0443\u0447\u043d\u0443\u044e \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443 \u0441 \u043a\u043e\u0434\u043e\u043c ** ` {code} ` **.",
"description": "\u0427\u0442\u043e\u0431\u044b \u0430\u043a\u0442\u0438\u0432\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0434\u0432\u0443\u0445\u0444\u0430\u043a\u0442\u043e\u0440\u043d\u0443\u044e \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044e \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u043e\u0434\u043d\u043e\u0440\u0430\u0437\u043e\u0432\u044b\u0445 \u043f\u0430\u0440\u043e\u043b\u0435\u0439, \u043e\u0441\u043d\u043e\u0432\u0430\u043d\u043d\u044b\u0445 \u043d\u0430 \u0432\u0440\u0435\u043c\u0435\u043d\u0438, \u043e\u0442\u0441\u043a\u0430\u043d\u0438\u0440\u0443\u0439\u0442\u0435 QR-\u043a\u043e\u0434 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043f\u043e\u0434\u043b\u0438\u043d\u043d\u043e\u0441\u0442\u0438. \u0415\u0441\u043b\u0438 \u0443 \u0432\u0430\u0441 \u0435\u0433\u043e \u043d\u0435\u0442, \u043c\u044b \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043b\u0438\u0431\u043e [Google Authenticator](https://support.google.com/accounts/answer/1066447), \u043b\u0438\u0431\u043e [Authy](https://authy.com/). \n\n {qr_code} \n \n\u041f\u043e\u0441\u043b\u0435 \u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f QR-\u043a\u043e\u0434\u0430 \u0432\u0432\u0435\u0434\u0438\u0442\u0435 \u0448\u0435\u0441\u0442\u0438\u0437\u043d\u0430\u0447\u043d\u044b\u0439 \u043a\u043e\u0434 \u0438\u0437 \u0432\u0430\u0448\u0435\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443. \u0415\u0441\u043b\u0438 \u0443 \u0432\u0430\u0441 \u0435\u0441\u0442\u044c \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b \u0441\u043e \u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435\u043c QR-\u043a\u043e\u0434\u0430, \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043a\u043e\u0434\u0430 **`{code}`**.",
"title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0434\u0432\u0443\u0445\u0444\u0430\u043a\u0442\u043e\u0440\u043d\u043e\u0439 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c TOTP"
}
},

View File

@@ -0,0 +1,16 @@
{
"mfa_setup": {
"totp": {
"error": {
"invalid_code": "Neveljavna koda, prosimo, poskusite znova. \u010ce dobite to sporo\u010dilo ve\u010dkrat, prosimo poskrbite, da bo ura va\u0161ega Home Assistenta to\u010dna."
},
"step": {
"init": {
"description": "\u010ce \u017eelite aktivirati preverjanje pristnosti dveh faktorjev z enkratnimi gesli, ki temeljijo na \u010dasu, skenirajte kodo QR s svojo aplikacijo za preverjanje pristnosti. \u010ce je nimate, priporo\u010damo bodisi [Google Authenticator] (https://support.google.com/accounts/answer/1066447) ali [Authy] (https://authy.com/). \n\n {qr_code} \n \n Po skeniranju kode vnesite \u0161estmestno kodo iz aplikacije, da preverite nastavitev. \u010ce imate te\u017eave pri skeniranju kode QR, naredite ro\u010dno nastavitev s kodo ** ` {code} ` **.",
"title": "Nastavite dvofaktorsko avtentifikacijo s pomo\u010djo TOTP-ja"
}
},
"title": "TOTP"
}
}
}

View File

@@ -0,0 +1,12 @@
{
"mfa_setup": {
"totp": {
"step": {
"init": {
"title": "St\u00e4ll in tv\u00e5faktorsautentisering med TOTP"
}
},
"title": "TOTP"
}
}
}

View File

@@ -158,27 +158,26 @@ def async_reload(hass):
return hass.services.async_call(DOMAIN, SERVICE_RELOAD)
@asyncio.coroutine
def async_setup(hass, config):
async def async_setup(hass, config):
"""Set up the automation."""
component = EntityComponent(_LOGGER, DOMAIN, hass,
group_name=GROUP_NAME_ALL_AUTOMATIONS)
yield from _async_process_config(hass, config, component)
await _async_process_config(hass, config, component)
@asyncio.coroutine
def trigger_service_handler(service_call):
async def trigger_service_handler(service_call):
"""Handle automation triggers."""
tasks = []
for entity in component.async_extract_from_service(service_call):
tasks.append(entity.async_trigger(
service_call.data.get(ATTR_VARIABLES), True))
service_call.data.get(ATTR_VARIABLES),
skip_condition=True,
context=service_call.context))
if tasks:
yield from asyncio.wait(tasks, loop=hass.loop)
await asyncio.wait(tasks, loop=hass.loop)
@asyncio.coroutine
def turn_onoff_service_handler(service_call):
async def turn_onoff_service_handler(service_call):
"""Handle automation turn on/off service calls."""
tasks = []
method = 'async_{}'.format(service_call.service)
@@ -186,10 +185,9 @@ def async_setup(hass, config):
tasks.append(getattr(entity, method)())
if tasks:
yield from asyncio.wait(tasks, loop=hass.loop)
await asyncio.wait(tasks, loop=hass.loop)
@asyncio.coroutine
def toggle_service_handler(service_call):
async def toggle_service_handler(service_call):
"""Handle automation toggle service calls."""
tasks = []
for entity in component.async_extract_from_service(service_call):
@@ -199,15 +197,14 @@ def async_setup(hass, config):
tasks.append(entity.async_turn_on())
if tasks:
yield from asyncio.wait(tasks, loop=hass.loop)
await asyncio.wait(tasks, loop=hass.loop)
@asyncio.coroutine
def reload_service_handler(service_call):
async def reload_service_handler(service_call):
"""Remove all automations and load new ones from config."""
conf = yield from component.async_prepare_reload()
conf = await component.async_prepare_reload()
if conf is None:
return
yield from _async_process_config(hass, conf, component)
await _async_process_config(hass, conf, component)
hass.services.async_register(
DOMAIN, SERVICE_TRIGGER, trigger_service_handler,
@@ -272,15 +269,14 @@ class AutomationEntity(ToggleEntity):
"""Return True if entity is on."""
return self._async_detach_triggers is not None
@asyncio.coroutine
def async_added_to_hass(self) -> None:
async def async_added_to_hass(self) -> None:
"""Startup with initial state or previous state."""
if self._initial_state is not None:
enable_automation = self._initial_state
_LOGGER.debug("Automation %s initial state %s from config "
"initial_state", self.entity_id, enable_automation)
else:
state = yield from async_get_last_state(self.hass, self.entity_id)
state = await async_get_last_state(self.hass, self.entity_id)
if state:
enable_automation = state.state == STATE_ON
self._last_triggered = state.attributes.get('last_triggered')
@@ -298,54 +294,50 @@ class AutomationEntity(ToggleEntity):
# HomeAssistant is starting up
if self.hass.state == CoreState.not_running:
@asyncio.coroutine
def async_enable_automation(event):
async def async_enable_automation(event):
"""Start automation on startup."""
yield from self.async_enable()
await self.async_enable()
self.hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_START, async_enable_automation)
# HomeAssistant is running
else:
yield from self.async_enable()
await self.async_enable()
@asyncio.coroutine
def async_turn_on(self, **kwargs) -> None:
async def async_turn_on(self, **kwargs) -> None:
"""Turn the entity on and update the state."""
if self.is_on:
return
yield from self.async_enable()
await self.async_enable()
@asyncio.coroutine
def async_turn_off(self, **kwargs) -> None:
async def async_turn_off(self, **kwargs) -> None:
"""Turn the entity off."""
if not self.is_on:
return
self._async_detach_triggers()
self._async_detach_triggers = None
yield from self.async_update_ha_state()
await self.async_update_ha_state()
@asyncio.coroutine
def async_trigger(self, variables, skip_condition=False):
async def async_trigger(self, variables, skip_condition=False,
context=None):
"""Trigger automation.
This method is a coroutine.
"""
if skip_condition or self._cond_func(variables):
yield from self._async_action(self.entity_id, variables)
self.async_set_context(context)
await self._async_action(self.entity_id, variables, context)
self._last_triggered = utcnow()
yield from self.async_update_ha_state()
await self.async_update_ha_state()
@asyncio.coroutine
def async_will_remove_from_hass(self):
async def async_will_remove_from_hass(self):
"""Remove listeners when removing automation from HASS."""
yield from self.async_turn_off()
await self.async_turn_off()
@asyncio.coroutine
def async_enable(self):
async def async_enable(self):
"""Enable this automation entity.
This method is a coroutine.
@@ -353,9 +345,9 @@ class AutomationEntity(ToggleEntity):
if self.is_on:
return
self._async_detach_triggers = yield from self._async_attach_triggers(
self._async_detach_triggers = await self._async_attach_triggers(
self.async_trigger)
yield from self.async_update_ha_state()
await self.async_update_ha_state()
@property
def device_state_attributes(self):
@@ -368,8 +360,7 @@ class AutomationEntity(ToggleEntity):
}
@asyncio.coroutine
def _async_process_config(hass, config, component):
async def _async_process_config(hass, config, component):
"""Process config and add automations.
This method is a coroutine.
@@ -411,20 +402,19 @@ def _async_process_config(hass, config, component):
entities.append(entity)
if entities:
yield from component.async_add_entities(entities)
await component.async_add_entities(entities)
def _async_get_action(hass, config, name):
"""Return an action based on a configuration."""
script_obj = script.Script(hass, config, name)
@asyncio.coroutine
def action(entity_id, variables):
async def action(entity_id, variables, context):
"""Execute an action."""
_LOGGER.info('Executing %s', name)
logbook.async_log_entry(
hass, name, 'has been triggered', DOMAIN, entity_id)
yield from script_obj.async_run(variables)
await script_obj.async_run(variables, context)
return action
@@ -448,8 +438,7 @@ def _async_process_if(hass, config, p_config):
return if_action
@asyncio.coroutine
def _async_process_trigger(hass, config, trigger_configs, name, action):
async def _async_process_trigger(hass, config, trigger_configs, name, action):
"""Set up the triggers.
This method is a coroutine.
@@ -457,13 +446,13 @@ def _async_process_trigger(hass, config, trigger_configs, name, action):
removes = []
for conf in trigger_configs:
platform = yield from async_prepare_setup_platform(
platform = await async_prepare_setup_platform(
hass, config, DOMAIN, conf.get(CONF_PLATFORM))
if platform is None:
return None
remove = yield from platform.async_trigger(hass, conf, action)
remove = await platform.async_trigger(hass, conf, action)
if not remove:
_LOGGER.error("Error setting up trigger %s", name)

View File

@@ -45,11 +45,11 @@ def async_trigger(hass, config, action):
# If event data doesn't match requested schema, skip event
return
hass.async_run_job(action, {
hass.async_run_job(action({
'trigger': {
'platform': 'event',
'event': event,
},
})
}, context=event.context))
return hass.bus.async_listen(event_type, handle_event)

View File

@@ -32,12 +32,12 @@ def async_trigger(hass, config, action):
@callback
def hass_shutdown(event):
"""Execute when Home Assistant is shutting down."""
hass.async_run_job(action, {
hass.async_run_job(action({
'trigger': {
'platform': 'homeassistant',
'event': event,
},
})
}, context=event.context))
return hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP,
hass_shutdown)
@@ -45,11 +45,11 @@ def async_trigger(hass, config, action):
# Automation are enabled while hass is starting up, fire right away
# Check state because a config reload shouldn't trigger it.
if hass.state == CoreState.starting:
hass.async_run_job(action, {
hass.async_run_job(action({
'trigger': {
'platform': 'homeassistant',
'event': event,
},
})
}))
return lambda: None

View File

@@ -66,7 +66,7 @@ def async_trigger(hass, config, action):
@callback
def call_action():
"""Call action with right context."""
hass.async_run_job(action, {
hass.async_run_job(action({
'trigger': {
'platform': 'numeric_state',
'entity_id': entity,
@@ -75,7 +75,7 @@ def async_trigger(hass, config, action):
'from_state': from_s,
'to_state': to_s,
}
})
}, context=to_s.context))
matching = check_numeric_state(entity, from_s, to_s)

View File

@@ -43,7 +43,7 @@ def async_trigger(hass, config, action):
@callback
def call_action():
"""Call action with right context."""
hass.async_run_job(action, {
hass.async_run_job(action({
'trigger': {
'platform': 'state',
'entity_id': entity,
@@ -51,7 +51,7 @@ def async_trigger(hass, config, action):
'to_state': to_s,
'for': time_delta,
}
})
}, context=to_s.context))
# Ignore changes to state attributes if from/to is in use
if (not match_all and from_s is not None and to_s is not None and

View File

@@ -32,13 +32,13 @@ def async_trigger(hass, config, action):
@callback
def template_listener(entity_id, from_s, to_s):
"""Listen for state changes and calls action."""
hass.async_run_job(action, {
hass.async_run_job(action({
'trigger': {
'platform': 'template',
'entity_id': entity_id,
'from_state': from_s,
'to_state': to_s,
},
})
}, context=to_s.context))
return async_track_template(hass, value_template, template_listener)

View File

@@ -51,7 +51,7 @@ def async_trigger(hass, config, action):
# pylint: disable=too-many-boolean-expressions
if event == EVENT_ENTER and not from_match and to_match or \
event == EVENT_LEAVE and from_match and not to_match:
hass.async_run_job(action, {
hass.async_run_job(action({
'trigger': {
'platform': 'zone',
'entity_id': entity,
@@ -60,7 +60,7 @@ def async_trigger(hass, config, action):
'zone': zone_state,
'event': event,
},
})
}, context=to_s.context))
return async_track_state_change(hass, entity_id, zone_automation_listener,
MATCH_ALL, MATCH_ALL)

View File

@@ -54,6 +54,11 @@ class DeconzBinarySensor(BinarySensorDevice):
self._sensor.register_async_callback(self.async_update_callback)
self.hass.data[DATA_DECONZ_ID][self.entity_id] = self._sensor.deconz_id
async def async_will_remove_from_hass(self) -> None:
"""Disconnect sensor object when removed."""
self._sensor.remove_callback(self.async_update_callback)
self._sensor = None
@callback
def async_update_callback(self, reason):
"""Update the sensor's state.

View File

@@ -7,12 +7,11 @@ https://home-assistant.io/components/binary_sensor.openuv/
import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.const import CONF_MONITORED_CONDITIONS
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.components.openuv import (
BINARY_SENSORS, DATA_PROTECTION_WINDOW, DOMAIN, TOPIC_UPDATE,
TYPE_PROTECTION_WINDOW, OpenUvEntity)
BINARY_SENSORS, DATA_OPENUV_CLIENT, DATA_PROTECTION_WINDOW, DOMAIN,
TOPIC_UPDATE, TYPE_PROTECTION_WINDOW, OpenUvEntity)
from homeassistant.util.dt import as_local, parse_datetime, utcnow
DEPENDENCIES = ['openuv']
@@ -26,17 +25,20 @@ ATTR_PROTECTION_WINDOW_ENDING_UV = 'end_uv'
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
"""Set up the OpenUV binary sensor platform."""
if discovery_info is None:
return
"""Set up an OpenUV sensor based on existing config."""
pass
openuv = hass.data[DOMAIN]
async def async_setup_entry(hass, entry, async_add_entities):
"""Set up an OpenUV sensor based on a config entry."""
openuv = hass.data[DOMAIN][DATA_OPENUV_CLIENT][entry.entry_id]
binary_sensors = []
for sensor_type in discovery_info[CONF_MONITORED_CONDITIONS]:
for sensor_type in openuv.binary_sensor_conditions:
name, icon = BINARY_SENSORS[sensor_type]
binary_sensors.append(
OpenUvBinarySensor(openuv, sensor_type, name, icon))
OpenUvBinarySensor(
openuv, sensor_type, name, icon, entry.entry_id))
async_add_entities(binary_sensors, True)
@@ -44,14 +46,16 @@ async def async_setup_platform(
class OpenUvBinarySensor(OpenUvEntity, BinarySensorDevice):
"""Define a binary sensor for OpenUV."""
def __init__(self, openuv, sensor_type, name, icon):
def __init__(self, openuv, sensor_type, name, icon, entry_id):
"""Initialize the sensor."""
super().__init__(openuv)
self._entry_id = entry_id
self._icon = icon
self._latitude = openuv.client.latitude
self._longitude = openuv.client.longitude
self._name = name
self._dispatch_remove = None
self._sensor_type = sensor_type
self._state = None
@@ -83,8 +87,9 @@ class OpenUvBinarySensor(OpenUvEntity, BinarySensorDevice):
async def async_added_to_hass(self):
"""Register callbacks."""
async_dispatcher_connect(
self._dispatch_remove = async_dispatcher_connect(
self.hass, TOPIC_UPDATE, self._update_data)
self.async_on_remove(self._dispatch_remove)
async def async_update(self):
"""Update the state."""

View File

@@ -374,11 +374,11 @@ class XiaomiCube(XiaomiBinarySensor):
self._last_action = None
self._state = False
if 'proto' not in device or int(device['proto'][0:1]) == 1:
self._data_key = 'status'
data_key = 'status'
else:
self._data_key = 'cube_status'
data_key = 'cube_status'
XiaomiBinarySensor.__init__(self, device, 'Cube', xiaomi_hub,
None, None)
data_key, None)
@property
def device_state_attributes(self):

View File

@@ -142,6 +142,68 @@ def async_snapshot(hass, filename, entity_id=None):
@bind_hass
async def async_get_image(hass, entity_id, timeout=10):
"""Fetch an image from a camera entity."""
camera = _get_camera_from_entity_id(hass, entity_id)
with suppress(asyncio.CancelledError, asyncio.TimeoutError):
with async_timeout.timeout(timeout, loop=hass.loop):
image = await camera.async_camera_image()
if image:
return Image(camera.content_type, image)
raise HomeAssistantError('Unable to get image')
@bind_hass
async def async_get_mjpeg_stream(hass, request, entity_id):
"""Fetch an mjpeg stream from a camera entity."""
camera = _get_camera_from_entity_id(hass, entity_id)
return await camera.handle_async_mjpeg_stream(request)
async def async_get_still_stream(request, image_cb, content_type, interval):
"""Generate an HTTP MJPEG stream from camera images.
This method must be run in the event loop.
"""
response = web.StreamResponse()
response.content_type = ('multipart/x-mixed-replace; '
'boundary=--frameboundary')
await response.prepare(request)
async def write_to_mjpeg_stream(img_bytes):
"""Write image to stream."""
await response.write(bytes(
'--frameboundary\r\n'
'Content-Type: {}\r\n'
'Content-Length: {}\r\n\r\n'.format(
content_type, len(img_bytes)),
'utf-8') + img_bytes + b'\r\n')
last_image = None
while True:
img_bytes = await image_cb()
if not img_bytes:
break
if img_bytes != last_image:
await write_to_mjpeg_stream(img_bytes)
# Chrome seems to always ignore first picture,
# print it twice.
if last_image is None:
await write_to_mjpeg_stream(img_bytes)
last_image = img_bytes
await asyncio.sleep(interval)
return response
def _get_camera_from_entity_id(hass, entity_id):
"""Get camera component from entity_id."""
component = hass.data.get(DOMAIN)
if component is None:
@@ -155,14 +217,7 @@ async def async_get_image(hass, entity_id, timeout=10):
if not camera.is_on:
raise HomeAssistantError('Camera is off')
with suppress(asyncio.CancelledError, asyncio.TimeoutError):
with async_timeout.timeout(timeout, loop=hass.loop):
image = await camera.async_camera_image()
if image:
return Image(camera.content_type, image)
raise HomeAssistantError('Unable to get image')
return camera
async def async_setup(hass, config):
@@ -290,39 +345,8 @@ class Camera(Entity):
This method must be run in the event loop.
"""
response = web.StreamResponse()
response.content_type = ('multipart/x-mixed-replace; '
'boundary=--frameboundary')
await response.prepare(request)
async def write_to_mjpeg_stream(img_bytes):
"""Write image to stream."""
await response.write(bytes(
'--frameboundary\r\n'
'Content-Type: {}\r\n'
'Content-Length: {}\r\n\r\n'.format(
self.content_type, len(img_bytes)),
'utf-8') + img_bytes + b'\r\n')
last_image = None
while True:
img_bytes = await self.async_camera_image()
if not img_bytes:
break
if img_bytes and img_bytes != last_image:
await write_to_mjpeg_stream(img_bytes)
# Chrome seems to always ignore first picture,
# print it twice.
if last_image is None:
await write_to_mjpeg_stream(img_bytes)
last_image = img_bytes
await asyncio.sleep(interval)
return response
return await async_get_still_stream(request, self.async_camera_image,
self.content_type, interval)
async def handle_async_mjpeg_stream(self, request):
"""Serve an HTTP MJPEG stream from the camera.

View File

@@ -7,17 +7,15 @@ https://www.home-assistant.io/components/camera.proxy/
import asyncio
import logging
import aiohttp
import async_timeout
import voluptuous as vol
from homeassistant.components.camera import PLATFORM_SCHEMA, Camera
from homeassistant.const import CONF_ENTITY_ID, CONF_NAME, HTTP_HEADER_HA_AUTH
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import (
async_aiohttp_proxy_web, async_get_clientsession)
from homeassistant.util.async_ import run_coroutine_threadsafe
import homeassistant.util.dt as dt_util
from . import async_get_still_stream
REQUIREMENTS = ['pillow==5.2.0']
@@ -158,22 +156,14 @@ class ProxyCamera(Camera):
return self._last_image
self._last_image_time = now
url = "{}/api/camera_proxy/{}".format(
self.hass.config.api.base_url, self._proxied_camera)
try:
websession = async_get_clientsession(self.hass)
with async_timeout.timeout(10, loop=self.hass.loop):
response = await websession.get(url, headers=self._headers)
image = await response.read()
except asyncio.TimeoutError:
_LOGGER.error("Timeout getting camera image")
return self._last_image
except aiohttp.ClientError as err:
_LOGGER.error("Error getting new camera image: %s", err)
image = await self.hass.components.camera.async_get_image(
self._proxied_camera)
if not image:
_LOGGER.error("Error getting original camera image")
return self._last_image
image = await self.hass.async_add_job(
_resize_image, image, self._image_opts)
_resize_image, image.content, self._image_opts)
if self._cache_images:
self._last_image = image
@@ -181,56 +171,28 @@ class ProxyCamera(Camera):
async def handle_async_mjpeg_stream(self, request):
"""Generate an HTTP MJPEG stream from camera images."""
websession = async_get_clientsession(self.hass)
url = "{}/api/camera_proxy_stream/{}".format(
self.hass.config.api.base_url, self._proxied_camera)
stream_coro = websession.get(url, headers=self._headers)
if not self._stream_opts:
return await async_aiohttp_proxy_web(
self.hass, request, stream_coro)
return await self.hass.components.camera.async_get_mjpeg_stream(
request, self._proxied_camera)
response = aiohttp.web.StreamResponse()
response.content_type = (
'multipart/x-mixed-replace; boundary=--frameboundary')
await response.prepare(request)
async def write(img_bytes):
"""Write image to stream."""
await response.write(bytes(
'--frameboundary\r\n'
'Content-Type: {}\r\n'
'Content-Length: {}\r\n\r\n'.format(
self.content_type, len(img_bytes)),
'utf-8') + img_bytes + b'\r\n')
with async_timeout.timeout(10, loop=self.hass.loop):
req = await stream_coro
try:
# This would be nicer as an async generator
# But that would only be supported for python >=3.6
data = b''
stream = req.content
while True:
chunk = await stream.read(102400)
if not chunk:
break
data += chunk
jpg_start = data.find(b'\xff\xd8')
jpg_end = data.find(b'\xff\xd9')
if jpg_start != -1 and jpg_end != -1:
image = data[jpg_start:jpg_end + 2]
image = await self.hass.async_add_job(
_resize_image, image, self._stream_opts)
await write(image)
data = data[jpg_end + 2:]
finally:
req.close()
return response
return await async_get_still_stream(
request, self._async_stream_image,
self.content_type, self.frame_interval)
@property
def name(self):
"""Return the name of this camera."""
return self._name
async def _async_stream_image(self):
"""Return a still image response from the camera."""
try:
image = await self.hass.components.camera.async_get_image(
self._proxied_camera)
if not image:
return None
except HomeAssistantError:
raise asyncio.CancelledError
return await self.hass.async_add_job(
_resize_image, image.content, self._stream_opts)

View File

@@ -0,0 +1,15 @@
{
"config": {
"abort": {
"no_devices_found": "Aucun appareil Google Cast trouv\u00e9 sur le r\u00e9seau.",
"single_instance_allowed": "Seulement une seule configuration de Google Cast est n\u00e9cessaire."
},
"step": {
"confirm": {
"description": "Voulez-vous configurer Google Cast?",
"title": "Google Cast"
}
},
"title": "Google Cast"
}
}

View File

@@ -251,6 +251,14 @@ class GenericThermostat(ClimateDevice):
# Ensure we update the current operation after changing the mode
self.schedule_update_ha_state()
async def async_turn_on(self):
"""Turn thermostat on."""
await self.async_set_operation_mode(self.operation_list[0])
async def async_turn_off(self):
"""Turn thermostat off."""
await self.async_set_operation_mode(STATE_OFF)
async def async_set_temperature(self, **kwargs):
"""Set new target temperature."""
temperature = kwargs.get(ATTR_TEMPERATURE)

View File

@@ -8,7 +8,8 @@ import logging
import voluptuous as vol
from homeassistant.components.nest import DATA_NEST, SIGNAL_NEST_UPDATE
from homeassistant.components.nest import (
DATA_NEST, SIGNAL_NEST_UPDATE, DOMAIN as NEST_DOMAIN)
from homeassistant.components.climate import (
STATE_AUTO, STATE_COOL, STATE_HEAT, STATE_ECO, ClimateDevice,
PLATFORM_SCHEMA, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
@@ -127,6 +128,19 @@ class NestThermostat(ClimateDevice):
"""Return unique ID for this device."""
return self.device.serial
@property
def device_info(self):
"""Return information about the device."""
return {
'identifiers': {
(NEST_DOMAIN, self.device.device_id),
},
'name': self.device.name_long,
'manufacturer': 'Nest Labs',
'model': "Thermostat",
'sw_version': self.device.software_version,
}
@property
def name(self):
"""Return the name of the nest, if any."""

View File

@@ -0,0 +1,189 @@
"""
Support for OpenTherm Gateway devices.
For more details about this component, please refer to the documentation at
http://home-assistant.io/components/climate.opentherm_gw/
"""
import logging
import voluptuous as vol
from homeassistant.components.climate import (ClimateDevice, PLATFORM_SCHEMA,
STATE_IDLE, STATE_HEAT,
STATE_COOL,
SUPPORT_TARGET_TEMPERATURE)
from homeassistant.const import (ATTR_TEMPERATURE, CONF_DEVICE, CONF_NAME,
PRECISION_HALVES, PRECISION_TENTHS,
TEMP_CELSIUS, PRECISION_WHOLE)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pyotgw==0.1b0']
CONF_FLOOR_TEMP = "floor_temperature"
CONF_PRECISION = 'precision'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_DEVICE): cv.string,
vol.Optional(CONF_NAME, default="OpenTherm Gateway"): cv.string,
vol.Optional(CONF_PRECISION): vol.In([PRECISION_TENTHS, PRECISION_HALVES,
PRECISION_WHOLE]),
vol.Optional(CONF_FLOOR_TEMP, default=False): cv.boolean,
})
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE)
_LOGGER = logging.getLogger(__name__)
async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
"""Set up the opentherm_gw device."""
gateway = OpenThermGateway(config)
async_add_entities([gateway])
class OpenThermGateway(ClimateDevice):
"""Representation of a climate device."""
def __init__(self, config):
"""Initialize the sensor."""
import pyotgw
self.pyotgw = pyotgw
self.gateway = self.pyotgw.pyotgw()
self._device = config[CONF_DEVICE]
self.friendly_name = config.get(CONF_NAME)
self.floor_temp = config.get(CONF_FLOOR_TEMP)
self.temp_precision = config.get(CONF_PRECISION)
self._current_operation = STATE_IDLE
self._current_temperature = 0.0
self._target_temperature = 0.0
self._away_mode_a = None
self._away_mode_b = None
self._away_state_a = False
self._away_state_b = False
async def async_added_to_hass(self):
"""Connect to the OpenTherm Gateway device."""
await self.gateway.connect(self.hass.loop, self._device)
self.gateway.subscribe(self.receive_report)
_LOGGER.debug("Connected to %s on %s", self.friendly_name,
self._device)
async def receive_report(self, status):
"""Receive and handle a new report from the Gateway."""
_LOGGER.debug("Received report: %s", status)
ch_active = status.get(self.pyotgw.DATA_SLAVE_CH_ACTIVE)
cooling_active = status.get(self.pyotgw.DATA_SLAVE_COOLING_ACTIVE)
if ch_active:
self._current_operation = STATE_HEAT
elif cooling_active:
self._current_operation = STATE_COOL
else:
self._current_operation = STATE_IDLE
self._current_temperature = status.get(self.pyotgw.DATA_ROOM_TEMP)
temp = status.get(self.pyotgw.DATA_ROOM_SETPOINT_OVRD)
if temp is None:
temp = status.get(self.pyotgw.DATA_ROOM_SETPOINT)
self._target_temperature = temp
# GPIO mode 5: 0 == Away
# GPIO mode 6: 1 == Away
gpio_a_state = status.get(self.pyotgw.OTGW_GPIO_A)
if gpio_a_state == 5:
self._away_mode_a = 0
elif gpio_a_state == 6:
self._away_mode_a = 1
else:
self._away_mode_a = None
gpio_b_state = status.get(self.pyotgw.OTGW_GPIO_B)
if gpio_b_state == 5:
self._away_mode_b = 0
elif gpio_b_state == 6:
self._away_mode_b = 1
else:
self._away_mode_b = None
if self._away_mode_a is not None:
self._away_state_a = (status.get(self.pyotgw.OTGW_GPIO_A_STATE) ==
self._away_mode_a)
if self._away_mode_b is not None:
self._away_state_b = (status.get(self.pyotgw.OTGW_GPIO_B_STATE) ==
self._away_mode_b)
self.async_schedule_update_ha_state()
@property
def name(self):
"""Return the friendly name."""
return self.friendly_name
@property
def precision(self):
"""Return the precision of the system."""
if self.temp_precision is not None:
return self.temp_precision
if self.hass.config.units.temperature_unit == TEMP_CELSIUS:
return PRECISION_HALVES
return PRECISION_WHOLE
@property
def should_poll(self):
"""Disable polling for this entity."""
return False
@property
def temperature_unit(self):
"""Return the unit of measurement used by the platform."""
return TEMP_CELSIUS
@property
def current_operation(self):
"""Return current operation ie. heat, cool, idle."""
return self._current_operation
@property
def current_temperature(self):
"""Return the current temperature."""
if self.floor_temp is True:
if self.temp_precision == PRECISION_HALVES:
return int(2 * self._current_temperature) / 2
if self.temp_precision == PRECISION_TENTHS:
return int(10 * self._current_temperature) / 10
return int(self._current_temperature)
return self._current_temperature
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
return self._target_temperature
@property
def target_temperature_step(self):
"""Return the supported step of target temperature."""
return self.temp_precision
@property
def is_away_mode_on(self):
"""Return true if away mode is on."""
return self._away_state_a or self._away_state_b
async def async_set_temperature(self, **kwargs):
"""Set new target temperature."""
if ATTR_TEMPERATURE in kwargs:
temp = float(kwargs[ATTR_TEMPERATURE])
self._target_temperature = await self.gateway.set_target_temp(
temp)
self.async_schedule_update_ha_state()
@property
def supported_features(self):
"""Return the list of supported features."""
return SUPPORT_FLAGS
@property
def min_temp(self):
"""Return the minimum temperature."""
return 1
@property
def max_temp(self):
"""Return the maximum temperature."""
return 30

View File

@@ -174,8 +174,8 @@ class RadioThermostat(ClimateDevice):
def device_state_attributes(self):
"""Return the device specific state attributes."""
return {
ATTR_FAN: self._fmode,
ATTR_MODE: self._tmode,
ATTR_FAN: self._fstate,
ATTR_MODE: self._tstate,
}
@property

View File

@@ -7,9 +7,6 @@ from homeassistant.helpers.data_entry_flow import (
FlowManagerIndexView, FlowManagerResourceView)
REQUIREMENTS = ['voluptuous-serialize==2.0.0']
@asyncio.coroutine
def async_setup(hass):
"""Enable the Home Assistant views."""

View File

@@ -0,0 +1,73 @@
"""
Support for Insteon covers via PowerLinc Modem.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/cover.insteon/
"""
import logging
import math
from homeassistant.components.insteon import InsteonEntity
from homeassistant.components.cover import (CoverDevice, ATTR_POSITION,
SUPPORT_OPEN, SUPPORT_CLOSE,
SUPPORT_SET_POSITION)
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['insteon']
SUPPORTED_FEATURES = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION
async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
"""Set up the Insteon platform."""
if not discovery_info:
return
insteon_modem = hass.data['insteon'].get('modem')
address = discovery_info['address']
device = insteon_modem.devices[address]
state_key = discovery_info['state_key']
_LOGGER.debug('Adding device %s entity %s to Cover platform',
device.address.hex, device.states[state_key].name)
new_entity = InsteonCoverDevice(device, state_key)
async_add_entities([new_entity])
class InsteonCoverDevice(InsteonEntity, CoverDevice):
"""A Class for an Insteon device."""
@property
def current_cover_position(self):
"""Return the current cover position."""
return int(math.ceil(self._insteon_device_state.value*100/255))
@property
def supported_features(self):
"""Return the supported features for this entity."""
return SUPPORTED_FEATURES
@property
def is_closed(self):
"""Return the boolean response if the node is on."""
return bool(self.current_cover_position)
async def async_open_cover(self, **kwargs):
"""Open device."""
self._insteon_device_state.open()
async def async_close_cover(self, **kwargs):
"""Close device."""
self._insteon_device_state.close()
async def async_set_cover_position(self, **kwargs):
"""Set the cover position."""
position = int(kwargs[ATTR_POSITION]*255/100)
if position == 0:
self._insteon_device_state.close()
else:
self._insteon_device_state.set_position(position)

View File

@@ -8,17 +8,25 @@ import logging
import voluptuous as vol
from homeassistant.components.cover import CoverDevice
from homeassistant.components.cover import (
CoverDevice, SUPPORT_CLOSE, SUPPORT_OPEN)
from homeassistant.const import (
CONF_USERNAME, CONF_PASSWORD, CONF_TYPE, STATE_CLOSED)
CONF_PASSWORD, CONF_TYPE, CONF_USERNAME, STATE_CLOSED, STATE_CLOSING,
STATE_OPENING)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pymyq==0.0.11']
REQUIREMENTS = ['pymyq==0.0.15']
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'myq'
MYQ_TO_HASS = {
'closed': STATE_CLOSED,
'closing': STATE_CLOSING,
'opening': STATE_OPENING
}
NOTIFICATION_ID = 'myq_notification'
NOTIFICATION_TITLE = 'MyQ Cover Setup'
@@ -87,7 +95,17 @@ class MyQDevice(CoverDevice):
@property
def is_closed(self):
"""Return true if cover is closed, else False."""
return self._status == STATE_CLOSED
return MYQ_TO_HASS[self._status] == STATE_CLOSED
@property
def is_closing(self):
"""Return if the cover is closing or not."""
return MYQ_TO_HASS[self._status] == STATE_CLOSING
@property
def is_opening(self):
"""Return if the cover is opening or not."""
return MYQ_TO_HASS[self._status] == STATE_OPENING
def close_cover(self, **kwargs):
"""Issue close command to cover."""
@@ -97,6 +115,16 @@ class MyQDevice(CoverDevice):
"""Issue open command to cover."""
self.myq.open_device(self.device_id)
@property
def supported_features(self):
"""Flag supported features."""
return SUPPORT_OPEN | SUPPORT_CLOSE
@property
def unique_id(self):
"""Return a unique, HASS-friendly identifier for this entity."""
return self.device_id
def update(self):
"""Update status of cover."""
self._status = self.myq.get_status(self.device_id)

View File

@@ -92,9 +92,9 @@ class RflinkCover(RflinkCommand, CoverDevice):
self.cancel_queued_send_commands()
command = event['command']
if command in ['on', 'allon']:
if command in ['on', 'allon', 'up']:
self._state = True
elif command in ['off', 'alloff']:
elif command in ['off', 'alloff', 'down']:
self._state = False
@property
@@ -105,7 +105,12 @@ class RflinkCover(RflinkCommand, CoverDevice):
@property
def is_closed(self):
"""Return if the cover is closed."""
return None
return not self._state
@property
def assumed_state(self):
"""Return True because covers can be stopped midway."""
return True
def async_close_cover(self, **kwargs):
"""Turn the device close."""

View File

@@ -22,7 +22,8 @@
},
"options": {
"data": {
"allow_clip_sensor": "Autoriser l'importation de capteurs virtuels"
"allow_clip_sensor": "Autoriser l'importation de capteurs virtuels",
"allow_deconz_groups": "Autoriser l'importation des groupes deCONZ"
},
"title": "Options de configuration suppl\u00e9mentaires pour deCONZ"
}

View File

@@ -28,6 +28,6 @@
"title": "Extra configuratieopties voor deCONZ"
}
},
"title": "deCONZ"
"title": "deCONZ Zigbee gateway"
}
}

View File

@@ -28,6 +28,6 @@
"title": "Ekstra konfigurasjonsalternativer for deCONZ"
}
},
"title": "deCONZ"
"title": "deCONZ Zigbee gateway"
}
}

View File

@@ -24,7 +24,7 @@ from .const import (
CONF_ALLOW_CLIP_SENSOR, CONFIG_FILE, DATA_DECONZ_EVENT,
DATA_DECONZ_ID, DATA_DECONZ_UNSUB, DOMAIN, _LOGGER)
REQUIREMENTS = ['pydeconz==44']
REQUIREMENTS = ['pydeconz==47']
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
@@ -46,6 +46,8 @@ SERVICE_SCHEMA = vol.Schema({
vol.Required(SERVICE_DATA): dict,
})
SERVICE_DEVICE_REFRESH = 'device_refresh'
async def async_setup(hass, config):
"""Load configuration for deCONZ component.
@@ -84,15 +86,17 @@ async def async_setup_entry(hass, config_entry):
@callback
def async_add_device_callback(device_type, device):
"""Handle event of new device creation in deCONZ."""
if not isinstance(device, list):
device = [device]
async_dispatcher_send(
hass, 'deconz_new_{}'.format(device_type), [device])
hass, 'deconz_new_{}'.format(device_type), device)
session = aiohttp_client.async_get_clientsession(hass)
deconz = DeconzSession(hass.loop, session, **config_entry.data,
async_add_device=async_add_device_callback)
result = await deconz.async_load_parameters()
if result is False:
_LOGGER.error("Failed to communicate with deCONZ")
return False
hass.data[DOMAIN] = deconz
@@ -149,16 +153,60 @@ async def async_setup_entry(hass, config_entry):
data = call.data.get(SERVICE_DATA)
deconz = hass.data[DOMAIN]
if entity_id:
entities = hass.data.get(DATA_DECONZ_ID)
if entities:
field = entities.get(entity_id)
if field is None:
_LOGGER.error('Could not find the entity %s', entity_id)
return
await deconz.async_put_state(field, data)
hass.services.async_register(
DOMAIN, SERVICE_DECONZ, async_configure, schema=SERVICE_SCHEMA)
async def async_refresh_devices(call):
"""Refresh available devices from deCONZ."""
deconz = hass.data[DOMAIN]
groups = list(deconz.groups.keys())
lights = list(deconz.lights.keys())
scenes = list(deconz.scenes.keys())
sensors = list(deconz.sensors.keys())
if not await deconz.async_load_parameters():
return
async_add_device_callback(
'group', [group
for group_id, group in deconz.groups.items()
if group_id not in groups]
)
async_add_device_callback(
'light', [light
for light_id, light in deconz.lights.items()
if light_id not in lights]
)
async_add_device_callback(
'scene', [scene
for scene_id, scene in deconz.scenes.items()
if scene_id not in scenes]
)
async_add_device_callback(
'sensor', [sensor
for sensor_id, sensor in deconz.sensors.items()
if sensor_id not in sensors]
)
hass.services.async_register(
DOMAIN, SERVICE_DEVICE_REFRESH, async_refresh_devices)
@callback
def deconz_shutdown(event):
"""
@@ -179,15 +227,22 @@ async def async_unload_entry(hass, config_entry):
deconz = hass.data.pop(DOMAIN)
hass.services.async_remove(DOMAIN, SERVICE_DECONZ)
deconz.close()
for component in ['binary_sensor', 'light', 'scene', 'sensor']:
for component in ['binary_sensor', 'light', 'scene', 'sensor', 'switch']:
await hass.config_entries.async_forward_entry_unload(
config_entry, component)
dispatchers = hass.data[DATA_DECONZ_UNSUB]
for unsub_dispatcher in dispatchers:
unsub_dispatcher()
hass.data[DATA_DECONZ_UNSUB] = []
hass.data[DATA_DECONZ_EVENT] = []
for event in hass.data[DATA_DECONZ_EVENT]:
event.async_will_remove_from_hass()
hass.data[DATA_DECONZ_EVENT].remove(event)
hass.data[DATA_DECONZ_ID] = []
return True
@@ -206,6 +261,12 @@ class DeconzEvent:
self._event = 'deconz_{}'.format(CONF_EVENT)
self._id = slugify(self._device.name)
@callback
def async_will_remove_from_hass(self) -> None:
"""Disconnect event object when removed."""
self._device.remove_callback(self.async_update_callback)
self._device = None
@callback
def async_update_callback(self, reason):
"""Fire the event if reason is that state is updated."""

View File

@@ -1,4 +1,3 @@
configure:
description: Set attribute of device in deCONZ. See https://home-assistant.io/components/deconz/#device-services for details.
fields:
@@ -11,3 +10,6 @@ configure:
data:
description: Data is a json object with what data you want to alter.
example: '{"on": true}'
device_refresh:
description: Refresh device lists from deCONZ.

View File

@@ -12,7 +12,8 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import track_point_in_utc_time
from homeassistant.components.device_tracker import (
YAML_DEVICES, CONF_TRACK_NEW, CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL,
load_config, PLATFORM_SCHEMA, DEFAULT_TRACK_NEW, SOURCE_TYPE_BLUETOOTH)
load_config, PLATFORM_SCHEMA, DEFAULT_TRACK_NEW, SOURCE_TYPE_BLUETOOTH,
DOMAIN)
import homeassistant.util.dt as dt_util
_LOGGER = logging.getLogger(__name__)
@@ -79,7 +80,13 @@ def setup_scanner(hass, config, see, discovery_info=None):
request_rssi = config.get(CONF_REQUEST_RSSI, False)
def update_bluetooth(now):
def update_bluetooth():
"""Update Bluetooth and set timer for the next update."""
update_bluetooth_once()
track_point_in_utc_time(
hass, update_bluetooth, dt_util.utcnow() + interval)
def update_bluetooth_once():
"""Lookup Bluetooth device and update status."""
try:
if track_new:
@@ -99,9 +106,14 @@ def setup_scanner(hass, config, see, discovery_info=None):
see_device(mac, result, rssi)
except bluetooth.BluetoothError:
_LOGGER.exception("Error looking up Bluetooth device")
track_point_in_utc_time(
hass, update_bluetooth, dt_util.utcnow() + interval)
update_bluetooth(dt_util.utcnow())
def handle_update_bluetooth(call):
"""Update bluetooth devices on demand."""
update_bluetooth_once()
update_bluetooth()
hass.services.register(
DOMAIN, "bluetooth_tracker_update", handle_update_bluetooth)
return True

View File

@@ -15,7 +15,7 @@ from homeassistant.const import ATTR_ID, CONF_PASSWORD, CONF_USERNAME
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import track_time_interval
from homeassistant.helpers.typing import ConfigType
from homeassistant.util import slugify
from homeassistant.util import slugify, dt as dt_util
REQUIREMENTS = ['locationsharinglib==2.0.11']
@@ -92,7 +92,7 @@ class GoogleMapsScanner:
ATTR_ADDRESS: person.address,
ATTR_FULL_NAME: person.full_name,
ATTR_ID: person.id,
ATTR_LAST_SEEN: person.datetime,
ATTR_LAST_SEEN: dt_util.as_utc(person.datetime),
ATTR_NICKNAME: person.nickname,
}
self.see(

View File

@@ -143,7 +143,7 @@ class FeedManager:
else:
self._has_published_parsed = False
_LOGGER.debug("No published_parsed info available for entry %s",
entry.title)
entry)
entry.update({'feed_url': self._url})
self._hass.bus.fire(self._event_type, entry)
@@ -164,7 +164,7 @@ class FeedManager:
self._update_and_fire_entry(entry)
new_entries = True
else:
_LOGGER.debug("Entry %s already processed", entry.title)
_LOGGER.debug("Entry %s already processed", entry)
if not new_entries:
self._log_no_entries()
self._firstrun = False

View File

@@ -26,7 +26,7 @@ from homeassistant.helpers.translation import async_get_translations
from homeassistant.loader import bind_hass
from homeassistant.util.yaml import load_yaml
REQUIREMENTS = ['home-assistant-frontend==20180829.0']
REQUIREMENTS = ['home-assistant-frontend==20180910.0']
DOMAIN = 'frontend'
DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log',

View File

@@ -0,0 +1,68 @@
"""
Geo Location component.
This component covers platforms that deal with external events that contain
a geo location related to the installed HA instance.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/geo_location/
"""
import logging
from datetime import timedelta
from typing import Optional
from homeassistant.const import ATTR_LATITUDE, ATTR_LONGITUDE
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
_LOGGER = logging.getLogger(__name__)
ATTR_DISTANCE = 'distance'
DOMAIN = 'geo_location'
ENTITY_ID_FORMAT = DOMAIN + '.{}'
GROUP_NAME_ALL_EVENTS = 'All Geo Location Events'
SCAN_INTERVAL = timedelta(seconds=60)
async def async_setup(hass, config):
"""Set up this component."""
component = EntityComponent(
_LOGGER, DOMAIN, hass, SCAN_INTERVAL, GROUP_NAME_ALL_EVENTS)
await component.async_setup(config)
return True
class GeoLocationEvent(Entity):
"""This represents an external event with an associated geo location."""
@property
def state(self):
"""Return the state of the sensor."""
if self.distance is not None:
return round(self.distance, 1)
return None
@property
def distance(self) -> Optional[float]:
"""Return distance value of this external event."""
return None
@property
def latitude(self) -> Optional[float]:
"""Return latitude value of this external event."""
return None
@property
def longitude(self) -> Optional[float]:
"""Return longitude value of this external event."""
return None
@property
def state_attributes(self):
"""Return the state attributes of this external event."""
data = {}
if self.latitude is not None:
data[ATTR_LATITUDE] = round(self.latitude, 5)
if self.longitude is not None:
data[ATTR_LONGITUDE] = round(self.longitude, 5)
return data

View File

@@ -0,0 +1,132 @@
"""
Demo platform for the geo location component.
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/demo/
"""
import logging
import random
from datetime import timedelta
from math import pi, cos, sin, radians
from typing import Optional
from homeassistant.components.geo_location import GeoLocationEvent
from homeassistant.helpers.event import track_time_interval
_LOGGER = logging.getLogger(__name__)
AVG_KM_PER_DEGREE = 111.0
DEFAULT_UNIT_OF_MEASUREMENT = "km"
DEFAULT_UPDATE_INTERVAL = timedelta(minutes=1)
MAX_RADIUS_IN_KM = 50
NUMBER_OF_DEMO_DEVICES = 5
EVENT_NAMES = ["Bushfire", "Hazard Reduction", "Grass Fire", "Burn off",
"Structure Fire", "Fire Alarm", "Thunderstorm", "Tornado",
"Cyclone", "Waterspout", "Dust Storm", "Blizzard", "Ice Storm",
"Earthquake", "Tsunami"]
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Demo geo locations."""
DemoManager(hass, add_entities)
class DemoManager:
"""Device manager for demo geo location events."""
def __init__(self, hass, add_entities):
"""Initialise the demo geo location event manager."""
self._hass = hass
self._add_entities = add_entities
self._managed_devices = []
self._update(count=NUMBER_OF_DEMO_DEVICES)
self._init_regular_updates()
def _generate_random_event(self):
"""Generate a random event in vicinity of this HA instance."""
home_latitude = self._hass.config.latitude
home_longitude = self._hass.config.longitude
# Approx. 111km per degree (north-south).
radius_in_degrees = random.random() * MAX_RADIUS_IN_KM / \
AVG_KM_PER_DEGREE
radius_in_km = radius_in_degrees * AVG_KM_PER_DEGREE
angle = random.random() * 2 * pi
# Compute coordinates based on radius and angle. Adjust longitude value
# based on HA's latitude.
latitude = home_latitude + radius_in_degrees * sin(angle)
longitude = home_longitude + radius_in_degrees * cos(angle) / \
cos(radians(home_latitude))
event_name = random.choice(EVENT_NAMES)
return DemoGeoLocationEvent(event_name, radius_in_km, latitude,
longitude, DEFAULT_UNIT_OF_MEASUREMENT)
def _init_regular_updates(self):
"""Schedule regular updates based on configured time interval."""
track_time_interval(self._hass, lambda now: self._update(),
DEFAULT_UPDATE_INTERVAL)
def _update(self, count=1):
"""Remove events and add new random events."""
# Remove devices.
for _ in range(1, count + 1):
if self._managed_devices:
device = random.choice(self._managed_devices)
if device:
_LOGGER.debug("Removing %s", device)
self._managed_devices.remove(device)
self._hass.add_job(device.async_remove())
# Generate new devices from events.
new_devices = []
for _ in range(1, count + 1):
new_device = self._generate_random_event()
_LOGGER.debug("Adding %s", new_device)
new_devices.append(new_device)
self._managed_devices.append(new_device)
self._add_entities(new_devices)
class DemoGeoLocationEvent(GeoLocationEvent):
"""This represents a demo geo location event."""
def __init__(self, name, distance, latitude, longitude,
unit_of_measurement):
"""Initialize entity with data provided."""
self._name = name
self._distance = distance
self._latitude = latitude
self._longitude = longitude
self._unit_of_measurement = unit_of_measurement
@property
def name(self) -> Optional[str]:
"""Return the name of the event."""
return self._name
@property
def should_poll(self):
"""No polling needed for a demo geo location event."""
return False
@property
def distance(self) -> Optional[float]:
"""Return distance value of this external event."""
return self._distance
@property
def latitude(self) -> Optional[float]:
"""Return latitude value of this external event."""
return self._latitude
@property
def longitude(self) -> Optional[float]:
"""Return longitude value of this external event."""
return self._longitude
@property
def unit_of_measurement(self):
"""Return the unit of measurement."""
return self._unit_of_measurement

View File

@@ -0,0 +1,158 @@
"""
The Habitica API component.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/habitica/
"""
import logging
from collections import namedtuple
import voluptuous as vol
from homeassistant.const import \
CONF_NAME, CONF_URL, CONF_SENSORS, CONF_PATH, CONF_API_KEY
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers import \
config_validation as cv, discovery
REQUIREMENTS = ['habitipy==0.2.0']
_LOGGER = logging.getLogger(__name__)
DOMAIN = "habitica"
CONF_API_USER = "api_user"
ST = SensorType = namedtuple('SensorType', [
"name", "icon", "unit", "path"
])
SENSORS_TYPES = {
'name': ST('Name', None, '', ["profile", "name"]),
'hp': ST('HP', 'mdi:heart', 'HP', ["stats", "hp"]),
'maxHealth': ST('max HP', 'mdi:heart', 'HP', ["stats", "maxHealth"]),
'mp': ST('Mana', 'mdi:auto-fix', 'MP', ["stats", "mp"]),
'maxMP': ST('max Mana', 'mdi:auto-fix', 'MP', ["stats", "maxMP"]),
'exp': ST('EXP', 'mdi:star', 'EXP', ["stats", "exp"]),
'toNextLevel': ST(
'Next Lvl', 'mdi:star', 'EXP', ["stats", "toNextLevel"]),
'lvl': ST(
'Lvl', 'mdi:arrow-up-bold-circle-outline', 'Lvl', ["stats", "lvl"]),
'gp': ST('Gold', 'mdi:coin', 'Gold', ["stats", "gp"]),
'class': ST('Class', 'mdi:sword', '', ["stats", "class"])
}
INSTANCE_SCHEMA = vol.Schema({
vol.Optional(CONF_URL, default='https://habitica.com'): cv.url,
vol.Optional(CONF_NAME): cv.string,
vol.Required(CONF_API_USER): cv.string,
vol.Required(CONF_API_KEY): cv.string,
vol.Optional(CONF_SENSORS, default=list(SENSORS_TYPES)):
vol.All(
cv.ensure_list,
vol.Unique(),
[vol.In(list(SENSORS_TYPES))])
})
has_unique_values = vol.Schema(vol.Unique()) # pylint: disable=invalid-name
# because we want a handy alias
def has_all_unique_users(value):
"""Validate that all `api_user`s are unique."""
api_users = [user[CONF_API_USER] for user in value]
has_unique_values(api_users)
return value
def has_all_unique_users_names(value):
"""Validate that all user's names are unique and set if any is set."""
names = [user.get(CONF_NAME) for user in value]
if None in names and any(name is not None for name in names):
raise vol.Invalid(
'user names of all users must be set if any is set')
if not all(name is None for name in names):
has_unique_values(names)
return value
INSTANCE_LIST_SCHEMA = vol.All(
cv.ensure_list,
has_all_unique_users,
has_all_unique_users_names,
[INSTANCE_SCHEMA])
CONFIG_SCHEMA = vol.Schema({
DOMAIN: INSTANCE_LIST_SCHEMA
}, extra=vol.ALLOW_EXTRA)
SERVICE_API_CALL = 'api_call'
ATTR_NAME = CONF_NAME
ATTR_PATH = CONF_PATH
ATTR_ARGS = "args"
EVENT_API_CALL_SUCCESS = "{0}_{1}_{2}".format(
DOMAIN, SERVICE_API_CALL, "success")
SERVICE_API_CALL_SCHEMA = vol.Schema({
vol.Required(ATTR_NAME): str,
vol.Required(ATTR_PATH): vol.All(cv.ensure_list, [str]),
vol.Optional(ATTR_ARGS): dict
})
async def async_setup(hass, config):
"""Set up the habitica service."""
conf = config[DOMAIN]
data = hass.data[DOMAIN] = {}
websession = async_get_clientsession(hass)
from habitipy.aio import HabitipyAsync
class HAHabitipyAsync(HabitipyAsync):
"""Closure API class to hold session."""
def __call__(self, **kwargs):
return super().__call__(websession, **kwargs)
for instance in conf:
url = instance[CONF_URL]
username = instance[CONF_API_USER]
password = instance[CONF_API_KEY]
name = instance.get(CONF_NAME)
config_dict = {"url": url, "login": username, "password": password}
api = HAHabitipyAsync(config_dict)
user = await api.user.get()
if name is None:
name = user['profile']['name']
data[name] = api
if CONF_SENSORS in instance:
hass.async_create_task(
discovery.async_load_platform(
hass, "sensor", DOMAIN,
{"name": name, "sensors": instance[CONF_SENSORS]},
config))
async def handle_api_call(call):
name = call.data[ATTR_NAME]
path = call.data[ATTR_PATH]
api = hass.data[DOMAIN].get(name)
if api is None:
_LOGGER.error(
"API_CALL: User '%s' not configured", name)
return
try:
for element in path:
api = api[element]
except KeyError:
_LOGGER.error(
"API_CALL: Path %s is invalid"
" for api on '{%s}' element", path, element)
return
kwargs = call.data.get(ATTR_ARGS, {})
data = await api(**kwargs)
hass.bus.async_fire(EVENT_API_CALL_SUCCESS, {
"name": name, "path": path, "data": data
})
hass.services.async_register(
DOMAIN, SERVICE_API_CALL,
handle_api_call,
schema=SERVICE_API_CALL_SCHEMA)
return True

View File

@@ -0,0 +1,15 @@
# Describes the format for Habitica service
---
api_call:
description: Call Habitica api
fields:
name:
description: Habitica's username to call for
example: 'xxxNotAValidNickxxx'
path:
description: "Items from API URL in form of an array with method attached at the end. Consult https://habitica.com/apidoc/. Example uses https://habitica.com/apidoc/#api-Task-CreateUserTasks"
example: '["tasks", "user", "post"]'
args:
description: Any additional json or url parameter arguments. See apidoc mentioned for path. Example uses same api endpoint
example: '{"text": "Use API from Home Assistant", "type": "todo"}'

View File

@@ -5,6 +5,7 @@
"unknown": "Ein unbekannter Fehler ist aufgetreten."
},
"error": {
"invalid_2fa": "Ung\u00fcltige 2-Faktor Authentifizierung, bitte versuche es erneut.",
"invalid_2fa_method": "Ung\u00fcltige 2FA Methode (mit Telefon verifizieren)",
"invalid_login": "Ung\u00fcltige Daten, bitte erneut versuchen."
},

View File

@@ -5,7 +5,7 @@
"unknown": "Unknown error occurred."
},
"error": {
"invalid_2fa": "Invalid 2 Factor Authorization, please try again.",
"invalid_2fa": "Invalid 2 Factor Authentication, please try again.",
"invalid_2fa_method": "Invalid 2FA Method (Verify on Phone).",
"invalid_login": "Invalid Login, please try again."
},
@@ -14,7 +14,7 @@
"data": {
"2fa": "2FA Pin"
},
"title": "2-Factor-Authorization"
"title": "2-Factor-Authentication"
},
"user": {
"data": {

View File

@@ -0,0 +1,18 @@
{
"config": {
"abort": {
"already_configured": "Google Hangouts ya est\u00e1 configurado",
"unknown": "Se produjo un error desconocido."
},
"step": {
"user": {
"data": {
"email": "Direcci\u00f3n de correo electr\u00f3nico",
"password": "Contrase\u00f1a"
},
"title": "Inicio de sesi\u00f3n de Google Hangouts"
}
},
"title": "Google Hangouts"
}
}

View File

@@ -0,0 +1,24 @@
{
"config": {
"abort": {
"already_configured": "Google Hangouts est d\u00e9j\u00e0 configur\u00e9",
"unknown": "Une erreur inconnue s'est produite"
},
"error": {
"invalid_login": "Login invalide, veuillez r\u00e9essayer."
},
"step": {
"2fa": {
"title": "Authentification \u00e0 2 facteurs"
},
"user": {
"data": {
"email": "Adresse e-mail",
"password": "Mot de passe"
},
"title": "Connexion \u00e0 Google Hangouts"
}
},
"title": "Google Hangouts"
}
}

View File

@@ -0,0 +1,29 @@
{
"config": {
"abort": {
"already_configured": "A Google Hangouts m\u00e1r konfigur\u00e1lva van",
"unknown": "Ismeretlen hiba t\u00f6rt\u00e9nt."
},
"error": {
"invalid_2fa": "\u00c9rv\u00e9nytelen K\u00e9tfaktoros hiteles\u00edt\u00e9s, pr\u00f3b\u00e1ld \u00fajra.",
"invalid_2fa_method": "\u00c9rv\u00e9nytelen 2FA M\u00f3dszer (Ellen\u0151rz\u00e9s a Telefonon).",
"invalid_login": "\u00c9rv\u00e9nytelen bejelentkez\u00e9s, pr\u00f3b\u00e1ld \u00fajra."
},
"step": {
"2fa": {
"data": {
"2fa": "2FA Pin"
},
"title": "K\u00e9tfaktoros Hiteles\u00edt\u00e9s"
},
"user": {
"data": {
"email": "E-Mail C\u00edm",
"password": "Jelsz\u00f3"
},
"title": "Google Hangouts Bejelentkez\u00e9s"
}
},
"title": "Google Hangouts"
}
}

View File

@@ -1,5 +1,29 @@
{
"config": {
"abort": {
"already_configured": "Google Hangouts \u00e8 gi\u00e0 configurato",
"unknown": "Si \u00e8 verificato un errore sconosciuto."
},
"error": {
"invalid_2fa": "Autenticazione a 2 fattori non valida, riprovare.",
"invalid_2fa_method": "Metodo 2FA non valido (verifica sul telefono).",
"invalid_login": "Accesso non valido, si prega di riprovare."
},
"step": {
"2fa": {
"data": {
"2fa": "2FA Pin"
},
"title": "Autenticazione a due fattori"
},
"user": {
"data": {
"email": "Indirizzo email",
"password": "Password"
},
"title": "Accesso a Google Hangouts"
}
},
"title": "Google Hangouts"
}
}

View File

@@ -0,0 +1,29 @@
{
"config": {
"abort": {
"already_configured": "Google Hangouts is al geconfigureerd",
"unknown": "Onbekende fout opgetreden."
},
"error": {
"invalid_2fa": "Ongeldige twee-factor-authenticatie, probeer het opnieuw.",
"invalid_2fa_method": "Ongeldige 2FA-methode (verifi\u00ebren op telefoon).",
"invalid_login": "Ongeldige aanmelding, probeer het opnieuw."
},
"step": {
"2fa": {
"data": {
"2fa": "2FA pin"
},
"title": "Twee-factor-authenticatie"
},
"user": {
"data": {
"email": "E-mailadres",
"password": "Wachtwoord"
},
"title": "Google Hangouts inlog"
}
},
"title": "Google Hangouts"
}
}

View File

@@ -1,11 +1,27 @@
{
"config": {
"abort": {
"already_configured": "Google Hangouts er allerede konfigurert",
"unknown": "Ukjent feil oppstod."
},
"error": {
"invalid_2fa": "Ugyldig tofaktorautentisering, vennligst pr\u00f8v igjen.",
"invalid_2fa_method": "Ugyldig 2FA-metode (Bekreft p\u00e5 telefon).",
"invalid_login": "Ugyldig innlogging, vennligst pr\u00f8v igjen."
},
"step": {
"2fa": {
"data": {
"2fa": "2FA Pin"
},
"title": "Tofaktorautentisering"
},
"user": {
"data": {
"email": "E-postadresse",
"password": "Passord"
}
},
"title": "Google Hangouts p\u00e5logging"
}
},
"title": "Google Hangouts"

View File

@@ -5,19 +5,14 @@
"unknown": "Ocorreu um erro desconhecido."
},
"error": {
"invalid_2fa_method": "M\u00e9todo 2FA inv\u00e1lido (verificar no telefone).",
"invalid_login": "Login inv\u00e1lido, por favor, tente novamente."
"invalid_2fa": "Autentica\u00e7\u00e3o de 2 fatores inv\u00e1lida, por favor, tente novamente."
},
"step": {
"2fa": {
"data": {
"2fa": "Pin 2FA"
},
"title": ""
},
"user": {
"data": {
"email": "Endere\u00e7o de e-mail",
"password": "Senha"
},
"title": "Login do Hangouts do Google"

View File

@@ -0,0 +1,31 @@
{
"config": {
"abort": {
"already_configured": "Google Hangouts j\u00e1 est\u00e1 configurado",
"unknown": "Ocorreu um erro desconhecido."
},
"error": {
"invalid_2fa": "Autoriza\u00e7\u00e3o por 2 factores inv\u00e1lida, por favor, tente novamente.",
"invalid_2fa_method": "M\u00e9todo 2FA inv\u00e1lido (verificar no telefone).",
"invalid_login": "Login inv\u00e1lido, por favor, tente novamente."
},
"step": {
"2fa": {
"data": {
"2fa": "Pin 2FA"
},
"description": "Vazio",
"title": ""
},
"user": {
"data": {
"email": "Endere\u00e7o de e-mail",
"password": "Palavra-passe"
},
"description": "Vazio",
"title": "Login Google Hangouts"
}
},
"title": ""
}
}

View File

@@ -0,0 +1,21 @@
{
"config": {
"abort": {
"already_configured": "Google Hangouts \u00e4r redan inst\u00e4llt",
"unknown": "Ett ok\u00e4nt fel intr\u00e4ffade"
},
"step": {
"2fa": {
"title": "Tv\u00e5faktorsautentisering"
},
"user": {
"data": {
"email": "E-postadress",
"password": "L\u00f6senord"
},
"title": "Google Hangouts-inloggning"
}
},
"title": "Google Hangouts"
}
}

View File

@@ -6,7 +6,7 @@
},
"error": {
"invalid_login": "Invalid Login, please try again.",
"invalid_2fa": "Invalid 2 Factor Authorization, please try again.",
"invalid_2fa": "Invalid 2 Factor Authentication, please try again.",
"invalid_2fa_method": "Invalid 2FA Method (Verify on Phone)."
},
"step": {
@@ -23,7 +23,7 @@
"2fa": "2FA Pin"
},
"description": "",
"title": "2-Factor-Authorization"
"title": "2-Factor-Authentication"
}
},
"title": "Google Hangouts"

View File

@@ -77,7 +77,7 @@ HM_DEVICE_TYPES = {
'FillingLevel', 'ValveDrive', 'EcoLogic', 'IPThermostatWall',
'IPSmoke', 'RFSiren', 'PresenceIP', 'IPAreaThermostat',
'IPWeatherSensor', 'RotaryHandleSensorIP', 'IPPassageSensor',
'IPKeySwitchPowermeter'],
'IPKeySwitchPowermeter', 'IPThermostatWall230V'],
DISCOVER_CLIMATE: [
'Thermostat', 'ThermostatWall', 'MAXThermostat', 'ThermostatWall2',
'MAXWallThermostat', 'IPThermostat', 'IPThermostatWall',

View File

@@ -2,6 +2,13 @@
"config": {
"error": {
"invalid_pin": "Ugyldig PIN, pr\u00f8v igen."
},
"step": {
"init": {
"data": {
"pin": "Pin kode (valgfri)"
}
}
}
}
}

View File

@@ -3,6 +3,7 @@
"abort": {
"already_configured": "Accesspoint ya est\u00e1 configurado",
"conection_aborted": "No se pudo conectar al servidor HMIP",
"connection_aborted": "No se pudo conectar al servidor HMIP",
"unknown": "Se produjo un error desconocido."
},
"error": {
@@ -18,6 +19,7 @@
"pin": "C\u00f3digo PIN (opcional)"
}
}
}
},
"title": "HomematicIP Cloud"
}
}

View File

@@ -0,0 +1,24 @@
{
"config": {
"abort": {
"already_configured": "Le point d'acc\u00e8s est d\u00e9j\u00e0 configur\u00e9",
"conection_aborted": "Impossible de se connecter au serveur HMIP",
"connection_aborted": "Impossible de se connecter au serveur HMIP",
"unknown": "Une erreur inconnue s'est produite"
},
"error": {
"invalid_pin": "Code PIN invalide, veuillez r\u00e9essayer.",
"press_the_button": "Veuillez appuyer sur le bouton bleu.",
"register_failed": "\u00c9chec d'enregistrement. Veuillez r\u00e9essayer."
},
"step": {
"init": {
"data": {
"hapid": "ID du point d'acc\u00e8s (SGTIN)",
"name": "Nom (facultatif, utilis\u00e9 comme pr\u00e9fixe de nom pour tous les p\u00e9riph\u00e9riques)",
"pin": "Code PIN (facultatif)"
}
}
}
}
}

View File

@@ -1,5 +1,14 @@
{
"config": {
"abort": {
"already_configured": "Il punto di accesso \u00e8 gi\u00e0 configurato",
"connection_aborted": "Impossibile connettersi al server HMIP"
},
"error": {
"invalid_pin": "PIN non valido, riprova.",
"press_the_button": "Si prega di premere il pulsante blu.",
"register_failed": "Registrazione fallita, si prega di riprovare."
},
"step": {
"init": {
"data": {

View File

@@ -1,7 +1,7 @@
{
"config": {
"abort": {
"already_configured": "Accesspoint is reeds geconfigureerd",
"already_configured": "Accesspoint is al geconfigureerd",
"conection_aborted": "Kon geen verbinding maken met de HMIP-server",
"connection_aborted": "Kon geen verbinding maken met de HMIP-server",
"unknown": "Er is een onbekende fout opgetreden."
@@ -19,11 +19,11 @@
"name": "Naam (optioneel, gebruikt als naamprefix voor alle apparaten)",
"pin": "Pin-Code (optioneel)"
},
"title": "Kies HomematicIP Accesspoint"
"title": "Kies HomematicIP accesspoint"
},
"link": {
"description": "Druk op de blauwe knop op de accesspoint en de verzendknop om HomematicIP met de Home Assistant te registreren. \n\n![Locatie van knop op brug](/static/images/config_flows/\nconfig_homematicip_cloud.png)",
"title": "Link Accesspoint"
"description": "Druk op de blauwe knop op het accesspoint en de verzendknop om HomematicIP bij Home Assistant te registreren. \n\n![Locatie van knop op bridge](/static/images/config_flows/\nconfig_homematicip_cloud.png)",
"title": "Link accesspoint"
}
},
"title": "HomematicIP Cloud"

View File

@@ -22,7 +22,7 @@
"title": "Velg HomematicIP tilgangspunkt"
},
"link": {
"description": "Trykk p\u00e5 den bl\u00e5 knappen p\u00e5 tilgangspunktet og send knappen for \u00e5 registrere HomematicIP med Home Assistant. \n\n![Plassering av knapp p\u00e5 bridge](/static/images/config_flows/config_homematicip_cloud.png)",
"description": "Trykk p\u00e5 den bl\u00e5 knappen p\u00e5 tilgangspunktet og p\u00e5 send knappen for \u00e5 registrere HomematicIP med Home Assistant. \n\n![Plassering av knapp p\u00e5 bridge](/static/images/config_flows/config_homematicip_cloud.png)",
"title": "Link tilgangspunkt"
}
},

View File

@@ -3,6 +3,7 @@
"abort": {
"already_configured": "O ponto de acesso j\u00e1 se encontra configurado",
"conection_aborted": "N\u00e3o foi poss\u00edvel ligar ao servidor HMIP",
"connection_aborted": "N\u00e3o foi poss\u00edvel ligar ao servidor HMIP",
"unknown": "Ocorreu um erro desconhecido."
},
"error": {

View File

@@ -24,6 +24,6 @@
"title": "Link Hub"
}
},
"title": "Philips Hue Bridge"
"title": "Philips Hue"
}
}

View File

@@ -11,7 +11,8 @@ import voluptuous as vol
from homeassistant import config_entries
from homeassistant.const import CONF_FILENAME, CONF_HOST
from homeassistant.helpers import aiohttp_client, config_validation as cv
from homeassistant.helpers import (
aiohttp_client, config_validation as cv, device_registry as dr)
from .const import DOMAIN, API_NUPNP
from .bridge import HueBridge
@@ -132,7 +133,28 @@ async def async_setup_entry(hass, entry):
bridge = HueBridge(hass, entry, allow_unreachable, allow_groups)
hass.data[DOMAIN][host] = bridge
return await bridge.async_setup()
if not await bridge.async_setup():
return False
config = bridge.api.config
device_registry = await dr.async_get_registry(hass)
device_registry.async_get_or_create(
config_entry=entry.entry_id,
connections={
(dr.CONNECTION_NETWORK_MAC, config.mac)
},
identifiers={
(DOMAIN, config.bridgeid)
},
manufacturer='Signify',
name=config.name,
# Not yet exposed as properties in aiohue
model=config.raw['modelid'],
sw_version=config.raw['swversion'],
)
return True
async def async_unload_entry(hass, entry):

View File

@@ -18,7 +18,7 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers import discovery
from homeassistant.helpers.entity import Entity
REQUIREMENTS = ['insteonplm==0.12.3']
REQUIREMENTS = ['insteonplm==0.13.1']
_LOGGER = logging.getLogger(__name__)
@@ -358,6 +358,8 @@ class IPDB:
def __init__(self):
"""Create the INSTEON Product Database (IPDB)."""
from insteonplm.states.cover import Cover
from insteonplm.states.onOff import (OnOffSwitch,
OnOffSwitch_OutletTop,
OnOffSwitch_OutletBottom,
@@ -383,7 +385,9 @@ class IPDB:
X10AllLightsOnSensor,
X10AllLightsOffSensor)
self.states = [State(OnOffSwitch_OutletTop, 'switch'),
self.states = [State(Cover, 'cover'),
State(OnOffSwitch_OutletTop, 'switch'),
State(OnOffSwitch_OutletBottom, 'switch'),
State(OpenClosedRelay, 'switch'),
State(OnOffSwitch, 'switch'),
@@ -470,11 +474,10 @@ class InsteonEntity(Entity):
return attributes
@callback
def async_entity_update(self, deviceid, statename, val):
def async_entity_update(self, deviceid, group, val):
"""Receive notification from transport that new data exists."""
_LOGGER.debug('Received update for device %s group %d statename %s',
self.address, self.group,
self._insteon_device_state.name)
_LOGGER.debug('Received update for device %s group %d value %s',
deviceid.human, group, val)
self.async_schedule_update_ha_state()
@asyncio.coroutine

View File

@@ -82,6 +82,11 @@ class DeconzLight(Light):
self._light.register_async_callback(self.async_update_callback)
self.hass.data[DATA_DECONZ_ID][self.entity_id] = self._light.deconz_id
async def async_will_remove_from_hass(self) -> None:
"""Disconnect light object when removed."""
self._light.remove_callback(self.async_update_callback)
self._light = None
@callback
def async_update_callback(self, reason):
"""Update the light's state."""

View File

@@ -285,6 +285,25 @@ class HueLight(Light):
"""Return the list of supported effects."""
return [EFFECT_COLORLOOP, EFFECT_RANDOM]
@property
def device_info(self):
"""Return the device info."""
if self.light.type in ('LightGroup', 'Room'):
return None
return {
'identifiers': {
(hue.DOMAIN, self.unique_id)
},
'name': self.name,
'manufacturer': self.light.manufacturername,
# productname added in Hue Bridge API 1.24
# (published 03/05/2018)
'model': self.light.productname or self.light.modelid,
# Not yet exposed as properties in aiohue
'sw_version': self.light.raw['swversion'],
}
async def async_turn_on(self, **kwargs):
"""Turn the specified or all lights on."""
command = {'on': True}

View File

@@ -167,9 +167,9 @@ async def async_setup_platform(hass,
return True
def lifx_features(device):
"""Return a feature map for this device, or a default map if unknown."""
return aiolifx().products.features_map.get(device.product) or \
def lifx_features(bulb):
"""Return a feature map for this bulb, or a default map if unknown."""
return aiolifx().products.features_map.get(bulb.product) or \
aiolifx().products.features_map.get(1)
@@ -256,7 +256,7 @@ class LIFXManager:
async def start_effect(self, entities, service, **kwargs):
"""Start a light effect on entities."""
devices = [light.device for light in entities]
bulbs = [light.bulb for light in entities]
if service == SERVICE_EFFECT_PULSE:
effect = aiolifx_effects().EffectPulse(
@@ -266,7 +266,7 @@ class LIFXManager:
mode=kwargs.get(ATTR_MODE),
hsbk=find_hsbk(**kwargs),
)
await self.effects_conductor.start(effect, devices)
await self.effects_conductor.start(effect, bulbs)
elif service == SERVICE_EFFECT_COLORLOOP:
preprocess_turn_on_alternatives(kwargs)
@@ -282,12 +282,12 @@ class LIFXManager:
transition=kwargs.get(ATTR_TRANSITION),
brightness=brightness,
)
await self.effects_conductor.start(effect, devices)
await self.effects_conductor.start(effect, bulbs)
elif service == SERVICE_EFFECT_STOP:
await self.effects_conductor.stop(devices)
await self.effects_conductor.stop(bulbs)
def service_to_entities(self, service):
"""Return the known devices that a service call mentions."""
"""Return the known entities that a service call mentions."""
entity_ids = extract_entity_ids(self.hass, service)
if entity_ids:
entities = [entity for entity in self.entities.values()
@@ -298,50 +298,50 @@ class LIFXManager:
return entities
@callback
def register(self, device):
def register(self, bulb):
"""Handle aiolifx detected bulb."""
self.hass.async_add_job(self.register_new_device(device))
self.hass.async_add_job(self.register_new_bulb(bulb))
async def register_new_device(self, device):
async def register_new_bulb(self, bulb):
"""Handle newly detected bulb."""
if device.mac_addr in self.entities:
entity = self.entities[device.mac_addr]
if bulb.mac_addr in self.entities:
entity = self.entities[bulb.mac_addr]
entity.registered = True
_LOGGER.debug("%s register AGAIN", entity.who)
await entity.update_hass()
else:
_LOGGER.debug("%s register NEW", device.ip_addr)
_LOGGER.debug("%s register NEW", bulb.ip_addr)
# Read initial state
ack = AwaitAioLIFX().wait
color_resp = await ack(device.get_color)
color_resp = await ack(bulb.get_color)
if color_resp:
version_resp = await ack(device.get_version)
version_resp = await ack(bulb.get_version)
if color_resp is None or version_resp is None:
_LOGGER.error("Failed to initialize %s", device.ip_addr)
device.registered = False
_LOGGER.error("Failed to initialize %s", bulb.ip_addr)
bulb.registered = False
else:
device.timeout = MESSAGE_TIMEOUT
device.retry_count = MESSAGE_RETRIES
device.unregister_timeout = UNAVAILABLE_GRACE
bulb.timeout = MESSAGE_TIMEOUT
bulb.retry_count = MESSAGE_RETRIES
bulb.unregister_timeout = UNAVAILABLE_GRACE
if lifx_features(device)["multizone"]:
entity = LIFXStrip(device, self.effects_conductor)
elif lifx_features(device)["color"]:
entity = LIFXColor(device, self.effects_conductor)
if lifx_features(bulb)["multizone"]:
entity = LIFXStrip(bulb, self.effects_conductor)
elif lifx_features(bulb)["color"]:
entity = LIFXColor(bulb, self.effects_conductor)
else:
entity = LIFXWhite(device, self.effects_conductor)
entity = LIFXWhite(bulb, self.effects_conductor)
_LOGGER.debug("%s register READY", entity.who)
self.entities[device.mac_addr] = entity
self.entities[bulb.mac_addr] = entity
self.async_add_entities([entity], True)
@callback
def unregister(self, device):
def unregister(self, bulb):
"""Handle aiolifx disappearing bulbs."""
if device.mac_addr in self.entities:
entity = self.entities[device.mac_addr]
if bulb.mac_addr in self.entities:
entity = self.entities[bulb.mac_addr]
_LOGGER.debug("%s unregister", entity.who)
entity.registered = False
self.hass.async_add_job(entity.async_update_ha_state())
@@ -352,20 +352,17 @@ class AwaitAioLIFX:
def __init__(self):
"""Initialize the wrapper."""
self.device = None
self.message = None
self.event = asyncio.Event()
@callback
def callback(self, device, message):
def callback(self, bulb, message):
"""Handle responses."""
self.device = device
self.message = message
self.event.set()
async def wait(self, method):
"""Call an aiolifx method and wait for its response."""
self.device = None
self.message = None
self.event.clear()
method(callb=self.callback)
@@ -387,9 +384,9 @@ def convert_16_to_8(value):
class LIFXLight(Light):
"""Representation of a LIFX light."""
def __init__(self, device, effects_conductor):
def __init__(self, bulb, effects_conductor):
"""Initialize the light."""
self.light = device
self.bulb = bulb
self.effects_conductor = effects_conductor
self.registered = True
self.postponed_update = None
@@ -397,34 +394,34 @@ class LIFXLight(Light):
@property
def available(self):
"""Return the availability of the device."""
"""Return the availability of the bulb."""
return self.registered
@property
def unique_id(self):
"""Return a unique ID."""
return self.light.mac_addr
return self.bulb.mac_addr
@property
def name(self):
"""Return the name of the device."""
return self.light.label
"""Return the name of the bulb."""
return self.bulb.label
@property
def who(self):
"""Return a string identifying the device."""
return "%s (%s)" % (self.light.ip_addr, self.name)
"""Return a string identifying the bulb."""
return "%s (%s)" % (self.bulb.ip_addr, self.name)
@property
def min_mireds(self):
"""Return the coldest color_temp that this light supports."""
kelvin = lifx_features(self.light)['max_kelvin']
kelvin = lifx_features(self.bulb)['max_kelvin']
return math.floor(color_util.color_temperature_kelvin_to_mired(kelvin))
@property
def max_mireds(self):
"""Return the warmest color_temp that this light supports."""
kelvin = lifx_features(self.light)['min_kelvin']
kelvin = lifx_features(self.bulb)['min_kelvin']
return math.ceil(color_util.color_temperature_kelvin_to_mired(kelvin))
@property
@@ -432,8 +429,8 @@ class LIFXLight(Light):
"""Flag supported features."""
support = SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION | SUPPORT_EFFECT
device_features = lifx_features(self.light)
if device_features['min_kelvin'] != device_features['max_kelvin']:
bulb_features = lifx_features(self.bulb)
if bulb_features['min_kelvin'] != bulb_features['max_kelvin']:
support |= SUPPORT_COLOR_TEMP
return support
@@ -441,25 +438,25 @@ class LIFXLight(Light):
@property
def brightness(self):
"""Return the brightness of this light between 0..255."""
return convert_16_to_8(self.light.color[2])
return convert_16_to_8(self.bulb.color[2])
@property
def color_temp(self):
"""Return the color temperature."""
_, sat, _, kelvin = self.light.color
_, sat, _, kelvin = self.bulb.color
if sat:
return None
return color_util.color_temperature_kelvin_to_mired(kelvin)
@property
def is_on(self):
"""Return true if device is on."""
return self.light.power_level != 0
"""Return true if light is on."""
return self.bulb.power_level != 0
@property
def effect(self):
"""Return the name of the currently running effect."""
effect = self.effects_conductor.effect(self.light)
effect = self.effects_conductor.effect(self.bulb)
if effect:
return 'lifx_effect_' + effect.name
return None
@@ -485,19 +482,19 @@ class LIFXLight(Light):
util.dt.utcnow() + timedelta(milliseconds=when))
async def async_turn_on(self, **kwargs):
"""Turn the device on."""
"""Turn the light on."""
kwargs[ATTR_POWER] = True
self.hass.async_add_job(self.set_state(**kwargs))
async def async_turn_off(self, **kwargs):
"""Turn the device off."""
"""Turn the light off."""
kwargs[ATTR_POWER] = False
self.hass.async_add_job(self.set_state(**kwargs))
async def set_state(self, **kwargs):
"""Set a color on the light and turn it on/off."""
async with self.lock:
bulb = self.light
bulb = self.bulb
await self.effects_conductor.stop([bulb])
@@ -544,13 +541,13 @@ class LIFXLight(Light):
await self.update_during_transition(fade)
async def set_power(self, ack, pwr, duration=0):
"""Send a power change to the device."""
await ack(partial(self.light.set_power, pwr, duration=duration))
"""Send a power change to the bulb."""
await ack(partial(self.bulb.set_power, pwr, duration=duration))
async def set_color(self, ack, hsbk, kwargs, duration=0):
"""Send a color change to the device."""
hsbk = merge_hsbk(self.light.color, hsbk)
await ack(partial(self.light.set_color, hsbk, duration=duration))
"""Send a color change to the bulb."""
hsbk = merge_hsbk(self.bulb.color, hsbk)
await ack(partial(self.bulb.set_color, hsbk, duration=duration))
async def default_effect(self, **kwargs):
"""Start an effect with default parameters."""
@@ -563,7 +560,7 @@ class LIFXLight(Light):
async def async_update(self):
"""Update bulb status."""
if self.available and not self.lock.locked():
await AwaitAioLIFX().wait(self.light.get_color)
await AwaitAioLIFX().wait(self.bulb.get_color)
class LIFXWhite(LIFXLight):
@@ -600,7 +597,7 @@ class LIFXColor(LIFXLight):
@property
def hs_color(self):
"""Return the hs value."""
hue, sat, _, _ = self.light.color
hue, sat, _, _ = self.bulb.color
hue = hue / 65535 * 360
sat = sat / 65535 * 100
return (hue, sat) if sat else None
@@ -610,8 +607,8 @@ class LIFXStrip(LIFXColor):
"""Representation of a LIFX light strip with multiple zones."""
async def set_color(self, ack, hsbk, kwargs, duration=0):
"""Send a color change to the device."""
bulb = self.light
"""Send a color change to the bulb."""
bulb = self.bulb
num_zones = len(bulb.color_zones)
zones = kwargs.get(ATTR_ZONES)
@@ -659,7 +656,7 @@ class LIFXStrip(LIFXColor):
while self.available and zone < top:
# Each get_color_zones can update 8 zones at once
resp = await AwaitAioLIFX().wait(partial(
self.light.get_color_zones,
self.bulb.get_color_zones,
start_index=zone))
if resp:
zone += 8

View File

@@ -54,6 +54,7 @@ CONF_WHITE_VALUE_SCALE = 'white_value_scale'
CONF_WHITE_VALUE_STATE_TOPIC = 'white_value_state_topic'
CONF_WHITE_VALUE_TEMPLATE = 'white_value_template'
CONF_ON_COMMAND_TYPE = 'on_command_type'
CONF_UNIQUE_ID = 'unique_id'
DEFAULT_BRIGHTNESS_SCALE = 255
DEFAULT_NAME = 'MQTT Light'
@@ -79,6 +80,7 @@ PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_EFFECT_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_EFFECT_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_UNIQUE_ID): cv.string,
vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string,
vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string,
@@ -111,6 +113,7 @@ async def async_setup_platform(hass, config, async_add_entities,
async_add_entities([MqttLight(
config.get(CONF_NAME),
config.get(CONF_UNIQUE_ID),
config.get(CONF_EFFECT_LIST),
{
key: config.get(key) for key in (
@@ -159,14 +162,15 @@ async def async_setup_platform(hass, config, async_add_entities,
class MqttLight(MqttAvailability, Light):
"""Representation of a MQTT light."""
def __init__(self, name, effect_list, topic, templates, qos,
retain, payload, optimistic, brightness_scale,
def __init__(self, name, unique_id, effect_list, topic, templates,
qos, retain, payload, optimistic, brightness_scale,
white_value_scale, on_command_type, availability_topic,
payload_available, payload_not_available):
"""Initialize MQTT light."""
super().__init__(availability_topic, qos, payload_available,
payload_not_available)
self._name = name
self._unique_id = unique_id
self._effect_list = effect_list
self._topic = topic
self._qos = qos
@@ -392,6 +396,11 @@ class MqttLight(MqttAvailability, Light):
"""Return the name of the device if any."""
return self._name
@property
def unique_id(self):
"""Return a unique ID."""
return self._unique_id
@property
def is_on(self):
"""Return true if device is on."""

View File

@@ -19,7 +19,7 @@ from homeassistant.util.color import \
from homeassistant.util.color import (
color_temperature_kelvin_to_mired as kelvin_to_mired)
REQUIREMENTS = ['pyHS100==0.3.2']
REQUIREMENTS = ['pyHS100==0.3.3']
_LOGGER = logging.getLogger(__name__)

View File

@@ -4,31 +4,33 @@ Provides functionality for mailboxes.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/mailbox/
"""
import asyncio
import logging
from contextlib import suppress
from datetime import timedelta
import async_timeout
import logging
from aiohttp import web
from aiohttp.web_exceptions import HTTPNotFound
import async_timeout
from homeassistant.core import callback
from homeassistant.helpers import config_per_platform, discovery
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity import Entity
from homeassistant.components.http import HomeAssistantView
from homeassistant.core import callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_per_platform, discovery
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.setup import async_prepare_setup_platform
_LOGGER = logging.getLogger(__name__)
CONTENT_TYPE_MPEG = 'audio/mpeg'
DEPENDENCIES = ['http']
DOMAIN = 'mailbox'
EVENT = 'mailbox_updated'
CONTENT_TYPE_MPEG = 'audio/mpeg'
SCAN_INTERVAL = timedelta(seconds=30)
_LOGGER = logging.getLogger(__name__)
@asyncio.coroutine

View File

@@ -7,17 +7,18 @@ https://home-assistant.io/components/mailbox.asteriskvm/
import asyncio
import logging
from homeassistant.core import callback
from homeassistant.components.asterisk_mbox import DOMAIN
from homeassistant.components.mailbox import (Mailbox, CONTENT_TYPE_MPEG,
StreamError)
from homeassistant.components.mailbox import (
CONTENT_TYPE_MPEG, Mailbox, StreamError)
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
DEPENDENCIES = ['asterisk_mbox']
_LOGGER = logging.getLogger(__name__)
SIGNAL_MESSAGE_UPDATE = 'asterisk_mbox.message_updated'
DEPENDENCIES = ['asterisk_mbox']
SIGNAL_MESSAGE_REQUEST = 'asterisk_mbox.message_request'
SIGNAL_MESSAGE_UPDATE = 'asterisk_mbox.message_updated'
@asyncio.coroutine

View File

@@ -5,16 +5,16 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/mailbox.asteriskvm/
"""
import asyncio
from hashlib import sha1
import logging
import os
from hashlib import sha1
from homeassistant.components.mailbox import (
CONTENT_TYPE_MPEG, Mailbox, StreamError)
from homeassistant.util import dt
from homeassistant.components.mailbox import (Mailbox, CONTENT_TYPE_MPEG,
StreamError)
_LOGGER = logging.getLogger(__name__)
DOMAIN = "DemoMailbox"
@@ -38,11 +38,15 @@ class DemoMailbox(Mailbox):
msgtxt = "Message {}. {}".format(
idx + 1, txt * (1 + idx * (idx % 2)))
msgsha = sha1(msgtxt.encode('utf-8')).hexdigest()
msg = {"info": {"origtime": msgtime,
"callerid": "John Doe <212-555-1212>",
"duration": "10"},
"text": msgtxt,
"sha": msgsha}
msg = {
'info': {
'origtime': msgtime,
'callerid': 'John Doe <212-555-1212>',
'duration': '10',
},
'text': msgtxt,
'sha': msgsha,
}
self._messages[msgsha] = msg
@property

View File

@@ -14,7 +14,7 @@ from homeassistant.components.media_player import (
SERVICE_PLAY_MEDIA)
from homeassistant.helpers import config_validation as cv
REQUIREMENTS = ['youtube_dl==2018.08.22']
REQUIREMENTS = ['youtube_dl==2018.09.10']
_LOGGER = logging.getLogger(__name__)

View File

@@ -15,33 +15,32 @@ from random import SystemRandom
from urllib.parse import urlparse
from aiohttp import web
from aiohttp.hdrs import CONTENT_TYPE, CACHE_CONTROL
from aiohttp.hdrs import CACHE_CONTROL, CONTENT_TYPE
import async_timeout
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.components import websocket_api
from homeassistant.components.http import KEY_AUTHENTICATED, HomeAssistantView
from homeassistant.const import (
STATE_OFF, STATE_IDLE, STATE_PLAYING, STATE_UNKNOWN, ATTR_ENTITY_ID,
SERVICE_TOGGLE, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_VOLUME_UP,
SERVICE_MEDIA_PLAY, SERVICE_MEDIA_SEEK, SERVICE_MEDIA_STOP,
SERVICE_VOLUME_SET, SERVICE_MEDIA_PAUSE, SERVICE_SHUFFLE_SET,
SERVICE_VOLUME_DOWN, SERVICE_VOLUME_MUTE, SERVICE_MEDIA_NEXT_TRACK,
SERVICE_MEDIA_PLAY_PAUSE, SERVICE_MEDIA_PREVIOUS_TRACK)
ATTR_ENTITY_ID, SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PAUSE,
SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PLAY_PAUSE, SERVICE_MEDIA_PREVIOUS_TRACK,
SERVICE_MEDIA_SEEK, SERVICE_MEDIA_STOP, SERVICE_SHUFFLE_SET,
SERVICE_TOGGLE, SERVICE_TURN_OFF, SERVICE_TURN_ON, SERVICE_VOLUME_DOWN,
SERVICE_VOLUME_MUTE, SERVICE_VOLUME_SET, SERVICE_VOLUME_UP, STATE_IDLE,
STATE_OFF, STATE_PLAYING, STATE_UNKNOWN)
from homeassistant.core import callback
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.loader import bind_hass
from homeassistant.components import websocket_api
_LOGGER = logging.getLogger(__name__)
_RND = SystemRandom()
DOMAIN = 'media_player'
DEPENDENCIES = ['http']
SCAN_INTERVAL = timedelta(seconds=10)
ENTITY_ID_FORMAT = DOMAIN + '.{}'
@@ -97,6 +96,8 @@ MEDIA_TYPE_CHANNEL = 'channel'
MEDIA_TYPE_PLAYLIST = 'playlist'
MEDIA_TYPE_URL = 'url'
SCAN_INTERVAL = timedelta(seconds=10)
SUPPORT_PAUSE = 1
SUPPORT_SEEK = 2
SUPPORT_VOLUME_SET = 4

View File

@@ -4,17 +4,17 @@ Support for Anthem Network Receivers and Processors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.anthemav/
"""
import logging
import asyncio
import logging
import voluptuous as vol
from homeassistant.components.media_player import (
PLATFORM_SCHEMA, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_SELECT_SOURCE,
PLATFORM_SCHEMA, SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, SUPPORT_TURN_ON,
SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, MediaPlayerDevice)
from homeassistant.const import (
CONF_NAME, CONF_HOST, CONF_PORT, STATE_OFF, STATE_ON, STATE_UNKNOWN,
EVENT_HOMEASSISTANT_STOP)
CONF_HOST, CONF_NAME, CONF_PORT, EVENT_HOMEASSISTANT_STOP, STATE_OFF,
STATE_ON, STATE_UNKNOWN)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['anthemav==1.1.8']
@@ -32,7 +32,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
})
})
@asyncio.coroutine

View File

@@ -7,20 +7,19 @@ https://home-assistant.io/components/media_player.apple_tv/
import asyncio
import logging
from homeassistant.core import callback
from homeassistant.components.apple_tv import (
ATTR_ATV, ATTR_POWER, DATA_APPLE_TV, DATA_ENTITIES)
from homeassistant.components.media_player import (
SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK,
SUPPORT_STOP, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_TURN_ON,
SUPPORT_TURN_OFF, MediaPlayerDevice, MEDIA_TYPE_MUSIC,
MEDIA_TYPE_VIDEO, MEDIA_TYPE_TVSHOW)
MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO, SUPPORT_NEXT_TRACK,
SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK,
SUPPORT_SEEK, SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON,
MediaPlayerDevice)
from homeassistant.const import (
STATE_IDLE, STATE_PAUSED, STATE_PLAYING, STATE_STANDBY, CONF_HOST,
STATE_OFF, CONF_NAME, EVENT_HOMEASSISTANT_STOP)
CONF_HOST, CONF_NAME, EVENT_HOMEASSISTANT_STOP, STATE_IDLE, STATE_OFF,
STATE_PAUSED, STATE_PLAYING, STATE_STANDBY)
from homeassistant.core import callback
import homeassistant.util.dt as dt_util
DEPENDENCIES = ['apple_tv']
_LOGGER = logging.getLogger(__name__)
@@ -31,8 +30,8 @@ SUPPORT_APPLE_TV = SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_PLAY_MEDIA | \
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
"""Set up the Apple TV platform."""
if not discovery_info:
return

View File

@@ -9,16 +9,13 @@ import logging
import voluptuous as vol
from homeassistant.components.media_player import (
SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK,
SUPPORT_TURN_ON, SUPPORT_TURN_OFF, SUPPORT_SELECT_SOURCE,
SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP, SUPPORT_PLAY,
SUPPORT_VOLUME_SET, MediaPlayerDevice, PLATFORM_SCHEMA)
PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY,
SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF,
SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
SUPPORT_VOLUME_STEP, MediaPlayerDevice)
from homeassistant.const import (
CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON, STATE_UNKNOWN,
CONF_PORT, CONF_USERNAME, CONF_PASSWORD, CONF_TIMEOUT)
CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_PORT, CONF_TIMEOUT,
CONF_USERNAME, STATE_OFF, STATE_ON, STATE_UNKNOWN)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['sharp_aquos_rc==0.3.2']

View File

@@ -13,15 +13,15 @@ from homeassistant.components.media_player import (
DOMAIN, MEDIA_PLAYER_SCHEMA, PLATFORM_SCHEMA, SUPPORT_SELECT_SOURCE,
SUPPORT_TURN_OFF, SUPPORT_TURN_ON, MediaPlayerDevice)
from homeassistant.const import (
ATTR_ENTITY_ID, CONF_NAME, CONF_HOST, CONF_PORT, STATE_OFF, STATE_ON)
ATTR_ENTITY_ID, CONF_HOST, CONF_NAME, CONF_PORT, CONF_TYPE, STATE_OFF,
STATE_ON)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pyblackbird==0.5']
_LOGGER = logging.getLogger(__name__)
SUPPORT_BLACKBIRD = SUPPORT_TURN_ON | SUPPORT_TURN_OFF | \
SUPPORT_SELECT_SOURCE
SUPPORT_BLACKBIRD = SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_SELECT_SOURCE
ZONE_SCHEMA = vol.Schema({
vol.Required(CONF_NAME): cv.string,
@@ -33,7 +33,6 @@ SOURCE_SCHEMA = vol.Schema({
CONF_ZONES = 'zones'
CONF_SOURCES = 'sources'
CONF_TYPE = 'type'
DATA_BLACKBIRD = 'blackbird'

View File

@@ -24,8 +24,8 @@ from homeassistant.components.media_player import (
MediaPlayerDevice)
from homeassistant.const import (
ATTR_ENTITY_ID, CONF_HOST, CONF_HOSTS, CONF_NAME, CONF_PORT,
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, STATE_IDLE,
STATE_OFF, STATE_PAUSED, STATE_PLAYING)
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, STATE_IDLE, STATE_OFF,
STATE_PAUSED, STATE_PLAYING)
from homeassistant.core import callback
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv

View File

@@ -10,11 +10,11 @@ import re
import voluptuous as vol
from homeassistant.components.media_player import (
SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK, SUPPORT_TURN_ON,
SUPPORT_TURN_OFF, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP, SUPPORT_PLAY,
SUPPORT_VOLUME_SET, SUPPORT_SELECT_SOURCE, MediaPlayerDevice,
PLATFORM_SCHEMA)
from homeassistant.const import (CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON)
PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY,
SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF,
SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
SUPPORT_VOLUME_STEP, MediaPlayerDevice)
from homeassistant.const import CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON
import homeassistant.helpers.config_validation as cv
from homeassistant.util.json import load_json, save_json

View File

@@ -7,26 +7,27 @@ https://home-assistant.io/components/media_player.cast/
import asyncio
import logging
import threading
from typing import Optional, Tuple
import voluptuous as vol
import attr
import voluptuous as vol
from homeassistant.exceptions import PlatformNotReady
from homeassistant.helpers.typing import HomeAssistantType, ConfigType
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import (dispatcher_send,
async_dispatcher_connect)
from homeassistant.components.cast import DOMAIN as CAST_DOMAIN
from homeassistant.components.media_player import (
MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_MOVIE, SUPPORT_NEXT_TRACK,
SUPPORT_PAUSE, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK,
SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
SUPPORT_STOP, SUPPORT_PLAY, MediaPlayerDevice, PLATFORM_SCHEMA)
MEDIA_TYPE_MOVIE, MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, PLATFORM_SCHEMA,
SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA,
SUPPORT_PREVIOUS_TRACK, SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON,
SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, MediaPlayerDevice)
from homeassistant.const import (
CONF_HOST, STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING,
EVENT_HOMEASSISTANT_STOP)
CONF_HOST, EVENT_HOMEASSISTANT_STOP, STATE_IDLE, STATE_OFF, STATE_PAUSED,
STATE_PLAYING)
from homeassistant.core import callback
from homeassistant.exceptions import PlatformNotReady
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import (
async_dispatcher_connect, dispatcher_send)
from homeassistant.helpers.typing import ConfigType, HomeAssistantType
import homeassistant.util.dt as dt_util
DEPENDENCIES = ('cast',)
@@ -57,8 +58,8 @@ SIGNAL_CAST_DISCOVERED = 'cast_discovered'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_HOST): cv.string,
vol.Optional(CONF_IGNORE_CEC, default=[]): vol.All(cv.ensure_list,
[cv.string])
vol.Optional(CONF_IGNORE_CEC, default=[]):
vol.All(cv.ensure_list, [cv.string]),
})
@@ -73,7 +74,8 @@ class ChromecastInfo:
port = attr.ib(type=int)
uuid = attr.ib(type=Optional[str], converter=attr.converters.optional(str),
default=None) # always convert UUID to string if not None
model_name = attr.ib(type=str, default='') # needed for cast type
manufacturer = attr.ib(type=str, default='')
model_name = attr.ib(type=str, default='')
friendly_name = attr.ib(type=Optional[str], default=None)
@property
@@ -111,6 +113,7 @@ def _fill_out_missing_chromecast_info(info: ChromecastInfo) -> ChromecastInfo:
host=info.host, port=info.port,
uuid=(info.uuid or http_device_status.uuid),
friendly_name=(info.friendly_name or http_device_status.friendly_name),
manufacturer=(info.manufacturer or http_device_status.manufacturer),
model_name=(info.model_name or http_device_status.model_name)
)
@@ -148,7 +151,13 @@ def _setup_internal_discovery(hass: HomeAssistantType) -> None:
def internal_callback(name):
"""Handle zeroconf discovery of a new chromecast."""
mdns = listener.services[name]
_discover_chromecast(hass, ChromecastInfo(*mdns))
_discover_chromecast(hass, ChromecastInfo(
host=mdns[0],
port=mdns[1],
uuid=mdns[2],
model_name=mdns[3],
friendly_name=mdns[4],
))
_LOGGER.debug("Starting internal pychromecast discovery.")
listener, browser = pychromecast.start_discovery(internal_callback)
@@ -365,7 +374,10 @@ class CastDevice(MediaPlayerDevice):
# pylint: disable=protected-access
_LOGGER.debug("Connecting to cast device %s", cast_info)
chromecast = await self.hass.async_add_job(
pychromecast._get_chromecast_from_host, attr.astuple(cast_info))
pychromecast._get_chromecast_from_host, (
cast_info.host, cast_info.port, cast_info.uuid,
cast_info.model_name, cast_info.friendly_name
))
self._chromecast = chromecast
self._status_listener = CastStatusListener(self, chromecast)
# Initialise connection status as connected because we can only
@@ -494,6 +506,23 @@ class CastDevice(MediaPlayerDevice):
"""Return the name of the device."""
return self._cast_info.friendly_name
@property
def device_info(self):
"""Return information about the device."""
cast_info = self._cast_info
if cast_info.model_name == "Google Cast Group":
return None
return {
'name': cast_info.friendly_name,
'identifiers': {
(CAST_DOMAIN, cast_info.uuid.replace('-', ''))
},
'model': cast_info.model_name,
'manufacturer': cast_info.manufacturer,
}
@property
def state(self):
"""Return the state of the player."""

View File

@@ -9,18 +9,18 @@ import logging
import voluptuous as vol
from homeassistant.components.media_player import (
MEDIA_TYPE_CHANNEL, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_EPISODE,
MEDIA_TYPE_MOVIE, SUPPORT_PLAY, SUPPORT_PAUSE, SUPPORT_STOP,
SUPPORT_VOLUME_MUTE, SUPPORT_NEXT_TRACK, SUPPORT_PREVIOUS_TRACK,
SUPPORT_PLAY_MEDIA, SUPPORT_SELECT_SOURCE, DOMAIN, PLATFORM_SCHEMA,
DOMAIN, MEDIA_TYPE_CHANNEL, MEDIA_TYPE_EPISODE, MEDIA_TYPE_MOVIE,
MEDIA_TYPE_TVSHOW, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE,
SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK,
SUPPORT_SELECT_SOURCE, SUPPORT_STOP, SUPPORT_VOLUME_MUTE,
MediaPlayerDevice)
from homeassistant.const import (
CONF_HOST, CONF_PORT, CONF_NAME, STATE_IDLE, STATE_PAUSED, STATE_PLAYING,
ATTR_ENTITY_ID)
ATTR_ENTITY_ID, CONF_HOST, CONF_NAME, CONF_PORT, STATE_IDLE, STATE_PAUSED,
STATE_PLAYING)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pychannels==1.0.0']
_LOGGER = logging.getLogger(__name__)
DATA_CHANNELS = 'channels'
@@ -52,16 +52,11 @@ CHANNELS_SEEK_BY_SCHEMA = CHANNELS_SCHEMA.extend({
vol.Required(ATTR_SECONDS): vol.Coerce(int),
})
REQUIREMENTS = ['pychannels==1.0.0']
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Channels platform."""
device = ChannelsPlayer(
config.get('name'),
config.get(CONF_HOST),
config.get(CONF_PORT)
)
config.get(CONF_NAME), config.get(CONF_HOST), config.get(CONF_PORT))
if DATA_CHANNELS not in hass.data:
hass.data[DATA_CHANNELS] = []
@@ -77,8 +72,8 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
device.entity_id == entity_id), None)
if device is None:
_LOGGER.warning("Unable to find Channels with entity_id: %s",
entity_id)
_LOGGER.warning(
"Unable to find Channels with entity_id: %s", entity_id)
return
if service.service == SERVICE_SEEK_FORWARD:
@@ -90,12 +85,10 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
device.seek_by(seconds)
hass.services.register(
DOMAIN, SERVICE_SEEK_FORWARD, service_handler,
schema=CHANNELS_SCHEMA)
DOMAIN, SERVICE_SEEK_FORWARD, service_handler, schema=CHANNELS_SCHEMA)
hass.services.register(
DOMAIN, SERVICE_SEEK_BACKWARD, service_handler,
schema=CHANNELS_SCHEMA)
DOMAIN, SERVICE_SEEK_BACKWARD, service_handler, schema=CHANNELS_SCHEMA)
hass.services.register(
DOMAIN, SERVICE_SEEK_BY, service_handler,

View File

@@ -4,7 +4,6 @@ Support for Clementine Music Player as media player.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.clementine/
"""
import asyncio
from datetime import timedelta
import logging
@@ -12,24 +11,24 @@ import time
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.media_player import (
SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK, PLATFORM_SCHEMA,
SUPPORT_VOLUME_STEP, SUPPORT_SELECT_SOURCE, SUPPORT_PLAY, MEDIA_TYPE_MUSIC,
SUPPORT_VOLUME_SET, MediaPlayerDevice)
MEDIA_TYPE_MUSIC, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE,
SUPPORT_PLAY, SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE,
SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP, MediaPlayerDevice)
from homeassistant.const import (
CONF_HOST, CONF_NAME, CONF_PORT, CONF_ACCESS_TOKEN,
STATE_OFF, STATE_PLAYING, STATE_PAUSED, STATE_UNKNOWN)
CONF_ACCESS_TOKEN, CONF_HOST, CONF_NAME, CONF_PORT, STATE_OFF,
STATE_PAUSED, STATE_PLAYING)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['python-clementine-remote==1.0.1']
SCAN_INTERVAL = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'Clementine Remote'
DEFAULT_PORT = 5500
SCAN_INTERVAL = timedelta(seconds=5)
SUPPORT_CLEMENTINE = SUPPORT_PAUSE | SUPPORT_VOLUME_STEP | \
SUPPORT_PREVIOUS_TRACK | SUPPORT_VOLUME_SET | \
SUPPORT_NEXT_TRACK | \
@@ -69,7 +68,7 @@ class ClementineDevice(MediaPlayerDevice):
self._track_name = ''
self._track_artist = ''
self._track_album_name = ''
self._state = STATE_UNKNOWN
self._state = None
def update(self):
"""Retrieve the latest data from the Clementine Player."""

View File

@@ -8,15 +8,14 @@ import logging
import voluptuous as vol
from homeassistant.components.media_player import (
MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE,
SUPPORT_PREVIOUS_TRACK, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_PLAY,
SUPPORT_VOLUME_SET, SUPPORT_PLAY_MEDIA, SUPPORT_SEEK, PLATFORM_SCHEMA,
MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK,
SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK,
SUPPORT_SEEK, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_SET,
MediaPlayerDevice)
from homeassistant.const import (
STATE_OFF, STATE_PAUSED, STATE_PLAYING, CONF_HOST, CONF_NAME, CONF_PORT,
CONF_PASSWORD)
CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_PORT, STATE_OFF, STATE_PAUSED,
STATE_PLAYING)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pycmus==0.1.1']

View File

@@ -5,11 +5,12 @@ For more details about this platform, please refer to the documentation
https://home-assistant.io/components/demo/
"""
from homeassistant.components.media_player import (
MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_MOVIE, SUPPORT_NEXT_TRACK,
SUPPORT_PAUSE, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK,
SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
SUPPORT_SELECT_SOURCE, SUPPORT_SELECT_SOUND_MODE, SUPPORT_CLEAR_PLAYLIST,
SUPPORT_PLAY, SUPPORT_SHUFFLE_SET, MediaPlayerDevice)
MEDIA_TYPE_MOVIE, MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW,
SUPPORT_CLEAR_PLAYLIST, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY,
SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOUND_MODE,
SUPPORT_SELECT_SOURCE, SUPPORT_SHUFFLE_SET, SUPPORT_TURN_OFF,
SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
MediaPlayerDevice)
from homeassistant.const import STATE_OFF, STATE_PAUSED, STATE_PLAYING
import homeassistant.util.dt as dt_util
@@ -20,8 +21,8 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
DemoYoutubePlayer(
'Living Room', 'eyU3bRy2x44',
'♥♥ The Best Fireplace Video (3 hours)', 300),
DemoYoutubePlayer('Bedroom', 'kxopViU98Xo', 'Epic sax guy 10 hours',
360000),
DemoYoutubePlayer(
'Bedroom', 'kxopViU98Xo', 'Epic sax guy 10 hours', 360000),
DemoMusicPlayer(), DemoTVShowPlayer(),
])

View File

@@ -10,10 +10,10 @@ import telnetlib
import voluptuous as vol
from homeassistant.components.media_player import (
PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_SELECT_SOURCE,
SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK, SUPPORT_TURN_OFF,
SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
SUPPORT_STOP, SUPPORT_PLAY, MediaPlayerDevice)
PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY,
SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE, SUPPORT_STOP,
SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
MediaPlayerDevice)
from homeassistant.const import (
CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON, STATE_UNKNOWN)
import homeassistant.helpers.config_validation as cv

View File

@@ -5,35 +5,37 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.denon/
"""
import logging
from collections import namedtuple
import logging
import voluptuous as vol
from homeassistant.components.media_player import (
SUPPORT_PAUSE, SUPPORT_NEXT_TRACK, SUPPORT_PREVIOUS_TRACK,
SUPPORT_TURN_OFF, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP,
SUPPORT_SELECT_SOURCE, SUPPORT_SELECT_SOUND_MODE,
SUPPORT_PLAY_MEDIA, MEDIA_TYPE_CHANNEL, MediaPlayerDevice,
PLATFORM_SCHEMA, SUPPORT_TURN_ON, MEDIA_TYPE_MUSIC,
SUPPORT_VOLUME_SET, SUPPORT_PLAY)
MEDIA_TYPE_CHANNEL, MEDIA_TYPE_MUSIC, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK,
SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK,
SUPPORT_SELECT_SOUND_MODE, SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF,
SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
SUPPORT_VOLUME_STEP, MediaPlayerDevice)
from homeassistant.const import (
CONF_HOST, STATE_OFF, STATE_PLAYING, STATE_PAUSED,
CONF_NAME, STATE_ON, CONF_ZONE, CONF_TIMEOUT)
CONF_HOST, CONF_NAME, CONF_TIMEOUT, CONF_ZONE, STATE_OFF, STATE_ON,
STATE_PAUSED, STATE_PLAYING)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['denonavr==0.7.5']
_LOGGER = logging.getLogger(__name__)
ATTR_SOUND_MODE_RAW = 'sound_mode_raw'
CONF_INVALID_ZONES_ERR = 'Invalid Zone (expected Zone2 or Zone3)'
CONF_SHOW_ALL_SOURCES = 'show_all_sources'
CONF_VALID_ZONES = ['Zone2', 'Zone3']
CONF_ZONES = 'zones'
DEFAULT_SHOW_SOURCES = False
DEFAULT_TIMEOUT = 2
CONF_SHOW_ALL_SOURCES = 'show_all_sources'
CONF_ZONES = 'zones'
CONF_VALID_ZONES = ['Zone2', 'Zone3']
CONF_INVALID_ZONES_ERR = 'Invalid Zone (expected Zone2 or Zone3)'
KEY_DENON_CACHE = 'denonavr_hosts'
ATTR_SOUND_MODE_RAW = 'sound_mode_raw'
KEY_DENON_CACHE = 'denonavr_hosts'
SUPPORT_DENON = SUPPORT_VOLUME_STEP | SUPPORT_VOLUME_MUTE | \
SUPPORT_TURN_ON | SUPPORT_TURN_OFF | \

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