Compare commits

...

251 Commits

Author SHA1 Message Date
Pascal Vizeli e620479cc8 Merge pull request #10069 from home-assistant/release-0-56-2
0.56.2
2017-10-23 17:40:58 +02:00
Fabian Affolter 79d71c6727 Version bump to 0.56.2 2017-10-23 16:09:26 +02:00
Fabian Affolter 32f58baa85 Fix merge conflict 2017-10-23 13:49:45 +02:00
Pascal Vizeli 9794336113 Remove warning 2017-10-23 13:48:25 +02:00
Teemu R ed82f23da3 switch.tplink: fix overlooked issue with statically defined names (#10053) 2017-10-23 13:46:00 +02:00
Maciej Bieniek 48c86e07fa fix gateway illumination sensor value (#10045) 2017-10-23 13:45:34 +02:00
Paulus Schoutsen 106bf467f8 0.56.1 (#10035)
* Version bump to 0.56.1

* Fix device update / entity_id with names (#10029)

* Fix device update

* Add tests

* add test for disabled warning

* fix temperature/humidity sensors valid values (#10024)
2017-10-22 19:56:20 +02:00
Fabian Affolter ead4e44cd6 Merge pull request #9969 from home-assistant/release-0-56
0.56
2017-10-22 00:37:23 +02:00
Paulus Schoutsen 2a4c5466ef Merge remote-tracking branch 'origin/master' into release-0-56 2017-10-21 14:50:22 -07:00
Pascal Vizeli f27ad76230 Remove async_update (#9997) 2017-10-21 21:51:58 +02:00
William Scanlon 60053a642c Moved siren to Wink from switch (#9879) 2017-10-21 21:51:57 +02:00
Lewis Juggins d9f5398c56 [tradfri] Update pytradfri, simplify dependencies. (#9875)
* Update pytradfri

* Process dep links

* Process dep links

* Process dep links

* Install all deps

* Update requirements

* Exclude aiocoap

* Install cython

* Remove cython

* Exclude DTLSSocket

* Add cython
2017-10-21 21:51:50 +02:00
Paulus Schoutsen 9722125234 Version bump to 0.56 2017-10-19 09:01:03 -07:00
boltgolt 78c302855a Add Toon support (#9483)
* Added Toon support again

* Forgot about .coveragerc

* Fixed style issues

* More styling and importing fixes

* Implemented the suggestions made by @pvizeli

* The smallest fix possible

* Removed custom names for Toon states

* Fix last push with 2 outdated lines

* Removed HOME and NOT_HOME, moved to just climate states

* Bumped dependency for better handling of smartplugs that don't report power consumption

* Implemented changes as suggested by @balloob

* Rebase, gen_requirements_all.py finally working
2017-10-19 08:59:57 -07:00
Pascal Vizeli c1b197419d Fix async probs (#9924)
* Update entity.py

* Update entity_component.py

* Update entity_component.py

* Update __init__.py

* Update entity_component.py

* Update entity_component.py

* Update entity.py

* cleanup entity

* Update entity_component.py

* Update entity_component.py

* Fix names & comments / fix tests

* Revert deadlock protection

* Add tests for entity

* Add test fix name

* Update other code

* Fix lint

* Remove restore state from template entities

* Lint
2017-10-19 10:56:25 +02:00
Paulus Schoutsen 6cce934f72 Improve SSL certs used by aiohttp (#9958)
* Improve SSL certs used by aiohttp

* Add certifi package

* Lint
2017-10-19 10:47:57 +02:00
Pascal Vizeli 38cb32afd6 Update ffmpeg 1.9 (#9963) 2017-10-19 10:46:32 +02:00
Pascal Vizeli c96c283293 Update ffmpeg.py 2017-10-19 10:36:09 +02:00
Pascal Vizeli 2fb4709a94 Update requirements_test_all.txt 2017-10-19 10:35:45 +02:00
Joe Lu 42f450d4e6 Use default clientsession to stream synology video (#9959) 2017-10-19 07:02:43 +02:00
Sean Gollschewsky 6ea866c7f7 Add emeter attributes (#9903)
* Add emeter attributes.

* Remove unused attributes.

* Rework supported features so it only queries the bulb once.

* Used cached supported_features, catch errors if energy usage not reported.
2017-10-18 21:52:44 -07:00
Daniel Perna 429b637885 Upgraded pyhomematic (#9956) 2017-10-19 01:31:25 +02:00
Alok Saboo f05a8bfa2a Update fritzconnection to 0.6.5 (#9950) 2017-10-18 20:58:26 +02:00
Alok Saboo 96e3dfeb53 Update fritzhome to 1.0.3 (#9951) 2017-10-18 20:57:53 +02:00
Alok Saboo 520de0d278 Update hikvision to 1.2 (#9953) 2017-10-18 20:57:13 +02:00
Alok Saboo 2cacfb5477 Update enocean to 0.40 (#9949) 2017-10-18 19:04:44 +02:00
Alok Saboo 4960892256 Update directpy to 0.2 (#9948) 2017-10-18 19:04:01 +02:00
Fabian Affolter 834d0e489e Move 'lights' to const.py (#9929) 2017-10-18 18:41:14 +02:00
Derek 1e1d593ef7 Changed returned attribute from "Game" to "game" (#9945)
I noticed the steam component "game" attribute is capitalized. This should be lowercase if I'm not mistaken.

From:
        return {'Game': self._game}
To:
        return {'game': self._game}

Not sure if i'm doing this correctly... apologizes if I'm not!
2017-10-18 18:27:02 +02:00
Pascal Vizeli 8a93cc147a FFmpeg 1.8 (#9944)
* Update requirements_all.txt

* Update requirements_test_all.txt

* Update ffmpeg.py

* Update ffmpeg.py

* Update yi.py

* Update onvif.py

* Update yi.py
2017-10-18 17:11:22 +02:00
Ludovic 628b9bd8d8 notify.xmpp - Add support for MUC (#9931)
* Add support for MUC

* Fix two spaces before inline comment
2017-10-18 16:28:37 +02:00
Daniel Welch 1bec2c005d using defusedxml ElementTree for safer parsing of untrusted XML data (#9934)
* using defusexml ElementTree for safer parsing of untrusted XML data

* move from core dependency to platform specific dependency

* style difference: put back end of list comma in setup.py
2017-10-18 16:21:46 +02:00
Daniel Høyer Iversen 587948ec06 Xiaomi config validation (#9941)
* validate xiaomi config

* Update xiaomi_aqara.py

* check for valid config

* use consts
2017-10-18 14:57:27 +02:00
Hugo Dupras f641a6aad3 Fix missing timeout for Netatmo binary sensor (#9850)
* Fix missing timeout for Netatmo binary sensor

This fix also merges timeout and offset because there were the same thing

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

* Fix lint errors

* Fix style
2017-10-18 14:56:24 +02:00
Fabian Affolter 8a2134b3a8 Add serial sensor (#9861)
* Add serial sensor

* Rename config variable and cancel
2017-10-18 11:20:19 +02:00
PeteBa c06d92900a Align away state tag with device_trackers (#9884) 2017-10-18 11:19:09 +02:00
Egor Tsinko 6e0efbe35e A new platform for controlling Monoprice 6-Zone amplifier (#9662)
* added implementation for monoprice 6-zone amplifier. This implementation is based on and very similar to russoun_rnet implementaion

* updated comments and cleaned up code

* updated comments and cleaned up code

* added unit tests

* removed 'name' attribute from platform schema.

* added monoprice.py to .coveragerc

* fixed lint

* fixed lint errors

* fixed lint errors

* added monoprice to requirements_all.txt

* fixed lint errors again

* implemented change requests

* fixed lint error

* added exception handling to setup_platform()

* replaced catchall with SerialException only

* added myself to CODEOWNERS

* fixed weird merge to CODEOWNERS
2017-10-18 11:11:36 +02:00
TopdRob bef4ae3e35 Update aioimaplib from v0.7.12 to v0.7.13 (#9930)
* Update aioimaplib from v0.7.12 to v0.7.13

Changelog v0.7.13:
[aiolib] adds a connection lost callback [test] imapserver : added APPENDUID response for APPEND cmd [test][fix] imapserver append should add to the connected user mb [test] imapserver : more accurate building of message headers (using python email module)

* run script/gen_requirements_all.py
2017-10-18 10:00:00 +02:00
Alok Saboo 818a52508e Bump py-synology to 0.1.5 (#9932) 2017-10-18 09:58:49 +02:00
TopdRob 33f8ca5abc update async_timeout from v1.4.0 tp v2.0.0 (#9938) 2017-10-18 09:48:00 +02:00
Paulus Schoutsen 3700fce859 Allow flexible relayer url (#9939) 2017-10-17 23:00:36 -07:00
Phil Kates 9d20a53d63 Google Actions for Assistant (#9632)
* http: Add headers key to json[_message]

* Add google_assistant component

This component provides API endpoints for the Actions on Google Smart
Home API to interact with Google Assistant.

* google_assistant: Re-add fan support

* google_assistant: Fix Scene handling

- The way I originally wrote the MAPPING_COMPONENT and the way it's actual
  used changed so the comment was updated to match that.
- Use const's in more places
- Handle the ActivateScene command correctly

* google_assistant: Fix flakey compare test

Was failing on 3.4.2 and 3.5, this is more correct anyway.

* google_assistant: Use volume attr for media_player
2017-10-17 22:00:59 -07:00
mclem 1d68777981 Add transmission sensor: number of active torrents (#9914)
* Add transmission sensor: number of active torrents

* Make variable name shorter
2017-10-17 22:45:37 +02:00
cgtobi f5b305c980 Fix the resource naming in the UI (#9927)
Use proper English for the UI representation without breaking the component.
2017-10-17 21:32:01 +02:00
Daniel Høyer Iversen 382f9a8f49 Update xiaomi_aqara.py (#9920) 2017-10-17 18:04:19 +02:00
cgtobi 778c3bb83d Fix the resource naming in the UI (#9916)
Use proper English for the UI representation without breaking the component.
2017-10-17 14:07:05 +02:00
Eugenio Panadero e57d0f345e Recorder: Extra check to incoming connections which could be not sqlite3 ones (#9867)
* Extra check to incoming connections

The incoming connection could be other than self.db_url, because
some 'custom_component' could be making these, and then, if they're not
sqlite3 connections, an error will raise because those haven't the
`dbapi_connection.isolation_level` attrib.

* lint fix

* simplify check: isinstance test only
2017-10-17 10:06:49 +02:00
Oliver ed70fc9322 Added support for Denon AVR-4810. (#9887) 2017-10-17 10:04:35 +02:00
Daniel Høyer Iversen 82c7195484 add last_action for xiaomi cube (#9897) 2017-10-17 10:03:46 +02:00
Aaron Bach 9be7763144 Fixes (#9911) 2017-10-17 10:02:03 +02:00
Eugenio Panadero 875edef3f0 Fix load of components without any config from packages (#9901)
* Fix load of components without any config from packages

- Add 'None' to the packages config schema validation, to be able to
load components without any more configuration from yaml package files,
like `wake_on_lan`, `media_extractor` and so on.

* test the ability to load components without configuration from packages
2017-10-17 09:59:33 +02:00
Aaron Bach 3de95c068a Fixes (#9912) 2017-10-17 09:24:52 +02:00
Bahnburner 51c5534c2a Update osramlightify.py (#9905) 2017-10-16 22:09:19 +02:00
Sergey Isachenko d95b75a10c Dependemcy version bump. (#9899)
Closes #8213.
Closes #7575.
2017-10-16 21:46:21 +02:00
Pascal Vizeli 1f25aa74dd Release 0.55.2 (#9904)
* Do not auto-install credstash (#9844)

* Pump release to 0.55.2
2017-10-16 21:01:25 +02:00
William Scanlon 5986d9ff5b Added super attributes to Wink binary sensors (#9824)
* Added super attributes to Wink binary sensors

* Removed unused import.
2017-10-16 14:58:23 +02:00
Jeroen ter Heerdt eb6fb5549f Changing clicksendaudio to clicksend_tts in .coveragerc (#9900) 2017-10-16 13:46:24 +02:00
Russell Cloran 7596ac23fc zha: Update to bellows 0.4.0 (#9890)
Fixes: #8822
2017-10-15 21:41:16 -07:00
Julius Mittenzwei c37883c9a9 Xknx improvements (#9871)
* Issue https://github.com/XKNX/xknx/issues/65 Make state_updater adjustable by config file (On/OFF)

* Issue https://github.com/XKNX/xknx/issues/48 updated home assistant plugin: added support for setpoint shift

* bumped version

* added missing docstrings.

* Bumped version.

* Fixed requirements_all.txt

* added new options to PLATFORM_SCHEMA
2017-10-15 23:46:55 +02:00
Paulus Schoutsen c6b285c666 Merge pull request #9885 from home-assistant/release-0-55-1
0.55.1
2017-10-15 14:30:45 -07:00
Eugenio Panadero b1dc48822d Upgrade python-telegram-bot to 8.1.1 (#9882)
* update python-telegram-bot to v8.1.1

* update python-telegram-bot to v8.1.1
2017-10-15 21:22:51 +02:00
Philipp Schmitt ff6f5cc116 Fix #9839 (#9880)
* Fix #9839

* Update requirements

* Default state: STATE_UNKNOWN -> None

* Default the state to None in the constructor as well
2017-10-15 12:20:22 -07:00
Philipp Schmitt da8be253bc Fix #9839 (#9880)
* Fix #9839

* Update requirements

* Default state: STATE_UNKNOWN -> None

* Default the state to None in the constructor as well
2017-10-15 21:16:23 +02:00
Adam Cooper 2547a235c1 Bugfix/9811 jinja autoescape (#9842)
* Added autoescape kwarg to Jinja environment

* Removed extra comma
2017-10-15 11:53:27 -07:00
Adam Cooper fdb698bef0 Changed yaml.load into yaml.safe_load (#9841) 2017-10-15 11:53:26 -07:00
Paulus Schoutsen 586e54f8bf OwnTracks: Fix handler is None checking (#9794)
* OwnTracks: Fix handler is None checking

* Update owntracks.py
2017-10-15 11:53:25 -07:00
Lewis Juggins 431201cb9b [light.tradfri] Fix transition time (#9785)
* Fix transition time, set a default

* Wrong default

* Use int for safety

* Revert default.
2017-10-15 11:53:25 -07:00
pascal 9b43388093 missing is_closed ( rflink cover fix ) (#9776)
* Added is_closed

* whitespaces --

* removed whitespace
2017-10-15 11:53:24 -07:00
Joe Lu 45620d6892 Fix for TypeError in synology camera (#9754) 2017-10-15 11:53:24 -07:00
Paulus Schoutsen 7ed21d90aa Version bump to 0.55.1 2017-10-15 11:51:21 -07:00
Fabian Affolter 959a7b2d59 Upgrade paho-mqtt to 1.3.1 (#9874) 2017-10-15 10:12:43 -07:00
Eugenio Panadero ac256d5943 handle OWM API error calls (#9865) 2017-10-15 10:31:34 +02:00
Paulus Schoutsen 0362a76cd6 Cloud connection via aiohttp (#9860)
* Cloud: connect to cloud

* Fix tests in py34

* Update warrant to 0.5.0

* Differentiate errors between unknown handler vs exception

* Lint

* Respond to cloud message to logout

* Refresh token exception handling

* Swap out bare exception for RuntimeError

* Add more tests

* Fix tests py34
2017-10-14 19:43:14 -07:00
Eugenio Panadero 26cb67dec2 minimal fixes in the owntracks mqtt device tracker (#9866)
* fix UnboundLocalError when unable to parse payload, and show bad topics that cannot be parsed ok

* Update owntracks.py
2017-10-14 15:46:06 -07:00
Fabian Affolter 00244380a8 Upgrade psutil to 5.4.0 (#9869) 2017-10-14 23:07:31 +02:00
Ryan Bahm f807a3a890 Darksky enhancements (#9851)
* Correct capitalization inconsistency in DarkSky

All two-word sensors ("Precip Intensity," "Nearest Storm Bearing," etc) in Darksky uses title case for the friendly name of the sensor, with the exception of "Dew point."

* Implement UV Index in Darksky

* Fixed whitespace for Tox compliance

* Add unit for UV Index.

Per recommendation of reviewer, added 'UV Index' as a CONST in const.py, then used that const in both DarkSky and ISY994. It looks like BloomSky might also support UV Index and it should probably be standardized.
2017-10-14 14:45:32 -04:00
Kevin Fronczak fd6c2598a7 Uptime sensor (#9856)
* Added uptime sensor for homeassistant

* Fixed pylint and flake8 errors

* Made requested changes from PR

- Fixed stale docstrings
- Changed default state to None
- Added ability for user to use hours or days

* Fixed typo

* Added unit_of_measurement check to test

* Converted to async

- Changed tests to work with async

* Minor updates
2017-10-14 20:06:44 +02:00
Fabian Affolter 79d1a0ab37 Upgrade youtube_dl to 2017.10.12 (#9862) 2017-10-14 19:07:28 +03:00
Jeroen ter Heerdt a787ab6d3c Changing name of clicksendaudio component to clicksend_tts (#9859) 2017-10-14 15:08:28 +02:00
Pascal Vizeli 8456cd0313 HassIO - TimeZone / Host services (#9846)
* HassIO - TimeZone / Host services

* Update hassio.py

* Update test_hassio.py
2017-10-13 15:45:22 +02:00
Charles Garwood fa37d9800e File permissions fix (#9847)
* Fixing file permissions

* Fixing file permissions
2017-10-13 14:22:41 +02:00
icovada 80826bc985 Add CAPSman master to mikrotik presence detection (#9729)
* Add CAPSman master to mikrotik presence detection

Automatically prefer caps-man registered clients over locally connected

* Remove blank line

* Trailing whitespace removed
2017-10-13 10:54:58 +02:00
rbflurry b00d0a1253 Use the Last Seen attribute in unify (#8998)
* Uses the Last Seen attribute in unify

* Update unifi.py

fix format

* Update unifi.py

formatting again

* update test_unifi to call CONF_CONSIDER_HOME

Updated.

* Update test_unifi.py

* Update test_unifi.py

* More unit test test

* Update where consider_home comes from.

* Update test_unifi.py

* Update unifi.py

* Update unifi.py

* Update test_unifi.py

* Update unifi.py

* Update unifi.py

* Update test_unifi.py

* fix hound

* Update test_unifi.py

* Update test_unifi.py

* Update unifi.py

* Update unifi.py

* Update test_unifi.py

* Update unifi.py

* Update unifi.py

* Update test_unifi.py

* Update unifi.py

* Update test_unifi.py

* Update test_unifi.py

* Update test_unifi.py

* Update unifi.py

* Update unifi.py

* Update unifi.py

* Update unifi.py

* Update test_unifi.py

Fix the butcher of tests.

* Update unifi.py

* Update test_unifi.py

* Update test_unifi.py

* Update unifi.py

* Update unifi.py

* Update test_unifi.py

* Update test_unifi.py

* Update test_unifi.py

* Update test_unifi.py

* Update test_unifi.py

* Update test_unifi.py

* Update test_unifi.py

* Update test_unifi.py

* Update test_unifi.py

* Update unifi.py

* Update test_unifi.py

* Update unifi.py

* Update unifi.py

* Update unifi.py

* Update unifi.py
2017-10-13 10:13:58 +02:00
Paulus Schoutsen f7545fe85c Remove namecheap dns service (#9845) 2017-10-13 09:47:13 +02:00
Martin Treml c69e9c1d49 Add namecheap DNS component (#9821)
* Add namecheap DNS component

* Updates for pull-request

* remove unused import in test file

* Update .coveragerc
2017-10-12 23:58:23 -07:00
Paulus Schoutsen 79b029a680 Do not auto-install credstash (#9844) 2017-10-12 23:57:45 -07:00
Aaron Bach 9891320e7c New PR (#9787) 2017-10-12 22:20:30 -07:00
Adam Cooper 64853bae32 Changed yaml.load into yaml.safe_load (#9841) 2017-10-12 22:05:33 -07:00
Adam Cooper a7f4bcc410 Bugfix/9811 jinja autoescape (#9842)
* Added autoescape kwarg to Jinja environment

* Removed extra comma
2017-10-12 22:01:29 -07:00
Lukas Barth bbb406626b Bugfix: Include MQTT schema (#9802) 2017-10-12 22:00:09 -07:00
Charles Garwood c5c594ba7d Add service descriptions (#9806)
* Added descriptions for services under homeassistant domain

* lint fixes

* Fixing file permissions
2017-10-12 21:59:07 -07:00
Adam Mills 8d83912649 Run initial generation for development mode (#9826)
* Run initial generation for development mode

* Use yarn dev
2017-10-12 21:56:38 -07:00
Teemu R 2c1f0f3449 fix climate services (missing indentation, wrongly formatted example) (#9805) 2017-10-12 21:29:17 +03:00
Kane610 c85b5561ee Update CODEOWNERS */axis.py (#9823)
Add code owner for */axis.py
2017-10-12 21:26:07 +03:00
Alan Fischer 4cf300a710 Fixed reporting of vera UV sensors (#9838) 2017-10-12 20:51:25 +03:00
Fabian Affolter 3bdb7052b8 Upgrade libnacl (#9769)
* Upgrade libnacl to 1.6.0

* Small style updates
2017-10-12 18:13:43 +02:00
Paulus Schoutsen 3b5a9e7796 OwnTracks: Handle lwt message (#9831)
* OwnTracks: Handle lwt message

* Update owntracks.py
2017-10-12 08:25:18 -07:00
Charles Garwood 5fcb0990c3 Adds image attribute to html5 notify (#9832) (#9835) 2017-10-12 17:01:12 +02:00
cdce8p be5c0b2d92 Wait_template - support for 'trigger.entity_id' and data_template values (#9807)
* *Added support for use of 'trigger.entity_id' and service->data_template->script in wait_template

* * Fixed style violations

* * Fixed regular expression (_RE_GET_POSSIBLE_ENTITIES)

* * combined 'extract_entities' and 'extract_entities_with_variables'
* fixed regular expression

* * Added first test for extract_entities_with_variables

* * Added Unittests (tests/helpers/test_template.py test_extract_entities_with_variables)

* * Added Unittests (tests/helpers/test_script.py test_wait_template_variables)

* * Added Unittests (tests/components/automation/test_template.py test_wait_template_with_trigger)

* * Added Unittests (tests/components/automation/test_state.py test_wait_template_with_trigger)

* * Added Unittests (tests/components/automation/test_numeric_state.py test_wait_template_with_trigger)

* * Fixed style violations

* * Fixed style violations

* * Fixed style violations

* * Fixed style violations

* * Fixed style violations

* * Fixed style violations

* * Updated regular expression and delete whitespaces
2017-10-12 16:57:18 +02:00
Paulus Schoutsen c33b179fb8 Fix ISY994 fan platform overwriting state property (#9817)
* ISY994 platform overwrote state

* Update isy994.py

* Update isy994.py
2017-10-12 00:36:24 -07:00
Adam Mills 765560e87a Restore home-assistant-polymer pointer from #9720 (#9825) 2017-10-11 21:53:12 -04:00
Charles Garwood f837302194 Split map panel out into its own component (#9814) 2017-10-11 17:45:55 +02:00
Fabian Affolter 19887f8742 Upgrade pyasn1 to 0.3.7 and pyasn1-modules to 0.1.5 (#9810) 2017-10-11 16:26:34 +03:00
Adam Cooper 0de2266a72 Resolving bug that prevents ssl_verify option for Unifi device_tracker (#9788)
* Added TODO to illustrate my intentions

* Resolved linting issue

* Resolved bool or file validation and updated tests

The tests have been updated to include mocks to assert a temp
ca cert exists as it should for the positive tests with an
additional negative test for a file not existing being tested.

* Resolved flake8 linting issues (test docstrings)
2017-10-11 00:08:36 +02:00
Paulus Schoutsen 8f06b35dfc Optimize event matcher (#9798)
* Optimize event matcher

* Tweak order of checks

* Add a benchmark for time_changed helper

* Add state change benchmark

* fix lint
2017-10-10 22:26:03 +02:00
Paulus Schoutsen a97e7bb22d Simplify track_same_state (#9795) 2017-10-10 21:16:19 +02:00
Paulus Schoutsen fc47e9443b OwnTracks: Fix handler is None checking (#9794)
* OwnTracks: Fix handler is None checking

* Update owntracks.py
2017-10-10 10:39:25 +02:00
Lewis Juggins e144b0f0f9 [light.tradfri] Fix transition time (#9785)
* Fix transition time, set a default

* Wrong default

* Use int for safety

* Revert default.
2017-10-10 00:35:28 -07:00
ziotibia81 a024c1b162 Communication timeout support in modbus hub. (#9780)
* Communication timeout support in modbus hub.

Timeout parameter are taken from configuration and passed to pymodbus constructor.

* CONF_TYPE and CONF_TIMEOUT imported from const.py
2017-10-09 23:51:18 +02:00
Sean Dague 581e2f22d5 Bump rxv library to 0.5.1 (#9784)
This fixes some bugs with interfacing with yamaha receivers, including
closing bug #5209.
2017-10-09 17:58:53 +02:00
William Scanlon 5232f2abdd Wink dome siren support (#9667)
* Support for Wink Dome siren/chimes
2017-10-09 11:16:36 -04:00
pascal cb52b80f7d missing is_closed ( rflink cover fix ) (#9776)
* Added is_closed

* whitespaces --

* removed whitespace
2017-10-09 16:57:44 +02:00
Sean Dague d0ec9301ab Fix off by one error in arwn platform (#9781)
There is an off by one error that causes period exceptions. Fix this.
2017-10-09 15:41:18 +02:00
Sergey Isachenko 9abd0fb92f Tesla bug fixes. (#9774)
* Tesla bug fixes.

* Added myself to CODEOWNERS for tesla.
2017-10-09 14:38:00 +03:00
Aaron Bach 43d77729c5 WIP: Fix Arlo Camera blocking IO (#9758)
* WIP: Fix Arlo Camera blocking IO

* Accidental undo

* Linting issues

* Owner-requested changes

* Bumped pyarlo version and added Throttle

* Fix

* Update requirements_all.txt
2017-10-09 11:35:05 +02:00
Jeroen ter Heerdt 04b3c89cf5 Adding myself as codeowner for egardia alarm control panel. (#9772)
Adding jeroenterheerdt as codeowner for egardia alarm control panel.
2017-10-09 11:52:51 +03:00
Jeroen ter Heerdt 09e2075c68 Updating pythonegardia package requirement to .22 because of fixed bug in passing default value for parameter SSL for egardiaserver (#9770) 2017-10-09 10:37:51 +02:00
Rob Connolly 3bd9684ca5 Add notification platform for Rocket.Chat. (#9553)
* Add notification platform for Rocket.Chat.

* Changes to Rocket.Chat notification platform based on feedback.

* Implement better error handling for Rocket.Chat platform.

* Return None if Rocket.Chat notify platform init fails.

* Refactor Rocket.Chat notifications.

Refactor Rocket.Chat notification platform to remove async and
simplify error handling.

* fix url
2017-10-09 09:38:48 +02:00
Paulus Schoutsen 414900fefb Expose time module in Python Scripts (#9736)
* Expose time module in Python Scripts

* Make dt_util available in Python Scripts

* Limit methods in time module

* Add time.mktime

* Limit access to datetime

* Add warning to time.sleep

* Lint
2017-10-09 08:51:32 +02:00
Egor Tsinko 35484ca086 fix for LocationParseError in netgear platform (#9683)
* fix for LocationParseError in netgear platform

* added unit tests for get_scanner()

* fixed houndci-bot warnings

* fixed lint warnings

* fixed lint warnings

* fixed broken test

* removed guard clause from netgear.py
removed all discovery related code from device_tracker
removed unnecessary unit test

* removed discovery related tests

* removed unused import

* removed unused import
2017-10-08 22:14:39 -07:00
Sebastian Muszynski 603765fe92 Xiaomi Smart WiFi Socket and Smart Power Strip integration (#9138)
* Xiaomi Smart WiFi Socket and Smart Power Strip integration

* Comment updated.

* Blank line removed.

* Typo fixed.

* Version of python-mirobo bumped.

* Version of python-mirobo bumped: Lightweight API changes.

* Additional API changes.

* Library version properly pinned again.

* Platform not ready behavior fixed.
Expose the device model as sensor attribute.
Device initialized log message added. Provides device model, firmware and hardware version.

* Component renamed: switch.xiaomi_plug -> switch.xiaomi_miio

* Revise based on review: Unused code removed. Filename updated.
2017-10-08 22:11:11 -07:00
Marcelo Moreira de Mello 80140732c3 Bump raincloudy version 0.0.3 (#9767)
* Bump raincloudy version 0.0.3

* Fix logic for raincloudy status binary_sensor

* Simplified binary_sensor logic

* Simplify
2017-10-08 21:08:40 -07:00
Teemu R c00647ace0 yeelight: implement min_mireds and max_mireds, fixes #9509 (#9763)
* yeelight: implement min_mireds and max_mireds, fixes #9509

thanks to @amelchio for pointing this out!

* remove typing infos
2017-10-08 21:05:49 -07:00
Andrey 2a2ee81957 Match test requirements by full package name. (#9764) 2017-10-08 20:49:51 -07:00
ChristianKuehnel b620c433c0 Initializing statistics sensor with data from database (#9753)
* Initializing statistics sensor with data from database

* fixed broken test case

* usage of recorder component is now optional, thx to @andrey-git

* added test case for initialize_from_database
2017-10-08 23:45:12 +02:00
Aaron Bach b80f00900d Adding my contributions (#9761) 2017-10-08 22:56:58 +02:00
Daniel Høyer Iversen a32fc10f1b Update CODEOWNERS (#9760) 2017-10-08 22:32:42 +02:00
Teemu R 672ff96754 add myself to yeelight owners, too (#9759) 2017-10-08 22:36:17 +03:00
Mister Wil 8132989f91 Skybell (#9681)
* New Skybell platform with components

* Added skybell components to omit.

* Preemptively fixing lint issues (hopefully).

* Removed unused variable.

* Requested changes.

* Additional CRs

* Hopefully the last of the CR's!
2017-10-08 20:14:39 +02:00
Paulus Schoutsen ca54bbfcc9 RFC: Use bind_hass for helpers (#9745)
* Add Helpers bind_hass functionality

* Update other helpers
2017-10-08 08:17:54 -07:00
Teemu R e19e9a1f2b switch.tplink, light.tplink: bump the pyhs100 version and adapt to api changes (#9454)
* bump the pyhs100 version and fix api changes

* switch.tplink: avoid I/O during __init__

* initialize _name to None in __init__

* update requirements_all.txt for the new version
2017-10-08 17:31:32 +03:00
Joe Lu e89e64263c Fix for TypeError in synology camera (#9754) 2017-10-08 13:31:00 +03:00
Marcelo Moreira de Mello f56bdd29ff Make Arlo battery_level icon dynamic (#9747)
* Make Arlo battery_level icon dynamic

* makes lint happy
2017-10-08 10:05:41 +02:00
Paulus Schoutsen 9eff9fa703 Fix I/O in event loop by Arlo alarm control panel (#9738) 2017-10-08 09:26:16 +02:00
Pascal Vizeli c1f156fd2b Rewrite Alexa Smart-Home skill to v3 (#9699)
* Rewrite Alexa Smart-Home skill to v3

* add discovery & fix brigness

* Rewrite Tests

* fix lint

* fix lint p2

* fix version

* fix tests

* fix test message generator

* Update smart_home.py

* fix test

* fix set bug

* fix list

* fix response name for discovery

* fix flucky tests
2017-10-07 13:31:57 -07:00
Adam Mills 4342d7aa17 Event trigger nested conditions (#9732)
* Test to supported nested event triggers

* Update event trigger to allow nested data tests
2017-10-07 13:13:32 -07:00
Paulus Schoutsen 8a2d7a3e11 Merge pull request #9704 from home-assistant/release-0-55
0.55
2017-10-07 12:50:52 -07:00
Ryan McLean af3ea5a321 Fix: Last Played Media Title persists in plex (#9664)
* Fix: Last Played Media Title in plex would stay even when player was idle/off
     Primary Fix is in the "if self._device" portion.
     code in "if self._session" is a catch all but i'm not 100% if it is needed.

* Fixed lint issues with previous commit

* 1st Pass at refactoring plex refresh
Moved _media** into clearMedia() which is called in _init_ and
at start of refresh.

Removed redunant _media_* = None entries

Grouped TV Show and Music under single if rather than testing
seperately for now.

* Fixed invalid name for _clearMedia()
Removed another media_* = None entry

* Removed print() statements used for debug

* Removed unneeded "if" statement
2017-10-07 15:31:01 -04:00
Lewis Juggins de4f610540 [light.tradfri] Clone all of aiocoap to ensure pinned commit will be present (#9713) 2017-10-07 18:28:55 +02:00
Paulus Schoutsen 770f8bd1c3 Fix coap commit (#9712) 2017-10-07 18:28:55 +02:00
Paulus Schoutsen 1ab942e0a2 Deprecate Python 3.4 support (#9684)
* Deprecate Python 3.4 support

* Update text
2017-10-07 18:28:50 +02:00
Andrey c09b7b5d6d Add andrey-git to codeowners (#9718) 2017-10-07 17:58:45 +02:00
Lewis Juggins 710454119f [light.tradfri] Clone all of aiocoap to ensure pinned commit will be present (#9713) 2017-10-07 08:54:51 -07:00
Fabian Affolter 25e6d694e1 Bump release to 0.56.0dev (#9726) 2017-10-07 16:07:49 +02:00
Fabian Affolter 19a20b3b13 Move 'show_on_map' to const (#9727) 2017-10-07 15:11:41 +02:00
Aaron Bach bd5b70c3cd Add show_on_map config option to AirVisual (#9654)
* Removed lat/long attributes

* Linting

* Revised PR to focus on show_on_map configuration
2017-10-07 13:38:52 +02:00
Marcelo Moreira de Mello ec5439e4d4 Introducing support to Travis-CI (#9701)
* Introduced support to Travis CI

* Added Last Build Started sensor and simplified code

* Fixed logic error

* Simplified _LOGGER.debug statement

* Introduced support to Travis CI

* Added Last Build Started sensor and simplified code

* Fixed logic error

* Simplified _LOGGER.debug statement

* Renamed parameter since the repository_names expects a list

* Refactoring code to synchronous

* Simplified variables names
2017-10-07 11:02:40 +02:00
Fabian Affolter fd509e188a Arlo clean-up (#9725)
* Fix remaining isses from #9711

* More clean-up
2017-10-07 10:59:46 +02:00
Mister Wil a5a839e72a Abode Temp, Humidity, and Light Sensor (#9709)
* Update to 0.12.1 and sensor implementation.

* Removing unnecessary dict gets.

* Added name property to actually use the _name variable.

* Update docstring
2017-10-07 10:25:53 +02:00
Vignesh Venkat 3b53952dbe arlo: Add alarm control panel component (#9711)
* arlo: Add alarm control panel component

Allows importing arlo base stations as an alarm control panel
component in HA. Lets the users configure a custom home mode since
arlo does not have a built-in home mode.

* fix lint and houndci comments

* Use async_update to update the state

Move the state updating code from state() to update() since it does
I/O.

* Do not set state in __init__

Make sure that update is called by passing the second parameter to
async_add_devices.

* Order imports and fix dos-strings
2017-10-07 10:07:38 +02:00
Fabian Affolter e502202de7 Upgrade pysnmp to 4.3.10 (#9722) 2017-10-07 09:47:52 +02:00
Kevin Fronczak 2479ce9123 More netdata sensors (#9719)
* Added more netdata sensors

* Changed precision on counts, packets, and uptime
2017-10-07 00:22:40 +02:00
Teemu R d3772d4abd bump the version and catch all exceptions to avoid showing backtraces… (#9720)
* bump the version and catch all exceptions to avoid showing backtraces but a more sane error message

* catch only BTLEExceptions, fix logging strings
2017-10-07 00:21:34 +02:00
Daniel Perna f4679cc870 Upgrade pyhomematic, add path setting and HM-CC-VG-1 support (#9707)
* Bump pyhomematic, add path setting, HM-CC-VG-1 support

* Added requirement
2017-10-06 11:09:50 +02:00
Marcelo Moreira de Mello 7b116b0207 Updating helper's icon_for_battery_level location (#9594) 2017-10-06 09:17:18 +03:00
Paulus Schoutsen ffb19381f1 Deprecate Python 3.4 support (#9684)
* Deprecate Python 3.4 support

* Update text
2017-10-05 21:47:51 -07:00
Paulus Schoutsen 1525cbfb93 Fix coap commit (#9712) 2017-10-05 21:12:49 -07:00
happyleavesaoc b83059c828 move icon battery function from util to helpers (#9708) 2017-10-05 20:55:19 -07:00
Florian Klien c7226ec28f fixed duplicate words (#9705) 2017-10-05 21:55:09 +02:00
Paulus Schoutsen 6541e789fb Version bump to 0.55 2017-10-05 09:23:09 -07:00
Paulus Schoutsen c1b5772f0f Merge remote-tracking branch 'origin/master' into dev 2017-10-05 09:22:49 -07:00
Paulus Schoutsen 6627c352e6 Update frontend 2017-10-05 09:17:26 -07:00
Fabian Affolter 6de403e0ac Support for The Things Network (#9627)
* Support for The Things network's Data Storage

* Rename platform and other changes (async and dict)

* Rename sensor platform and remove check for 200
2017-10-05 09:12:02 -07:00
Paulus Schoutsen 75f902f57e RFC: Create a secrets file and enable HTTP password by default (#9685)
* Create a secret and enable password by default

* Comment out api password secret

* Lint/fix tests
2017-10-05 09:10:29 -07:00
Lewis Juggins 8db4641455 [light.tradfri] async support with resource observation. (#7815)
* [light.tradfri] Initial support for observe

* Update for pytradfri 2.0

* Fix imports

* Fix missing call

* Don't yield from add devices

* Fix imports

* Minor fixes to async code.

* Imports, formatting

* Docker updates, some minor async code changes.

* Lint

* Lint

* Update pytradfri

* Minor updates for release version

* Build fixes

* Retry observation if failed

* Revert

* Additional logging, fix returns

* Fix rename

* Bump version

* Bump version

* Support transitions

* Lint

* Fix transitions

* Update Dockerfile

* Set temp first

* Observation error handling

* Lint

* Lint

* Lint

* Merge upstream changes

* Fix bugs

* Fix bugs

* Fix bugs

* Lint

* Add sensor

* Add sensor

* Move sensor attrs

* Filter devices better

* Lint

* Address comments

* Pin aiocoap

* Fix bug if no devices

* Requirements
2017-10-05 09:05:38 -07:00
Alok Saboo 89042439b8 Fixed typo in opencv (#9697) 2017-10-05 00:04:39 +02:00
Pascal Vizeli f34ebf733d HassIO replace config changes (#9695)
* Update flow

* fix tests

* Update hassio.py
2017-10-04 18:31:50 +02:00
bestlibre 84271a2dac Refactoring of onewire sensor component (#9691) 2017-10-04 16:35:58 +02:00
Jeroen ter Heerdt e753c51e34 Updating clicksendaudio component based on feedback (#9692)
* Updating clicksendaudio component based on feedback

* Updating .coveragerc - forgot to add new file clicksendaudio.py
2017-10-04 16:34:37 +02:00
milanvo 65de739489 Fix restore state by filter out null value row from DB query (#9690) 2017-10-04 14:13:58 +02:00
milanvo 3f9d052218 Add recorder purge service, rework purge timer (#9523)
* Add recorder purge service

* Recorder test to match purge config

* Removed purge timer, move service handler to setup, add service description file

* Tests for recorder purge service

* Recorder purge timer rework, add purge service parameter, tests

* Purge service schema change

* Service description change value range

* First cleanup

* Fix name of config
2017-10-04 14:07:42 +02:00
Daniel Høyer Iversen 4314dc251f Add Tibber sensor (#9661)
* Add Tibber sensor

* remove extra space
2017-10-04 10:31:42 +02:00
Johan Bloemberg e0de521388 Implement DSMR5 support. (#9686)
* Allow configuring DSMR5 protocol.

* Give good example.

* Using dev branch until released upstream.

* Update to dsmr_parser supporting v5 arguments.

* Update to latest dmsr parser, preventing exceptions thrown where warnings would suffice.

* Update even more

* Update requirements.

* Update requirements
2017-10-04 10:20:08 +02:00
Martin Berg 3a282702d9 Fix Google Calendar/oauth2client warning (#9677)
* Fixes oauth2client warning.

* Fix permission.
2017-10-04 10:01:20 +02:00
Jeroen ter Heerdt 7759ae26fd Adding ignore capability to Egardia component (#9676) 2017-10-04 09:59:38 +02:00
Pascal Vizeli 4be91a103d Support new feature to push API data to hassio (#9679)
* Support new featuer to push API data to hassio

* Add tests & services
2017-10-03 22:52:45 -07:00
Alan Fischer a4b64dec39 Properly handle an invalid end_time (#9675) 2017-10-03 22:51:08 -07:00
BioSehnsucht 3c0d02f057 Rename input_slider to input_number and add numeric text box option (#9494)
* * Rename input_slider to input_number
* Update input_number to optionally display slider, input box, or both

* input_number support either input box or slider mode, but not both

* input_number : change service from select_value to set_value

* input_number : add test for mode setting to tests
2017-10-03 21:34:13 +02:00
FletcherAU 29e973d060 Fix typo in cancel_command description (#9671)
"wasn't going to use it"
2017-10-03 15:24:59 +02:00
Alan Fischer 12b2cfa9b5 Upgrade pyitachip2ir to 0.0.7 (#9669) 2017-10-03 08:17:36 +02:00
Adam Mills c4810da82f Unit tests to improve core coverage (#9659)
* Code coverage of logging util

* Improve async util coverage

* Add test coverage for restore_state

* get_random_string test
2017-10-02 20:25:04 -07:00
Mister Wil 0aa22d9d91 Bump abode to 0.11.9 (#9660) 2017-10-02 22:55:26 +02:00
Alok Saboo 670bd0ce48 Update google-api-python-client to 1.6.4 (#9658) 2017-10-02 22:42:23 +02:00
Jeroen ter Heerdt 4803721120 Fixed bugs related to exception handling in pythonegardia. Updating package requirement accordingly (#9663) 2017-10-02 22:41:46 +02:00
Matt White 755a2a8291 mqtt_statestream: Add options to publish attributes/timestamps (#9645) 2017-10-02 17:41:07 +02:00
Fabian Affolter 3bd31b91fb Upgrade googlemaps to 2.5.1 (#9653) 2017-10-02 17:17:22 +02:00
Fabian Affolter 25e00556d0 Upgrade influxdb to 4.1.1 (#9652)
* Upgrade influxdb to 4.1.1

* Upgrade influxdb to 4.1.1
2017-10-02 17:17:08 +02:00
Fabian Affolter 8a90ad9e28 Upgrade netdisco to 1.2.2 (#9649) 2017-10-02 17:16:50 +02:00
Fabian Affolter 3f19be9717 Upgrade discord.py to 0.16.12 (#9648) 2017-10-02 17:16:37 +02:00
Fabian Affolter 13fe5857b3 Upgrade youtube_dl to 2017.10.01 (#9647) 2017-10-02 17:16:09 +02:00
Fabian Affolter 5327d2dd1a Upgrade numpy to 1.13.3 (#9646) 2017-10-02 17:15:50 +02:00
Sam Birch da4048a9ec Add hysteresis attribute to threshold binary sensor (#9596)
* Added hysteresis attribute to threshold binary sensor

* Added threshold binary sensor hysteresis test case

* Changed threshold binary sensor property name to be more self explanatory

* Pulled default hysteresis value into top level declaration

* Fixed linter errors

* Fixed additional linter errors

* Move comment to docs
2017-10-02 17:15:19 +02:00
Vignesh Venkat b4551cc127 arlo: Add battery level sensor (#9637)
* arlo: Add battery level sensor

Adds a battery level sensor that monitors the battery level on
Arlo cameras.

* Fix lint issue
2017-10-02 12:38:55 +02:00
Michel Weimerskirch 3337107e79 Facebook Messenger notify component: add support for sending messages to specific page user IDs (#9643) 2017-10-02 11:29:31 +02:00
Paulus Schoutsen f7609e9cb1 Move group services into their own YAML (#9597)
* Move group services into their own YAML

* Fix lint

* Move persistent notification to package
2017-10-01 23:18:10 -07:00
David Byrne 52671842d5 Fixes broken source links in API docs (#9636)
* Fixes broken source links in API docs

* Removes illegal blank line
2017-10-01 23:10:01 -07:00
Gabor SZOLLOSI fc4a21e491 raspihats: unmet dependency fix (#9638)
* raspihats: update to 2.2.3 (deps fix)

Raspihats platform update, upstream fixed enum34 requirements, added smbus dependency
Fixes #9547

* raspihats: update to 2.2.3, smbus-cffi dependency

Raspihats platform update, upstream fixed enum34 requirements, added smbus dependency
Fixes #9547

* raspihats: update to 2.2.3

* raspihats: update to 2.2.3, smbus-cffi dependency

* raspihats: update to 2.2.3, smbus-cffi dependency

* raspihats: update to 2.2.3 (deps fix)

Raspihats platform update, upstream fixed enum34 requirements, added smbus dependency
Fixes #9547

* raspihats: update to 2.2.3, smbus-cffi dependency
2017-10-01 23:05:24 -07:00
Teemu R 70c8970555 add myself to codeowners (#9642) 2017-10-01 23:04:33 -07:00
Gabor SZOLLOSI fa32411ab1 wunderground: fix supported language codes #9631 (#9633)
* removed PU, added TR language code (https://www.wunderground.com/weather/api/d/docs?d=language-support&MR=1), fixes #9631
2017-10-01 12:41:21 +02:00
Lukas Barth 29c40622d3 MQTT climate platform [continuation of #8750] (#9589)
* New climate platform with MQTT

* Use STATE_OFF

* Basic tests for climate.mqtt

* lint

* actualy collect coverage

* First tests and fixes

* Add possibility to receive temperature via MQTT

* Require only either sensor or mqtt topic

* Add mqtt publishing for away mode, hold mode and aux heat.

* Use configurabe on/off payloads

* Add pessimistic mode

* Initialize aux and away with False instead of None

* Remove Sensor

* Use correct scheduling method

* Move all methods to coroutines
2017-09-30 16:29:40 +02:00
Phil Kates 80a15977ff splunk: Handle datetime objects in event payload (#9628)
If an event contained a datetime.datetime object it would cause an
exception in the Splunk component. Most of the media_player
components do this in their `media_position_updated_at` attribute.

Use the JSONEncoder from homeassistant.remote instead of just using the
standard json.dumps encoder.

Fixes #9590
2017-09-30 09:35:25 +02:00
Alan Fischer e406c57ec9 Switched VeraSensor to use category ids (#9624) 2017-09-29 23:34:14 +02:00
Egor Tsinko 9232fa06e4 Fixed away_mode for Ecobee thermostat. (#9559)
* Fixed away_mode for Ecobee thermostat. Now away mode is properly turned on using indefinite away hold.

* fixed lint warnings

* fixed lint warnings

* - now it is possible to use float values for ecobee temperature holds
- fixed a bug that caused an exception when temperature hold was set in away mode
- added unit tests for ecobee thermostat

* fixed lint errors

* fixed lint errors
2017-09-29 16:57:31 +02:00
Jan Almeroth 94370eda54 Yamaha MusicCast: check known_hosts (#9580)
* Yamaha MusicCast: check known_hosts

- pymusiccast: Version bump

* Update requirements
2017-09-29 16:45:25 +02:00
Fabian Affolter 52561d4f7c Move 'voltage' to const (#9621) 2017-09-29 12:05:02 +02:00
Teemu R 9381f187a4 yeelight: allow turn_off transitions, fixes #9602 (#9605) 2017-09-29 12:04:22 +02:00
Joe Lu 445b0f6f94 Rewrite synology camera by using py-synology package (#9583)
* - Rewrite synology camera by intruducing Api and SurveillanceStation classes to get cameras, motion settings, enable/disable motion detection, etc ...
- Synology camera now shows correct state based on is_recording and is_streaming flag. Also it now supports enable / disable motion detection and show the correct motion detection status
- Newly added Api and SurveillanceStation classes will be moved to a lib but it's here just for review

* - Updated how payload are merged with kwargs so it works with python <3.5

* - Fixed class name conflict

* - Addressed flake8 error

* - Addressed pylint error

* - Moved synology API related code to py-synology lib
- Added py-synology==0.1.1 requirement
- Removed hass from SynologyCamera constructor

* - Updated requirements_all.txt

* - renamed variable back to original

* - Sync call to retrieve camera image should be done in camera_image() instead

* - Sync call to update camera info should be done in update() instead

* - Removed unused import
2017-09-29 12:02:48 +02:00
Marcelo Moreira de Mello 19932bce53 Introducing support to Melnor RainCloud sprinkler systems (#9287)
*  Introducing support to Melnor RainCloud sprinkler systems

* Make monitored_conditions optional for sub-components

*  Part 1/2 - Modified attributes, added DATA_ constant and using battery helper

* Part 2/2 - Refactored self-update hub

* Fixed change requested:
- Dispatcher signal connection
- Don't send raincloud object via dispatcher_send()
- Honoring the dynamic scan_interval value on track_time_interval()

* Inherents async_added_to_hass() on all device classes

* Makes lint happy

* * Refactored RainCloud code to incorporate suggestions.
  Many thanks to @pvizelli and @martinhjelmare!!

* Removed Entity from RainCloud sensor and fixed docstrings

* Update raincloud.py

* Update raincloud.py

* fix lint
2017-09-29 10:08:41 +02:00
pascal cc5256b8fb Cover component for RFlink (#9432)
* second try on rflink / cover

* no newline at end of file

* changed entity

* fixed comments from pvizeli

* removed :

* removed return 'unknown'

* Fixed comments from Rytilahti

* removed newline

* Reverted to None

* cleanup

* Cleanup
2017-09-29 00:49:03 +02:00
Lukas Barth 236d5f8742 Add an input_datetime (#9313)
* Initial proposal for the input_datetime

* Linting

* Further linting, don't define time validation twice

* Make pylint *and* flake8 happy at the same time

* Move todos to the PR to make lint happy

* Actually validate the type of date/time

* First testing

* Linting

* Address code review issues

* Code review: Remove forgotten print()s

* Make set_datetime a coroutine

* Create contains_at_least_one_key_value CV method, use it

* Add timestamp to the attributes

* Test and fix corner case where restore data is bogus

* Add FIXME

* Fix date/time setting

* Fix Validation

* Merge date / time validation, add tests

* Simplify service data validation

* No default for initial state, allow 'unknown' as state

* cleanup

* fix schema
2017-09-28 23:57:49 +02:00
Paulus Schoutsen 2df433eb0a Migrate Alexa smart home to registry (#9616)
* Migrate Alexa smart home to registry

* Fix tests
2017-09-28 21:26:27 +02:00
Dan Chen 44838937d1 Change TP-Link Switch power statistics attribute names (#9607) 2017-09-28 14:12:02 -05:00
Nolan Gilley 8b6a5eef4c upgrade python-ecobee-api (#9612) 2017-09-28 20:38:15 +02:00
Paulus Schoutsen 6fb55b363a Add OwnTracks over HTTP (#9582)
* Add OwnTracks over HTTP

* Fix tests
2017-09-28 09:49:35 +02:00
Pascal Vizeli 7c8e7d6eb0 Cleanup entity & remove warning (#9606)
* Cleanup entity & remove warning

* Update comment
2017-09-27 16:21:39 -07:00
Aaron Bach eb2338249f FedEx: Adds "packages" as a unit (#9588)
* Adds "packages" as a unit

* Collaborator-requested changes
2017-09-27 10:44:41 -07:00
Aaron Bach d499c18e63 Fixes UPS MyChoice exception (#9587)
* Fixes UPS MyChoice exception

* Added unit of measurement

* Collaborator-requested changes
2017-09-27 10:44:32 -07:00
William Scanlon 312de6b3a3 New Wink services. pair new device, rename, and delete, add new lock key code. Add water heater support (#9303)
* Pair new device, rename, delete, and lock key code services. Also add water heater support.

* Fixed tox
2017-09-26 23:17:55 -07:00
Pierre Ståhl 9d839f1f53 Bump pyatv to 0.3.5 (#9586) 2017-09-26 21:01:17 +02:00
Fabian Affolter 475f6f5f82 Upgrade Sphinx to 1.6.4 (#9584) 2017-09-26 13:11:10 +02:00
rbflurry fd9ceb7381 Replace emulated_hue: with emulated_hue_hidden: for consistency. (#9382)
* Update __init__.py

* fix lint errors

* Update __init__.py

* Update __init__.py

* Update __init__.py

* Update __init__.py

* Update __init__.py

Lint errors

* use get_deprecated instead to log old attr

* Updated tests to hide fan.ceiling_fan

* remove space fix lint
2017-09-26 00:31:35 -07:00
Anders Melchiorsen 154b070eae IMAP Unread sensor updated for async and push (#9562)
* IMAP Unread sensor updated for async and push

* Implement renames suggested in review

* Use async_timeout

* Keep push capability in a variable

* Reword for Hound
2017-09-26 00:26:26 -07:00
Mike Megally 8a3dcbf10f Allow customizable turn on action for LG WebOS tv (#9206)
* allow customizable action for webos tv turn on as not all models allow for WOL

* trying to fix the houndci-bot

* last few fixes hopefully

* I guess not

* last time!

* This is a breaking change. I have removed the build-in wake-on-lan functionality and have opted for a script which can be a wake-on-lan switch. I have also removed any reference to wol.

* hoping to fix formatting

* linter errors
2017-09-26 00:03:40 -07:00
joe248 bf176c405a Increase Comed timeout since it sometimes takes a long time for the API to respond (#9536)
* Increase Comed timeout since it sometimes takes a long time for the API to respond

* Rewrite ComEd sensor to use asyncio

* Fix whitespace and build issues
2017-09-25 23:43:02 -07:00
Enrique Gonzalez cf8e6d8d86 Upgrade lyft_rides to 0.2 (#9578) 2017-09-25 22:34:48 +02:00
Fabian Affolter 4a6a53c1ad Upgrade youtube_dl to 2017.9.24 (#9575) 2017-09-25 22:34:05 +02:00
Timo S 896ba7e3fa Added new statistic attributes (#9433)
* Added new statistic attributes

Added new attributes:
  - Cleaning count
  - Total cleaning time
  - Total cleaning area
  - Time left to change main brush, side brush and filter

* Code corrections

Code corrections

* Remove wronge hanging indentation

* Added new attributes

ATTR_MAIN_BRUSH_LEFT
ATTR_SIDE_BRUSH_LEFT
ATTR_FILTER_LEFT
ATTR_CLEANING_COUNT
ATTR_CLEANED_TOTAL_AREA
ATTR_CLEANING_TOTAL_TIME

* Remove trailing white space

* Corrections of the unit test for new attributes

* Hound corrections

* Init self.clean_history, self.consumable_state

* Hound correction

* - Cleaning time and total cleaning time shown in minutes
- Cleaned area and total cleaned area shown in square meters
- Main brush left, side brush left, filter left time shown in hours
- Display of the unit of measurement

* Remove trailing white spaces

* Fixed wrong continued indentation

* Fixed Hound

* Fixed Hound

* Added new statistic attributes

Added new attributes:
  - Cleaning count
  - Total cleaning time
  - Total cleaning area
  - Time left to change main brush, side brush and filter

* Code corrections

Code corrections

* Remove wronge hanging indentation

* Init self.clean_history, self.consumable_state

* Hound correction

* Remove UOM

* Merge

* Init self.clean_history, self.consumable_state

* Hound correction

* Init self.clean_history, self.consumable_state

* Hound correction

* Removed double declarations
2017-09-25 22:27:27 +02:00
Fabian Affolter fafc4a6042 Upgrade dsmr_parser to 0.11 (#9576) 2017-09-25 22:19:44 +02:00
marthoc a298b0790b MQTT Cover: Add availability topic and configurable payloads (#9445)
* MQTT Cover - Add availability_topic for online/offline status

Added topic, configurable payloads, and tests.

* Merge branch 'dev' into mqtt-cover-availability

* Revert "Merge branch 'dev' into mqtt-cover-availability"

This reverts commit 46d29794ba.

* Added newline at end of test_mqtt.py

* Fixed lint issue (newline at EOF)

* Fixed lint issue (newline at EOF)

* Updated call signature for other tests

* Fixed availability message callback
2017-09-25 19:35:11 +02:00
Paulus Schoutsen 1baf0da627 Clean up OwnTracks (#9569)
* Clean up OwnTracks

* Address comments
2017-09-25 09:05:09 -07:00
Paulus Schoutsen fc4cd39cdd Add DuckDNS component (#9556)
* Add DuckDNS component

* Address comments
2017-09-24 15:48:45 -07:00
Fabian Affolter 2486c9af35 Use simplepush module, enable event, and allow encrypted communication (#9568)
* Use simplepush module, enable event, and allow encrypted communication

* Fix check
2017-09-24 15:48:30 -07:00
Johan Bloemberg 515d1bdbd3 Add test cases and fix for device_defaults fire_event option. (#9567)
* Add test cases and fix for device_defaults fire_event option.

* Also for light.

* Change docstring mood.
2017-09-24 15:47:59 -07:00
Paul Sokolovsky ff7db218b1 Update yeelight to 0.3.3. (#9561)
Fixes basic light control in case complex transition effects were defined
on a light (possibly, externally to Home Assistant):
https://gitlab.com/stavros/python-yeelight/issues/17
2017-09-24 13:19:05 -07:00
Paulus Schoutsen 350b8e09e6 Allow specifying multiple ports for UPNP component (#9560)
* Update UPNP component

* Bump dep

* Fix flakiness in test
2017-09-24 13:08:58 -07:00
Aaron Bach 1b91218a60 Updated Arlo cameras with new attributes (#9565) 2017-09-24 21:44:34 +02:00
Aaron Bach aa0fc339c0 Various AirVisual bugfixes (#9554)
* Various AirVisual bugfixes

* Updating requirements

* Added better logging for failed data retrieval
2017-09-24 21:25:18 +02:00
Joe Lu bbf6e9ea47 Added support for ARM_NIGHT for manual_mqtt alarm (#9358)
* - Added support for ARM_NIGHT for manual_mqtt alarm

* - port "Add post_pending_state attribute to manual alarm_control_panel #9291" to manuql_mqtt

* - port "Fixed manual alarm not re-arm after 2nd trigger #9249" to manuql_mqtt

* - port "Add manual alarm_control_panel pending time per state #9264" to manuql_mqtt

* - Updated test_trigger_with_specific_pending to simulate real scenario e.g. arm the system then trigger
2017-09-24 08:57:37 +02:00
happyleavesaoc 84524e0712 fix usps? (#9557) 2017-09-24 08:28:11 +02:00
Mister Wil e2ce1d05ae Fixed bug with all switch devices being excluded (#9555) 2017-09-24 08:22:15 +02:00
Malte Franken 0d75cd484b GeoRSS sensor (#9331)
* new geo rss events sensor

* SCAN_INTERVAL instead of DEFAULT_SCAN_INTERVAL

* removed redefinition CONF_SCAN_INTERVAL

* definition of self._name not required

* removed unnecessary check and unnecessary parameter

* changed log levels

* fixed default name not used

* streamlined sensor name and entity id generation, removed unnecessary parameter

* fixed issue for entries without geometry data

* fixed tests after code changes

* simplified code

* simplified code; removed unnecessary imports

* fixed invalid variable name

* shorter sensor name and in turn entity id

* increasing test coverage for previously untested code

* fixed indentation and variable usage

* simplified test code

* merged two similar tests

* fixed an issue if no data could be fetched from external service; added test case for this case
2017-09-24 08:12:38 +02:00
Andrey 499382a9a9 Add history_graph component (#9472)
* Add support for multi-entity recent fetch of history. Add graph component

* Rename graph to history_graph. Support fast fetch without current state.

* Address comments
2017-09-23 10:01:48 -07:00
Fabian Affolter f1aef33dd6 Upgrade pyasn1 to 0.3.6 (#9548) 2017-09-23 18:32:29 +02:00
Tom Matheussen 6c0f4c35f6 Catch no longer existing process in systemmonitor (#9535)
* Catch no longer existing process in systemmonitor

* Update log message

* Again line length
2017-09-23 18:31:25 +02:00
Michael Prokop 08b0629eca Fix a bunch of typos (#9545)
s/Addres /Address /
s/Chnage/Change/
s/Converion/Conversion/
s/Supressing/Suppressing/
s/agains /against /
s/allready/already/
s/analagous/analogous/
s/aquired/acquired/
s/arbitray/arbitrary/
s/argment/argument/
s/aroung/around/
s/attibute/attribute/
s/auxillary/auxiliary/
s/befor /before /
s/commmand/command/
s/conatin/contain/
s/conection/connection/
s/coresponding/corresponding/
s/entites/entities/
s/enviroment/environment/
s/everyhing/everything/
s/expected expected/expected/
s/explicity/explicitly/
s/formated/formatted/
s/incomming/incoming/
s/informations/information/
s/inital/initial/
s/inteface/interface/
s/interupt/interrupt/
s/mimick/mimic/
s/mulitple/multiple/
s/multible/multiple/
s/occured/occurred/
s/occuring/occurring/
s/overrided/overridden/
s/overriden/overridden/
s/platfrom/platform/
s/positon/position/
s/progess/progress/
s/recieved/received/
s/reciever/receiver/
s/recieving/receiving/
s/reponse/response/
s/representaion/representation/
s/resgister/register/
s/retrive/retrieve/
s/reuqests/requests/
s/segements/segments/
s/seperated/separated/
s/sheduled/scheduled/
s/succesfully/successfully/
s/suppport/support/
s/targetting/targeting/
s/thats/that's/
s/the the/the/
s/unkown/unknown/
s/verison/version/
s/while loggin out/while logging out/
2017-09-23 17:15:46 +02:00
Pascal Vizeli 3704a18da5 Bugfix Homematic hub object (#9544)
* Bugfix Homematic hub object

* fix hass instance

* fix state unknow if 0 states
2017-09-23 15:53:48 +02:00
happyleavesaoc a8784f9adf update usps (#9540)
* update usps

* fix syntax issue
2017-09-22 21:53:16 -07:00
Alok Saboo e7c08921eb Bump python_openzwave to 0.4.0.35 (#9542)
* Bump python_openzwave to 0.4.0.35

* Cleanup
2017-09-22 19:00:35 -07:00
Mister Wil 5e35beb41a Update AbodePy to 0.11.8 (#9537)
* Update requirements_all.txt

* Update abode.py
2017-09-22 22:37:16 +02:00
Daniel Høyer Iversen e980ced0b7 flux led lib 0.20 (#9533) 2017-09-22 10:39:53 +02:00
Paulus Schoutsen fee922c4be Version bump to 0.55.0.dev0 2017-09-21 21:19:04 -07:00
398 changed files with 15349 additions and 3510 deletions
+21 -3
View File
@@ -52,7 +52,7 @@ omit =
homeassistant/components/digital_ocean.py
homeassistant/components/*/digital_ocean.py
homeassistant/components/doorbird.py
homeassistant/components/*/doorbird.py
@@ -149,6 +149,9 @@ omit =
homeassistant/components/rachio.py
homeassistant/components/*/rachio.py
homeassistant/components/raincloud.py
homeassistant/components/*/raincloud.py
homeassistant/components/raspihats.py
homeassistant/components/*/raspihats.py
@@ -167,6 +170,9 @@ omit =
homeassistant/components/scsgate.py
homeassistant/components/*/scsgate.py
homeassistant/components/skybell.py
homeassistant/components/*/skybell.py
homeassistant/components/tado.py
homeassistant/components/*/tado.py
@@ -179,8 +185,14 @@ omit =
homeassistant/components/tesla.py
homeassistant/components/*/tesla.py
homeassistant/components/thethingsnetwork.py
homeassistant/components/*/thethingsnetwork.py
homeassistant/components/*/thinkingcleaner.py
homeassistant/components/toon.py
homeassistant/components/*/toon.py
homeassistant/components/tradfri.py
homeassistant/components/*/tradfri.py
@@ -211,7 +223,7 @@ omit =
homeassistant/components/wemo.py
homeassistant/components/*/wemo.py
homeassistant/components/wink.py
homeassistant/components/wink/*
homeassistant/components/*/wink.py
homeassistant/components/xiaomi_aqara.py
@@ -261,6 +273,7 @@ omit =
homeassistant/components/camera/rpi_camera.py
homeassistant/components/camera/onvif.py
homeassistant/components/camera/synology.py
homeassistant/components/camera/yi.py
homeassistant/components/climate/eq3btsmart.py
homeassistant/components/climate/flexit.py
homeassistant/components/climate/heatmiser.py
@@ -399,6 +412,7 @@ omit =
homeassistant/components/notify/aws_sqs.py
homeassistant/components/notify/ciscospark.py
homeassistant/components/notify/clicksend.py
homeassistant/components/notify/clicksend_tts.py
homeassistant/components/notify/discord.py
homeassistant/components/notify/facebook.py
homeassistant/components/notify/free_mobile.py
@@ -420,6 +434,7 @@ omit =
homeassistant/components/notify/pushover.py
homeassistant/components/notify/pushsafer.py
homeassistant/components/notify/rest.py
homeassistant/components/notify/rocketchat.py
homeassistant/components/notify/sendgrid.py
homeassistant/components/notify/simplepush.py
homeassistant/components/notify/slack.py
@@ -522,6 +537,7 @@ omit =
homeassistant/components/sensor/sabnzbd.py
homeassistant/components/sensor/scrape.py
homeassistant/components/sensor/sensehat.py
homeassistant/components/sensor/serial.py
homeassistant/components/sensor/serial_pm.py
homeassistant/components/sensor/shodan.py
homeassistant/components/sensor/skybeacon.py
@@ -538,9 +554,11 @@ omit =
homeassistant/components/sensor/tank_utility.py
homeassistant/components/sensor/ted5000.py
homeassistant/components/sensor/temper.py
homeassistant/components/sensor/tibber.py
homeassistant/components/sensor/time_date.py
homeassistant/components/sensor/torque.py
homeassistant/components/sensor/transmission.py
homeassistant/components/sensor/travisci.py
homeassistant/components/sensor/twitch.py
homeassistant/components/sensor/uber.py
homeassistant/components/sensor/upnp.py
@@ -577,11 +595,11 @@ omit =
homeassistant/components/switch/telnet.py
homeassistant/components/switch/transmission.py
homeassistant/components/switch/wake_on_lan.py
homeassistant/components/switch/xiaomi_miio.py
homeassistant/components/telegram_bot/*
homeassistant/components/thingspeak.py
homeassistant/components/tts/amazon_polly.py
homeassistant/components/tts/picotts.py
homeassistant/components/upnp.py
homeassistant/components/vacuum/roomba.py
homeassistant/components/weather/bom.py
homeassistant/components/weather/buienradar.py
+25
View File
@@ -29,6 +29,9 @@ homeassistant/components/weblink.py @home-assistant/core
homeassistant/components/websocket_api.py @home-assistant/core
homeassistant/components/zone.py @home-assistant/core
# To monitor non-pypi additions
requirements_all.txt @andrey-git
Dockerfile @home-assistant/docker
virtualization/Docker/* @home-assistant/docker
@@ -36,6 +39,28 @@ homeassistant/components/zwave/* @home-assistant/z-wave
homeassistant/components/*/zwave.py @home-assistant/z-wave
# Indiviudal components
homeassistant/components/alarm_control_panel/egardia.py @jeroenterheerdt
homeassistant/components/camera/yi.py @bachya
homeassistant/components/climate/eq3btsmart.py @rytilahti
homeassistant/components/climate/sensibo.py @andrey-git
homeassistant/components/cover/template.py @PhracturedBlue
homeassistant/components/device_tracker/automatic.py @armills
homeassistant/components/history_graph.py @andrey-git
homeassistant/components/light/tplink.py @rytilahti
homeassistant/components/light/yeelight.py @rytilahti
homeassistant/components/media_player/kodi.py @armills
homeassistant/components/media_player/monoprice.py @etsinko
homeassistant/components/sensor/airvisual.py @bachya
homeassistant/components/sensor/miflora.py @danielhiversen
homeassistant/components/sensor/tibber.py @danielhiversen
homeassistant/components/sensor/waqi.py @andrey-git
homeassistant/components/switch/rainmachine.py @bachya
homeassistant/components/switch/tplink.py @rytilahti
homeassistant/components/*/axis.py @Kane610
homeassistant/components/*/broadlink.py @danielhiversen
homeassistant/components/*/rfxtrx.py @danielhiversen
homeassistant/components/tesla.py @zabuldon
homeassistant/components/*/tesla.py @zabuldon
homeassistant/components/*/xiaomi_aqara.py @danielhiversen
homeassistant/components/*/xiaomi_miio.py @rytilahti
+2 -4
View File
@@ -11,7 +11,6 @@ MAINTAINER Paulus Schoutsen <Paulus@PaulusSchoutsen.nl>
#ENV INSTALL_FFMPEG no
#ENV INSTALL_LIBCEC no
#ENV INSTALL_PHANTOMJS no
#ENV INSTALL_COAP_CLIENT no
#ENV INSTALL_SSOCR no
VOLUME /config
@@ -25,11 +24,10 @@ RUN virtualization/Docker/setup_docker_prereqs
# Install hass component dependencies
COPY requirements_all.txt requirements_all.txt
# Uninstall enum34 because some depenndecies install it but breaks Python 3.4+.
# Uninstall enum34 because some dependencies install it but breaks Python 3.4+.
# See PR #8103 for more info.
RUN pip3 install --no-cache-dir -r requirements_all.txt && \
pip3 install --no-cache-dir mysqlclient psycopg2 uvloop cchardet
pip3 install --no-cache-dir mysqlclient psycopg2 uvloop cchardet cython
# Copy source
COPY . .
+5 -1
View File
@@ -117,7 +117,11 @@ def linkcode_resolve(domain, info):
linespec = "#L%d" % (lineno + 1)
else:
linespec = ""
fn = relpath(fn, start='../')
index = fn.find("/homeassistant/")
if index == -1:
index = 0
fn = fn[index:]
return '{}/blob/{}/{}{}'.format(GITHUB_URL, code_branch, fn, linespec)
+14 -7
View File
@@ -11,13 +11,11 @@ from typing import Any, Optional, Dict
import voluptuous as vol
import homeassistant.components as core_components
from homeassistant import (
core, config as conf_util, loader, components as core_components)
from homeassistant.components import persistent_notification
import homeassistant.config as conf_util
import homeassistant.core as core
from homeassistant.const import EVENT_HOMEASSISTANT_CLOSE
from homeassistant.setup import async_setup_component
import homeassistant.loader as loader
from homeassistant.util.logging import AsyncHandler
from homeassistant.util.package import async_get_user_site, get_user_site
from homeassistant.util.yaml import clear_secret_cache
@@ -83,6 +81,18 @@ def async_from_config_dict(config: Dict[str, Any],
This method is a coroutine.
"""
start = time()
if enable_log:
async_enable_logging(hass, verbose, log_rotate_days, log_file)
if sys.version_info[:2] < (3, 5):
_LOGGER.warning(
'Python 3.4 support has been deprecated and will be removed in '
'the begining of 2018. Please upgrade Python or your operating '
'system. More info: https://home-assistant.io/blog/2017/10/06/'
'deprecating-python-3.4-support/'
)
core_config = config.get(core.DOMAIN, {})
try:
@@ -93,9 +103,6 @@ def async_from_config_dict(config: Dict[str, Any],
yield from hass.async_add_job(conf_util.process_ha_config_upgrade, hass)
if enable_log:
async_enable_logging(hass, verbose, log_rotate_days, log_file)
hass.config.skip_pip = skip_pip
if skip_pip:
_LOGGER.warning("Skipping pip installation of required modules. "
+20 -7
View File
@@ -10,6 +10,7 @@ Component design guidelines:
import asyncio
import itertools as it
import logging
import os
import homeassistant.core as ha
import homeassistant.config as conf_util
@@ -110,6 +111,11 @@ def async_reload_core_config(hass):
@asyncio.coroutine
def async_setup(hass, config):
"""Set up general services related to Home Assistant."""
descriptions = yield from hass.async_add_job(
conf_util.load_yaml_config_file, os.path.join(
os.path.dirname(__file__), 'services.yaml')
)
@asyncio.coroutine
def async_handle_turn_service(service):
"""Handle calls to homeassistant.turn_on/off."""
@@ -149,11 +155,14 @@ def async_setup(hass, config):
yield from asyncio.wait(tasks, loop=hass.loop)
hass.services.async_register(
ha.DOMAIN, SERVICE_TURN_OFF, async_handle_turn_service)
ha.DOMAIN, SERVICE_TURN_OFF, async_handle_turn_service,
descriptions[ha.DOMAIN][SERVICE_TURN_OFF])
hass.services.async_register(
ha.DOMAIN, SERVICE_TURN_ON, async_handle_turn_service)
ha.DOMAIN, SERVICE_TURN_ON, async_handle_turn_service,
descriptions[ha.DOMAIN][SERVICE_TURN_ON])
hass.services.async_register(
ha.DOMAIN, SERVICE_TOGGLE, async_handle_turn_service)
ha.DOMAIN, SERVICE_TOGGLE, async_handle_turn_service,
descriptions[ha.DOMAIN][SERVICE_TOGGLE])
@asyncio.coroutine
def async_handle_core_service(call):
@@ -178,11 +187,14 @@ def async_setup(hass, config):
hass.async_add_job(hass.async_stop(RESTART_EXIT_CODE))
hass.services.async_register(
ha.DOMAIN, SERVICE_HOMEASSISTANT_STOP, async_handle_core_service)
ha.DOMAIN, SERVICE_HOMEASSISTANT_STOP, async_handle_core_service,
descriptions[ha.DOMAIN][SERVICE_HOMEASSISTANT_STOP])
hass.services.async_register(
ha.DOMAIN, SERVICE_HOMEASSISTANT_RESTART, async_handle_core_service)
ha.DOMAIN, SERVICE_HOMEASSISTANT_RESTART, async_handle_core_service,
descriptions[ha.DOMAIN][SERVICE_HOMEASSISTANT_RESTART])
hass.services.async_register(
ha.DOMAIN, SERVICE_CHECK_CONFIG, async_handle_core_service)
ha.DOMAIN, SERVICE_CHECK_CONFIG, async_handle_core_service,
descriptions[ha.DOMAIN][SERVICE_CHECK_CONFIG])
@asyncio.coroutine
def async_handle_reload_config(call):
@@ -197,6 +209,7 @@ def async_setup(hass, config):
hass, conf.get(ha.DOMAIN) or {})
hass.services.async_register(
ha.DOMAIN, SERVICE_RELOAD_CORE_CONFIG, async_handle_reload_config)
ha.DOMAIN, SERVICE_RELOAD_CORE_CONFIG, async_handle_reload_config,
descriptions[ha.DOMAIN][SERVICE_RELOAD_CORE_CONFIG])
return True
+16 -18
View File
@@ -10,24 +10,23 @@ from functools import partial
from os import path
import voluptuous as vol
from requests.exceptions import HTTPError, ConnectTimeout
from homeassistant.helpers import discovery
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.config import load_yaml_config_file
from homeassistant.const import (ATTR_ATTRIBUTION, ATTR_DATE, ATTR_TIME,
ATTR_ENTITY_ID, CONF_USERNAME, CONF_PASSWORD,
CONF_EXCLUDE, CONF_NAME,
EVENT_HOMEASSISTANT_STOP,
EVENT_HOMEASSISTANT_START)
REQUIREMENTS = ['abodepy==0.11.8']
from homeassistant.config import load_yaml_config_file
from homeassistant.const import (
ATTR_ATTRIBUTION, ATTR_DATE, ATTR_TIME, ATTR_ENTITY_ID, CONF_USERNAME,
CONF_PASSWORD, CONF_EXCLUDE, CONF_NAME, CONF_LIGHTS,
EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_START)
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers import discovery
from homeassistant.helpers.entity import Entity
from requests.exceptions import HTTPError, ConnectTimeout
REQUIREMENTS = ['abodepy==0.12.1']
_LOGGER = logging.getLogger(__name__)
CONF_ATTRIBUTION = "Data provided by goabode.com"
CONF_LIGHTS = "lights"
CONF_POLLING = "polling"
CONF_POLLING = 'polling'
DOMAIN = 'abode'
@@ -93,10 +92,9 @@ class AbodeSystem(object):
def __init__(self, username, password, name, polling, exclude, lights):
"""Initialize the system."""
import abodepy
self.abode = abodepy.Abode(username, password,
auto_login=True,
get_devices=True,
get_automations=True)
self.abode = abodepy.Abode(
username, password, auto_login=True, get_devices=True,
get_automations=True)
self.name = name
self.polling = polling
self.exclude = exclude
@@ -210,7 +208,7 @@ def setup_hass_services(hass):
def setup_hass_events(hass):
"""Home assistant start and stop callbacks."""
"""Home Assistant start and stop callbacks."""
def startup(event):
"""Listen for push events."""
hass.data[DOMAIN].abode.events.start()
@@ -124,20 +124,13 @@ def async_setup(hass, config):
method = "async_{}".format(SERVICE_TO_METHOD[service.service])
update_tasks = []
for alarm in target_alarms:
yield from getattr(alarm, method)(code)
update_tasks = []
for alarm in target_alarms:
if not alarm.should_poll:
continue
update_coro = hass.async_add_job(
alarm.async_update_ha_state(True))
if hasattr(alarm, 'async_update'):
update_tasks.append(update_coro)
else:
yield from update_coro
update_tasks.append(alarm.async_update_ha_state(True))
if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)
@@ -0,0 +1,121 @@
"""
Support for Arlo Alarm Control Panels.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.arlo/
"""
import asyncio
import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.alarm_control_panel import (
AlarmControlPanel, PLATFORM_SCHEMA)
from homeassistant.components.arlo import (DATA_ARLO, CONF_ATTRIBUTION)
from homeassistant.const import (
ATTR_ATTRIBUTION, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME,
STATE_ALARM_DISARMED)
_LOGGER = logging.getLogger(__name__)
ARMED = 'armed'
CONF_HOME_MODE_NAME = 'home_mode_name'
DEPENDENCIES = ['arlo']
DISARMED = 'disarmed'
ICON = 'mdi:security'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_HOME_MODE_NAME, default=ARMED): cv.string,
})
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up the Arlo Alarm Control Panels."""
data = hass.data[DATA_ARLO]
if not data.base_stations:
return
home_mode_name = config.get(CONF_HOME_MODE_NAME)
base_stations = []
for base_station in data.base_stations:
base_stations.append(ArloBaseStation(base_station, home_mode_name))
async_add_devices(base_stations, True)
class ArloBaseStation(AlarmControlPanel):
"""Representation of an Arlo Alarm Control Panel."""
def __init__(self, data, home_mode_name):
"""Initialize the alarm control panel."""
self._base_station = data
self._home_mode_name = home_mode_name
self._state = None
@property
def icon(self):
"""Return icon."""
return ICON
@property
def state(self):
"""Return the state of the device."""
return self._state
def update(self):
"""Update the state of the device."""
# PyArlo sometimes returns None for mode. So retry 3 times before
# returning None.
num_retries = 3
i = 0
while i < num_retries:
mode = self._base_station.mode
if mode:
self._state = self._get_state_from_mode(mode)
return
i += 1
self._state = None
@asyncio.coroutine
def async_alarm_disarm(self, code=None):
"""Send disarm command."""
self._base_station.mode = DISARMED
@asyncio.coroutine
def async_alarm_arm_away(self, code=None):
"""Send arm away command."""
self._base_station.mode = ARMED
@asyncio.coroutine
def async_alarm_arm_home(self, code=None):
"""Send arm home command. Uses custom mode."""
self._base_station.mode = self._home_mode_name
@property
def name(self):
"""Return the name of the base station."""
return self._base_station.name
@property
def device_state_attributes(self):
"""Return the state attributes."""
return {
ATTR_ATTRIBUTION: CONF_ATTRIBUTION,
'device_id': self._base_station.device_id
}
def _get_state_from_mode(self, mode):
"""Convert Arlo mode to Home Assistant state."""
if mode == ARMED:
return STATE_ALARM_ARMED_AWAY
elif mode == DISARMED:
return STATE_ALARM_DISARMED
elif mode == self._home_mode_name:
return STATE_ALARM_ARMED_HOME
return None
@@ -107,7 +107,7 @@ class Concord232Alarm(alarm.AlarmControlPanel):
newstate = STATE_ALARM_ARMED_AWAY
if not newstate == self._state:
_LOGGER.info("State Chnage from %s to %s", self._state, newstate)
_LOGGER.info("State Change from %s to %s", self._state, newstate)
self._state = newstate
return self._state
@@ -18,13 +18,14 @@ from homeassistant.const import (
CONF_NAME, STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_AWAY, STATE_ALARM_TRIGGERED)
REQUIREMENTS = ['pythonegardia==1.0.20']
REQUIREMENTS = ['pythonegardia==1.0.22']
_LOGGER = logging.getLogger(__name__)
CONF_REPORT_SERVER_CODES = 'report_server_codes'
CONF_REPORT_SERVER_ENABLED = 'report_server_enabled'
CONF_REPORT_SERVER_PORT = 'report_server_port'
CONF_REPORT_SERVER_CODES_IGNORE = 'ignore'
DEFAULT_NAME = 'Egardia'
DEFAULT_PORT = 80
@@ -148,9 +149,15 @@ class EgardiaAlarm(alarm.AlarmControlPanel):
def parsestatus(self, status):
"""Parse the status."""
newstatus = ([v for k, v in STATES.items()
if status.upper() == k][0])
self._status = newstatus
_LOGGER.debug("Parsing status %s", status)
# Ignore the statuscode if it is IGNORE
if status.lower().strip() != CONF_REPORT_SERVER_CODES_IGNORE:
_LOGGER.debug("Not ignoring status")
newstatus = ([v for k, v in STATES.items()
if status.upper() == k][0])
self._status = newstatus
else:
_LOGGER.error("Ignoring status")
def update(self):
"""Update the alarm status."""
@@ -5,6 +5,7 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.manual_mqtt/
"""
import asyncio
import copy
import datetime
import logging
@@ -13,9 +14,9 @@ import voluptuous as vol
import homeassistant.components.alarm_control_panel as alarm
import homeassistant.util.dt as dt_util
from homeassistant.const import (
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED,
STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, CONF_PLATFORM,
CONF_NAME, CONF_CODE, CONF_PENDING_TIME, CONF_TRIGGER_TIME,
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT,
STATE_ALARM_DISARMED, STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED,
CONF_PLATFORM, CONF_NAME, CONF_CODE, CONF_PENDING_TIME, CONF_TRIGGER_TIME,
CONF_DISARM_AFTER_TRIGGER)
import homeassistant.components.mqtt as mqtt
@@ -28,6 +29,7 @@ from homeassistant.helpers.event import track_point_in_time
CONF_PAYLOAD_DISARM = 'payload_disarm'
CONF_PAYLOAD_ARM_HOME = 'payload_arm_home'
CONF_PAYLOAD_ARM_AWAY = 'payload_arm_away'
CONF_PAYLOAD_ARM_NIGHT = 'payload_arm_night'
DEFAULT_ALARM_NAME = 'HA Alarm'
DEFAULT_PENDING_TIME = 60
@@ -35,11 +37,32 @@ DEFAULT_TRIGGER_TIME = 120
DEFAULT_DISARM_AFTER_TRIGGER = False
DEFAULT_ARM_AWAY = 'ARM_AWAY'
DEFAULT_ARM_HOME = 'ARM_HOME'
DEFAULT_ARM_NIGHT = 'ARM_NIGHT'
DEFAULT_DISARM = 'DISARM'
SUPPORTED_PENDING_STATES = [STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_NIGHT, STATE_ALARM_TRIGGERED]
ATTR_POST_PENDING_STATE = 'post_pending_state'
def _state_validator(config):
config = copy.deepcopy(config)
for state in SUPPORTED_PENDING_STATES:
if CONF_PENDING_TIME not in config[state]:
config[state][CONF_PENDING_TIME] = config[CONF_PENDING_TIME]
return config
STATE_SETTING_SCHEMA = vol.Schema({
vol.Optional(CONF_PENDING_TIME):
vol.All(vol.Coerce(int), vol.Range(min=0))
})
DEPENDENCIES = ['mqtt']
PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({
PLATFORM_SCHEMA = vol.Schema(vol.All(mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({
vol.Required(CONF_PLATFORM): 'manual_mqtt',
vol.Optional(CONF_NAME, default=DEFAULT_ALARM_NAME): cv.string,
vol.Optional(CONF_CODE): cv.string,
@@ -49,12 +72,17 @@ PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({
vol.All(vol.Coerce(int), vol.Range(min=1)),
vol.Optional(CONF_DISARM_AFTER_TRIGGER,
default=DEFAULT_DISARM_AFTER_TRIGGER): cv.boolean,
vol.Optional(STATE_ALARM_ARMED_AWAY, default={}): STATE_SETTING_SCHEMA,
vol.Optional(STATE_ALARM_ARMED_HOME, default={}): STATE_SETTING_SCHEMA,
vol.Optional(STATE_ALARM_ARMED_NIGHT, default={}): STATE_SETTING_SCHEMA,
vol.Optional(STATE_ALARM_TRIGGERED, default={}): STATE_SETTING_SCHEMA,
vol.Required(mqtt.CONF_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Required(mqtt.CONF_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_PAYLOAD_ARM_AWAY, default=DEFAULT_ARM_AWAY): cv.string,
vol.Optional(CONF_PAYLOAD_ARM_HOME, default=DEFAULT_ARM_HOME): cv.string,
vol.Optional(CONF_PAYLOAD_ARM_NIGHT, default=DEFAULT_ARM_NIGHT): cv.string,
vol.Optional(CONF_PAYLOAD_DISARM, default=DEFAULT_DISARM): cv.string,
})
}), _state_validator))
_LOGGER = logging.getLogger(__name__)
@@ -73,7 +101,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
config.get(mqtt.CONF_QOS),
config.get(CONF_PAYLOAD_DISARM),
config.get(CONF_PAYLOAD_ARM_HOME),
config.get(CONF_PAYLOAD_ARM_AWAY))])
config.get(CONF_PAYLOAD_ARM_AWAY),
config.get(CONF_PAYLOAD_ARM_NIGHT),
config)])
class ManualMQTTAlarm(alarm.AlarmControlPanel):
@@ -89,7 +119,8 @@ class ManualMQTTAlarm(alarm.AlarmControlPanel):
def __init__(self, hass, name, code, pending_time,
trigger_time, disarm_after_trigger,
state_topic, command_topic, qos,
payload_disarm, payload_arm_home, payload_arm_away):
payload_disarm, payload_arm_home, payload_arm_away,
payload_arm_night, config):
"""Init the manual MQTT alarm panel."""
self._state = STATE_ALARM_DISARMED
self._hass = hass
@@ -101,12 +132,18 @@ class ManualMQTTAlarm(alarm.AlarmControlPanel):
self._pre_trigger_state = self._state
self._state_ts = None
self._pending_time_by_state = {}
for state in SUPPORTED_PENDING_STATES:
self._pending_time_by_state[state] = datetime.timedelta(
seconds=config[state][CONF_PENDING_TIME])
self._state_topic = state_topic
self._command_topic = command_topic
self._qos = qos
self._payload_disarm = payload_disarm
self._payload_arm_home = payload_arm_home
self._payload_arm_away = payload_arm_away
self._payload_arm_night = payload_arm_night
@property
def should_poll(self):
@@ -121,23 +158,27 @@ class ManualMQTTAlarm(alarm.AlarmControlPanel):
@property
def state(self):
"""Return the state of the device."""
if self._state in (STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_AWAY) and \
self._pending_time and self._state_ts + self._pending_time > \
dt_util.utcnow():
return STATE_ALARM_PENDING
if self._state == STATE_ALARM_TRIGGERED and self._trigger_time:
if self._state_ts + self._pending_time > dt_util.utcnow():
if self._within_pending_time(self._state):
return STATE_ALARM_PENDING
elif (self._state_ts + self._pending_time +
elif (self._state_ts + self._pending_time_by_state[self._state] +
self._trigger_time) < dt_util.utcnow():
if self._disarm_after_trigger:
return STATE_ALARM_DISARMED
return self._pre_trigger_state
else:
self._state = self._pre_trigger_state
return self._state
if self._state in SUPPORTED_PENDING_STATES and \
self._within_pending_time(self._state):
return STATE_ALARM_PENDING
return self._state
def _within_pending_time(self, state):
pending_time = self._pending_time_by_state[state]
return self._state_ts + pending_time > dt_util.utcnow()
@property
def code_format(self):
"""One or more characters."""
@@ -157,44 +198,47 @@ class ManualMQTTAlarm(alarm.AlarmControlPanel):
if not self._validate_code(code, STATE_ALARM_ARMED_HOME):
return
self._state = STATE_ALARM_ARMED_HOME
self._state_ts = dt_util.utcnow()
self.schedule_update_ha_state()
if self._pending_time:
track_point_in_time(
self._hass, self.async_update_ha_state,
self._state_ts + self._pending_time)
self._update_state(STATE_ALARM_ARMED_HOME)
def alarm_arm_away(self, code=None):
"""Send arm away command."""
if not self._validate_code(code, STATE_ALARM_ARMED_AWAY):
return
self._state = STATE_ALARM_ARMED_AWAY
self._state_ts = dt_util.utcnow()
self.schedule_update_ha_state()
self._update_state(STATE_ALARM_ARMED_AWAY)
if self._pending_time:
track_point_in_time(
self._hass, self.async_update_ha_state,
self._state_ts + self._pending_time)
def alarm_arm_night(self, code=None):
"""Send arm night command."""
if not self._validate_code(code, STATE_ALARM_ARMED_NIGHT):
return
self._update_state(STATE_ALARM_ARMED_NIGHT)
def alarm_trigger(self, code=None):
"""Send alarm trigger command. No code needed."""
self._pre_trigger_state = self._state
self._state = STATE_ALARM_TRIGGERED
self._update_state(STATE_ALARM_TRIGGERED)
def _update_state(self, state):
self._state = state
self._state_ts = dt_util.utcnow()
self.schedule_update_ha_state()
if self._trigger_time:
pending_time = self._pending_time_by_state[state]
if state == STATE_ALARM_TRIGGERED and self._trigger_time:
track_point_in_time(
self._hass, self.async_update_ha_state,
self._state_ts + self._pending_time)
self._state_ts + pending_time)
track_point_in_time(
self._hass, self.async_update_ha_state,
self._state_ts + self._pending_time + self._trigger_time)
self._state_ts + self._trigger_time + pending_time)
elif state in SUPPORTED_PENDING_STATES and pending_time:
track_point_in_time(
self._hass, self.async_update_ha_state,
self._state_ts + pending_time)
def _validate_code(self, code, state):
"""Validate given code."""
@@ -203,6 +247,16 @@ class ManualMQTTAlarm(alarm.AlarmControlPanel):
_LOGGER.warning("Invalid code given for %s", state)
return check
@property
def device_state_attributes(self):
"""Return the state attributes."""
state_attr = {}
if self.state == STATE_ALARM_PENDING:
state_attr[ATTR_POST_PENDING_STATE] = self._state
return state_attr
def async_added_to_hass(self):
"""Subscribe mqtt events.
@@ -221,6 +275,8 @@ class ManualMQTTAlarm(alarm.AlarmControlPanel):
self.async_alarm_arm_home(self._code)
elif payload == self._payload_arm_away:
self.async_alarm_arm_away(self._code)
elif payload == self._payload_arm_night:
self.async_alarm_arm_night(self._code)
else:
_LOGGER.warning("Received unexpected payload: %s", payload)
return
+91 -73
View File
@@ -6,88 +6,100 @@ from uuid import uuid4
from homeassistant.const import (
ATTR_SUPPORTED_FEATURES, ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF)
from homeassistant.components import switch, light
from homeassistant.util.decorator import Registry
HANDLERS = Registry()
_LOGGER = logging.getLogger(__name__)
ATTR_HEADER = 'header'
ATTR_NAME = 'name'
ATTR_NAMESPACE = 'namespace'
ATTR_MESSAGE_ID = 'messageId'
ATTR_PAYLOAD = 'payload'
ATTR_PAYLOAD_VERSION = 'payloadVersion'
API_DIRECTIVE = 'directive'
API_EVENT = 'event'
API_HEADER = 'header'
API_PAYLOAD = 'payload'
API_ENDPOINT = 'endpoint'
MAPPING_COMPONENT = {
switch.DOMAIN: ['SWITCH', ('turnOff', 'turnOn'), None],
switch.DOMAIN: ['SWITCH', ('Alexa.PowerController',), None],
light.DOMAIN: [
'LIGHT', ('turnOff', 'turnOn'), {
light.SUPPORT_BRIGHTNESS: 'setPercentage'
'LIGHT', ('Alexa.PowerController',), {
light.SUPPORT_BRIGHTNESS: 'Alexa.BrightnessController'
}
],
}
def mapping_api_function(name):
"""Return function pointer to api function for name.
Async friendly.
"""
mapping = {
'DiscoverAppliancesRequest': async_api_discovery,
'TurnOnRequest': async_api_turn_on,
'TurnOffRequest': async_api_turn_off,
'SetPercentageRequest': async_api_set_percentage,
}
return mapping.get(name, None)
@asyncio.coroutine
def async_handle_message(hass, message):
"""Handle incomming API messages."""
assert int(message[ATTR_HEADER][ATTR_PAYLOAD_VERSION]) == 2
"""Handle incoming API messages."""
assert message[API_DIRECTIVE][API_HEADER]['payloadVersion'] == '3'
# Read head data
message = message[API_DIRECTIVE]
namespace = message[API_HEADER]['namespace']
name = message[API_HEADER]['name']
# Do we support this API request?
funct_ref = mapping_api_function(message[ATTR_HEADER][ATTR_NAME])
funct_ref = HANDLERS.get((namespace, name))
if not funct_ref:
_LOGGER.warning(
"Unsupported API request %s", message[ATTR_HEADER][ATTR_NAME])
"Unsupported API request %s/%s", namespace, name)
return api_error(message)
return (yield from funct_ref(hass, message))
def api_message(name, namespace, payload=None):
"""Create a API formated response message.
def api_message(request, name='Response', namespace='Alexa', payload=None):
"""Create a API formatted response message.
Async friendly.
"""
payload = payload or {}
return {
ATTR_HEADER: {
ATTR_MESSAGE_ID: uuid4(),
ATTR_NAME: name,
ATTR_NAMESPACE: namespace,
ATTR_PAYLOAD_VERSION: '2',
},
ATTR_PAYLOAD: payload,
response = {
API_EVENT: {
API_HEADER: {
'namespace': namespace,
'name': name,
'messageId': str(uuid4()),
'payloadVersion': '3',
},
API_PAYLOAD: payload,
}
}
# If a correlation token exsits, add it to header / Need by Async requests
token = request[API_HEADER].get('correlationToken')
if token:
response[API_EVENT][API_HEADER]['correlationToken'] = token
def api_error(request, exc='DriverInternalError'):
"""Create a API formated error response.
# Extend event with endpoint object / Need by Async requests
if API_ENDPOINT in request:
response[API_EVENT][API_ENDPOINT] = request[API_ENDPOINT].copy()
return response
def api_error(request, error_type='INTERNAL_ERROR', error_message=""):
"""Create a API formatted error response.
Async friendly.
"""
return api_message(exc, request[ATTR_HEADER][ATTR_NAMESPACE])
payload = {
'type': error_type,
'message': error_message,
}
return api_message(request, name='ErrorResponse', payload=payload)
@HANDLERS.register(('Alexa.Discovery', 'Discover'))
@asyncio.coroutine
def async_api_discovery(hass, request):
"""Create a API formated discovery response.
"""Create a API formatted discovery response.
Async friendly.
"""
discovered_appliances = []
discovery_endpoints = []
for entity in hass.states.async_all():
class_data = MAPPING_COMPONENT.get(entity.domain)
@@ -95,35 +107,42 @@ def async_api_discovery(hass, request):
if not class_data:
continue
appliance = {
'actions': [],
'applianceTypes': [class_data[0]],
endpoint = {
'displayCategories': [class_data[0]],
'additionalApplianceDetails': {},
'applianceId': entity.entity_id.replace('.', '#'),
'friendlyDescription': '',
'endpointId': entity.entity_id.replace('.', '#'),
'friendlyName': entity.name,
'isReachable': True,
'description': '',
'manufacturerName': 'Unknown',
'modelName': 'Unknown',
'version': 'Unknown',
}
actions = set()
# static actions
if class_data[1]:
appliance['actions'].extend(list(class_data[1]))
actions |= set(class_data[1])
# dynamic actions
if class_data[2]:
supported = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
for feature, action_name in class_data[2].items():
if feature & supported > 0:
appliance['actions'].append(action_name)
actions.add(action_name)
discovered_appliances.append(appliance)
# Write action into capabilities
capabilities = []
for action in actions:
capabilities.append({
'type': 'AlexaInterface',
'interface': action,
'version': 3,
})
endpoint['capabilities'] = capabilities
discovery_endpoints.append(endpoint)
return api_message(
'DiscoverAppliancesResponse', 'Alexa.ConnectedHome.Discovery',
payload={'discoveredAppliances': discovered_appliances})
request, name='Discover.Response', namespace='Alexa.Discovery',
payload={'endpoints': discovery_endpoints})
def extract_entity(funct):
@@ -131,21 +150,21 @@ def extract_entity(funct):
@asyncio.coroutine
def async_api_entity_wrapper(hass, request):
"""Process a turn on request."""
entity_id = \
request[ATTR_PAYLOAD]['appliance']['applianceId'].replace('#', '.')
entity_id = request[API_ENDPOINT]['endpointId'].replace('#', '.')
# extract state object
entity = hass.states.get(entity_id)
if not entity:
_LOGGER.error("Can't process %s for %s",
request[ATTR_HEADER][ATTR_NAME], entity_id)
return api_error(request)
request[API_HEADER]['name'], entity_id)
return api_error(request, error_type='NO_SUCH_ENDPOINT')
return (yield from funct(hass, request, entity))
return async_api_entity_wrapper
@HANDLERS.register(('Alexa.PowerController', 'TurnOn'))
@extract_entity
@asyncio.coroutine
def async_api_turn_on(hass, request, entity):
@@ -154,9 +173,10 @@ def async_api_turn_on(hass, request, entity):
ATTR_ENTITY_ID: entity.entity_id
}, blocking=True)
return api_message('TurnOnConfirmation', 'Alexa.ConnectedHome.Control')
return api_message(request)
@HANDLERS.register(('Alexa.PowerController', 'TurnOff'))
@extract_entity
@asyncio.coroutine
def async_api_turn_off(hass, request, entity):
@@ -165,21 +185,19 @@ def async_api_turn_off(hass, request, entity):
ATTR_ENTITY_ID: entity.entity_id
}, blocking=True)
return api_message('TurnOffConfirmation', 'Alexa.ConnectedHome.Control')
return api_message(request)
@HANDLERS.register(('Alexa.BrightnessController', 'SetBrightness'))
@extract_entity
@asyncio.coroutine
def async_api_set_percentage(hass, request, entity):
"""Process a set percentage request."""
if entity.domain == light.DOMAIN:
brightness = request[ATTR_PAYLOAD]['percentageState']['value']
yield from hass.services.async_call(entity.domain, SERVICE_TURN_ON, {
ATTR_ENTITY_ID: entity.entity_id,
light.ATTR_BRIGHTNESS: brightness,
}, blocking=True)
else:
return api_error(request)
def async_api_set_brightness(hass, request, entity):
"""Process a set brightness request."""
brightness = request[API_PAYLOAD]['brightness']
return api_message(
'SetPercentageConfirmation', 'Alexa.ConnectedHome.Control')
yield from hass.services.async_call(entity.domain, SERVICE_TURN_ON, {
ATTR_ENTITY_ID: entity.entity_id,
light.ATTR_BRIGHTNESS: brightness,
}, blocking=True)
return api_message(request)
+1 -1
View File
@@ -18,7 +18,7 @@ from homeassistant.helpers import discovery
from homeassistant.components.discovery import SERVICE_APPLE_TV
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pyatv==0.3.4']
REQUIREMENTS = ['pyatv==0.3.5']
_LOGGER = logging.getLogger(__name__)
+3 -3
View File
@@ -1,5 +1,5 @@
"""
This component provides basic support for Netgear Arlo IP cameras.
This component provides support for Netgear Arlo IP cameras.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/arlo/
@@ -12,7 +12,7 @@ from requests.exceptions import HTTPError, ConnectTimeout
from homeassistant.helpers import config_validation as cv
from homeassistant.const import CONF_USERNAME, CONF_PASSWORD
REQUIREMENTS = ['pyarlo==0.0.4']
REQUIREMENTS = ['pyarlo==0.0.7']
_LOGGER = logging.getLogger(__name__)
@@ -23,7 +23,7 @@ DEFAULT_BRAND = 'Netgear Arlo'
DOMAIN = 'arlo'
NOTIFICATION_ID = 'arlo_notification'
NOTIFICATION_TITLE = 'Arlo Camera Setup'
NOTIFICATION_TITLE = 'Arlo Component Setup'
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
+17 -10
View File
@@ -21,7 +21,7 @@ _LOGGER = logging.getLogger(__name__)
TRIGGER_SCHEMA = vol.Schema({
vol.Required(CONF_PLATFORM): 'event',
vol.Required(CONF_EVENT_TYPE): cv.string,
vol.Optional(CONF_EVENT_DATA): dict,
vol.Optional(CONF_EVENT_DATA, default={}): dict,
})
@@ -29,18 +29,25 @@ TRIGGER_SCHEMA = vol.Schema({
def async_trigger(hass, config, action):
"""Listen for events based on configuration."""
event_type = config.get(CONF_EVENT_TYPE)
event_data = config.get(CONF_EVENT_DATA)
event_data_schema = vol.Schema(
config.get(CONF_EVENT_DATA),
extra=vol.ALLOW_EXTRA) if config.get(CONF_EVENT_DATA) else None
@callback
def handle_event(event):
"""Listen for events and calls the action when data matches."""
if not event_data or all(val == event.data.get(key) for key, val
in event_data.items()):
hass.async_run_job(action, {
'trigger': {
'platform': 'event',
'event': event,
},
})
if event_data_schema:
try:
event_data_schema(event.data)
except vol.Invalid:
# If event data doesn't match requested schema, skip event
return
hass.async_run_job(action, {
'trigger': {
'platform': 'event',
'event': event,
},
})
return hass.bus.async_listen(event_type, handle_event)
@@ -99,8 +99,8 @@ def async_trigger(hass, config, action):
return
async_remove_track_same = async_track_same_state(
hass, True, time_delta, call_action, entity_ids=entity_id,
async_check_func=check_numeric_state)
hass, time_delta, call_action, entity_ids=entity_id,
async_check_same_func=check_numeric_state)
unsub = async_track_state_change(
hass, entity_id, state_automation_listener)
+3 -1
View File
@@ -65,7 +65,9 @@ def async_trigger(hass, config, action):
return
async_remove_track_same = async_track_same_state(
hass, to_s.state, time_delta, call_action, entity_ids=entity_id)
hass, time_delta, call_action,
lambda _, _2, to_state: to_state.state == to_s.state,
entity_ids=entity_id)
unsub = async_track_state_change(
hass, entity_id, state_automation_listener, from_state, to_state)
@@ -55,12 +55,12 @@ class InsteonPLMBinarySensorDevice(BinarySensorDevice):
@property
def address(self):
"""Return the the address of the node."""
"""Return the address of the node."""
return self._address
@property
def name(self):
"""Return the the name of the node."""
"""Return the name of the node."""
return self._name
@property
@@ -13,7 +13,8 @@ import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA)
from homeassistant.const import (CONF_NAME, ATTR_LONGITUDE, ATTR_LATITUDE)
from homeassistant.const import (
CONF_NAME, ATTR_LONGITUDE, ATTR_LATITUDE, CONF_SHOW_ON_MAP)
from homeassistant.util import Throttle
REQUIREMENTS = ['pyiss==1.0.1']
@@ -23,8 +24,6 @@ _LOGGER = logging.getLogger(__name__)
ATTR_ISS_NEXT_RISE = 'next_rise'
ATTR_ISS_NUMBER_PEOPLE_SPACE = 'number_of_people_in_space'
CONF_SHOW_ON_MAP = 'show_on_map'
DEFAULT_NAME = 'ISS'
DEFAULT_DEVICE_CLASS = 'visible'
@@ -14,7 +14,7 @@ from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA)
from homeassistant.components.netatmo import CameraData
from homeassistant.loader import get_component
from homeassistant.const import CONF_TIMEOUT, CONF_OFFSET
from homeassistant.const import CONF_TIMEOUT
from homeassistant.helpers import config_validation as cv
_LOGGER = logging.getLogger(__name__)
@@ -44,14 +44,12 @@ CONF_WELCOME_SENSORS = 'welcome_sensors'
CONF_PRESENCE_SENSORS = 'presence_sensors'
CONF_TAG_SENSORS = 'tag_sensors'
DEFAULT_TIMEOUT = 15
DEFAULT_OFFSET = 90
DEFAULT_TIMEOUT = 90
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_CAMERAS, default=[]):
vol.All(cv.ensure_list, [cv.string]),
vol.Optional(CONF_HOME): cv.string,
vol.Optional(CONF_OFFSET, default=DEFAULT_OFFSET): cv.positive_int,
vol.Optional(CONF_PRESENCE_SENSORS, default=PRESENCE_SENSOR_TYPES):
vol.All(cv.ensure_list, [vol.In(PRESENCE_SENSOR_TYPES)]),
vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
@@ -66,7 +64,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
netatmo = get_component('netatmo')
home = config.get(CONF_HOME)
timeout = config.get(CONF_TIMEOUT)
offset = config.get(CONF_OFFSET)
if timeout is None:
timeout = DEFAULT_TIMEOUT
module_name = None
@@ -94,7 +93,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
for variable in welcome_sensors:
add_devices([NetatmoBinarySensor(
data, camera_name, module_name, home, timeout,
offset, camera_type, variable)], True)
camera_type, variable)], True)
if camera_type == 'NOC':
if CONF_CAMERAS in config:
if config[CONF_CAMERAS] != [] and \
@@ -102,14 +101,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
continue
for variable in presence_sensors:
add_devices([NetatmoBinarySensor(
data, camera_name, module_name, home, timeout, offset,
data, camera_name, module_name, home, timeout,
camera_type, variable)], True)
for module_name in data.get_module_names(camera_name):
for variable in tag_sensors:
camera_type = None
add_devices([NetatmoBinarySensor(
data, camera_name, module_name, home, timeout, offset,
data, camera_name, module_name, home, timeout,
camera_type, variable)], True)
@@ -117,14 +116,13 @@ class NetatmoBinarySensor(BinarySensorDevice):
"""Represent a single binary sensor in a Netatmo Camera device."""
def __init__(self, data, camera_name, module_name, home,
timeout, offset, camera_type, sensor):
timeout, camera_type, sensor):
"""Set up for access to the Netatmo camera events."""
self._data = data
self._camera_name = camera_name
self._module_name = module_name
self._home = home
self._timeout = timeout
self._offset = offset
if home:
self._name = '{} / {}'.format(home, camera_name)
else:
@@ -173,40 +171,39 @@ class NetatmoBinarySensor(BinarySensorDevice):
if self._sensor_name == "Someone known":
self._state =\
self._data.camera_data.someoneKnownSeen(
self._home, self._camera_name, self._timeout*60)
self._home, self._camera_name, self._timeout)
elif self._sensor_name == "Someone unknown":
self._state =\
self._data.camera_data.someoneUnknownSeen(
self._home, self._camera_name, self._timeout*60)
self._home, self._camera_name, self._timeout)
elif self._sensor_name == "Motion":
self._state =\
self._data.camera_data.motionDetected(
self._home, self._camera_name, self._timeout*60)
self._home, self._camera_name, self._timeout)
elif self._cameratype == 'NOC':
if self._sensor_name == "Outdoor motion":
self._state =\
self._data.camera_data.outdoormotionDetected(
self._home, self._camera_name, self._offset)
self._home, self._camera_name, self._timeout)
elif self._sensor_name == "Outdoor human":
self._state =\
self._data.camera_data.humanDetected(
self._home, self._camera_name, self._offset)
self._home, self._camera_name, self._timeout)
elif self._sensor_name == "Outdoor animal":
self._state =\
self._data.camera_data.animalDetected(
self._home, self._camera_name, self._offset)
self._home, self._camera_name, self._timeout)
elif self._sensor_name == "Outdoor vehicle":
self._state =\
self._data.camera_data.carDetected(
self._home, self._camera_name, self._offset)
self._home, self._camera_name, self._timeout)
if self._sensor_name == "Tag Vibration":
self._state =\
self._data.camera_data.moduleMotionDetected(
self._home, self._module_name, self._camera_name,
self._timeout*60)
self._timeout)
elif self._sensor_name == "Tag Open":
self._state =\
self._data.camera_data.moduleOpened(
self._home, self._module_name, self._camera_name)
else:
return None
self._home, self._module_name, self._camera_name,
self._timeout)
@@ -0,0 +1,72 @@
"""
Support for Melnor RainCloud sprinkler water timer.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.raincloud/
"""
import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.raincloud import (
BINARY_SENSORS, DATA_RAINCLOUD, ICON_MAP, RainCloudEntity)
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA)
from homeassistant.const import CONF_MONITORED_CONDITIONS
DEPENDENCIES = ['raincloud']
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_MONITORED_CONDITIONS, default=list(BINARY_SENSORS)):
vol.All(cv.ensure_list, [vol.In(BINARY_SENSORS)]),
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up a sensor for a raincloud device."""
raincloud = hass.data[DATA_RAINCLOUD].data
sensors = []
for sensor_type in config.get(CONF_MONITORED_CONDITIONS):
if sensor_type == 'status':
sensors.append(
RainCloudBinarySensor(raincloud.controller, sensor_type))
sensors.append(
RainCloudBinarySensor(raincloud.controller.faucet,
sensor_type))
else:
# create an sensor for each zone managed by faucet
for zone in raincloud.controller.faucet.zones:
sensors.append(RainCloudBinarySensor(zone, sensor_type))
add_devices(sensors, True)
return True
class RainCloudBinarySensor(RainCloudEntity, BinarySensorDevice):
"""A sensor implementation for raincloud device."""
@property
def is_on(self):
"""Return true if the binary sensor is on."""
return self._state
def update(self):
"""Get the latest data and updates the state."""
_LOGGER.debug("Updating RainCloud sensor: %s", self._name)
self._state = getattr(self.data, self._sensor_type)
if self._sensor_type == 'status':
self._state = self._state == 'Online'
@property
def icon(self):
"""Return the icon of this device."""
if self._sensor_type == 'is_watering':
return 'mdi:water' if self.is_on else 'mdi:water-off'
elif self._sensor_type == 'status':
return 'mdi:pipe' if self.is_on else 'mdi:pipe-disconnected'
return ICON_MAP.get(self._sensor_type)
@@ -0,0 +1,97 @@
"""
Binary sensor support for the Skybell HD Doorbell.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.skybell/
"""
from datetime import timedelta
import logging
import voluptuous as vol
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA)
from homeassistant.components.skybell import (
DEFAULT_ENTITY_NAMESPACE, DOMAIN as SKYBELL_DOMAIN, SkybellDevice)
from homeassistant.const import (
CONF_ENTITY_NAMESPACE, CONF_MONITORED_CONDITIONS)
import homeassistant.helpers.config_validation as cv
DEPENDENCIES = ['skybell']
_LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(seconds=5)
# Sensor types: Name, device_class, event
SENSOR_TYPES = {
'button': ['Button', 'occupancy', 'device:sensor:button'],
'motion': ['Motion', 'motion', 'device:sensor:motion'],
}
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_ENTITY_NAMESPACE, default=DEFAULT_ENTITY_NAMESPACE):
cv.string,
vol.Required(CONF_MONITORED_CONDITIONS, default=[]):
vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]),
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the platform for a Skybell device."""
skybell = hass.data.get(SKYBELL_DOMAIN)
sensors = []
for sensor_type in config.get(CONF_MONITORED_CONDITIONS):
for device in skybell.get_devices():
sensors.append(SkybellBinarySensor(device, sensor_type))
add_devices(sensors, True)
class SkybellBinarySensor(SkybellDevice, BinarySensorDevice):
"""A binary sensor implementation for Skybell devices."""
def __init__(self, device, sensor_type):
"""Initialize a binary sensor for a Skybell device."""
super().__init__(device)
self._sensor_type = sensor_type
self._name = "{0} {1}".format(self._device.name,
SENSOR_TYPES[self._sensor_type][0])
self._device_class = SENSOR_TYPES[self._sensor_type][1]
self._event = {}
self._state = None
@property
def name(self):
"""Return the name of the sensor."""
return self._name
@property
def is_on(self):
"""Return True if the binary sensor is on."""
return self._state
@property
def device_class(self):
"""Return the class of the binary sensor."""
return self._device_class
@property
def device_state_attributes(self):
"""Return the state attributes."""
attrs = super().device_state_attributes
attrs['event_date'] = self._event.get('createdAt')
return attrs
def update(self):
"""Get the latest data and updates the state."""
super().update()
event = self._device.latest(SENSOR_TYPES[self._sensor_type][2])
self._state = bool(event and event.get('id') != self._event.get('id'))
self._event = event
@@ -15,13 +15,12 @@ from homeassistant.components.binary_sensor import (
DEVICE_CLASSES_SCHEMA)
from homeassistant.const import (
ATTR_FRIENDLY_NAME, ATTR_ENTITY_ID, CONF_VALUE_TEMPLATE,
CONF_SENSORS, CONF_DEVICE_CLASS, EVENT_HOMEASSISTANT_START, STATE_ON)
CONF_SENSORS, CONF_DEVICE_CLASS, EVENT_HOMEASSISTANT_START)
from homeassistant.exceptions import TemplateError
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import async_generate_entity_id
from homeassistant.helpers.event import (
async_track_state_change, async_track_same_state)
from homeassistant.helpers.restore_state import async_get_last_state
_LOGGER = logging.getLogger(__name__)
@@ -94,10 +93,6 @@ class BinarySensorTemplate(BinarySensorDevice):
@asyncio.coroutine
def async_added_to_hass(self):
"""Register callbacks."""
state = yield from async_get_last_state(self.hass, self.entity_id)
if state:
self._state = state.state == STATE_ON
@callback
def template_bsensor_state_listener(entity, old_state, new_state):
"""Handle the target device state changes."""
@@ -135,7 +130,7 @@ class BinarySensorTemplate(BinarySensorDevice):
return False
@callback
def _async_render(self, *args):
def _async_render(self):
"""Get the state of template."""
try:
return self._template.async_render().lower() == 'true'
@@ -171,5 +166,5 @@ class BinarySensorTemplate(BinarySensorDevice):
period = self._delay_on if state else self._delay_off
async_track_same_state(
self.hass, state, period, set_state, entity_ids=self._entities,
async_check_func=self._async_render)
self.hass, period, set_state, entity_ids=self._entities,
async_check_same_func=lambda *args: self._async_render() == state)
@@ -30,7 +30,6 @@ class TeslaBinarySensor(TeslaDevice, BinarySensorDevice):
def __init__(self, tesla_device, controller, sensor_type):
"""Initialisation of binary sensor."""
super().__init__(tesla_device, controller)
self._name = self.tesla_device.name
self._state = False
self.entity_id = ENTITY_ID_FORMAT.format(self.tesla_id)
self._sensor_type = sensor_type
@@ -20,15 +20,18 @@ from homeassistant.helpers.event import async_track_state_change
_LOGGER = logging.getLogger(__name__)
ATTR_HYSTERESIS = 'hysteresis'
ATTR_SENSOR_VALUE = 'sensor_value'
ATTR_THRESHOLD = 'threshold'
ATTR_TYPE = 'type'
CONF_HYSTERESIS = 'hysteresis'
CONF_LOWER = 'lower'
CONF_THRESHOLD = 'threshold'
CONF_UPPER = 'upper'
DEFAULT_NAME = 'Threshold'
DEFAULT_HYSTERESIS = 0.0
SENSOR_TYPES = [CONF_LOWER, CONF_UPPER]
@@ -36,6 +39,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_ENTITY_ID): cv.entity_id,
vol.Required(CONF_THRESHOLD): vol.Coerce(float),
vol.Required(CONF_TYPE): vol.In(SENSOR_TYPES),
vol.Optional(
CONF_HYSTERESIS, default=DEFAULT_HYSTERESIS): vol.Coerce(float),
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
})
@@ -47,28 +52,32 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
entity_id = config.get(CONF_ENTITY_ID)
name = config.get(CONF_NAME)
threshold = config.get(CONF_THRESHOLD)
hysteresis = config.get(CONF_HYSTERESIS)
limit_type = config.get(CONF_TYPE)
device_class = config.get(CONF_DEVICE_CLASS)
async_add_devices(
[ThresholdSensor(hass, entity_id, name, threshold, limit_type,
device_class)], True)
async_add_devices([ThresholdSensor(
hass, entity_id, name, threshold,
hysteresis, limit_type, device_class)
], True)
return True
class ThresholdSensor(BinarySensorDevice):
"""Representation of a Threshold sensor."""
def __init__(self, hass, entity_id, name, threshold, limit_type,
device_class):
def __init__(self, hass, entity_id, name, threshold,
hysteresis, limit_type, device_class):
"""Initialize the Threshold sensor."""
self._hass = hass
self._entity_id = entity_id
self.is_upper = limit_type == 'upper'
self._name = name
self._threshold = threshold
self._hysteresis = hysteresis
self._device_class = device_class
self._deviation = False
self._state = False
self.sensor_value = 0
@callback
@@ -97,7 +106,7 @@ class ThresholdSensor(BinarySensorDevice):
@property
def is_on(self):
"""Return true if sensor is on."""
return self._deviation
return self._state
@property
def should_poll(self):
@@ -116,13 +125,16 @@ class ThresholdSensor(BinarySensorDevice):
ATTR_ENTITY_ID: self._entity_id,
ATTR_SENSOR_VALUE: self.sensor_value,
ATTR_THRESHOLD: self._threshold,
ATTR_HYSTERESIS: self._hysteresis,
ATTR_TYPE: CONF_UPPER if self.is_upper else CONF_LOWER,
}
@asyncio.coroutine
def async_update(self):
"""Get the latest data and updates the states."""
if self.is_upper:
self._deviation = bool(self.sensor_value > self._threshold)
else:
self._deviation = bool(self.sensor_value < self._threshold)
if self._hysteresis == 0 and self.sensor_value == self._threshold:
self._state = False
elif self.sensor_value > (self._threshold + self._hysteresis):
self._state = self.is_upper
elif self.sensor_value < (self._threshold - self._hysteresis):
self._state = not self.is_upper
+24 -19
View File
@@ -9,7 +9,6 @@ import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.wink import WinkDevice, DOMAIN
from homeassistant.helpers.entity import Entity
_LOGGER = logging.getLogger(__name__)
@@ -87,7 +86,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
_LOGGER.info("Device isn't a sensor, skipping")
class WinkBinarySensorDevice(WinkDevice, BinarySensorDevice, Entity):
class WinkBinarySensorDevice(WinkDevice, BinarySensorDevice):
"""Representation of a Wink binary sensor."""
def __init__(self, wink, hass):
@@ -117,6 +116,11 @@ class WinkBinarySensorDevice(WinkDevice, BinarySensorDevice, Entity):
"""Return the class of this sensor, from DEVICE_CLASSES."""
return SENSOR_TYPES.get(self.capability)
@property
def device_state_attributes(self):
"""Return the state attributes."""
return super().device_state_attributes
class WinkSmokeDetector(WinkBinarySensorDevice):
"""Representation of a Wink Smoke detector."""
@@ -124,9 +128,9 @@ class WinkSmokeDetector(WinkBinarySensorDevice):
@property
def device_state_attributes(self):
"""Return the state attributes."""
return {
'test_activated': self.wink.test_activated()
}
_attributes = super().device_state_attributes
_attributes['test_activated'] = self.wink.test_activated()
return _attributes
class WinkHub(WinkBinarySensorDevice):
@@ -135,10 +139,11 @@ class WinkHub(WinkBinarySensorDevice):
@property
def device_state_attributes(self):
"""Return the state attributes."""
return {
'update needed': self.wink.update_needed(),
'firmware version': self.wink.firmware_version()
}
_attributes = super().device_state_attributes
_attributes['update_needed'] = self.wink.update_needed()
_attributes['firmware_version'] = self.wink.firmware_version()
_attributes['pairing_mode'] = self.wink.pairing_mode()
return _attributes
class WinkRemote(WinkBinarySensorDevice):
@@ -147,12 +152,12 @@ class WinkRemote(WinkBinarySensorDevice):
@property
def device_state_attributes(self):
"""Return the state attributes."""
return {
'button_on_pressed': self.wink.button_on_pressed(),
'button_off_pressed': self.wink.button_off_pressed(),
'button_up_pressed': self.wink.button_up_pressed(),
'button_down_pressed': self.wink.button_down_pressed()
}
_attributes = super().device_state_attributes
_attributes['button_on_pressed'] = self.wink.button_on_pressed()
_attributes['button_off_pressed'] = self.wink.button_off_pressed()
_attributes['button_up_pressed'] = self.wink.button_up_pressed()
_attributes['button_down_pressed'] = self.wink.button_down_pressed()
return _attributes
@property
def device_class(self):
@@ -166,10 +171,10 @@ class WinkButton(WinkBinarySensorDevice):
@property
def device_state_attributes(self):
"""Return the state attributes."""
return {
'pressed': self.wink.pressed(),
'long_pressed': self.wink.long_pressed()
}
_attributes = super().device_state_attributes
_attributes['pressed'] = self.wink.pressed()
_attributes['long_pressed'] = self.wink.long_pressed()
return _attributes
class WinkGang(WinkBinarySensorDevice):
@@ -12,6 +12,7 @@ ATTR_OPEN_SINCE = 'Open since'
MOTION = 'motion'
NO_MOTION = 'no_motion'
ATTR_LAST_ACTION = 'last_action'
ATTR_NO_MOTION_SINCE = 'No motion since'
DENSITY = 'density'
@@ -327,10 +328,18 @@ class XiaomiCube(XiaomiBinarySensor):
def __init__(self, device, hass, xiaomi_hub):
"""Initialize the Xiaomi Cube."""
self._hass = hass
self._last_action = None
self._state = False
XiaomiBinarySensor.__init__(self, device, 'Cube', xiaomi_hub,
None, None)
@property
def device_state_attributes(self):
"""Return the state attributes."""
attrs = {ATTR_LAST_ACTION: self._last_action}
attrs.update(super().device_state_attributes)
return attrs
def parse_data(self, data):
"""Parse data sent by gateway."""
if 'status' in data:
@@ -338,6 +347,7 @@ class XiaomiCube(XiaomiBinarySensor):
'entity_id': self.entity_id,
'action_type': data['status']
})
self._last_action = data['status']
if 'rotate' in data:
self._hass.bus.fire('cube_action', {
@@ -345,4 +355,6 @@ class XiaomiCube(XiaomiBinarySensor):
'action_type': 'rotate',
'action_value': float(data['rotate'].replace(",", "."))
})
return False
self._last_action = 'rotate'
return True
+1 -1
View File
@@ -277,7 +277,7 @@ class TodoistProjectData(object):
"""
Class used by the Task Device service object to hold all Todoist Tasks.
This is analagous to the GoogleCalendarData found in the Google Calendar
This is analogous to the GoogleCalendarData found in the Google Calendar
component.
Takes an object with a 'name' field and optionally an 'id' field (either
+2 -9
View File
@@ -126,23 +126,16 @@ def async_setup(hass, config):
"""Handle calls to the camera services."""
target_cameras = component.async_extract_from_service(service)
update_tasks = []
for camera in target_cameras:
if service.service == SERVICE_EN_MOTION:
yield from camera.async_enable_motion_detection()
elif service.service == SERVICE_DISEN_MOTION:
yield from camera.async_disable_motion_detection()
update_tasks = []
for camera in target_cameras:
if not camera.should_poll:
continue
update_coro = hass.async_add_job(
camera.async_update_ha_state(True))
if hasattr(camera, 'async_update'):
update_tasks.append(update_coro)
else:
yield from update_coro
update_tasks.append(camera.async_update_ha_state(True))
if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)
+1 -1
View File
@@ -62,7 +62,7 @@ class AmcrestCam(Camera):
self._token = self._auth = authentication
def camera_image(self):
"""Return a still image reponse from the camera."""
"""Return a still image response from the camera."""
# Send the request to snap a picture and return raw jpg data
response = self._camera.snapshot(channel=self._resolution)
return response.data
+59 -11
View File
@@ -1,30 +1,50 @@
"""
This component provides basic support for Netgear Arlo IP cameras.
Support for Netgear Arlo IP cameras.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.arlo/
"""
import asyncio
import logging
from datetime import timedelta
import voluptuous as vol
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream
import homeassistant.helpers.config_validation as cv
from homeassistant.components.arlo import DEFAULT_BRAND, DATA_ARLO
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
from homeassistant.components.ffmpeg import DATA_FFMPEG
DEPENDENCIES = ['arlo', 'ffmpeg']
from homeassistant.const import ATTR_BATTERY_LEVEL
from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream
_LOGGER = logging.getLogger(__name__)
CONF_FFMPEG_ARGUMENTS = 'ffmpeg_arguments'
SCAN_INTERVAL = timedelta(minutes=10)
ARLO_MODE_ARMED = 'armed'
ARLO_MODE_DISARMED = 'disarmed'
ATTR_BRIGHTNESS = 'brightness'
ATTR_FLIPPED = 'flipped'
ATTR_MIRRORED = 'mirrored'
ATTR_MOTION = 'motion_detection_sensitivity'
ATTR_POWERSAVE = 'power_save_mode'
ATTR_SIGNAL_STRENGTH = 'signal_strength'
ATTR_UNSEEN_VIDEOS = 'unseen_videos'
CONF_FFMPEG_ARGUMENTS = 'ffmpeg_arguments'
DEPENDENCIES = ['arlo', 'ffmpeg']
POWERSAVE_MODE_MAPPING = {
1: 'best_battery_life',
2: 'optimized',
3: 'best_video'
}
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_FFMPEG_ARGUMENTS): cv.string,
vol.Optional(CONF_FFMPEG_ARGUMENTS):
cv.string,
})
@@ -53,6 +73,7 @@ class ArloCam(Camera):
self._motion_status = False
self._ffmpeg = hass.data[DATA_FFMPEG]
self._ffmpeg_arguments = device_info.get(CONF_FFMPEG_ARGUMENTS)
self.attrs = {}
def camera_image(self):
"""Return a still image response from the camera."""
@@ -80,14 +101,28 @@ class ArloCam(Camera):
"""Return the name of this camera."""
return self._name
@property
def device_state_attributes(self):
"""Return the state attributes."""
return {
ATTR_BATTERY_LEVEL: self.attrs.get(ATTR_BATTERY_LEVEL),
ATTR_BRIGHTNESS: self.attrs.get(ATTR_BRIGHTNESS),
ATTR_FLIPPED: self.attrs.get(ATTR_FLIPPED),
ATTR_MIRRORED: self.attrs.get(ATTR_MIRRORED),
ATTR_MOTION: self.attrs.get(ATTR_MOTION),
ATTR_POWERSAVE: self.attrs.get(ATTR_POWERSAVE),
ATTR_SIGNAL_STRENGTH: self.attrs.get(ATTR_SIGNAL_STRENGTH),
ATTR_UNSEEN_VIDEOS: self.attrs.get(ATTR_UNSEEN_VIDEOS),
}
@property
def model(self):
"""Camera model."""
"""Return the camera model."""
return self._camera.model_id
@property
def brand(self):
"""Camera brand."""
"""Return the camera brand."""
return DEFAULT_BRAND
@property
@@ -97,7 +132,7 @@ class ArloCam(Camera):
@property
def motion_detection_enabled(self):
"""Camera Motion Detection Status."""
"""Return the camera motion detection status."""
return self._motion_status
def set_base_station_mode(self, mode):
@@ -105,7 +140,7 @@ class ArloCam(Camera):
# Get the list of base stations identified by library
base_stations = self.hass.data[DATA_ARLO].base_stations
# Some Arlo cameras does not have basestation
# Some Arlo cameras does not have base station
# So check if there is base station detected first
# if yes, then choose the primary base station
# Set the mode on the chosen base station
@@ -122,3 +157,16 @@ class ArloCam(Camera):
"""Disable the motion detection in base station (Disarm)."""
self._motion_status = False
self.set_base_station_mode(ARLO_MODE_DISARMED)
def update(self):
"""Add an attribute-update task to the executor pool."""
self.attrs[ATTR_BATTERY_LEVEL] = self._camera.get_battery_level
self.attrs[ATTR_BRIGHTNESS] = self._camera.get_battery_level
self.attrs[ATTR_FLIPPED] = self._camera.get_flip_state,
self.attrs[ATTR_MIRRORED] = self._camera.get_mirror_state,
self.attrs[
ATTR_MOTION] = self._camera.get_motion_detection_sensitivity,
self.attrs[ATTR_POWERSAVE] = POWERSAVE_MODE_MAPPING[
self._camera.get_powersave_mode],
self.attrs[ATTR_SIGNAL_STRENGTH] = self._camera.get_signal_strength,
self.attrs[ATTR_UNSEEN_VIDEOS] = self._camera.unseen_videos
+1 -1
View File
@@ -76,6 +76,6 @@ class BlinkCamera(Camera):
return self.data.camera_thumbs[self._name]
def camera_image(self):
"""Return a still image reponse from the camera."""
"""Return a still image response from the camera."""
self.request_image()
return self.response.content
+2 -2
View File
@@ -55,9 +55,9 @@ class FFmpegCamera(Camera):
from haffmpeg import ImageFrame, IMAGE_JPEG
ffmpeg = ImageFrame(self._manager.binary, loop=self.hass.loop)
image = yield from ffmpeg.get_image(
image = yield from asyncio.shield(ffmpeg.get_image(
self._input, output_format=IMAGE_JPEG,
extra_cmd=self._extra_arguments)
extra_cmd=self._extra_arguments), loop=self.hass.loop)
return image
@asyncio.coroutine
+1 -1
View File
@@ -59,7 +59,7 @@ class FoscamCam(Camera):
self._password, verbose=False)
def camera_image(self):
"""Return a still image reponse from the camera."""
"""Return a still image response from the camera."""
# Send the request to snap a picture and return raw jpg data
# Handle exception if host is not reachable or url failed
result, response = self._foscam_session.snap_picture_2()
+2 -2
View File
@@ -78,9 +78,9 @@ class ONVIFCamera(Camera):
ffmpeg = ImageFrame(
self.hass.data[DATA_FFMPEG].binary, loop=self.hass.loop)
image = yield from ffmpeg.get_image(
image = yield from asyncio.shield(ffmpeg.get_image(
self._input, output_format=IMAGE_JPEG,
extra_cmd=self._ffmpeg_arguments)
extra_cmd=self._ffmpeg_arguments), loop=self.hass.loop)
return image
@asyncio.coroutine
@@ -0,0 +1,67 @@
"""
Camera support for the Skybell HD Doorbell.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.skybell/
"""
from datetime import timedelta
import logging
import requests
from homeassistant.components.camera import Camera
from homeassistant.components.skybell import (
DOMAIN as SKYBELL_DOMAIN, SkybellDevice)
DEPENDENCIES = ['skybell']
_LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(seconds=90)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the platform for a Skybell device."""
skybell = hass.data.get(SKYBELL_DOMAIN)
sensors = []
for device in skybell.get_devices():
sensors.append(SkybellCamera(device))
add_devices(sensors, True)
class SkybellCamera(SkybellDevice, Camera):
"""A camera implementation for Skybell devices."""
def __init__(self, device):
"""Initialize a camera for a Skybell device."""
SkybellDevice.__init__(self, device)
Camera.__init__(self)
self._name = self._device.name
self._url = None
self._response = None
@property
def name(self):
"""Return the name of the sensor."""
return self._name
def camera_image(self):
"""Get the latest camera image."""
super().update()
if self._url != self._device.image:
self._url = self._device.image
try:
self._response = requests.get(
self._url, stream=True, timeout=10)
except requests.HTTPError as err:
_LOGGER.warning("Failed to get camera image: %s", err)
self._response = None
if not self._response:
return None
return self._response.content
+58 -176
View File
@@ -7,44 +7,25 @@ https://home-assistant.io/components/camera.synology/
import asyncio
import logging
import requests
import voluptuous as vol
import aiohttp
import async_timeout
from homeassistant.const import (
CONF_NAME, CONF_USERNAME, CONF_PASSWORD,
CONF_URL, CONF_WHITELIST, CONF_VERIFY_SSL, CONF_TIMEOUT)
from homeassistant.components.camera import (
Camera, PLATFORM_SCHEMA)
from homeassistant.helpers.aiohttp_client import (
async_get_clientsession, async_create_clientsession,
async_aiohttp_proxy_web)
async_aiohttp_proxy_web,
async_get_clientsession)
import homeassistant.helpers.config_validation as cv
from homeassistant.util.async import run_coroutine_threadsafe
REQUIREMENTS = ['py-synology==0.1.5']
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'Synology Camera'
DEFAULT_STREAM_ID = '0'
DEFAULT_TIMEOUT = 5
CONF_CAMERA_NAME = 'camera_name'
CONF_STREAM_ID = 'stream_id'
QUERY_CGI = 'query.cgi'
QUERY_API = 'SYNO.API.Info'
AUTH_API = 'SYNO.API.Auth'
CAMERA_API = 'SYNO.SurveillanceStation.Camera'
STREAMING_API = 'SYNO.SurveillanceStation.VideoStream'
SESSION_ID = '0'
WEBAPI_PATH = '/webapi/'
AUTH_PATH = 'auth.cgi'
CAMERA_PATH = 'camera.cgi'
STREAMING_PATH = 'SurveillanceStation/videoStreaming.cgi'
CONTENT_TYPE_HEADER = 'Content-Type'
SYNO_API_URL = '{0}{1}{2}'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
@@ -62,189 +43,90 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up a Synology IP Camera."""
verify_ssl = config.get(CONF_VERIFY_SSL)
timeout = config.get(CONF_TIMEOUT)
websession_init = async_get_clientsession(hass, verify_ssl)
# Determine API to use for authentication
syno_api_url = SYNO_API_URL.format(
config.get(CONF_URL), WEBAPI_PATH, QUERY_CGI)
query_payload = {
'api': QUERY_API,
'method': 'Query',
'version': '1',
'query': 'SYNO.'
}
try:
with async_timeout.timeout(timeout, loop=hass.loop):
query_req = yield from websession_init.get(
syno_api_url,
params=query_payload
)
# Skip content type check because Synology doesn't return JSON with
# right content type
query_resp = yield from query_req.json(content_type=None)
auth_path = query_resp['data'][AUTH_API]['path']
camera_api = query_resp['data'][CAMERA_API]['path']
camera_path = query_resp['data'][CAMERA_API]['path']
streaming_path = query_resp['data'][STREAMING_API]['path']
except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.exception("Error on %s", syno_api_url)
from synology.surveillance_station import SurveillanceStation
surveillance = SurveillanceStation(
config.get(CONF_URL),
config.get(CONF_USERNAME),
config.get(CONF_PASSWORD),
verify_ssl=verify_ssl,
timeout=timeout
)
except (requests.exceptions.RequestException, ValueError):
_LOGGER.exception("Error when initializing SurveillanceStation")
return False
# Authticate to NAS to get a session id
syno_auth_url = SYNO_API_URL.format(
config.get(CONF_URL), WEBAPI_PATH, auth_path)
session_id = yield from get_session_id(
hass,
websession_init,
config.get(CONF_USERNAME),
config.get(CONF_PASSWORD),
syno_auth_url,
timeout
)
# init websession
websession = async_create_clientsession(
hass, verify_ssl, cookies={'id': session_id})
# Use SessionID to get cameras in system
syno_camera_url = SYNO_API_URL.format(
config.get(CONF_URL), WEBAPI_PATH, camera_api)
camera_payload = {
'api': CAMERA_API,
'method': 'List',
'version': '1'
}
try:
with async_timeout.timeout(timeout, loop=hass.loop):
camera_req = yield from websession.get(
syno_camera_url,
params=camera_payload
)
except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.exception("Error on %s", syno_camera_url)
return False
camera_resp = yield from camera_req.json(content_type=None)
cameras = camera_resp['data']['cameras']
cameras = surveillance.get_all_cameras()
# add cameras
devices = []
for camera in cameras:
if not config.get(CONF_WHITELIST):
camera_id = camera['id']
snapshot_path = camera['snapshot_path']
device = SynologyCamera(
hass, websession, config, camera_id, camera['name'],
snapshot_path, streaming_path, camera_path, auth_path, timeout
)
device = SynologyCamera(surveillance, camera.camera_id, verify_ssl)
devices.append(device)
async_add_devices(devices)
@asyncio.coroutine
def get_session_id(hass, websession, username, password, login_url, timeout):
"""Get a session id."""
auth_payload = {
'api': AUTH_API,
'method': 'Login',
'version': '2',
'account': username,
'passwd': password,
'session': 'SurveillanceStation',
'format': 'sid'
}
try:
with async_timeout.timeout(timeout, loop=hass.loop):
auth_req = yield from websession.get(
login_url,
params=auth_payload
)
auth_resp = yield from auth_req.json(content_type=None)
return auth_resp['data']['sid']
except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.exception("Error on %s", login_url)
return False
class SynologyCamera(Camera):
"""An implementation of a Synology NAS based IP camera."""
def __init__(self, hass, websession, config, camera_id,
camera_name, snapshot_path, streaming_path, camera_path,
auth_path, timeout):
def __init__(self, surveillance, camera_id, verify_ssl):
"""Initialize a Synology Surveillance Station camera."""
super().__init__()
self.hass = hass
self._websession = websession
self._name = camera_name
self._synology_url = config.get(CONF_URL)
self._camera_name = config.get(CONF_CAMERA_NAME)
self._stream_id = config.get(CONF_STREAM_ID)
self._surveillance = surveillance
self._camera_id = camera_id
self._snapshot_path = snapshot_path
self._streaming_path = streaming_path
self._camera_path = camera_path
self._auth_path = auth_path
self._timeout = timeout
self._verify_ssl = verify_ssl
self._camera = self._surveillance.get_camera(camera_id)
self._motion_setting = self._surveillance.get_motion_setting(camera_id)
self.is_streaming = self._camera.is_enabled
def camera_image(self):
"""Return bytes of camera image."""
return run_coroutine_threadsafe(
self.async_camera_image(), self.hass.loop).result()
@asyncio.coroutine
def async_camera_image(self):
"""Return a still image response from the camera."""
image_url = SYNO_API_URL.format(
self._synology_url, WEBAPI_PATH, self._camera_path)
image_payload = {
'api': CAMERA_API,
'method': 'GetSnapshot',
'version': '1',
'cameraId': self._camera_id
}
try:
with async_timeout.timeout(self._timeout, loop=self.hass.loop):
response = yield from self._websession.get(
image_url,
params=image_payload
)
except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.error("Error fetching %s", image_url)
return None
image = yield from response.read()
return image
return self._surveillance.get_camera_image(self._camera_id)
@asyncio.coroutine
def handle_async_mjpeg_stream(self, request):
"""Return a MJPEG stream image response directly from the camera."""
streaming_url = SYNO_API_URL.format(
self._synology_url, WEBAPI_PATH, self._streaming_path)
streaming_url = self._camera.video_stream_url
streaming_payload = {
'api': STREAMING_API,
'method': 'Stream',
'version': '1',
'cameraId': self._camera_id,
'format': 'mjpeg'
}
stream_coro = self._websession.get(
streaming_url, params=streaming_payload)
websession = async_get_clientsession(self.hass, self._verify_ssl)
stream_coro = websession.get(streaming_url)
yield from async_aiohttp_proxy_web(self.hass, request, stream_coro)
@property
def name(self):
"""Return the name of this device."""
return self._name
return self._camera.name
@property
def is_recording(self):
"""Return true if the device is recording."""
return self._camera.is_recording
def should_poll(self):
"""Update the recording state periodically."""
return True
def update(self):
"""Update the status of the camera."""
self._surveillance.update()
self._camera = self._surveillance.get_camera(self._camera.camera_id)
self._motion_setting = self._surveillance.get_motion_setting(
self._camera.camera_id)
self.is_streaming = self._camera.is_enabled
@property
def motion_detection_enabled(self):
"""Return the camera motion detection status."""
return self._motion_setting.is_enabled
def enable_motion_detection(self):
"""Enable motion detection in the camera."""
self._surveillance.enable_motion_detection(self._camera_id)
def disable_motion_detection(self):
"""Disable motion detection in camera."""
self._surveillance.disable_motion_detection(self._camera_id)
+137
View File
@@ -0,0 +1,137 @@
"""
This component provides support for Xiaomi Cameras (HiSilicon Hi3518e V200).
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.yi/
"""
import asyncio
import logging
import voluptuous as vol
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
from homeassistant.components.ffmpeg import DATA_FFMPEG
from homeassistant.const import (CONF_HOST, CONF_NAME, CONF_PATH,
CONF_PASSWORD, CONF_PORT, CONF_USERNAME)
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream
DEPENDENCIES = ['ffmpeg']
_LOGGER = logging.getLogger(__name__)
DEFAULT_BRAND = 'YI Home Camera'
DEFAULT_PASSWORD = ''
DEFAULT_PATH = '/tmp/sd/record'
DEFAULT_PORT = 21
DEFAULT_USERNAME = 'root'
CONF_FFMPEG_ARGUMENTS = 'ffmpeg_arguments'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_NAME): cv.string,
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.string,
vol.Optional(CONF_PATH, default=DEFAULT_PATH): cv.string,
vol.Optional(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_FFMPEG_ARGUMENTS): cv.string
})
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up a Yi Camera."""
_LOGGER.debug('Received configuration: %s', config)
async_add_devices([YiCamera(hass, config)], True)
class YiCamera(Camera):
"""Define an implementation of a Yi Camera."""
def __init__(self, hass, config):
"""Initialize."""
super().__init__()
self._extra_arguments = config.get(CONF_FFMPEG_ARGUMENTS)
self._last_image = None
self._last_url = None
self._manager = hass.data[DATA_FFMPEG]
self._name = config.get(CONF_NAME)
self.host = config.get(CONF_HOST)
self.port = config.get(CONF_PORT)
self.path = config.get(CONF_PATH)
self.user = config.get(CONF_USERNAME)
self.passwd = config.get(CONF_PASSWORD)
@property
def name(self):
"""Return the name of this camera."""
return self._name
@property
def brand(self):
"""Camera brand."""
return DEFAULT_BRAND
def get_latest_video_url(self):
"""Retrieve the latest video file from the customized Yi FTP server."""
from ftplib import FTP, error_perm
ftp = FTP(self.host)
try:
ftp.login(self.user, self.passwd)
except error_perm as exc:
_LOGGER.error('There was an error while logging into the camera')
_LOGGER.debug(exc)
return False
try:
ftp.cwd(self.path)
except error_perm as exc:
_LOGGER.error('Unable to find path: %s', self.path)
_LOGGER.debug(exc)
return False
dirs = [d for d in ftp.nlst() if '.' not in d]
if not dirs:
_LOGGER.warning("There don't appear to be any uploaded videos")
return False
latest_dir = dirs[-1]
ftp.cwd(latest_dir)
videos = ftp.nlst()
if not videos:
_LOGGER.info('Video folder "%s" is empty; delaying', latest_dir)
return False
return 'ftp://{0}:{1}@{2}:{3}{4}/{5}/{6}'.format(
self.user, self.passwd, self.host, self.port, self.path,
latest_dir, videos[-1])
@asyncio.coroutine
def async_camera_image(self):
"""Return a still image response from the camera."""
from haffmpeg import ImageFrame, IMAGE_JPEG
url = yield from self.hass.async_add_job(self.get_latest_video_url)
if url != self._last_url:
ffmpeg = ImageFrame(self._manager.binary, loop=self.hass.loop)
self._last_image = yield from asyncio.shield(ffmpeg.get_image(
url, output_format=IMAGE_JPEG,
extra_cmd=self._extra_arguments), loop=self.hass.loop)
self._last_url = url
return self._last_image
@asyncio.coroutine
def handle_async_mjpeg_stream(self, request):
"""Generate an HTTP MJPEG stream from the camera."""
from haffmpeg import CameraMjpeg
stream = CameraMjpeg(self._manager.binary, loop=self.hass.loop)
yield from stream.open_camera(
self._last_url, extra_cmd=self._extra_arguments)
yield from async_aiohttp_proxy_stream(
self.hass, request, stream,
'multipart/x-mixed-replace;boundary=ffserver')
yield from stream.close()
+63 -31
View File
@@ -44,6 +44,12 @@ STATE_IDLE = 'idle'
STATE_AUTO = 'auto'
STATE_DRY = 'dry'
STATE_FAN_ONLY = 'fan_only'
STATE_ECO = 'eco'
STATE_ELECTRIC = 'electric'
STATE_PERFORMANCE = 'performance'
STATE_HIGH_DEMAND = 'high_demand'
STATE_HEAT_PUMP = 'heat_pump'
STATE_GAS = 'gas'
ATTR_CURRENT_TEMPERATURE = 'current_temperature'
ATTR_MAX_TEMP = 'max_temp'
@@ -147,7 +153,7 @@ def set_hold_mode(hass, hold_mode, entity_id=None):
@bind_hass
def set_aux_heat(hass, aux_heat, entity_id=None):
"""Turn all or specified climate devices auxillary heater on."""
"""Turn all or specified climate devices auxiliary heater on."""
data = {
ATTR_AUX_HEAT: aux_heat
}
@@ -230,24 +236,6 @@ def async_setup(hass, config):
load_yaml_config_file,
os.path.join(os.path.dirname(__file__), 'services.yaml'))
@asyncio.coroutine
def _async_update_climate(target_climate):
"""Update climate entity after service stuff."""
update_tasks = []
for climate in target_climate:
if not climate.should_poll:
continue
update_coro = hass.async_add_job(
climate.async_update_ha_state(True))
if hasattr(climate, 'async_update'):
update_tasks.append(update_coro)
else:
yield from update_coro
if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)
@asyncio.coroutine
def async_away_mode_set_service(service):
"""Set away mode on target climate devices."""
@@ -255,13 +243,19 @@ def async_setup(hass, config):
away_mode = service.data.get(ATTR_AWAY_MODE)
update_tasks = []
for climate in target_climate:
if away_mode:
yield from climate.async_turn_away_mode_on()
else:
yield from climate.async_turn_away_mode_off()
yield from _async_update_climate(target_climate)
if not climate.should_poll:
continue
update_tasks.append(climate.async_update_ha_state(True))
if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)
hass.services.async_register(
DOMAIN, SERVICE_SET_AWAY_MODE, async_away_mode_set_service,
@@ -275,10 +269,16 @@ def async_setup(hass, config):
hold_mode = service.data.get(ATTR_HOLD_MODE)
update_tasks = []
for climate in target_climate:
yield from climate.async_set_hold_mode(hold_mode)
yield from _async_update_climate(target_climate)
if not climate.should_poll:
continue
update_tasks.append(climate.async_update_ha_state(True))
if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)
hass.services.async_register(
DOMAIN, SERVICE_SET_HOLD_MODE, async_hold_mode_set_service,
@@ -292,13 +292,19 @@ def async_setup(hass, config):
aux_heat = service.data.get(ATTR_AUX_HEAT)
update_tasks = []
for climate in target_climate:
if aux_heat:
yield from climate.async_turn_aux_heat_on()
else:
yield from climate.async_turn_aux_heat_off()
yield from _async_update_climate(target_climate)
if not climate.should_poll:
continue
update_tasks.append(climate.async_update_ha_state(True))
if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)
hass.services.async_register(
DOMAIN, SERVICE_SET_AUX_HEAT, async_aux_heat_set_service,
@@ -310,6 +316,7 @@ def async_setup(hass, config):
"""Set temperature on the target climate devices."""
target_climate = component.async_extract_from_service(service)
update_tasks = []
for climate in target_climate:
kwargs = {}
for value, temp in service.data.items():
@@ -324,7 +331,12 @@ def async_setup(hass, config):
yield from climate.async_set_temperature(**kwargs)
yield from _async_update_climate(target_climate)
if not climate.should_poll:
continue
update_tasks.append(climate.async_update_ha_state(True))
if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)
hass.services.async_register(
DOMAIN, SERVICE_SET_TEMPERATURE, async_temperature_set_service,
@@ -338,10 +350,15 @@ def async_setup(hass, config):
humidity = service.data.get(ATTR_HUMIDITY)
update_tasks = []
for climate in target_climate:
yield from climate.async_set_humidity(humidity)
if not climate.should_poll:
continue
update_tasks.append(climate.async_update_ha_state(True))
yield from _async_update_climate(target_climate)
if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)
hass.services.async_register(
DOMAIN, SERVICE_SET_HUMIDITY, async_humidity_set_service,
@@ -355,10 +372,15 @@ def async_setup(hass, config):
fan = service.data.get(ATTR_FAN_MODE)
update_tasks = []
for climate in target_climate:
yield from climate.async_set_fan_mode(fan)
if not climate.should_poll:
continue
update_tasks.append(climate.async_update_ha_state(True))
yield from _async_update_climate(target_climate)
if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)
hass.services.async_register(
DOMAIN, SERVICE_SET_FAN_MODE, async_fan_mode_set_service,
@@ -372,10 +394,15 @@ def async_setup(hass, config):
operation_mode = service.data.get(ATTR_OPERATION_MODE)
update_tasks = []
for climate in target_climate:
yield from climate.async_set_operation_mode(operation_mode)
if not climate.should_poll:
continue
update_tasks.append(climate.async_update_ha_state(True))
yield from _async_update_climate(target_climate)
if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)
hass.services.async_register(
DOMAIN, SERVICE_SET_OPERATION_MODE, async_operation_set_service,
@@ -389,10 +416,15 @@ def async_setup(hass, config):
swing_mode = service.data.get(ATTR_SWING_MODE)
update_tasks = []
for climate in target_climate:
yield from climate.async_set_swing_mode(swing_mode)
if not climate.should_poll:
continue
update_tasks.append(climate.async_update_ha_state(True))
yield from _async_update_climate(target_climate)
if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)
hass.services.async_register(
DOMAIN, SERVICE_SET_SWING_MODE, async_swing_set_service,
@@ -661,22 +693,22 @@ class ClimateDevice(Entity):
return self.hass.async_add_job(self.set_hold_mode, hold_mode)
def turn_aux_heat_on(self):
"""Turn auxillary heater on."""
"""Turn auxiliary heater on."""
raise NotImplementedError()
def async_turn_aux_heat_on(self):
"""Turn auxillary heater on.
"""Turn auxiliary heater on.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.async_add_job(self.turn_aux_heat_on)
def turn_aux_heat_off(self):
"""Turn auxillary heater off."""
"""Turn auxiliary heater off."""
raise NotImplementedError()
def async_turn_aux_heat_off(self):
"""Turn auxillary heater off.
"""Turn auxiliary heater off.
This method must be run in the event loop and returns a coroutine.
"""
+3 -3
View File
@@ -114,7 +114,7 @@ class DemoClimate(ClimateDevice):
@property
def is_aux_heat_on(self):
"""Return true if away mode is on."""
"""Return true if aux heat is on."""
return self._aux
@property
@@ -183,11 +183,11 @@ class DemoClimate(ClimateDevice):
self.schedule_update_ha_state()
def turn_aux_heat_on(self):
"""Turn away auxillary heater on."""
"""Turn auxillary heater on."""
self._aux = True
self.schedule_update_ha_state()
def turn_aux_heat_off(self):
"""Turn auxillary heater off."""
"""Turn auxiliary heater off."""
self._aux = False
self.schedule_update_ha_state()
+31 -24
View File
@@ -27,6 +27,7 @@ ATTR_RESUME_ALL = 'resume_all'
DEFAULT_RESUME_ALL = False
TEMPERATURE_HOLD = 'temp'
VACATION_HOLD = 'vacation'
AWAY_MODE = 'awayMode'
DEPENDENCIES = ['ecobee']
@@ -144,20 +145,20 @@ class Thermostat(ClimateDevice):
@property
def current_temperature(self):
"""Return the current temperature."""
return self.thermostat['runtime']['actualTemperature'] / 10
return self.thermostat['runtime']['actualTemperature'] / 10.0
@property
def target_temperature_low(self):
"""Return the lower bound temperature we try to reach."""
if self.current_operation == STATE_AUTO:
return int(self.thermostat['runtime']['desiredHeat'] / 10)
return self.thermostat['runtime']['desiredHeat'] / 10.0
return None
@property
def target_temperature_high(self):
"""Return the upper bound temperature we try to reach."""
if self.current_operation == STATE_AUTO:
return int(self.thermostat['runtime']['desiredCool'] / 10)
return self.thermostat['runtime']['desiredCool'] / 10.0
return None
@property
@@ -166,9 +167,9 @@ class Thermostat(ClimateDevice):
if self.current_operation == STATE_AUTO:
return None
if self.current_operation == STATE_HEAT:
return int(self.thermostat['runtime']['desiredHeat'] / 10)
return self.thermostat['runtime']['desiredHeat'] / 10.0
elif self.current_operation == STATE_COOL:
return int(self.thermostat['runtime']['desiredCool'] / 10)
return self.thermostat['runtime']['desiredCool'] / 10.0
return None
@property
@@ -186,6 +187,11 @@ class Thermostat(ClimateDevice):
@property
def current_hold_mode(self):
"""Return current hold mode."""
mode = self._current_hold_mode
return None if mode == AWAY_MODE else mode
@property
def _current_hold_mode(self):
events = self.thermostat['events']
for event in events:
if event['running']:
@@ -195,8 +201,8 @@ class Thermostat(ClimateDevice):
int(event['startDate'][0:4]) <= 1:
# A temporary hold from away climate is a hold
return 'away'
# A permanent hold from away climate is away_mode
return None
# A permanent hold from away climate
return AWAY_MODE
elif event['holdClimateRef'] != "":
# Any other hold based on climate
return event['holdClimateRef']
@@ -269,7 +275,7 @@ class Thermostat(ClimateDevice):
@property
def is_away_mode_on(self):
"""Return true if away mode is on."""
return self.current_hold_mode == 'away'
return self._current_hold_mode == AWAY_MODE
@property
def is_aux_heat_on(self):
@@ -277,12 +283,17 @@ class Thermostat(ClimateDevice):
return 'auxHeat' in self.thermostat['equipmentStatus']
def turn_away_mode_on(self):
"""Turn away on."""
self.set_hold_mode('away')
"""Turn away mode on by setting it on away hold indefinitely."""
if self._current_hold_mode != AWAY_MODE:
self.data.ecobee.set_climate_hold(self.thermostat_index, 'away',
'indefinite')
self.update_without_throttle = True
def turn_away_mode_off(self):
"""Turn away off."""
self.set_hold_mode(None)
if self._current_hold_mode == AWAY_MODE:
self.data.ecobee.resume_program(self.thermostat_index)
self.update_without_throttle = True
def set_hold_mode(self, hold_mode):
"""Set hold mode (away, home, temp, sleep, etc.)."""
@@ -299,7 +310,7 @@ class Thermostat(ClimateDevice):
self.data.ecobee.resume_program(self.thermostat_index)
else:
if hold_mode == TEMPERATURE_HOLD:
self.set_temp_hold(int(self.current_temperature))
self.set_temp_hold(self.current_temperature)
else:
self.data.ecobee.set_climate_hold(
self.thermostat_index, hold_mode, self.hold_preference())
@@ -325,15 +336,11 @@ class Thermostat(ClimateDevice):
elif self.current_operation == STATE_COOL:
heat_temp = temp - 20
cool_temp = temp
self.data.ecobee.set_hold_temp(self.thermostat_index, cool_temp,
heat_temp, self.hold_preference())
_LOGGER.debug("Setting ecobee hold_temp to: low=%s, is=%s, "
"cool=%s, is=%s", heat_temp, isinstance(
heat_temp, (int, float)), cool_temp,
isinstance(cool_temp, (int, float)))
self.update_without_throttle = True
else:
# In auto mode set temperature between
heat_temp = temp - 10
cool_temp = temp + 10
self.set_auto_temp_hold(heat_temp, cool_temp)
def set_temperature(self, **kwargs):
"""Set new target temperature."""
@@ -343,9 +350,9 @@ class Thermostat(ClimateDevice):
if self.current_operation == STATE_AUTO and low_temp is not None \
and high_temp is not None:
self.set_auto_temp_hold(int(low_temp), int(high_temp))
self.set_auto_temp_hold(low_temp, high_temp)
elif temp is not None:
self.set_temp_hold(int(temp))
self.set_temp_hold(temp)
else:
_LOGGER.error(
"Missing valid arguments for set_temperature in %s", kwargs)
@@ -364,7 +371,7 @@ class Thermostat(ClimateDevice):
def resume_program(self, resume_all):
"""Resume the thermostat schedule program."""
self.data.ecobee.resume_program(
self.thermostat_index, str(resume_all).lower())
self.thermostat_index, 'true' if resume_all else 'false')
self.update_without_throttle = True
def hold_preference(self):
@@ -17,7 +17,7 @@ from homeassistant.const import (
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['python-eq3bt==0.1.5']
REQUIREMENTS = ['python-eq3bt==0.1.6']
_LOGGER = logging.getLogger(__name__)
@@ -164,4 +164,8 @@ class EQ3BTSmartThermostat(ClimateDevice):
def update(self):
"""Update the data from the thermostat."""
self._thermostat.update()
from bluepy.btle import BTLEException
try:
self._thermostat.update()
except BTLEException as ex:
_LOGGER.warning("Updating the state failed: %s", ex)
+29 -5
View File
@@ -14,6 +14,8 @@ from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
CONF_SETPOINT_ADDRESS = 'setpoint_address'
CONF_SETPOINT_SHIFT_ADDRESS = 'setpoint_shift_address'
CONF_SETPOINT_SHIFT_STATE_ADDRESS = 'setpoint_shift_state_address'
CONF_TEMPERATURE_ADDRESS = 'temperature_address'
CONF_TARGET_TEMPERATURE_ADDRESS = 'target_temperature_address'
CONF_OPERATION_MODE_ADDRESS = 'operation_mode_address'
@@ -33,6 +35,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_SETPOINT_ADDRESS): cv.string,
vol.Required(CONF_TEMPERATURE_ADDRESS): cv.string,
vol.Required(CONF_TARGET_TEMPERATURE_ADDRESS): cv.string,
vol.Optional(CONF_SETPOINT_SHIFT_ADDRESS): cv.string,
vol.Optional(CONF_SETPOINT_SHIFT_STATE_ADDRESS): cv.string,
vol.Optional(CONF_OPERATION_MODE_ADDRESS): cv.string,
vol.Optional(CONF_OPERATION_MODE_STATE_ADDRESS): cv.string,
vol.Optional(CONF_CONTROLLER_STATUS_ADDRESS): cv.string,
@@ -82,6 +86,10 @@ def async_add_devices_config(hass, config, async_add_devices):
CONF_TARGET_TEMPERATURE_ADDRESS),
group_address_setpoint=config.get(
CONF_SETPOINT_ADDRESS),
group_address_setpoint_shift=config.get(
CONF_SETPOINT_SHIFT_ADDRESS),
group_address_setpoint_shift_state=config.get(
CONF_SETPOINT_SHIFT_STATE_ADDRESS),
group_address_operation_mode=config.get(
CONF_OPERATION_MODE_ADDRESS),
group_address_operation_mode_state=config.get(
@@ -140,13 +148,29 @@ class KNXClimate(ClimateDevice):
@property
def current_temperature(self):
"""Return the current temperature."""
return self.device.temperature
return self.device.temperature.value
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
if self.device.supports_target_temperature:
return self.device.target_temperature
return self.device.target_temperature_comfort
@property
def target_temperature_high(self):
"""Return the highbound target temperature we try to reach."""
if self.device.target_temperature_comfort:
return max(
self.device.target_temperature_comfort,
self.device.target_temperature.value)
return None
@property
def target_temperature_low(self):
"""Return the lowbound target temperature we try to reach."""
if self.device.target_temperature_comfort:
return min(
self.device.target_temperature_comfort,
self.device.target_temperature.value)
return None
@asyncio.coroutine
@@ -155,8 +179,8 @@ class KNXClimate(ClimateDevice):
temperature = kwargs.get(ATTR_TEMPERATURE)
if temperature is None:
return
if self.device.supports_target_temperature:
yield from self.device.set_target_temperature(temperature)
yield from self.device.set_target_temperature_comfort(temperature)
yield from self.async_update_ha_state()
@property
def current_operation(self):
+485
View File
@@ -0,0 +1,485 @@
"""
Support for MQTT climate devices.
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/climate.mqtt/
"""
import asyncio
import logging
import voluptuous as vol
from homeassistant.core import callback
import homeassistant.components.mqtt as mqtt
from homeassistant.components.climate import (
STATE_HEAT, STATE_COOL, STATE_DRY, STATE_FAN_ONLY, ClimateDevice,
PLATFORM_SCHEMA as CLIMATE_PLATFORM_SCHEMA, STATE_AUTO,
ATTR_OPERATION_MODE)
from homeassistant.const import (
STATE_ON, STATE_OFF, ATTR_TEMPERATURE, CONF_NAME)
from homeassistant.components.mqtt import (CONF_QOS, CONF_RETAIN,
MQTT_BASE_PLATFORM_SCHEMA)
import homeassistant.helpers.config_validation as cv
from homeassistant.components.fan import (SPEED_LOW, SPEED_MEDIUM,
SPEED_HIGH)
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['mqtt']
DEFAULT_NAME = 'MQTT HVAC'
CONF_POWER_COMMAND_TOPIC = 'power_command_topic'
CONF_POWER_STATE_TOPIC = 'power_state_topic'
CONF_MODE_COMMAND_TOPIC = 'mode_command_topic'
CONF_MODE_STATE_TOPIC = 'mode_state_topic'
CONF_TEMPERATURE_COMMAND_TOPIC = 'temperature_command_topic'
CONF_TEMPERATURE_STATE_TOPIC = 'temperature_state_topic'
CONF_FAN_MODE_COMMAND_TOPIC = 'fan_mode_command_topic'
CONF_FAN_MODE_STATE_TOPIC = 'fan_mode_state_topic'
CONF_SWING_MODE_COMMAND_TOPIC = 'swing_mode_command_topic'
CONF_SWING_MODE_STATE_TOPIC = 'swing_mode_state_topic'
CONF_AWAY_MODE_COMMAND_TOPIC = 'away_mode_command_topic'
CONF_AWAY_MODE_STATE_TOPIC = 'away_mode_state_topic'
CONF_HOLD_COMMAND_TOPIC = 'hold_command_topic'
CONF_HOLD_STATE_TOPIC = 'hold_state_topic'
CONF_AUX_COMMAND_TOPIC = 'aux_command_topic'
CONF_AUX_STATE_TOPIC = 'aux_state_topic'
CONF_CURRENT_TEMPERATURE_TOPIC = 'current_temperature_topic'
CONF_PAYLOAD_ON = 'payload_on'
CONF_PAYLOAD_OFF = 'payload_off'
CONF_FAN_MODE_LIST = 'fan_modes'
CONF_MODE_LIST = 'modes'
CONF_SWING_MODE_LIST = 'swing_modes'
CONF_INITIAL = 'initial'
CONF_SEND_IF_OFF = 'send_if_off'
SCHEMA_BASE = CLIMATE_PLATFORM_SCHEMA.extend(MQTT_BASE_PLATFORM_SCHEMA.schema)
PLATFORM_SCHEMA = SCHEMA_BASE.extend({
vol.Optional(CONF_POWER_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_TEMPERATURE_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_FAN_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_SWING_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_AWAY_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_HOLD_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_AUX_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_POWER_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_TEMPERATURE_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_FAN_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_SWING_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_AWAY_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_HOLD_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_AUX_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_CURRENT_TEMPERATURE_TOPIC):
mqtt.valid_subscribe_topic,
vol.Optional(CONF_FAN_MODE_LIST,
default=[STATE_AUTO, SPEED_LOW,
SPEED_MEDIUM, SPEED_HIGH]): cv.ensure_list,
vol.Optional(CONF_SWING_MODE_LIST,
default=[STATE_ON, STATE_OFF]): cv.ensure_list,
vol.Optional(CONF_MODE_LIST,
default=[STATE_AUTO, STATE_OFF, STATE_COOL, STATE_HEAT,
STATE_DRY, STATE_FAN_ONLY]): cv.ensure_list,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_INITIAL, default=21): cv.positive_int,
vol.Optional(CONF_SEND_IF_OFF, default=True): cv.boolean,
vol.Optional(CONF_PAYLOAD_ON, default="ON"): cv.string,
vol.Optional(CONF_PAYLOAD_OFF, default="OFF"): cv.string,
})
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up the MQTT climate devices."""
async_add_devices([
MqttClimate(
hass,
config.get(CONF_NAME),
{
key: config.get(key) for key in (
CONF_POWER_COMMAND_TOPIC,
CONF_MODE_COMMAND_TOPIC,
CONF_TEMPERATURE_COMMAND_TOPIC,
CONF_FAN_MODE_COMMAND_TOPIC,
CONF_SWING_MODE_COMMAND_TOPIC,
CONF_AWAY_MODE_COMMAND_TOPIC,
CONF_HOLD_COMMAND_TOPIC,
CONF_AUX_COMMAND_TOPIC,
CONF_POWER_STATE_TOPIC,
CONF_MODE_STATE_TOPIC,
CONF_TEMPERATURE_STATE_TOPIC,
CONF_FAN_MODE_STATE_TOPIC,
CONF_SWING_MODE_STATE_TOPIC,
CONF_AWAY_MODE_STATE_TOPIC,
CONF_HOLD_STATE_TOPIC,
CONF_AUX_STATE_TOPIC,
CONF_CURRENT_TEMPERATURE_TOPIC
)
},
config.get(CONF_QOS),
config.get(CONF_RETAIN),
config.get(CONF_MODE_LIST),
config.get(CONF_FAN_MODE_LIST),
config.get(CONF_SWING_MODE_LIST),
config.get(CONF_INITIAL),
False, None, SPEED_LOW,
STATE_OFF, STATE_OFF, False,
config.get(CONF_SEND_IF_OFF),
config.get(CONF_PAYLOAD_ON),
config.get(CONF_PAYLOAD_OFF))
])
class MqttClimate(ClimateDevice):
"""Representation of a demo climate device."""
def __init__(self, hass, name, topic, qos, retain, mode_list,
fan_mode_list, swing_mode_list, target_temperature, away,
hold, current_fan_mode, current_swing_mode,
current_operation, aux, send_if_off, payload_on,
payload_off):
"""Initialize the climate device."""
self.hass = hass
self._name = name
self._topic = topic
self._qos = qos
self._retain = retain
self._target_temperature = target_temperature
self._unit_of_measurement = hass.config.units.temperature_unit
self._away = away
self._hold = hold
self._current_temperature = None
self._current_fan_mode = current_fan_mode
self._current_operation = current_operation
self._aux = aux
self._current_swing_mode = current_swing_mode
self._fan_list = fan_mode_list
self._operation_list = mode_list
self._swing_list = swing_mode_list
self._target_temperature_step = 1
self._send_if_off = send_if_off
self._payload_on = payload_on
self._payload_off = payload_off
def async_added_to_hass(self):
"""Handle being added to home assistant."""
@callback
def handle_current_temp_received(topic, payload, qos):
"""Handle current temperature coming via MQTT."""
try:
self._current_temperature = float(payload)
self.async_schedule_update_ha_state()
except ValueError:
_LOGGER.error("Could not parse temperature from %s", payload)
if self._topic[CONF_CURRENT_TEMPERATURE_TOPIC] is not None:
yield from mqtt.async_subscribe(
self.hass, self._topic[CONF_CURRENT_TEMPERATURE_TOPIC],
handle_current_temp_received, self._qos)
@callback
def handle_mode_received(topic, payload, qos):
"""Handle receiving mode via MQTT."""
if payload not in self._operation_list:
_LOGGER.error("Invalid mode: %s", payload)
else:
self._current_operation = payload
self.async_schedule_update_ha_state()
if self._topic[CONF_MODE_STATE_TOPIC] is not None:
yield from mqtt.async_subscribe(
self.hass, self._topic[CONF_MODE_STATE_TOPIC],
handle_mode_received, self._qos)
@callback
def handle_temperature_received(topic, payload, qos):
"""Handle target temperature coming via MQTT."""
try:
self._target_temperature = float(payload)
self.async_schedule_update_ha_state()
except ValueError:
_LOGGER.error("Could not parse temperature from %s", payload)
if self._topic[CONF_TEMPERATURE_STATE_TOPIC] is not None:
yield from mqtt.async_subscribe(
self.hass, self._topic[CONF_TEMPERATURE_STATE_TOPIC],
handle_temperature_received, self._qos)
@callback
def handle_fan_mode_received(topic, payload, qos):
"""Handle receiving fan mode via MQTT."""
if payload not in self._fan_list:
_LOGGER.error("Invalid fan mode: %s", payload)
else:
self._current_fan_mode = payload
self.async_schedule_update_ha_state()
if self._topic[CONF_FAN_MODE_STATE_TOPIC] is not None:
yield from mqtt.async_subscribe(
self.hass, self._topic[CONF_FAN_MODE_STATE_TOPIC],
handle_fan_mode_received, self._qos)
@callback
def handle_swing_mode_received(topic, payload, qos):
"""Handle receiving swing mode via MQTT."""
if payload not in self._swing_list:
_LOGGER.error("Invalid swing mode: %s", payload)
else:
self._current_swing_mode = payload
self.async_schedule_update_ha_state()
if self._topic[CONF_SWING_MODE_STATE_TOPIC] is not None:
yield from mqtt.async_subscribe(
self.hass, self._topic[CONF_SWING_MODE_STATE_TOPIC],
handle_swing_mode_received, self._qos)
@callback
def handle_away_mode_received(topic, payload, qos):
"""Handle receiving away mode via MQTT."""
if payload == self._payload_on:
self._away = True
elif payload == self._payload_off:
self._away = False
else:
_LOGGER.error("Invalid away mode: %s", payload)
self.async_schedule_update_ha_state()
if self._topic[CONF_AWAY_MODE_STATE_TOPIC] is not None:
yield from mqtt.async_subscribe(
self.hass, self._topic[CONF_AWAY_MODE_STATE_TOPIC],
handle_away_mode_received, self._qos)
@callback
def handle_aux_mode_received(topic, payload, qos):
"""Handle receiving aux mode via MQTT."""
if payload == self._payload_on:
self._aux = True
elif payload == self._payload_off:
self._aux = False
else:
_LOGGER.error("Invalid aux mode: %s", payload)
self.async_schedule_update_ha_state()
if self._topic[CONF_AUX_STATE_TOPIC] is not None:
yield from mqtt.async_subscribe(
self.hass, self._topic[CONF_AUX_STATE_TOPIC],
handle_aux_mode_received, self._qos)
@callback
def handle_hold_mode_received(topic, payload, qos):
"""Handle receiving hold mode via MQTT."""
self._hold = payload
self.async_schedule_update_ha_state()
if self._topic[CONF_HOLD_STATE_TOPIC] is not None:
yield from mqtt.async_subscribe(
self.hass, self._topic[CONF_HOLD_STATE_TOPIC],
handle_hold_mode_received, self._qos)
@property
def should_poll(self):
"""Return the polling state."""
return False
@property
def name(self):
"""Return the name of the climate device."""
return self._name
@property
def temperature_unit(self):
"""Return the unit of measurement."""
return self._unit_of_measurement
@property
def current_temperature(self):
"""Return the current temperature."""
return self._current_temperature
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
return self._target_temperature
@property
def current_operation(self):
"""Return current operation ie. heat, cool, idle."""
return self._current_operation
@property
def operation_list(self):
"""Return the list of available operation modes."""
return self._operation_list
@property
def target_temperature_step(self):
"""Return the supported step of target temperature."""
return self._target_temperature_step
@property
def is_away_mode_on(self):
"""Return if away mode is on."""
return self._away
@property
def current_hold_mode(self):
"""Return hold mode setting."""
return self._hold
@property
def is_aux_heat_on(self):
"""Return true if away mode is on."""
return self._aux
@property
def current_fan_mode(self):
"""Return the fan setting."""
return self._current_fan_mode
@property
def fan_list(self):
"""Return the list of available fan modes."""
return self._fan_list
@asyncio.coroutine
def async_set_temperature(self, **kwargs):
"""Set new target temperatures."""
if kwargs.get(ATTR_OPERATION_MODE) is not None:
operation_mode = kwargs.get(ATTR_OPERATION_MODE)
yield from self.async_set_operation_mode(operation_mode)
if kwargs.get(ATTR_TEMPERATURE) is not None:
if self._topic[CONF_TEMPERATURE_STATE_TOPIC] is None:
# optimistic mode
self._target_temperature = kwargs.get(ATTR_TEMPERATURE)
if self._send_if_off or self._current_operation != STATE_OFF:
mqtt.async_publish(
self.hass, self._topic[CONF_TEMPERATURE_COMMAND_TOPIC],
kwargs.get(ATTR_TEMPERATURE), self._qos, self._retain)
self.async_schedule_update_ha_state()
@asyncio.coroutine
def async_set_swing_mode(self, swing_mode):
"""Set new swing mode."""
if self._send_if_off or self._current_operation != STATE_OFF:
mqtt.async_publish(
self.hass, self._topic[CONF_SWING_MODE_COMMAND_TOPIC],
swing_mode, self._qos, self._retain)
if self._topic[CONF_SWING_MODE_STATE_TOPIC] is None:
self._current_swing_mode = swing_mode
self.async_schedule_update_ha_state()
@asyncio.coroutine
def async_set_fan_mode(self, fan):
"""Set new target temperature."""
if self._send_if_off or self._current_operation != STATE_OFF:
mqtt.async_publish(
self.hass, self._topic[CONF_FAN_MODE_COMMAND_TOPIC],
fan, self._qos, self._retain)
if self._topic[CONF_FAN_MODE_STATE_TOPIC] is None:
self._current_fan_mode = fan
self.async_schedule_update_ha_state()
@asyncio.coroutine
def async_set_operation_mode(self, operation_mode) -> None:
"""Set new operation mode."""
if self._topic[CONF_POWER_COMMAND_TOPIC] is not None:
if (self._current_operation == STATE_OFF and
operation_mode != STATE_OFF):
mqtt.async_publish(
self.hass, self._topic[CONF_POWER_COMMAND_TOPIC],
self._payload_on, self._qos, self._retain)
elif (self._current_operation != STATE_OFF and
operation_mode == STATE_OFF):
mqtt.async_publish(
self.hass, self._topic[CONF_POWER_COMMAND_TOPIC],
self._payload_off, self._qos, self._retain)
if self._topic[CONF_MODE_COMMAND_TOPIC] is not None:
mqtt.async_publish(
self.hass, self._topic[CONF_MODE_COMMAND_TOPIC],
operation_mode, self._qos, self._retain)
if self._topic[CONF_MODE_STATE_TOPIC] is None:
self._current_operation = operation_mode
self.async_schedule_update_ha_state()
@property
def current_swing_mode(self):
"""Return the swing setting."""
return self._current_swing_mode
@property
def swing_list(self):
"""List of available swing modes."""
return self._swing_list
@asyncio.coroutine
def async_turn_away_mode_on(self):
"""Turn away mode on."""
if self._topic[CONF_AWAY_MODE_COMMAND_TOPIC] is not None:
mqtt.async_publish(self.hass,
self._topic[CONF_AWAY_MODE_COMMAND_TOPIC],
self._payload_on, self._qos, self._retain)
if self._topic[CONF_AWAY_MODE_STATE_TOPIC] is None:
self._away = True
self.async_schedule_update_ha_state()
@asyncio.coroutine
def async_turn_away_mode_off(self):
"""Turn away mode off."""
if self._topic[CONF_AWAY_MODE_COMMAND_TOPIC] is not None:
mqtt.async_publish(self.hass,
self._topic[CONF_AWAY_MODE_COMMAND_TOPIC],
self._payload_off, self._qos, self._retain)
if self._topic[CONF_AWAY_MODE_STATE_TOPIC] is None:
self._away = False
self.async_schedule_update_ha_state()
@asyncio.coroutine
def async_set_hold_mode(self, hold):
"""Update hold mode on."""
if self._topic[CONF_HOLD_COMMAND_TOPIC] is not None:
mqtt.async_publish(self.hass,
self._topic[CONF_HOLD_COMMAND_TOPIC],
hold, self._qos, self._retain)
if self._topic[CONF_HOLD_STATE_TOPIC] is None:
self._hold = hold
self.async_schedule_update_ha_state()
@asyncio.coroutine
def async_turn_aux_heat_on(self):
"""Turn auxillary heater on."""
if self._topic[CONF_AUX_COMMAND_TOPIC] is not None:
mqtt.async_publish(self.hass, self._topic[CONF_AUX_COMMAND_TOPIC],
self._payload_on, self._qos, self._retain)
if self._topic[CONF_AUX_STATE_TOPIC] is None:
self._aux = True
self.async_schedule_update_ha_state()
@asyncio.coroutine
def async_turn_aux_heat_off(self):
"""Turn auxillary heater off."""
if self._topic[CONF_AUX_COMMAND_TOPIC] is not None:
mqtt.async_publish(self.hass, self._topic[CONF_AUX_COMMAND_TOPIC],
self._payload_off, self._qos, self._retain)
if self._topic[CONF_AUX_STATE_TOPIC] is None:
self._aux = False
self.async_schedule_update_ha_state()
@@ -1,5 +1,5 @@
set_aux_heat:
description: Turn auxillary heater on/off for climate device
description: Turn auxiliary heater on/off for climate device
fields:
entity_id:
@@ -101,11 +101,11 @@ set_swing_mode:
fields:
entity_id:
description: Name(s) of entities to change
example: '.nest'
example: 'climate.nest'
swing_mode:
description: New value of swing mode
example: 1
description: New value of swing mode
example: 1
ecobee_set_fan_min_on_time:
description: Set the minimum fan on time
@@ -35,7 +35,6 @@ class TeslaThermostat(TeslaDevice, ClimateDevice):
self.entity_id = ENTITY_ID_FORMAT.format(self.tesla_id)
self._target_temperature = None
self._temperature = None
self._name = self.tesla_device.name
@property
def current_operation(self):
+95
View File
@@ -0,0 +1,95 @@
"""
Toon van Eneco Thermostat Support.
This provides a component for the rebranded Quby thermostat as provided by
Eneco.
"""
from homeassistant.components.climate import (ClimateDevice,
ATTR_TEMPERATURE,
STATE_PERFORMANCE,
STATE_HEAT,
STATE_ECO,
STATE_COOL)
from homeassistant.const import TEMP_CELSIUS
import homeassistant.components.toon as toon_main
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup thermostat."""
# Add toon
add_devices((ThermostatDevice(hass), ), True)
class ThermostatDevice(ClimateDevice):
"""Interface class for the toon module and HA."""
def __init__(self, hass):
"""Initialize the device."""
self._name = 'Toon van Eneco'
self.hass = hass
self.thermos = hass.data[toon_main.TOON_HANDLE]
# set up internal state vars
self._state = None
self._temperature = None
self._setpoint = None
self._operation_list = [STATE_PERFORMANCE,
STATE_HEAT,
STATE_ECO,
STATE_COOL]
@property
def name(self):
"""Name of this Thermostat."""
return self._name
@property
def should_poll(self):
"""Polling is required."""
return True
@property
def temperature_unit(self):
"""The unit of measurement used by the platform."""
return TEMP_CELSIUS
@property
def current_operation(self):
"""Return current operation i.e. comfort, home, away."""
state = self.thermos.get_data('state')
return state
@property
def operation_list(self):
"""List of available operation modes."""
return self._operation_list
@property
def current_temperature(self):
"""Return the current temperature."""
return self.thermos.get_data('temp')
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
return self.thermos.get_data('setpoint')
def set_temperature(self, **kwargs):
"""Change the setpoint of the thermostat."""
temp = kwargs.get(ATTR_TEMPERATURE)
self.thermos.set_temp(temp)
def set_operation_mode(self, operation_mode):
"""Set new operation mode as toonlib requires it."""
toonlib_values = {STATE_PERFORMANCE: 'Comfort',
STATE_HEAT: 'Home',
STATE_ECO: 'Away',
STATE_COOL: 'Sleep'}
self.thermos.set_state(toonlib_values[operation_mode])
def update(self):
"""Update local state."""
self.thermos.update()
+161 -94
View File
@@ -1,30 +1,45 @@
"""
Support for Wink thermostats.
Support for Wink thermostats, Air Conditioners, and Water Heaters.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.wink/
"""
import logging
import asyncio
from homeassistant.components.wink import WinkDevice, DOMAIN
from homeassistant.components.climate import (
STATE_AUTO, STATE_COOL, STATE_HEAT, ClimateDevice,
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
ATTR_TEMPERATURE,
ATTR_CURRENT_HUMIDITY)
ATTR_TEMPERATURE, STATE_FAN_ONLY,
ATTR_CURRENT_HUMIDITY, STATE_ECO, STATE_ELECTRIC,
STATE_PERFORMANCE, STATE_HIGH_DEMAND,
STATE_HEAT_PUMP, STATE_GAS)
from homeassistant.const import (
TEMP_CELSIUS, STATE_ON,
STATE_OFF, STATE_UNKNOWN)
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['wink']
STATE_AUX = 'aux'
STATE_ECO = 'eco'
STATE_FAN = 'fan'
SPEED_LOW = 'low'
SPEED_MEDIUM = 'medium'
SPEED_HIGH = 'high'
HA_STATE_TO_WINK = {STATE_AUTO: 'auto',
STATE_ECO: 'eco',
STATE_FAN_ONLY: 'fan_only',
STATE_HEAT: 'heat_only',
STATE_COOL: 'cool_only',
STATE_PERFORMANCE: 'performance',
STATE_HIGH_DEMAND: 'high_demand',
STATE_HEAT_PUMP: 'heat_pump',
STATE_ELECTRIC: 'electric_only',
STATE_GAS: 'gas',
STATE_OFF: 'off'}
WINK_STATE_TO_HA = {value: key for key, value in HA_STATE_TO_WINK.items()}
ATTR_EXTERNAL_TEMPERATURE = "external_temperature"
ATTR_SMART_TEMPERATURE = "smart_temperature"
ATTR_ECO_TARGET = "eco_target"
@@ -32,28 +47,26 @@ ATTR_OCCUPIED = "occupied"
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Wink thermostat."""
"""Set up the Wink climate devices."""
import pywink
temp_unit = hass.config.units.temperature_unit
for climate in pywink.get_thermostats():
_id = climate.object_id() + climate.name()
if _id not in hass.data[DOMAIN]['unique_ids']:
add_devices([WinkThermostat(climate, hass, temp_unit)])
add_devices([WinkThermostat(climate, hass)])
for climate in pywink.get_air_conditioners():
_id = climate.object_id() + climate.name()
if _id not in hass.data[DOMAIN]['unique_ids']:
add_devices([WinkAC(climate, hass, temp_unit)])
add_devices([WinkAC(climate, hass)])
for water_heater in pywink.get_water_heaters():
_id = water_heater.object_id() + water_heater.name()
if _id not in hass.data[DOMAIN]['unique_ids']:
add_devices([WinkWaterHeater(water_heater, hass)])
# pylint: disable=abstract-method
class WinkThermostat(WinkDevice, ClimateDevice):
"""Representation of a Wink thermostat."""
def __init__(self, wink, hass, temp_unit):
"""Initialize the Wink device."""
super().__init__(wink, hass)
self._config_temp_unit = temp_unit
@asyncio.coroutine
def async_added_to_hass(self):
"""Callback when entity is added to hass."""
@@ -139,18 +152,12 @@ class WinkThermostat(WinkDevice, ClimateDevice):
"""Return current operation ie. heat, cool, idle."""
if not self.wink.is_on():
current_op = STATE_OFF
elif self.wink.current_hvac_mode() == 'cool_only':
current_op = STATE_COOL
elif self.wink.current_hvac_mode() == 'heat_only':
current_op = STATE_HEAT
elif self.wink.current_hvac_mode() == 'aux':
current_op = STATE_HEAT
elif self.wink.current_hvac_mode() == 'auto':
current_op = STATE_AUTO
elif self.wink.current_hvac_mode() == 'eco':
current_op = STATE_ECO
else:
current_op = STATE_UNKNOWN
current_op = WINK_STATE_TO_HA.get(self.wink.current_hvac_mode())
if current_op == 'aux':
return STATE_HEAT
if current_op is None:
current_op = STATE_UNKNOWN
return current_op
@property
@@ -199,11 +206,12 @@ class WinkThermostat(WinkDevice, ClimateDevice):
@property
def is_aux_heat_on(self):
"""Return true if aux heater."""
if self.wink.current_hvac_mode() == 'aux' and self.wink.is_on():
if 'aux' not in self.wink.hvac_modes():
return None
if self.wink.current_hvac_mode() == 'aux':
return True
elif self.wink.current_hvac_mode() == 'aux' and not self.wink.is_on():
return False
return None
return False
def set_temperature(self, **kwargs):
"""Set new target temperature."""
@@ -223,32 +231,27 @@ class WinkThermostat(WinkDevice, ClimateDevice):
def set_operation_mode(self, operation_mode):
"""Set operation mode."""
if operation_mode == STATE_HEAT:
self.wink.set_operation_mode('heat_only')
elif operation_mode == STATE_COOL:
self.wink.set_operation_mode('cool_only')
elif operation_mode == STATE_AUTO:
self.wink.set_operation_mode('auto')
elif operation_mode == STATE_OFF:
self.wink.set_operation_mode('off')
elif operation_mode == STATE_AUX:
self.wink.set_operation_mode('aux')
elif operation_mode == STATE_ECO:
self.wink.set_operation_mode('eco')
op_mode_to_set = HA_STATE_TO_WINK.get(operation_mode)
# The only way to disable aux heat is with the toggle
if self.is_aux_heat_on and op_mode_to_set == STATE_HEAT:
return
self.wink.set_operation_mode(op_mode_to_set)
@property
def operation_list(self):
"""List of available operation modes."""
op_list = ['off']
modes = self.wink.hvac_modes()
if 'cool_only' in modes:
op_list.append(STATE_COOL)
if 'heat_only' in modes or 'aux' in modes:
op_list.append(STATE_HEAT)
if 'auto' in modes:
op_list.append(STATE_AUTO)
if 'eco' in modes:
op_list.append(STATE_ECO)
for mode in modes:
if mode == 'aux':
continue
ha_mode = WINK_STATE_TO_HA.get(mode)
if ha_mode is not None:
op_list.append(ha_mode)
else:
error = "Invaid operation mode mapping. " + mode + \
" doesn't map. Please report this."
_LOGGER.error(error)
return op_list
def turn_away_mode_on(self):
@@ -281,12 +284,12 @@ class WinkThermostat(WinkDevice, ClimateDevice):
self.wink.set_fan_mode(fan.lower())
def turn_aux_heat_on(self):
"""Turn auxillary heater on."""
self.set_operation_mode(STATE_AUX)
"""Turn auxiliary heater on."""
self.wink.set_operation_mode('aux')
def turn_aux_heat_off(self):
"""Turn auxillary heater off."""
self.set_operation_mode(STATE_AUTO)
"""Turn auxiliary heater off."""
self.set_operation_mode(STATE_HEAT)
@property
def min_temp(self):
@@ -344,11 +347,6 @@ class WinkThermostat(WinkDevice, ClimateDevice):
class WinkAC(WinkDevice, ClimateDevice):
"""Representation of a Wink air conditioner."""
def __init__(self, wink, hass, temp_unit):
"""Initialize the Wink device."""
super().__init__(wink, hass)
self._config_temp_unit = temp_unit
@property
def temperature_unit(self):
"""Return the unit of measurement."""
@@ -382,14 +380,10 @@ class WinkAC(WinkDevice, ClimateDevice):
"""Return current operation ie. heat, cool, idle."""
if not self.wink.is_on():
current_op = STATE_OFF
elif self.wink.current_mode() == 'cool_only':
current_op = STATE_COOL
elif self.wink.current_mode() == 'auto_eco':
current_op = STATE_ECO
elif self.wink.current_mode() == 'fan_only':
current_op = STATE_FAN
else:
current_op = STATE_UNKNOWN
current_op = WINK_STATE_TO_HA.get(self.wink.current_hvac_mode())
if current_op is None:
current_op = STATE_UNKNOWN
return current_op
@property
@@ -397,12 +391,14 @@ class WinkAC(WinkDevice, ClimateDevice):
"""List of available operation modes."""
op_list = ['off']
modes = self.wink.modes()
if 'cool_only' in modes:
op_list.append(STATE_COOL)
if 'auto_eco' in modes:
op_list.append(STATE_ECO)
if 'fan_only' in modes:
op_list.append(STATE_FAN)
for mode in modes:
ha_mode = WINK_STATE_TO_HA.get(mode)
if ha_mode is not None:
op_list.append(ha_mode)
else:
error = "Invaid operation mode mapping. " + mode + \
" doesn't map. Please report this."
_LOGGER.error(error)
return op_list
def set_temperature(self, **kwargs):
@@ -412,30 +408,16 @@ class WinkAC(WinkDevice, ClimateDevice):
def set_operation_mode(self, operation_mode):
"""Set operation mode."""
if operation_mode == STATE_COOL:
self.wink.set_operation_mode('cool_only')
elif operation_mode == STATE_ECO:
self.wink.set_operation_mode('auto_eco')
elif operation_mode == STATE_OFF:
self.wink.set_operation_mode('off')
elif operation_mode == STATE_FAN:
self.wink.set_operation_mode('fan_only')
op_mode_to_set = HA_STATE_TO_WINK.get(operation_mode)
if op_mode_to_set == 'eco':
op_mode_to_set = 'auto_eco'
self.wink.set_operation_mode(op_mode_to_set)
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
return self.wink.current_max_set_point()
@property
def target_temperature_low(self):
"""Only supports cool."""
return None
@property
def target_temperature_high(self):
"""Only supports cool."""
return None
@property
def current_fan_mode(self):
"""Return the current fan mode."""
@@ -453,12 +435,97 @@ class WinkAC(WinkDevice, ClimateDevice):
"""Return a list of available fan modes."""
return [SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]
def set_fan_mode(self, mode):
def set_fan_mode(self, fan):
"""Set fan speed."""
if mode == SPEED_LOW:
if fan == SPEED_LOW:
speed = 0.4
elif mode == SPEED_MEDIUM:
elif fan == SPEED_MEDIUM:
speed = 0.8
elif mode == SPEED_HIGH:
elif fan == SPEED_HIGH:
speed = 1.0
self.wink.set_ac_fan_speed(speed)
class WinkWaterHeater(WinkDevice, ClimateDevice):
"""Representation of a Wink water heater."""
@property
def temperature_unit(self):
"""Return the unit of measurement."""
# The Wink API always returns temp in Celsius
return TEMP_CELSIUS
@property
def device_state_attributes(self):
"""Return the optional state attributes."""
data = {}
data["vacation_mode"] = self.wink.vacation_mode_enabled()
data["rheem_type"] = self.wink.rheem_type()
return data
@property
def current_operation(self):
"""
Return current operation one of the following.
["eco", "performance", "heat_pump",
"high_demand", "electric_only", "gas]
"""
if not self.wink.is_on():
current_op = STATE_OFF
else:
current_op = WINK_STATE_TO_HA.get(self.wink.current_mode())
if current_op is None:
current_op = STATE_UNKNOWN
return current_op
@property
def operation_list(self):
"""List of available operation modes."""
op_list = ['off']
modes = self.wink.modes()
for mode in modes:
if mode == 'aux':
continue
ha_mode = WINK_STATE_TO_HA.get(mode)
if ha_mode is not None:
op_list.append(ha_mode)
else:
error = "Invaid operation mode mapping. " + mode + \
" doesn't map. Please report this."
_LOGGER.error(error)
return op_list
def set_temperature(self, **kwargs):
"""Set new target temperature."""
target_temp = kwargs.get(ATTR_TEMPERATURE)
self.wink.set_temperature(target_temp)
def set_operation_mode(self, operation_mode):
"""Set operation mode."""
op_mode_to_set = HA_STATE_TO_WINK.get(operation_mode)
self.wink.set_operation_mode(op_mode_to_set)
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
return self.wink.current_set_point()
def turn_away_mode_on(self):
"""Turn away on."""
self.wink.set_vacation_mode(True)
def turn_away_mode_off(self):
"""Turn away off."""
self.wink.set_vacation_mode(False)
@property
def min_temp(self):
"""Return the minimum temperature."""
return self.wink.min_set_point()
@property
def max_temp(self):
"""Return the maximum temperature."""
return self.wink.max_set_point()
+117 -17
View File
@@ -1,47 +1,147 @@
"""Component to integrate the Home Assistant cloud."""
import asyncio
import json
import logging
import os
import voluptuous as vol
from . import http_api, auth_api
from .const import DOMAIN
from homeassistant.const import EVENT_HOMEASSISTANT_START
from . import http_api, iot
from .const import CONFIG_DIR, DOMAIN, SERVERS
REQUIREMENTS = ['warrant==0.2.0']
REQUIREMENTS = ['warrant==0.5.0']
DEPENDENCIES = ['http']
CONF_MODE = 'mode'
CONF_COGNITO_CLIENT_ID = 'cognito_client_id'
CONF_USER_POOL_ID = 'user_pool_id'
CONF_REGION = 'region'
CONF_RELAYER = 'relayer'
MODE_DEV = 'development'
MODE_STAGING = 'staging'
MODE_PRODUCTION = 'production'
DEFAULT_MODE = MODE_DEV
_LOGGER = logging.getLogger(__name__)
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Optional(CONF_MODE, default=DEFAULT_MODE):
vol.In([MODE_DEV, MODE_STAGING, MODE_PRODUCTION]),
vol.In([MODE_DEV] + list(SERVERS)),
# Change to optional when we include real servers
vol.Required(CONF_COGNITO_CLIENT_ID): str,
vol.Required(CONF_USER_POOL_ID): str,
vol.Required(CONF_REGION): str,
vol.Required(CONF_RELAYER): str,
}),
}, extra=vol.ALLOW_EXTRA)
_LOGGER = logging.getLogger(__name__)
@asyncio.coroutine
def async_setup(hass, config):
"""Initialize the Home Assistant cloud."""
mode = MODE_PRODUCTION
if DOMAIN in config:
mode = config[DOMAIN].get(CONF_MODE)
kwargs = config[DOMAIN]
else:
kwargs = {CONF_MODE: DEFAULT_MODE}
if mode != 'development':
_LOGGER.error('Only development mode is currently allowed.')
return False
cloud = hass.data[DOMAIN] = Cloud(hass, **kwargs)
data = hass.data[DOMAIN] = {
'mode': mode
}
@asyncio.coroutine
def init_cloud(event):
"""Initialize connection."""
yield from cloud.initialize()
data['auth'] = yield from hass.async_add_job(auth_api.load_auth, hass)
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, init_cloud)
yield from http_api.async_setup(hass)
return True
class Cloud:
"""Store the configuration of the cloud connection."""
def __init__(self, hass, mode, cognito_client_id=None, user_pool_id=None,
region=None, relayer=None):
"""Create an instance of Cloud."""
self.hass = hass
self.mode = mode
self.email = None
self.id_token = None
self.access_token = None
self.refresh_token = None
self.iot = iot.CloudIoT(self)
if mode == MODE_DEV:
self.cognito_client_id = cognito_client_id
self.user_pool_id = user_pool_id
self.region = region
self.relayer = relayer
else:
info = SERVERS[mode]
self.cognito_client_id = info['cognito_client_id']
self.user_pool_id = info['user_pool_id']
self.region = info['region']
self.relayer = info['relayer']
@property
def is_logged_in(self):
"""Get if cloud is logged in."""
return self.email is not None
@property
def user_info_path(self):
"""Get path to the stored auth."""
return self.path('{}_auth.json'.format(self.mode))
@asyncio.coroutine
def initialize(self):
"""Initialize and load cloud info."""
def load_config():
"""Load the configuration."""
# Ensure config dir exists
path = self.hass.config.path(CONFIG_DIR)
if not os.path.isdir(path):
os.mkdir(path)
user_info = self.user_info_path
if os.path.isfile(user_info):
with open(user_info, 'rt') as file:
info = json.loads(file.read())
self.email = info['email']
self.id_token = info['id_token']
self.access_token = info['access_token']
self.refresh_token = info['refresh_token']
yield from self.hass.async_add_job(load_config)
if self.email is not None:
yield from self.iot.connect()
def path(self, *parts):
"""Get config path inside cloud dir."""
return self.hass.config.path(CONFIG_DIR, *parts)
@asyncio.coroutine
def logout(self):
"""Close connection and remove all credentials."""
yield from self.iot.disconnect()
self.email = None
self.id_token = None
self.access_token = None
self.refresh_token = None
yield from self.hass.async_add_job(
lambda: os.remove(self.user_info_path))
def write_user_info(self):
"""Write user info to a file."""
with open(self.user_info_path, 'wt') as file:
file.write(json.dumps({
'email': self.email,
'id_token': self.id_token,
'access_token': self.access_token,
'refresh_token': self.refresh_token,
}, indent=4))
+64 -157
View File
@@ -1,10 +1,7 @@
"""Package to offer tools to authenticate with the cloud."""
import json
"""Package to communicate with the authentication API."""
import hashlib
import logging
import os
from .const import AUTH_FILE, SERVERS
from .util import get_mode
_LOGGER = logging.getLogger(__name__)
@@ -61,210 +58,120 @@ def _map_aws_exception(err):
return ex(err.response['Error']['Message'])
def load_auth(hass):
"""Load authentication from disk and verify it."""
info = _read_info(hass)
if info is None:
return Auth(hass)
auth = Auth(hass, _cognito(
hass,
id_token=info['id_token'],
access_token=info['access_token'],
refresh_token=info['refresh_token'],
))
if auth.validate_auth():
return auth
return Auth(hass)
def _generate_username(email):
"""Generate a username from an email address."""
return hashlib.sha512(email.encode('utf-8')).hexdigest()
def register(hass, email, password):
def register(cloud, email, password):
"""Register a new account."""
from botocore.exceptions import ClientError
cognito = _cognito(hass, username=email)
cognito = _cognito(cloud)
try:
cognito.register(email, password)
cognito.register(_generate_username(email), password, email=email)
except ClientError as err:
raise _map_aws_exception(err)
def confirm_register(hass, confirmation_code, email):
def confirm_register(cloud, confirmation_code, email):
"""Confirm confirmation code after registration."""
from botocore.exceptions import ClientError
cognito = _cognito(hass, username=email)
cognito = _cognito(cloud)
try:
cognito.confirm_sign_up(confirmation_code, email)
cognito.confirm_sign_up(confirmation_code, _generate_username(email))
except ClientError as err:
raise _map_aws_exception(err)
def forgot_password(hass, email):
def forgot_password(cloud, email):
"""Initiate forgotten password flow."""
from botocore.exceptions import ClientError
cognito = _cognito(hass, username=email)
cognito = _cognito(cloud, username=_generate_username(email))
try:
cognito.initiate_forgot_password()
except ClientError as err:
raise _map_aws_exception(err)
def confirm_forgot_password(hass, confirmation_code, email, new_password):
def confirm_forgot_password(cloud, confirmation_code, email, new_password):
"""Confirm forgotten password code and change password."""
from botocore.exceptions import ClientError
cognito = _cognito(hass, username=email)
cognito = _cognito(cloud, username=_generate_username(email))
try:
cognito.confirm_forgot_password(confirmation_code, new_password)
except ClientError as err:
raise _map_aws_exception(err)
class Auth(object):
"""Class that holds Cloud authentication."""
def __init__(self, hass, cognito=None):
"""Initialize Hass cloud info object."""
self.hass = hass
self.cognito = cognito
self.account = None
@property
def is_logged_in(self):
"""Return if user is logged in."""
return self.account is not None
def validate_auth(self):
"""Validate that the contained auth is valid."""
from botocore.exceptions import ClientError
try:
self._refresh_account_info()
except ClientError as err:
if err.response['Error']['Code'] != 'NotAuthorizedException':
_LOGGER.error('Unexpected error verifying auth: %s', err)
return False
try:
self.renew_access_token()
self._refresh_account_info()
except ClientError:
_LOGGER.error('Unable to refresh auth token: %s', err)
return False
return True
def login(self, username, password):
"""Login using a username and password."""
from botocore.exceptions import ClientError
from warrant.exceptions import ForceChangePasswordException
cognito = _cognito(self.hass, username=username)
try:
cognito.authenticate(password=password)
self.cognito = cognito
self._refresh_account_info()
_write_info(self.hass, self)
except ForceChangePasswordException as err:
raise PasswordChangeRequired
except ClientError as err:
raise _map_aws_exception(err)
def _refresh_account_info(self):
"""Refresh the account info.
Raises boto3 exceptions.
"""
self.account = self.cognito.get_user()
def renew_access_token(self):
"""Refresh token."""
from botocore.exceptions import ClientError
try:
self.cognito.renew_access_token()
_write_info(self.hass, self)
return True
except ClientError as err:
_LOGGER.error('Error refreshing token: %s', err)
return False
def logout(self):
"""Invalidate token."""
from botocore.exceptions import ClientError
try:
self.cognito.logout()
self.account = None
_write_info(self.hass, self)
except ClientError as err:
raise _map_aws_exception(err)
def login(cloud, email, password):
"""Log user in and fetch certificate."""
cognito = _authenticate(cloud, email, password)
cloud.id_token = cognito.id_token
cloud.access_token = cognito.access_token
cloud.refresh_token = cognito.refresh_token
cloud.email = email
cloud.write_user_info()
def _read_info(hass):
"""Read auth file."""
path = hass.config.path(AUTH_FILE)
def check_token(cloud):
"""Check that the token is valid and verify if needed."""
from botocore.exceptions import ClientError
if not os.path.isfile(path):
return None
cognito = _cognito(
cloud,
access_token=cloud.access_token,
refresh_token=cloud.refresh_token)
with open(path) as file:
return json.load(file).get(get_mode(hass))
try:
if cognito.check_token():
cloud.id_token = cognito.id_token
cloud.access_token = cognito.access_token
cloud.write_user_info()
except ClientError as err:
raise _map_aws_exception(err)
def _write_info(hass, auth):
"""Write auth info for specified mode.
def _authenticate(cloud, email, password):
"""Log in and return an authenticated Cognito instance."""
from botocore.exceptions import ClientError
from warrant.exceptions import ForceChangePasswordException
Pass in None for data to remove authentication for that mode.
"""
path = hass.config.path(AUTH_FILE)
mode = get_mode(hass)
assert not cloud.is_logged_in, 'Cannot login if already logged in.'
if os.path.isfile(path):
with open(path) as file:
content = json.load(file)
else:
content = {}
cognito = _cognito(cloud, username=email)
if auth.is_logged_in:
content[mode] = {
'id_token': auth.cognito.id_token,
'access_token': auth.cognito.access_token,
'refresh_token': auth.cognito.refresh_token,
}
else:
content.pop(mode, None)
try:
cognito.authenticate(password=password)
return cognito
with open(path, 'wt') as file:
file.write(json.dumps(content, indent=4, sort_keys=True))
except ForceChangePasswordException as err:
raise PasswordChangeRequired
except ClientError as err:
raise _map_aws_exception(err)
def _cognito(hass, **kwargs):
def _cognito(cloud, **kwargs):
"""Get the client credentials."""
import botocore
import boto3
from warrant import Cognito
mode = get_mode(hass)
info = SERVERS.get(mode)
if info is None:
raise ValueError('Mode {} is not supported.'.format(mode))
cognito = Cognito(
user_pool_id=info['identity_pool_id'],
client_id=info['client_id'],
user_pool_region=info['region'],
access_key=info['access_key_id'],
secret_key=info['secret_access_key'],
user_pool_id=cloud.user_pool_id,
client_id=cloud.cognito_client_id,
user_pool_region=cloud.region,
**kwargs
)
cognito.client = boto3.client(
'cognito-idp',
region_name=cloud.region,
config=botocore.config.Config(
signature_version=botocore.UNSIGNED
)
)
return cognito
+8 -8
View File
@@ -1,14 +1,14 @@
"""Constants for the cloud component."""
DOMAIN = 'cloud'
CONFIG_DIR = '.cloud'
REQUEST_TIMEOUT = 10
AUTH_FILE = '.cloud'
SERVERS = {
'development': {
'client_id': '3k755iqfcgv8t12o4pl662mnos',
'identity_pool_id': 'us-west-2_vDOfweDJo',
'region': 'us-west-2',
'access_key_id': 'AKIAJGRK7MILPRJTT2ZQ',
'secret_access_key': 'lscdYBApxrLWL0HKuVqVXWv3ou8ZVXgG7rZBu/Sz'
}
# Example entry:
# 'production': {
# 'cognito_client_id': '',
# 'user_pool_id': '',
# 'region': '',
# 'relayer': ''
# }
}
+20 -15
View File
@@ -10,7 +10,7 @@ from homeassistant.components.http import (
HomeAssistantView, RequestDataValidator)
from . import auth_api
from .const import REQUEST_TIMEOUT
from .const import DOMAIN, REQUEST_TIMEOUT
_LOGGER = logging.getLogger(__name__)
@@ -74,13 +74,14 @@ class CloudLoginView(HomeAssistantView):
def post(self, request, data):
"""Handle login request."""
hass = request.app['hass']
auth = hass.data['cloud']['auth']
cloud = hass.data[DOMAIN]
with async_timeout.timeout(REQUEST_TIMEOUT, loop=hass.loop):
yield from hass.async_add_job(auth.login, data['email'],
yield from hass.async_add_job(auth_api.login, cloud, data['email'],
data['password'])
hass.async_add_job(cloud.iot.connect)
return self.json(_auth_data(auth))
return self.json(_account_data(cloud))
class CloudLogoutView(HomeAssistantView):
@@ -94,10 +95,10 @@ class CloudLogoutView(HomeAssistantView):
def post(self, request):
"""Handle logout request."""
hass = request.app['hass']
auth = hass.data['cloud']['auth']
cloud = hass.data[DOMAIN]
with async_timeout.timeout(REQUEST_TIMEOUT, loop=hass.loop):
yield from hass.async_add_job(auth.logout)
yield from cloud.logout()
return self.json_message('ok')
@@ -112,12 +113,12 @@ class CloudAccountView(HomeAssistantView):
def get(self, request):
"""Get account info."""
hass = request.app['hass']
auth = hass.data['cloud']['auth']
cloud = hass.data[DOMAIN]
if not auth.is_logged_in:
if not cloud.is_logged_in:
return self.json_message('Not logged in', 400)
return self.json(_auth_data(auth))
return self.json(_account_data(cloud))
class CloudRegisterView(HomeAssistantView):
@@ -135,10 +136,11 @@ class CloudRegisterView(HomeAssistantView):
def post(self, request, data):
"""Handle registration request."""
hass = request.app['hass']
cloud = hass.data[DOMAIN]
with async_timeout.timeout(REQUEST_TIMEOUT, loop=hass.loop):
yield from hass.async_add_job(
auth_api.register, hass, data['email'], data['password'])
auth_api.register, cloud, data['email'], data['password'])
return self.json_message('ok')
@@ -158,10 +160,11 @@ class CloudConfirmRegisterView(HomeAssistantView):
def post(self, request, data):
"""Handle registration confirmation request."""
hass = request.app['hass']
cloud = hass.data[DOMAIN]
with async_timeout.timeout(REQUEST_TIMEOUT, loop=hass.loop):
yield from hass.async_add_job(
auth_api.confirm_register, hass, data['confirmation_code'],
auth_api.confirm_register, cloud, data['confirmation_code'],
data['email'])
return self.json_message('ok')
@@ -181,10 +184,11 @@ class CloudForgotPasswordView(HomeAssistantView):
def post(self, request, data):
"""Handle forgot password request."""
hass = request.app['hass']
cloud = hass.data[DOMAIN]
with async_timeout.timeout(REQUEST_TIMEOUT, loop=hass.loop):
yield from hass.async_add_job(
auth_api.forgot_password, hass, data['email'])
auth_api.forgot_password, cloud, data['email'])
return self.json_message('ok')
@@ -205,18 +209,19 @@ class CloudConfirmForgotPasswordView(HomeAssistantView):
def post(self, request, data):
"""Handle forgot password confirm request."""
hass = request.app['hass']
cloud = hass.data[DOMAIN]
with async_timeout.timeout(REQUEST_TIMEOUT, loop=hass.loop):
yield from hass.async_add_job(
auth_api.confirm_forgot_password, hass,
auth_api.confirm_forgot_password, cloud,
data['confirmation_code'], data['email'],
data['new_password'])
return self.json_message('ok')
def _auth_data(auth):
def _account_data(cloud):
"""Generate the auth data JSON response."""
return {
'email': auth.account.email
'email': cloud.email
}
+194
View File
@@ -0,0 +1,194 @@
"""Module to handle messages from Home Assistant cloud."""
import asyncio
import logging
from aiohttp import hdrs, client_exceptions, WSMsgType
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.components.alexa import smart_home
from homeassistant.util.decorator import Registry
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from . import auth_api
HANDLERS = Registry()
_LOGGER = logging.getLogger(__name__)
class UnknownHandler(Exception):
"""Exception raised when trying to handle unknown handler."""
class CloudIoT:
"""Class to manage the IoT connection."""
def __init__(self, cloud):
"""Initialize the CloudIoT class."""
self.cloud = cloud
self.client = None
self.close_requested = False
self.tries = 0
@property
def is_connected(self):
"""Return if connected to the cloud."""
return self.client is not None
@asyncio.coroutine
def connect(self):
"""Connect to the IoT broker."""
if self.client is not None:
raise RuntimeError('Cannot connect while already connected')
self.close_requested = False
hass = self.cloud.hass
remove_hass_stop_listener = None
session = async_get_clientsession(self.cloud.hass)
@asyncio.coroutine
def _handle_hass_stop(event):
"""Handle Home Assistant shutting down."""
nonlocal remove_hass_stop_listener
remove_hass_stop_listener = None
yield from self.disconnect()
client = None
disconnect_warn = None
try:
yield from hass.async_add_job(auth_api.check_token, self.cloud)
self.client = client = yield from session.ws_connect(
self.cloud.relayer, headers={
hdrs.AUTHORIZATION:
'Bearer {}'.format(self.cloud.access_token)
})
self.tries = 0
remove_hass_stop_listener = hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_STOP, _handle_hass_stop)
_LOGGER.info('Connected')
while not client.closed:
msg = yield from client.receive()
if msg.type in (WSMsgType.ERROR, WSMsgType.CLOSED,
WSMsgType.CLOSING):
disconnect_warn = 'Closed by server'
break
elif msg.type != WSMsgType.TEXT:
disconnect_warn = 'Received non-Text message: {}'.format(
msg.type)
break
try:
msg = msg.json()
except ValueError:
disconnect_warn = 'Received invalid JSON.'
break
_LOGGER.debug('Received message: %s', msg)
response = {
'msgid': msg['msgid'],
}
try:
result = yield from async_handle_message(
hass, self.cloud, msg['handler'], msg['payload'])
# No response from handler
if result is None:
continue
response['payload'] = result
except UnknownHandler:
response['error'] = 'unknown-handler'
except Exception: # pylint: disable=broad-except
_LOGGER.exception('Error handling message')
response['error'] = 'exception'
_LOGGER.debug('Publishing message: %s', response)
yield from client.send_json(response)
except auth_api.CloudError:
_LOGGER.warning('Unable to connect: Unable to refresh token.')
except client_exceptions.WSServerHandshakeError as err:
if err.code == 401:
disconnect_warn = 'Invalid auth.'
self.close_requested = True
# Should we notify user?
else:
_LOGGER.warning('Unable to connect: %s', err)
except client_exceptions.ClientError as err:
_LOGGER.warning('Unable to connect: %s', err)
except Exception: # pylint: disable=broad-except
if not self.close_requested:
_LOGGER.exception('Unexpected error')
finally:
if disconnect_warn is not None:
_LOGGER.warning('Connection closed: %s', disconnect_warn)
if remove_hass_stop_listener is not None:
remove_hass_stop_listener()
if client is not None:
self.client = None
yield from client.close()
if not self.close_requested:
self.tries += 1
# Sleep 0, 5, 10, 15 … up to 30 seconds between retries
yield from asyncio.sleep(
min(30, (self.tries - 1) * 5), loop=hass.loop)
hass.async_add_job(self.connect())
@asyncio.coroutine
def disconnect(self):
"""Disconnect the client."""
self.close_requested = True
yield from self.client.close()
@asyncio.coroutine
def async_handle_message(hass, cloud, handler_name, payload):
"""Handle incoming IoT message."""
handler = HANDLERS.get(handler_name)
if handler is None:
raise UnknownHandler()
return (yield from handler(hass, cloud, payload))
@HANDLERS.register('alexa')
@asyncio.coroutine
def async_handle_alexa(hass, cloud, payload):
"""Handle an incoming IoT message for Alexa."""
return (yield from smart_home.async_handle_message(hass, payload))
@HANDLERS.register('cloud')
@asyncio.coroutine
def async_handle_cloud(hass, cloud, payload):
"""Handle an incoming IoT message for cloud component."""
action = payload['action']
if action == 'logout':
yield from cloud.logout()
_LOGGER.error('You have been logged out from Home Assistant cloud: %s',
payload['reason'])
else:
_LOGGER.warning('Received unknown cloud action: %s', action)
return None
-10
View File
@@ -1,10 +0,0 @@
"""Utilities for the cloud integration."""
from .const import DOMAIN
def get_mode(hass):
"""Return the current mode of the cloud component.
Async friendly.
"""
return hass.data[DOMAIN]['mode']
+2 -11
View File
@@ -169,21 +169,12 @@ def async_setup(hass, config):
params.pop(ATTR_ENTITY_ID, None)
# call method
update_tasks = []
for cover in covers:
yield from getattr(cover, method['method'])(**params)
update_tasks = []
for cover in covers:
if not cover.should_poll:
continue
update_coro = hass.async_add_job(
cover.async_update_ha_state(True))
if hasattr(cover, 'async_update'):
update_tasks.append(update_coro)
else:
yield from update_coro
update_tasks.append(cover.async_update_ha_state(True))
if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)
+46 -8
View File
@@ -21,8 +21,8 @@ from homeassistant.const import (
CONF_NAME, CONF_VALUE_TEMPLATE, CONF_OPTIMISTIC, STATE_OPEN,
STATE_CLOSED, STATE_UNKNOWN)
from homeassistant.components.mqtt import (
CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN,
valid_publish_topic, valid_subscribe_topic)
CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_AVAILABILITY_TOPIC,
CONF_QOS, CONF_RETAIN, valid_publish_topic, valid_subscribe_topic)
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
@@ -37,6 +37,8 @@ CONF_SET_POSITION_TEMPLATE = 'set_position_template'
CONF_PAYLOAD_OPEN = 'payload_open'
CONF_PAYLOAD_CLOSE = 'payload_close'
CONF_PAYLOAD_STOP = 'payload_stop'
CONF_PAYLOAD_AVAILABLE = 'payload_available'
CONF_PAYLOAD_NOT_AVAILABLE = 'payload_not_available'
CONF_STATE_OPEN = 'state_open'
CONF_STATE_CLOSED = 'state_closed'
CONF_TILT_CLOSED_POSITION = 'tilt_closed_value'
@@ -50,6 +52,8 @@ DEFAULT_NAME = 'MQTT Cover'
DEFAULT_PAYLOAD_OPEN = 'OPEN'
DEFAULT_PAYLOAD_CLOSE = 'CLOSE'
DEFAULT_PAYLOAD_STOP = 'STOP'
DEFAULT_PAYLOAD_AVAILABLE = 'online'
DEFAULT_PAYLOAD_NOT_AVAILABLE = 'offline'
DEFAULT_OPTIMISTIC = False
DEFAULT_RETAIN = False
DEFAULT_TILT_CLOSED_POSITION = 0
@@ -69,11 +73,16 @@ PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_SET_POSITION_TEMPLATE, default=None): cv.template,
vol.Optional(CONF_RETAIN, default=DEFAULT_RETAIN): cv.boolean,
vol.Optional(CONF_STATE_TOPIC): valid_subscribe_topic,
vol.Optional(CONF_AVAILABILITY_TOPIC, default=None): valid_subscribe_topic,
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PAYLOAD_OPEN, default=DEFAULT_PAYLOAD_OPEN): cv.string,
vol.Optional(CONF_PAYLOAD_CLOSE, default=DEFAULT_PAYLOAD_CLOSE): cv.string,
vol.Optional(CONF_PAYLOAD_STOP, default=DEFAULT_PAYLOAD_STOP): cv.string,
vol.Optional(CONF_PAYLOAD_AVAILABLE,
default=DEFAULT_PAYLOAD_AVAILABLE): cv.string,
vol.Optional(CONF_PAYLOAD_NOT_AVAILABLE,
default=DEFAULT_PAYLOAD_NOT_AVAILABLE): cv.string,
vol.Optional(CONF_STATE_OPEN, default=STATE_OPEN): cv.string,
vol.Optional(CONF_STATE_CLOSED, default=STATE_CLOSED): cv.string,
vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
@@ -106,6 +115,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
config.get(CONF_NAME),
config.get(CONF_STATE_TOPIC),
config.get(CONF_COMMAND_TOPIC),
config.get(CONF_AVAILABILITY_TOPIC),
config.get(CONF_TILT_COMMAND_TOPIC),
config.get(CONF_TILT_STATUS_TOPIC),
config.get(CONF_QOS),
@@ -115,6 +125,8 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
config.get(CONF_PAYLOAD_OPEN),
config.get(CONF_PAYLOAD_CLOSE),
config.get(CONF_PAYLOAD_STOP),
config.get(CONF_PAYLOAD_AVAILABLE),
config.get(CONF_PAYLOAD_NOT_AVAILABLE),
config.get(CONF_OPTIMISTIC),
value_template,
config.get(CONF_TILT_OPEN_POSITION),
@@ -131,9 +143,10 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
class MqttCover(CoverDevice):
"""Representation of a cover that can be controlled using MQTT."""
def __init__(self, name, state_topic, command_topic, tilt_command_topic,
tilt_status_topic, qos, retain, state_open, state_closed,
payload_open, payload_close, payload_stop,
def __init__(self, name, state_topic, command_topic, availability_topic,
tilt_command_topic, tilt_status_topic, qos, retain,
state_open, state_closed, payload_open, payload_close,
payload_stop, payload_available, payload_not_available,
optimistic, value_template, tilt_open_position,
tilt_closed_position, tilt_min, tilt_max, tilt_optimistic,
tilt_invert, position_topic, set_position_template):
@@ -143,12 +156,16 @@ class MqttCover(CoverDevice):
self._name = name
self._state_topic = state_topic
self._command_topic = command_topic
self._availability_topic = availability_topic
self._available = True if availability_topic is None else False
self._tilt_command_topic = tilt_command_topic
self._tilt_status_topic = tilt_status_topic
self._qos = qos
self._payload_open = payload_open
self._payload_close = payload_close
self._payload_stop = payload_stop
self._payload_available = payload_available
self._payload_not_available = payload_not_available
self._state_open = state_open
self._state_closed = state_closed
self._retain = retain
@@ -181,8 +198,8 @@ class MqttCover(CoverDevice):
self.async_schedule_update_ha_state()
@callback
def message_received(topic, payload, qos):
"""Handle new MQTT message."""
def state_message_received(topic, payload, qos):
"""Handle new MQTT state messages."""
if self._template is not None:
payload = self._template.async_render_with_possible_json_value(
payload)
@@ -205,12 +222,28 @@ class MqttCover(CoverDevice):
self.async_schedule_update_ha_state()
@callback
def availability_message_received(topic, payload, qos):
"""Handle new MQTT availability messages."""
if payload == self._payload_available:
self._available = True
elif payload == self._payload_not_available:
self._available = False
self.async_schedule_update_ha_state()
if self._state_topic is None:
# Force into optimistic mode.
self._optimistic = True
else:
yield from mqtt.async_subscribe(
self.hass, self._state_topic, message_received, self._qos)
self.hass, self._state_topic,
state_message_received, self._qos)
if self._availability_topic is not None:
yield from mqtt.async_subscribe(
self.hass, self._availability_topic,
availability_message_received, self._qos)
if self._tilt_status_topic is None:
self._tilt_optimistic = True
@@ -230,6 +263,11 @@ class MqttCover(CoverDevice):
"""Return the name of the cover."""
return self._name
@property
def available(self) -> bool:
"""Return if cover is available."""
return self._available
@property
def is_closed(self):
"""Return if the cover is closed."""
+121
View File
@@ -0,0 +1,121 @@
"""
Support for Rflink Cover devices.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/cover.rflink/
"""
import asyncio
import logging
import voluptuous as vol
from homeassistant.components.rflink import (
DATA_ENTITY_GROUP_LOOKUP, DATA_ENTITY_LOOKUP,
DEVICE_DEFAULTS_SCHEMA, EVENT_KEY_COMMAND, RflinkCommand)
from homeassistant.components.cover import (
CoverDevice, PLATFORM_SCHEMA)
import homeassistant.helpers.config_validation as cv
from homeassistant.const import CONF_NAME
DEPENDENCIES = ['rflink']
_LOGGER = logging.getLogger(__name__)
CONF_ALIASES = 'aliases'
CONF_GROUP_ALIASES = 'group_aliases'
CONF_GROUP = 'group'
CONF_NOGROUP_ALIASES = 'nogroup_aliases'
CONF_DEVICE_DEFAULTS = 'device_defaults'
CONF_DEVICES = 'devices'
CONF_AUTOMATIC_ADD = 'automatic_add'
CONF_FIRE_EVENT = 'fire_event'
CONF_IGNORE_DEVICES = 'ignore_devices'
CONF_RECONNECT_INTERVAL = 'reconnect_interval'
CONF_SIGNAL_REPETITIONS = 'signal_repetitions'
CONF_WAIT_FOR_ACK = 'wait_for_ack'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_DEVICE_DEFAULTS, default=DEVICE_DEFAULTS_SCHEMA({})):
DEVICE_DEFAULTS_SCHEMA,
vol.Optional(CONF_DEVICES, default={}): vol.Schema({
cv.string: {
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_ALIASES, default=[]):
vol.All(cv.ensure_list, [cv.string]),
vol.Optional(CONF_GROUP_ALIASES, default=[]):
vol.All(cv.ensure_list, [cv.string]),
vol.Optional(CONF_NOGROUP_ALIASES, default=[]):
vol.All(cv.ensure_list, [cv.string]),
vol.Optional(CONF_FIRE_EVENT, default=False): cv.boolean,
vol.Optional(CONF_SIGNAL_REPETITIONS): vol.Coerce(int),
vol.Optional(CONF_GROUP, default=True): cv.boolean,
},
}),
})
def devices_from_config(domain_config, hass=None):
"""Parse configuration and add Rflink cover devices."""
devices = []
for device_id, config in domain_config[CONF_DEVICES].items():
device_config = dict(domain_config[CONF_DEVICE_DEFAULTS], **config)
device = RflinkCover(device_id, hass, **device_config)
devices.append(device)
# Register entity (and aliases) to listen to incoming rflink events
# Device id and normal aliases respond to normal and group command
hass.data[DATA_ENTITY_LOOKUP][
EVENT_KEY_COMMAND][device_id].append(device)
if config[CONF_GROUP]:
hass.data[DATA_ENTITY_GROUP_LOOKUP][
EVENT_KEY_COMMAND][device_id].append(device)
for _id in config[CONF_ALIASES]:
hass.data[DATA_ENTITY_LOOKUP][
EVENT_KEY_COMMAND][_id].append(device)
hass.data[DATA_ENTITY_GROUP_LOOKUP][
EVENT_KEY_COMMAND][_id].append(device)
return devices
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up the Rflink cover platform."""
async_add_devices(devices_from_config(config, hass))
class RflinkCover(RflinkCommand, CoverDevice):
"""Rflink entity which can switch on/stop/off (eg: cover)."""
def _handle_event(self, event):
"""Adjust state if Rflink picks up a remote command for this device."""
self.cancel_queued_send_commands()
command = event['command']
if command in ['on', 'allon']:
self._state = True
elif command in ['off', 'alloff']:
self._state = False
@property
def should_poll(self):
"""No polling available in RFlink cover."""
return False
@property
def is_closed(self):
"""Return if the cover is closed."""
return None
def async_close_cover(self, **kwargs):
"""Turn the device close."""
return self._async_handle_command("close_cover")
def async_open_cover(self, **kwargs):
"""Turn the device open."""
return self._async_handle_command("open_cover")
def async_stop_cover(self, **kwargs):
"""Turn the device stop."""
return self._async_handle_command("stop_cover")
+1 -6
View File
@@ -24,7 +24,6 @@ from homeassistant.exceptions import TemplateError
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import async_generate_entity_id
from homeassistant.helpers.event import async_track_state_change
from homeassistant.helpers.restore_state import async_get_last_state
from homeassistant.helpers.script import Script
_LOGGER = logging.getLogger(__name__)
@@ -134,7 +133,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
_LOGGER.error("No covers added")
return False
async_add_devices(covers, True)
async_add_devices(covers)
return True
@@ -190,10 +189,6 @@ class CoverTemplate(CoverDevice):
@asyncio.coroutine
def async_added_to_hass(self):
"""Register callbacks."""
state = yield from async_get_last_state(self.hass, self.entity_id)
if state:
self._position = 100 if state.state == STATE_OPEN else 0
@callback
def template_cover_state_listener(entity, old_state, new_state):
"""Handle target device state changes."""
+3 -3
View File
@@ -87,8 +87,8 @@ def async_setup(hass, config):
# Set up input boolean
tasks.append(bootstrap.async_setup_component(
hass, 'input_slider',
{'input_slider': {
hass, 'input_number',
{'input_number': {
'noise_allowance': {'icon': 'mdi:bell-ring',
'min': 0,
'max': 10,
@@ -163,7 +163,7 @@ def async_setup(hass, config):
'scene.romantic_lights']))
tasks2.append(group.Group.async_create_group(hass, 'Bedroom', [
lights[0], switches[1], media_players[0],
'input_slider.noise_allowance']))
'input_number.noise_allowance']))
tasks2.append(group.Group.async_create_group(hass, 'Kitchen', [
lights[2], 'cover.kitchen_window', 'lock.kitchen_door']))
tasks2.append(group.Group.async_create_group(hass, 'Doors', [
@@ -18,11 +18,10 @@ from homeassistant.setup import async_prepare_setup_platform
from homeassistant.core import callback
from homeassistant.loader import bind_hass
from homeassistant.components import group, zone
from homeassistant.components.discovery import SERVICE_NETGEAR
from homeassistant.config import load_yaml_config_file, async_log_exception
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers import config_per_platform, discovery
from homeassistant.helpers import config_per_platform
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.restore_state import async_get_last_state
@@ -89,10 +88,6 @@ PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({
cv.time_period, cv.positive_timedelta)
})
DISCOVERY_PLATFORMS = {
SERVICE_NETGEAR: 'netgear',
}
@bind_hass
def is_on(hass: HomeAssistantType, entity_id: str=None):
@@ -180,22 +175,6 @@ def async_setup(hass: HomeAssistantType, config: ConfigType):
tracker.async_setup_group()
@callback
def async_device_tracker_discovered(service, info):
"""Handle the discovery of device tracker platforms."""
hass.async_add_job(
async_setup_platform(DISCOVERY_PLATFORMS[service], {}, info))
discovery.async_listen(
hass, DISCOVERY_PLATFORMS.keys(), async_device_tracker_discovered)
@asyncio.coroutine
def async_platform_discovered(platform, info):
"""Load a platform."""
yield from async_setup_platform(platform, {}, disc_info=info)
discovery.async_listen_platform(hass, DOMAIN, async_platform_discovered)
# Clean up stale devices
async_track_utc_time_change(
hass, tracker.async_update_stale, second=range(0, 60, 5))
@@ -13,7 +13,7 @@ from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
REQUIREMENTS = ['fritzconnection==0.6.3']
REQUIREMENTS = ['fritzconnection==0.6.5']
_LOGGER = logging.getLogger(__name__)
@@ -248,7 +248,7 @@ class Icloud(DeviceScanner):
self._trusted_device, self._verification_code):
raise PyiCloudException('Unknown failure')
except PyiCloudException as error:
# Reset to the inital 2FA state to allow the user to retry
# Reset to the initial 2FA state to allow the user to retry
_LOGGER.error("Failed to verify verification code: %s", error)
self._trusted_device = None
self._verification_code = None
@@ -14,7 +14,7 @@ from homeassistant.components.device_tracker import (
from homeassistant.const import (
CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_PORT)
REQUIREMENTS = ['librouteros==1.0.2']
REQUIREMENTS = ['librouteros==1.0.4']
MTK_DEFAULT_API_PORT = '8728'
@@ -83,6 +83,15 @@ class MikrotikScanner(DeviceScanner):
routerboard_info[0].get('model', 'Router'),
self.host)
self.connected = True
self.capsman_exist = self.client(
cmd='/capsman/interface/getall'
)
if not self.capsman_exist:
_LOGGER.info(
'Mikrotik %s: Not a CAPSman controller. Trying '
'local interfaces ',
self.host
)
self.wireless_exist = self.client(
cmd='/interface/wireless/getall'
)
@@ -111,7 +120,9 @@ class MikrotikScanner(DeviceScanner):
def _update_info(self):
"""Retrieve latest information from the Mikrotik box."""
if self.wireless_exist:
if self.capsman_exist:
devices_tracker = 'capsman'
elif self.wireless_exist:
devices_tracker = 'wireless'
else:
devices_tracker = 'ip'
@@ -123,7 +134,11 @@ class MikrotikScanner(DeviceScanner):
)
device_names = self.client(cmd='/ip/dhcp-server/lease/getall')
if self.wireless_exist:
if devices_tracker == 'capsman':
devices = self.client(
cmd='/caps-man/registration-table/getall'
)
elif devices_tracker == 'wireless':
devices = self.client(
cmd='/interface/wireless/registration-table/getall'
)
@@ -1,30 +1,31 @@
"""
Support the OwnTracks platform.
Device tracker platform that adds support for OwnTracks over MQTT.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.owntracks/
"""
import asyncio
import base64
import json
import logging
import base64
from collections import defaultdict
import voluptuous as vol
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
import homeassistant.components.mqtt as mqtt
from homeassistant.const import STATE_HOME
from homeassistant.util import convert, slugify
import homeassistant.helpers.config_validation as cv
from homeassistant.components import zone as zone_comp
from homeassistant.components.device_tracker import PLATFORM_SCHEMA
from homeassistant.const import STATE_HOME
from homeassistant.core import callback
from homeassistant.util import slugify, decorator
DEPENDENCIES = ['mqtt']
REQUIREMENTS = ['libnacl==1.5.2']
REQUIREMENTS = ['libnacl==1.6.0']
_LOGGER = logging.getLogger(__name__)
HANDLERS = decorator.Registry()
BEACON_DEV_ID = 'beacon'
CONF_MAX_GPS_ACCURACY = 'max_gps_accuracy'
@@ -32,17 +33,9 @@ CONF_SECRET = 'secret'
CONF_WAYPOINT_IMPORT = 'waypoints'
CONF_WAYPOINT_WHITELIST = 'waypoint_whitelist'
EVENT_TOPIC = 'owntracks/+/+/event'
DEPENDENCIES = ['mqtt']
LOCATION_TOPIC = 'owntracks/+/+'
VALIDATE_LOCATION = 'location'
VALIDATE_TRANSITION = 'transition'
VALIDATE_WAYPOINTS = 'waypoints'
WAYPOINT_LAT_KEY = 'lat'
WAYPOINT_LON_KEY = 'lon'
WAYPOINT_TOPIC = 'owntracks/{}/{}/waypoints'
OWNTRACKS_TOPIC = 'owntracks/#'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_MAX_GPS_ACCURACY): vol.Coerce(float),
@@ -72,300 +65,65 @@ def get_cipher():
@asyncio.coroutine
def async_setup_scanner(hass, config, async_see, discovery_info=None):
"""Set up an OwnTracks tracker."""
max_gps_accuracy = config.get(CONF_MAX_GPS_ACCURACY)
waypoint_import = config.get(CONF_WAYPOINT_IMPORT)
waypoint_whitelist = config.get(CONF_WAYPOINT_WHITELIST)
secret = config.get(CONF_SECRET)
context = context_from_config(async_see, config)
mobile_beacons_active = defaultdict(list)
regions_entered = defaultdict(list)
def decrypt_payload(topic, ciphertext):
"""Decrypt encrypted payload."""
@asyncio.coroutine
def async_handle_mqtt_message(topic, payload, qos):
"""Handle incoming OwnTracks message."""
try:
keylen, decrypt = get_cipher()
except OSError:
_LOGGER.warning(
"Ignoring encrypted payload because libsodium not installed")
return None
if isinstance(secret, dict):
key = secret.get(topic)
else:
key = secret
if key is None:
_LOGGER.warning(
"Ignoring encrypted payload because no decryption key known "
"for topic %s", topic)
return None
key = key.encode("utf-8")
key = key[:keylen]
key = key.ljust(keylen, b'\0')
try:
ciphertext = base64.b64decode(ciphertext)
message = decrypt(ciphertext, key)
message = message.decode("utf-8")
_LOGGER.debug("Decrypted payload: %s", message)
return message
except ValueError:
_LOGGER.warning(
"Ignoring encrypted payload because unable to decrypt using "
"key for topic %s", topic)
return None
def validate_payload(topic, payload, data_type):
"""Validate the OwnTracks payload."""
try:
data = json.loads(payload)
message = json.loads(payload)
except ValueError:
# If invalid JSON
_LOGGER.error("Unable to parse payload as JSON: %s", payload)
return None
if isinstance(data, dict) and \
data.get('_type') == 'encrypted' and \
'data' in data:
plaintext_payload = decrypt_payload(topic, data['data'])
if plaintext_payload is None:
return None
return validate_payload(topic, plaintext_payload, data_type)
if not isinstance(data, dict) or data.get('_type') != data_type:
_LOGGER.debug("Skipping %s update for following data "
"because of missing or malformatted data: %s",
data_type, data)
return None
if data_type == VALIDATE_TRANSITION or data_type == VALIDATE_WAYPOINTS:
return data
if max_gps_accuracy is not None and \
convert(data.get('acc'), float, 0.0) > max_gps_accuracy:
_LOGGER.info("Ignoring %s update because expected GPS "
"accuracy %s is not met: %s",
data_type, max_gps_accuracy, payload)
return None
if convert(data.get('acc'), float, 1.0) == 0.0:
_LOGGER.warning(
"Ignoring %s update because GPS accuracy is zero: %s",
data_type, payload)
return None
return data
@callback
def async_owntracks_location_update(topic, payload, qos):
"""MQTT message received."""
# Docs on available data:
# http://owntracks.org/booklet/tech/json/#_typelocation
data = validate_payload(topic, payload, VALIDATE_LOCATION)
if not data:
return
dev_id, kwargs = _parse_see_args(topic, data)
message['topic'] = topic
if regions_entered[dev_id]:
_LOGGER.debug(
"Location update ignored, inside region %s",
regions_entered[-1])
return
hass.async_add_job(async_see(**kwargs))
async_see_beacons(dev_id, kwargs)
@callback
def async_owntracks_event_update(topic, payload, qos):
"""Handle MQTT event (geofences)."""
# Docs on available data:
# http://owntracks.org/booklet/tech/json/#_typetransition
data = validate_payload(topic, payload, VALIDATE_TRANSITION)
if not data:
return
if data.get('desc') is None:
_LOGGER.error(
"Location missing from `Entering/Leaving` message - "
"please turn `Share` on in OwnTracks app")
return
# OwnTracks uses - at the start of a beacon zone
# to switch on 'hold mode' - ignore this
location = data['desc'].lstrip("-")
if location.lower() == 'home':
location = STATE_HOME
dev_id, kwargs = _parse_see_args(topic, data)
def enter_event():
"""Execute enter event."""
zone = hass.states.get("zone.{}".format(slugify(location)))
if zone is None and data.get('t') == 'b':
# Not a HA zone, and a beacon so assume mobile
beacons = mobile_beacons_active[dev_id]
if location not in beacons:
beacons.append(location)
_LOGGER.info("Added beacon %s", location)
else:
# Normal region
regions = regions_entered[dev_id]
if location not in regions:
regions.append(location)
_LOGGER.info("Enter region %s", location)
_set_gps_from_zone(kwargs, location, zone)
hass.async_add_job(async_see(**kwargs))
async_see_beacons(dev_id, kwargs)
def leave_event():
"""Execute leave event."""
regions = regions_entered[dev_id]
if location in regions:
regions.remove(location)
new_region = regions[-1] if regions else None
if new_region:
# Exit to previous region
zone = hass.states.get(
"zone.{}".format(slugify(new_region)))
_set_gps_from_zone(kwargs, new_region, zone)
_LOGGER.info("Exit to %s", new_region)
hass.async_add_job(async_see(**kwargs))
async_see_beacons(dev_id, kwargs)
else:
_LOGGER.info("Exit to GPS")
# Check for GPS accuracy
valid_gps = True
if 'acc' in data:
if data['acc'] == 0.0:
valid_gps = False
_LOGGER.warning(
"Ignoring GPS in region exit because accuracy"
"is zero: %s", payload)
if (max_gps_accuracy is not None and
data['acc'] > max_gps_accuracy):
valid_gps = False
_LOGGER.info(
"Ignoring GPS in region exit because expected "
"GPS accuracy %s is not met: %s",
max_gps_accuracy, payload)
if valid_gps:
hass.async_add_job(async_see(**kwargs))
async_see_beacons(dev_id, kwargs)
beacons = mobile_beacons_active[dev_id]
if location in beacons:
beacons.remove(location)
_LOGGER.info("Remove beacon %s", location)
if data['event'] == 'enter':
enter_event()
elif data['event'] == 'leave':
leave_event()
else:
_LOGGER.error(
"Misformatted mqtt msgs, _type=transition, event=%s",
data['event'])
return
@callback
def async_owntracks_waypoint_update(topic, payload, qos):
"""List of waypoints published by a user."""
# Docs on available data:
# http://owntracks.org/booklet/tech/json/#_typewaypoints
data = validate_payload(topic, payload, VALIDATE_WAYPOINTS)
if not data:
return
wayps = data['waypoints']
_LOGGER.info("Got %d waypoints from %s", len(wayps), topic)
for wayp in wayps:
name = wayp['desc']
pretty_name = parse_topic(topic, True)[1] + ' - ' + name
lat = wayp[WAYPOINT_LAT_KEY]
lon = wayp[WAYPOINT_LON_KEY]
rad = wayp['rad']
# check zone exists
entity_id = zone_comp.ENTITY_ID_FORMAT.format(slugify(pretty_name))
# Check if state already exists
if hass.states.get(entity_id) is not None:
continue
zone = zone_comp.Zone(hass, pretty_name, lat, lon, rad,
zone_comp.ICON_IMPORT, False)
zone.entity_id = entity_id
hass.async_add_job(zone.async_update_ha_state())
@callback
def async_see_beacons(dev_id, kwargs_param):
"""Set active beacons to the current location."""
kwargs = kwargs_param.copy()
# the battery state applies to the tracking device, not the beacon
kwargs.pop('battery', None)
for beacon in mobile_beacons_active[dev_id]:
kwargs['dev_id'] = "{}_{}".format(BEACON_DEV_ID, beacon)
kwargs['host_name'] = beacon
hass.async_add_job(async_see(**kwargs))
yield from async_handle_message(hass, context, message)
yield from mqtt.async_subscribe(
hass, LOCATION_TOPIC, async_owntracks_location_update, 1)
yield from mqtt.async_subscribe(
hass, EVENT_TOPIC, async_owntracks_event_update, 1)
if waypoint_import:
if waypoint_whitelist is None:
yield from mqtt.async_subscribe(
hass, WAYPOINT_TOPIC.format('+', '+'),
async_owntracks_waypoint_update, 1)
else:
for whitelist_user in waypoint_whitelist:
yield from mqtt.async_subscribe(
hass, WAYPOINT_TOPIC.format(whitelist_user, '+'),
async_owntracks_waypoint_update, 1)
hass, OWNTRACKS_TOPIC, async_handle_mqtt_message, 1)
return True
def parse_topic(topic, pretty=False):
def _parse_topic(topic):
"""Parse an MQTT topic owntracks/user/dev, return (user, dev) tuple.
Async friendly.
"""
parts = topic.split('/')
dev_id_format = ''
if pretty:
dev_id_format = '{} {}'
else:
dev_id_format = '{}_{}'
dev_id = slugify(dev_id_format.format(parts[1], parts[2]))
host_name = parts[1]
return (host_name, dev_id)
try:
_, user, device, *_ = topic.split('/', 3)
except ValueError:
_LOGGER.error("Can't parse topic: '%s'", topic)
raise
return user, device
def _parse_see_args(topic, data):
def _parse_see_args(message):
"""Parse the OwnTracks location parameters, into the format see expects.
Async friendly.
"""
(host_name, dev_id) = parse_topic(topic, False)
user, device = _parse_topic(message['topic'])
dev_id = slugify('{}_{}'.format(user, device))
kwargs = {
'dev_id': dev_id,
'host_name': host_name,
'gps': (data[WAYPOINT_LAT_KEY], data[WAYPOINT_LON_KEY]),
'host_name': user,
'gps': (message['lat'], message['lon']),
'attributes': {}
}
if 'acc' in data:
kwargs['gps_accuracy'] = data['acc']
if 'batt' in data:
kwargs['battery'] = data['batt']
if 'vel' in data:
kwargs['attributes']['velocity'] = data['vel']
if 'tid' in data:
kwargs['attributes']['tid'] = data['tid']
if 'addr' in data:
kwargs['attributes']['address'] = data['addr']
if 'acc' in message:
kwargs['gps_accuracy'] = message['acc']
if 'batt' in message:
kwargs['battery'] = message['batt']
if 'vel' in message:
kwargs['attributes']['velocity'] = message['vel']
if 'tid' in message:
kwargs['attributes']['tid'] = message['tid']
if 'addr' in message:
kwargs['attributes']['address'] = message['addr']
return dev_id, kwargs
@@ -382,3 +140,288 @@ def _set_gps_from_zone(kwargs, location, zone):
kwargs['gps_accuracy'] = zone.attributes['radius']
kwargs['location_name'] = location
return kwargs
def _decrypt_payload(secret, topic, ciphertext):
"""Decrypt encrypted payload."""
try:
keylen, decrypt = get_cipher()
except OSError:
_LOGGER.warning(
"Ignoring encrypted payload because libsodium not installed")
return None
if isinstance(secret, dict):
key = secret.get(topic)
else:
key = secret
if key is None:
_LOGGER.warning(
"Ignoring encrypted payload because no decryption key known "
"for topic %s", topic)
return None
key = key.encode("utf-8")
key = key[:keylen]
key = key.ljust(keylen, b'\0')
try:
ciphertext = base64.b64decode(ciphertext)
message = decrypt(ciphertext, key)
message = message.decode("utf-8")
_LOGGER.debug("Decrypted payload: %s", message)
return message
except ValueError:
_LOGGER.warning(
"Ignoring encrypted payload because unable to decrypt using "
"key for topic %s", topic)
return None
def context_from_config(async_see, config):
"""Create an async context from Home Assistant config."""
max_gps_accuracy = config.get(CONF_MAX_GPS_ACCURACY)
waypoint_import = config.get(CONF_WAYPOINT_IMPORT)
waypoint_whitelist = config.get(CONF_WAYPOINT_WHITELIST)
secret = config.get(CONF_SECRET)
return OwnTracksContext(async_see, secret, max_gps_accuracy,
waypoint_import, waypoint_whitelist)
class OwnTracksContext:
"""Hold the current OwnTracks context."""
def __init__(self, async_see, secret, max_gps_accuracy, import_waypoints,
waypoint_whitelist):
"""Initialize an OwnTracks context."""
self.async_see = async_see
self.secret = secret
self.max_gps_accuracy = max_gps_accuracy
self.mobile_beacons_active = defaultdict(list)
self.regions_entered = defaultdict(list)
self.import_waypoints = import_waypoints
self.waypoint_whitelist = waypoint_whitelist
@callback
def async_valid_accuracy(self, message):
"""Check if we should ignore this message."""
acc = message.get('acc')
if acc is None:
return False
try:
acc = float(acc)
except ValueError:
return False
if acc == 0:
_LOGGER.warning(
"Ignoring %s update because GPS accuracy is zero: %s",
message['_type'], message)
return False
if self.max_gps_accuracy is not None and \
acc > self.max_gps_accuracy:
_LOGGER.info("Ignoring %s update because expected GPS "
"accuracy %s is not met: %s",
message['_type'], self.max_gps_accuracy,
message)
return False
return True
@asyncio.coroutine
def async_see_beacons(self, dev_id, kwargs_param):
"""Set active beacons to the current location."""
kwargs = kwargs_param.copy()
# the battery state applies to the tracking device, not the beacon
kwargs.pop('battery', None)
for beacon in self.mobile_beacons_active[dev_id]:
kwargs['dev_id'] = "{}_{}".format(BEACON_DEV_ID, beacon)
kwargs['host_name'] = beacon
yield from self.async_see(**kwargs)
@HANDLERS.register('location')
@asyncio.coroutine
def async_handle_location_message(hass, context, message):
"""Handle a location message."""
if not context.async_valid_accuracy(message):
return
dev_id, kwargs = _parse_see_args(message)
if context.regions_entered[dev_id]:
_LOGGER.debug(
"Location update ignored, inside region %s",
context.regions_entered[-1])
return
yield from context.async_see(**kwargs)
yield from context.async_see_beacons(dev_id, kwargs)
@asyncio.coroutine
def _async_transition_message_enter(hass, context, message, location):
"""Execute enter event."""
zone = hass.states.get("zone.{}".format(slugify(location)))
dev_id, kwargs = _parse_see_args(message)
if zone is None and message.get('t') == 'b':
# Not a HA zone, and a beacon so assume mobile
beacons = context.mobile_beacons_active[dev_id]
if location not in beacons:
beacons.append(location)
_LOGGER.info("Added beacon %s", location)
else:
# Normal region
regions = context.regions_entered[dev_id]
if location not in regions:
regions.append(location)
_LOGGER.info("Enter region %s", location)
_set_gps_from_zone(kwargs, location, zone)
yield from context.async_see(**kwargs)
yield from context.async_see_beacons(dev_id, kwargs)
@asyncio.coroutine
def _async_transition_message_leave(hass, context, message, location):
"""Execute leave event."""
dev_id, kwargs = _parse_see_args(message)
regions = context.regions_entered[dev_id]
if location in regions:
regions.remove(location)
new_region = regions[-1] if regions else None
if new_region:
# Exit to previous region
zone = hass.states.get(
"zone.{}".format(slugify(new_region)))
_set_gps_from_zone(kwargs, new_region, zone)
_LOGGER.info("Exit to %s", new_region)
yield from context.async_see(**kwargs)
yield from context.async_see_beacons(dev_id, kwargs)
return
else:
_LOGGER.info("Exit to GPS")
# Check for GPS accuracy
if context.async_valid_accuracy(message):
yield from context.async_see(**kwargs)
yield from context.async_see_beacons(dev_id, kwargs)
beacons = context.mobile_beacons_active[dev_id]
if location in beacons:
beacons.remove(location)
_LOGGER.info("Remove beacon %s", location)
@HANDLERS.register('transition')
@asyncio.coroutine
def async_handle_transition_message(hass, context, message):
"""Handle a transition message."""
if message.get('desc') is None:
_LOGGER.error(
"Location missing from `Entering/Leaving` message - "
"please turn `Share` on in OwnTracks app")
return
# OwnTracks uses - at the start of a beacon zone
# to switch on 'hold mode' - ignore this
location = message['desc'].lstrip("-")
if location.lower() == 'home':
location = STATE_HOME
if message['event'] == 'enter':
yield from _async_transition_message_enter(
hass, context, message, location)
elif message['event'] == 'leave':
yield from _async_transition_message_leave(
hass, context, message, location)
else:
_LOGGER.error(
"Misformatted mqtt msgs, _type=transition, event=%s",
message['event'])
@HANDLERS.register('waypoints')
@asyncio.coroutine
def async_handle_waypoints_message(hass, context, message):
"""Handle a waypoints message."""
if not context.import_waypoints:
return
if context.waypoint_whitelist is not None:
user = _parse_topic(message['topic'])[0]
if user not in context.waypoint_whitelist:
return
wayps = message['waypoints']
_LOGGER.info("Got %d waypoints from %s", len(wayps), message['topic'])
name_base = ' '.join(_parse_topic(message['topic']))
for wayp in wayps:
name = wayp['desc']
pretty_name = '{} - {}'.format(name_base, name)
lat = wayp['lat']
lon = wayp['lon']
rad = wayp['rad']
# check zone exists
entity_id = zone_comp.ENTITY_ID_FORMAT.format(slugify(pretty_name))
# Check if state already exists
if hass.states.get(entity_id) is not None:
continue
zone = zone_comp.Zone(hass, pretty_name, lat, lon, rad,
zone_comp.ICON_IMPORT, False)
zone.entity_id = entity_id
yield from zone.async_update_ha_state()
@HANDLERS.register('encrypted')
@asyncio.coroutine
def async_handle_encrypted_message(hass, context, message):
"""Handle an encrypted message."""
plaintext_payload = _decrypt_payload(context.secret, message['topic'],
message['data'])
if plaintext_payload is None:
return
decrypted = json.loads(plaintext_payload)
decrypted['topic'] = message['topic']
yield from async_handle_message(hass, context, decrypted)
@HANDLERS.register('lwt')
@asyncio.coroutine
def async_handle_lwt_message(hass, context, message):
"""Handle an lwt message."""
_LOGGER.debug('Not handling lwt message: %s', message)
@asyncio.coroutine
def async_handle_message(hass, context, message):
"""Handle an OwnTracks message."""
msgtype = message.get('_type')
handler = HANDLERS.get(msgtype)
if handler is None:
_LOGGER.warning(
'Received unsupported message type: %s.', msgtype)
return
yield from handler(hass, context, message)
@@ -0,0 +1,54 @@
"""
Device tracker platform that adds support for OwnTracks over HTTP.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.owntracks_http/
"""
import asyncio
from aiohttp.web_exceptions import HTTPInternalServerError
from homeassistant.components.http import HomeAssistantView
# pylint: disable=unused-import
from .owntracks import ( # NOQA
REQUIREMENTS, PLATFORM_SCHEMA, context_from_config, async_handle_message)
DEPENDENCIES = ['http']
@asyncio.coroutine
def async_setup_scanner(hass, config, async_see, discovery_info=None):
"""Set up an OwnTracks tracker."""
context = context_from_config(async_see, config)
hass.http.register_view(OwnTracksView(context))
return True
class OwnTracksView(HomeAssistantView):
"""View to handle OwnTracks HTTP requests."""
url = '/api/owntracks/{user}/{device}'
name = 'api:owntracks'
def __init__(self, context):
"""Initialize OwnTracks URL endpoints."""
self.context = context
@asyncio.coroutine
def post(self, request, user, device):
"""Handle an OwnTracks message."""
hass = request.app['hass']
message = yield from request.json()
message['topic'] = 'owntracks/{}/{}'.format(user, device)
try:
yield from async_handle_message(hass, self.context, message)
return self.json([])
except ValueError:
raise HTTPInternalServerError
@@ -16,7 +16,7 @@ from homeassistant.const import CONF_HOST
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['pysnmp==4.3.9']
REQUIREMENTS = ['pysnmp==4.3.10']
CONF_COMMUNITY = 'community'
CONF_AUTHKEY = 'authkey'
@@ -36,7 +36,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
# pylint: disable=unused-argument
def get_scanner(hass, config):
"""Validate the configuration and return an snmp scanner."""
"""Validate the configuration and return an SNMP scanner."""
scanner = SnmpScanner(config[DOMAIN])
return scanner if scanner.success_init else None
@@ -75,7 +75,7 @@ class SnmpScanner(DeviceScanner):
return [client['mac'] for client in self.last_results
if client.get('mac')]
# Supressing no-self-use warning
# Suppressing no-self-use warning
# pylint: disable=R0201
def get_device_name(self, device):
"""Return the name of the given device or None if we don't know."""
@@ -5,6 +5,7 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.unifi/
"""
import logging
from datetime import timedelta
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
@@ -12,16 +13,19 @@ from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD
from homeassistant.const import CONF_VERIFY_SSL
import homeassistant.util.dt as dt_util
REQUIREMENTS = ['pyunifi==2.13']
_LOGGER = logging.getLogger(__name__)
CONF_PORT = 'port'
CONF_SITE_ID = 'site_id'
CONF_DETECTION_TIME = 'detection_time'
DEFAULT_HOST = 'localhost'
DEFAULT_PORT = 8443
DEFAULT_VERIFY_SSL = True
DEFAULT_DETECTION_TIME = timedelta(seconds=300)
NOTIFICATION_ID = 'unifi_notification'
NOTIFICATION_TITLE = 'Unifi Device Tracker Setup'
@@ -32,7 +36,10 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean,
vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): vol.Any(
cv.boolean, cv.isfile),
vol.Optional(CONF_DETECTION_TIME, default=DEFAULT_DETECTION_TIME): vol.All(
cv.time_period, cv.positive_timedelta)
})
@@ -46,6 +53,7 @@ def get_scanner(hass, config):
site_id = config[DOMAIN].get(CONF_SITE_ID)
port = config[DOMAIN].get(CONF_PORT)
verify_ssl = config[DOMAIN].get(CONF_VERIFY_SSL)
detection_time = config[DOMAIN].get(CONF_DETECTION_TIME)
try:
ctrl = Controller(host, username, password, port, version='v4',
@@ -61,14 +69,15 @@ def get_scanner(hass, config):
notification_id=NOTIFICATION_ID)
return False
return UnifiScanner(ctrl)
return UnifiScanner(ctrl, detection_time)
class UnifiScanner(DeviceScanner):
"""Provide device_tracker support from Unifi WAP client data."""
def __init__(self, controller):
def __init__(self, controller, detection_time: timedelta):
"""Initialize the scanner."""
self._detection_time = detection_time
self._controller = controller
self._update()
@@ -81,7 +90,11 @@ class UnifiScanner(DeviceScanner):
_LOGGER.error("Failed to scan clients: %s", ex)
clients = []
self._clients = {client['mac']: client for client in clients}
self._clients = {
client['mac']: client
for client in clients
if (dt_util.utcnow() - dt_util.utc_from_timestamp(float(
client['last_seen']))) < self._detection_time}
def scan_devices(self):
"""Scan for devices."""
@@ -96,5 +109,5 @@ class UnifiScanner(DeviceScanner):
"""
client = self._clients.get(mac, {})
name = client.get('name') or client.get('hostname')
_LOGGER.debug("Device %s name %s", mac, name)
_LOGGER.debug("Device mac %s name %s", mac, name)
return name
@@ -6,7 +6,6 @@ https://home-assistant.io/components/device_tracker.upc_connect/
"""
import asyncio
import logging
import xml.etree.ElementTree as ET
import aiohttp
import async_timeout
@@ -19,6 +18,8 @@ from homeassistant.const import CONF_HOST
from homeassistant.helpers.aiohttp_client import async_get_clientsession
REQUIREMENTS = ['defusedxml==0.5.0']
_LOGGER = logging.getLogger(__name__)
DEFAULT_IP = '192.168.0.1'
@@ -63,6 +64,8 @@ class UPCDeviceScanner(DeviceScanner):
@asyncio.coroutine
def async_scan_devices(self):
"""Scan for new devices and return a list with found device IDs."""
import defusedxml.ElementTree as ET
if self.token is None:
token_initialized = yield from self.async_initialize_token()
if not token_initialized:
@@ -69,7 +69,7 @@ class XiaomiDeviceScanner(DeviceScanner):
return self.mac2name.get(device.upper(), None)
def _update_info(self):
"""Ensure the informations from the router are up to date.
"""Ensure the information from the router are up to date.
Returns true if scanning successful.
"""
+1 -1
View File
@@ -21,7 +21,7 @@ from homeassistant.helpers.event import async_track_point_in_utc_time
from homeassistant.helpers.discovery import async_load_platform, async_discover
import homeassistant.util.dt as dt_util
REQUIREMENTS = ['netdisco==1.2.0']
REQUIREMENTS = ['netdisco==1.2.2']
DOMAIN = 'discovery'
+1 -1
View File
@@ -122,7 +122,7 @@ def setup(hass, config):
_LOGGER.info("Downloading of %s done", url)
except requests.exceptions.ConnectionError:
_LOGGER.exception("ConnectionError occured for %s", url)
_LOGGER.exception("ConnectionError occurred for %s", url)
# Remove file if we started downloading but failed
if final_path and os.path.isfile(final_path):
+102
View File
@@ -0,0 +1,102 @@
"""Integrate with DuckDNS."""
import asyncio
from datetime import timedelta
import logging
import voluptuous as vol
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_DOMAIN
from homeassistant.loader import bind_hass
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.aiohttp_client import async_get_clientsession
DOMAIN = 'duckdns'
UPDATE_URL = 'https://www.duckdns.org/update'
INTERVAL = timedelta(minutes=5)
_LOGGER = logging.getLogger(__name__)
SERVICE_SET_TXT = 'set_txt'
ATTR_TXT = 'txt'
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_DOMAIN): cv.string,
vol.Required(CONF_ACCESS_TOKEN): cv.string,
})
}, extra=vol.ALLOW_EXTRA)
SERVICE_TXT_SCHEMA = vol.Schema({
vol.Required(ATTR_TXT): vol.Any(None, cv.string)
})
@bind_hass
@asyncio.coroutine
def async_set_txt(hass, txt):
"""Set the txt record. Pass in None to remove it."""
yield from hass.services.async_call(DOMAIN, SERVICE_SET_TXT, {
ATTR_TXT: txt
}, blocking=True)
@asyncio.coroutine
def async_setup(hass, config):
"""Initialize the DuckDNS component."""
domain = config[DOMAIN][CONF_DOMAIN]
token = config[DOMAIN][CONF_ACCESS_TOKEN]
session = async_get_clientsession(hass)
result = yield from _update_duckdns(session, domain, token)
if not result:
return False
@asyncio.coroutine
def update_domain_interval(now):
"""Update the DuckDNS entry."""
yield from _update_duckdns(session, domain, token)
@asyncio.coroutine
def update_domain_service(call):
"""Update the DuckDNS entry."""
yield from _update_duckdns(session, domain, token,
txt=call.data[ATTR_TXT])
async_track_time_interval(hass, update_domain_interval, INTERVAL)
hass.services.async_register(
DOMAIN, SERVICE_SET_TXT, update_domain_service,
schema=SERVICE_TXT_SCHEMA)
return result
_SENTINEL = object()
@asyncio.coroutine
def _update_duckdns(session, domain, token, *, txt=_SENTINEL, clear=False):
"""Update DuckDNS."""
params = {
'domains': domain,
'token': token,
}
if txt is not _SENTINEL:
if txt is None:
# Pass in empty txt value to indicate it's clearing txt record
params['txt'] = ''
clear = True
else:
params['txt'] = txt
if clear:
params['clear'] = 'true'
resp = yield from session.get(UPDATE_URL, params=params)
body = yield from resp.text()
if body != 'OK':
_LOGGER.warning('Updating DuckDNS domain %s failed', domain)
return False
return True
+1 -1
View File
@@ -15,7 +15,7 @@ from homeassistant.helpers import discovery
from homeassistant.const import CONF_API_KEY
from homeassistant.util import Throttle
REQUIREMENTS = ['python-ecobee-api==0.0.9']
REQUIREMENTS = ['python-ecobee-api==0.0.10']
_CONFIGURING = {}
_LOGGER = logging.getLogger(__name__)
@@ -16,6 +16,7 @@ from homeassistant.const import (
)
from homeassistant.components.http import REQUIREMENTS # NOQA
from homeassistant.components.http import HomeAssistantWSGI
from homeassistant.helpers.deprecation import get_deprecated
import homeassistant.helpers.config_validation as cv
from .hue_api import (
HueUsernameView, HueAllLightsStateView, HueOneLightStateView,
@@ -66,6 +67,7 @@ CONFIG_SCHEMA = vol.Schema({
}, extra=vol.ALLOW_EXTRA)
ATTR_EMULATED_HUE = 'emulated_hue'
ATTR_EMULATED_HUE_HIDDEN = 'emulated_hue_hidden'
def setup(hass, yaml_config):
@@ -148,7 +150,7 @@ class Config(object):
self.listen_port)
if self.type == TYPE_GOOGLE and self.listen_port != 80:
_LOGGER.warning("When targetting Google Home, listening port has "
_LOGGER.warning("When targeting Google Home, listening port has "
"to be port 80")
# Get whether or not UPNP binds to multicast address (239.255.255.250)
@@ -223,7 +225,15 @@ class Config(object):
domain = entity.domain.lower()
explicit_expose = entity.attributes.get(ATTR_EMULATED_HUE, None)
explicit_hidden = entity.attributes.get(ATTR_EMULATED_HUE_HIDDEN, None)
if explicit_expose is True or explicit_hidden is False:
expose = True
elif explicit_expose is False or explicit_hidden is True:
expose = False
else:
expose = None
get_deprecated(entity.attributes, ATTR_EMULATED_HUE_HIDDEN,
ATTR_EMULATED_HUE, None)
domain_exposed_by_default = \
self.expose_by_default and domain in self.exposed_domains
@@ -231,9 +241,9 @@ class Config(object):
# the configuration doesn't explicitly exclude it from being
# exposed, or if the entity is explicitly exposed
is_default_exposed = \
domain_exposed_by_default and explicit_expose is not False
domain_exposed_by_default and expose is not False
return is_default_exposed or explicit_expose
return is_default_exposed or expose
def _load_numbers_json(self):
"""Set up helper method to load numbers json."""
@@ -1,4 +1,4 @@
"""Provides a UPNP discovery method that mimicks Hue hubs."""
"""Provides a UPNP discovery method that mimics Hue hubs."""
import threading
import socket
import logging
@@ -123,14 +123,14 @@ USN: uuid:Socket-1_0-221438K0100073::urn:schemas-upnp-org:device:basic:1
if ssdp_socket in read:
data, addr = ssdp_socket.recvfrom(1024)
else:
# most likely the timeout, so check for interupt
# most likely the timeout, so check for interrupt
continue
except socket.error as ex:
if self._interrupted:
clean_socket_close(ssdp_socket)
return
_LOGGER.error("UPNP Responder socket exception occured: %s",
_LOGGER.error("UPNP Responder socket exception occurred: %s",
ex.__str__)
# without the following continue, a second exception occurs
# because the data object has not been initialized
+1 -1
View File
@@ -11,7 +11,7 @@ import voluptuous as vol
from homeassistant.const import CONF_DEVICE
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['enocean==0.31']
REQUIREMENTS = ['enocean==0.40']
_LOGGER = logging.getLogger(__name__)
+2 -10
View File
@@ -215,20 +215,12 @@ def async_setup(hass, config: dict):
target_fans = component.async_extract_from_service(service)
params.pop(ATTR_ENTITY_ID, None)
update_tasks = []
for fan in target_fans:
yield from getattr(fan, method['method'])(**params)
update_tasks = []
for fan in target_fans:
if not fan.should_poll:
continue
update_coro = hass.async_add_job(fan.async_update_ha_state(True))
if hasattr(fan, 'async_update'):
update_tasks.append(update_coro)
else:
yield from update_coro
update_tasks.append(fan.async_update_ha_state(True))
if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)
@@ -137,7 +137,7 @@ class InsteonLocalFanDevice(FanEntity):
@property
def name(self):
"""Return the the name of the node."""
"""Return the name of the node."""
return self.node.deviceName
@property
+7 -13
View File
@@ -11,7 +11,7 @@ from homeassistant.components.fan import (FanEntity, DOMAIN, SPEED_OFF,
SPEED_LOW, SPEED_MEDIUM,
SPEED_HIGH)
import homeassistant.components.isy994 as isy
from homeassistant.const import STATE_UNKNOWN, STATE_ON, STATE_OFF
from homeassistant.const import STATE_ON, STATE_OFF
from homeassistant.helpers.typing import ConfigType
_LOGGER = logging.getLogger(__name__)
@@ -73,19 +73,16 @@ class ISYFanDevice(isy.ISYDevice, FanEntity):
@property
def speed(self) -> str:
"""Return the current speed."""
return self.state
return VALUE_TO_STATE.get(self.value)
@property
def state(self) -> str:
"""Get the state of the ISY994 fan device."""
return VALUE_TO_STATE.get(self.value, STATE_UNKNOWN)
def is_on(self) -> str:
"""Get if the fan is on."""
return self.value != 0
def set_speed(self, speed: str) -> None:
"""Send the set speed command to the ISY994 fan device."""
if not self._node.on(val=STATE_TO_VALUE.get(speed, 0)):
_LOGGER.debug("Unable to set fan speed")
else:
self.speed = self.state
self._node.on(val=STATE_TO_VALUE.get(speed, 255))
def turn_on(self, speed: str=None, **kwargs) -> None:
"""Send the turn on command to the ISY994 fan device."""
@@ -93,10 +90,7 @@ class ISYFanDevice(isy.ISYDevice, FanEntity):
def turn_off(self, **kwargs) -> None:
"""Send the turn off command to the ISY994 fan device."""
if not self._node.off():
_LOGGER.debug("Unable to set fan speed")
else:
self.speed = self.state
self._node.off()
@property
def speed_list(self) -> list:
+1 -1
View File
@@ -19,7 +19,7 @@ from homeassistant.helpers.dispatcher import (
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
REQUIREMENTS = ['ha-ffmpeg==1.7']
REQUIREMENTS = ['ha-ffmpeg==1.9']
DOMAIN = 'ffmpeg'
@@ -225,8 +225,6 @@ def setup(hass, config):
if DATA_EXTRA_HTML_URL not in hass.data:
hass.data[DATA_EXTRA_HTML_URL] = set()
register_built_in_panel(hass, 'map', 'Map', 'mdi:account-location')
for panel in ('dev-event', 'dev-info', 'dev-service', 'dev-state',
'dev-template', 'dev-mqtt', 'kiosk'):
register_built_in_panel(hass, panel)
@@ -329,6 +327,7 @@ class IndexView(HomeAssistantView):
from jinja2 import FileSystemLoader, Environment
self.templates = Environment(
autoescape=True,
loader=FileSystemLoader(
os.path.join(os.path.dirname(__file__), 'templates/')
)
+3 -3
View File
@@ -3,10 +3,10 @@
FINGERPRINTS = {
"compatibility.js": "1686167ff210e001f063f5c606b2e74b",
"core.js": "2a7d01e45187c7d4635da05065b5e54e",
"frontend.html": "7e13ce36d3141182a62a5b061e87e77a",
"mdi.html": "89074face5529f5fe6fbae49ecb3e88b",
"frontend.html": "2de1bde3b4a6c6c47dd95504fc098906",
"mdi.html": "2e848b4da029bf73d426d5ba058a088d",
"micromarkdown-js.html": "93b5ec4016f0bba585521cf4d18dec1a",
"panels/ha-panel-config.html": "61f65e75e39368e07441d7d6a4e36ae3",
"panels/ha-panel-config.html": "52e2e1d477bfd6dc3708d65b8337f0af",
"panels/ha-panel-dev-event.html": "d409e7ab537d9fe629126d122345279c",
"panels/ha-panel-dev-info.html": "b0e55eb657fd75f21aba2426ac0cedc0",
"panels/ha-panel-dev-mqtt.html": "94b222b013a98583842de3e72d5888c6",
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -37,7 +37,7 @@
/* eslint-disable indent, no-unused-vars, no-multiple-empty-lines, max-nested-callbacks, space-before-function-paren, quotes, comma-spacing */
'use strict';
var precacheConfig = [["/","da6c72cc82251a0456b2e678ebb6795c"],["/frontend/panels/dev-event-d409e7ab537d9fe629126d122345279c.html","936814991f2a5e23d61d29f0d40f81b8"],["/frontend/panels/dev-info-b0e55eb657fd75f21aba2426ac0cedc0.html","1fa953b0224470f70d4e87bbe4dff191"],["/frontend/panels/dev-mqtt-94b222b013a98583842de3e72d5888c6.html","dc3ddfac58397feda97317358f0aecbb"],["/frontend/panels/dev-service-422b2c181ee0713fa31d45a64e605baf.html","ae7d26b1c8c3309fd3c65944f89ea03f"],["/frontend/panels/dev-state-7948d3dba058f31517d880df8ed0e857.html","ff8156bb1a52490fcc07466556fce0e1"],["/frontend/panels/dev-template-928e7b81b9c113b70edc9f4a1d051827.html","312c8313800b44c83bcb8dc2df30c759"],["/frontend/panels/map-565db019147162080c21af962afc097f.html","a1a360042395682335e2f471dddad309"],["/static/compatibility-1686167ff210e001f063f5c606b2e74b.js","6ee7b5e2dd82b510c3bd92f7e215988e"],["/static/core-2a7d01e45187c7d4635da05065b5e54e.js","90a0a8a6a6dd0ca41b16f40e7d23924d"],["/static/frontend-7e13ce36d3141182a62a5b061e87e77a.html","73f53a9b597e1e69e0b3e56f4fc8f020"],["/static/mdi-89074face5529f5fe6fbae49ecb3e88b.html","97754e463f9e56a95c813d4d8e792347"],["static/fonts/roboto/Roboto-Bold.ttf","d329cc8b34667f114a95422aaad1b063"],["static/fonts/roboto/Roboto-Light.ttf","7b5fb88f12bec8143f00e21bc3222124"],["static/fonts/roboto/Roboto-Medium.ttf","fe13e4170719c2fc586501e777bde143"],["static/fonts/roboto/Roboto-Regular.ttf","ac3f799d5bbaf5196fab15ab8de8431c"],["static/icons/favicon-192x192.png","419903b8422586a7e28021bbe9011175"],["static/icons/favicon.ico","04235bda7843ec2fceb1cbe2bc696cf4"],["static/images/card_media_player_bg.png","a34281d1c1835d338a642e90930e61aa"]];
var precacheConfig = [["/","517bbe13d945f188a0896870cd3055d0"],["/frontend/panels/dev-event-d409e7ab537d9fe629126d122345279c.html","936814991f2a5e23d61d29f0d40f81b8"],["/frontend/panels/dev-info-b0e55eb657fd75f21aba2426ac0cedc0.html","1fa953b0224470f70d4e87bbe4dff191"],["/frontend/panels/dev-mqtt-94b222b013a98583842de3e72d5888c6.html","dc3ddfac58397feda97317358f0aecbb"],["/frontend/panels/dev-service-422b2c181ee0713fa31d45a64e605baf.html","ae7d26b1c8c3309fd3c65944f89ea03f"],["/frontend/panels/dev-state-7948d3dba058f31517d880df8ed0e857.html","ff8156bb1a52490fcc07466556fce0e1"],["/frontend/panels/dev-template-928e7b81b9c113b70edc9f4a1d051827.html","312c8313800b44c83bcb8dc2df30c759"],["/frontend/panels/map-565db019147162080c21af962afc097f.html","a1a360042395682335e2f471dddad309"],["/static/compatibility-1686167ff210e001f063f5c606b2e74b.js","6ee7b5e2dd82b510c3bd92f7e215988e"],["/static/core-2a7d01e45187c7d4635da05065b5e54e.js","90a0a8a6a6dd0ca41b16f40e7d23924d"],["/static/frontend-2de1bde3b4a6c6c47dd95504fc098906.html","51faf83c8a2d86529bc76a67fe6b5234"],["/static/mdi-89074face5529f5fe6fbae49ecb3e88b.html","97754e463f9e56a95c813d4d8e792347"],["static/fonts/roboto/Roboto-Bold.ttf","d329cc8b34667f114a95422aaad1b063"],["static/fonts/roboto/Roboto-Light.ttf","7b5fb88f12bec8143f00e21bc3222124"],["static/fonts/roboto/Roboto-Medium.ttf","fe13e4170719c2fc586501e777bde143"],["static/fonts/roboto/Roboto-Regular.ttf","ac3f799d5bbaf5196fab15ab8de8431c"],["static/icons/favicon-192x192.png","419903b8422586a7e28021bbe9011175"],["static/icons/favicon.ico","04235bda7843ec2fceb1cbe2bc696cf4"],["static/images/card_media_player_bg.png","a34281d1c1835d338a642e90930e61aa"]];
var cacheName = 'sw-precache-v3--' + (self.registration ? self.registration.scope : '');
@@ -7,6 +7,13 @@
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
@@ -20,174 +27,170 @@ The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
'use strict';var Jb="undefined"!=typeof window&&window===this?this:"undefined"!=typeof global&&null!=global?global:this;
(function(){function k(){var a=this;this.s={};this.g=document.documentElement;var b=new Ka;b.rules=[];this.h=t.set(this.g,new t(b));this.i=!1;this.b=this.a=null;Kb(function(){a.c()})}function F(){this.customStyles=[];this.enqueued=!1}function Lb(){}function pa(a){this.cache={};this.c=void 0===a?100:a}function p(){}function t(a,b,c,d,e){this.G=a||null;this.b=b||null;this.ra=c||[];this.P=null;this.Z=e||"";this.a=this.B=this.K=null}function r(){}function Ka(){this.end=this.start=0;this.rules=this.parent=
this.previous=null;this.cssText=this.parsedCssText="";this.atRule=!1;this.type=0;this.parsedSelector=this.selector=this.keyframesName=""}function Id(a){function b(b,c){Object.defineProperty(b,"innerHTML",{enumerable:c.enumerable,configurable:!0,get:c.get,set:function(b){var d=this,e=void 0;n(this)&&(e=[],P(this,function(a){a!==d&&e.push(a)}));c.set.call(this,b);if(e)for(var f=0;f<e.length;f++){var g=e[f];1===g.__CE_state&&a.disconnectedCallback(g)}this.ownerDocument.__CE_hasRegistry?a.f(this):a.l(this);
return b}})}function c(b,c){u(b,"insertAdjacentElement",function(b,d){var e=n(d);b=c.call(this,b,d);e&&a.a(d);n(b)&&a.b(d);return b})}Mb?u(Element.prototype,"attachShadow",function(a){return this.__CE_shadowRoot=a=Mb.call(this,a)}):console.warn("Custom Elements: `Element#attachShadow` was not patched.");if(La&&La.get)b(Element.prototype,La);else if(Ma&&Ma.get)b(HTMLElement.prototype,Ma);else{var d=Na.call(document,"div");a.v(function(a){b(a,{enumerable:!0,configurable:!0,get:function(){return Nb.call(this,
!0).innerHTML},set:function(a){var b="template"===this.localName?this.content:this;for(d.innerHTML=a;0<b.childNodes.length;)Oa.call(b,b.childNodes[0]);for(;0<d.childNodes.length;)qa.call(b,d.childNodes[0])}})})}u(Element.prototype,"setAttribute",function(b,c){if(1!==this.__CE_state)return Ob.call(this,b,c);var d=Pa.call(this,b);Ob.call(this,b,c);c=Pa.call(this,b);a.attributeChangedCallback(this,b,d,c,null)});u(Element.prototype,"setAttributeNS",function(b,c,d){if(1!==this.__CE_state)return Pb.call(this,
b,c,d);var e=ra.call(this,b,c);Pb.call(this,b,c,d);d=ra.call(this,b,c);a.attributeChangedCallback(this,c,e,d,b)});u(Element.prototype,"removeAttribute",function(b){if(1!==this.__CE_state)return Qb.call(this,b);var c=Pa.call(this,b);Qb.call(this,b);null!==c&&a.attributeChangedCallback(this,b,c,null,null)});u(Element.prototype,"removeAttributeNS",function(b,c){if(1!==this.__CE_state)return Rb.call(this,b,c);var d=ra.call(this,b,c);Rb.call(this,b,c);var e=ra.call(this,b,c);d!==e&&a.attributeChangedCallback(this,
c,d,e,b)});Sb?c(HTMLElement.prototype,Sb):Tb?c(Element.prototype,Tb):console.warn("Custom Elements: `Element#insertAdjacentElement` was not patched.");Ub(a,Element.prototype,{Ka:Jd,append:Kd});Ld(a,{kb:Md,jb:Nd,replaceWith:Od,remove:Pd})}function Ld(a,b){var c=Element.prototype;c.before=function(c){for(var d=[],f=0;f<arguments.length;++f)d[f-0]=arguments[f];f=d.filter(function(a){return a instanceof Node&&n(a)});b.kb.apply(this,d);for(var g=0;g<f.length;g++)a.a(f[g]);if(n(this))for(f=0;f<d.length;f++)g=
d[f],g instanceof Element&&a.b(g)};c.after=function(c){for(var d=[],f=0;f<arguments.length;++f)d[f-0]=arguments[f];f=d.filter(function(a){return a instanceof Node&&n(a)});b.jb.apply(this,d);for(var g=0;g<f.length;g++)a.a(f[g]);if(n(this))for(f=0;f<d.length;f++)g=d[f],g instanceof Element&&a.b(g)};c.replaceWith=function(c){for(var d=[],f=0;f<arguments.length;++f)d[f-0]=arguments[f];f=d.filter(function(a){return a instanceof Node&&n(a)});var g=n(this);b.replaceWith.apply(this,d);for(var h=0;h<f.length;h++)a.a(f[h]);
if(g)for(a.a(this),f=0;f<d.length;f++)g=d[f],g instanceof Element&&a.b(g)};c.remove=function(){var c=n(this);b.remove.call(this);c&&a.a(this)}}function Qd(a){function b(b,d){Object.defineProperty(b,"textContent",{enumerable:d.enumerable,configurable:!0,get:d.get,set:function(b){if(this.nodeType===Node.TEXT_NODE)d.set.call(this,b);else{var c=void 0;if(this.firstChild){var e=this.childNodes,h=e.length;if(0<h&&n(this)){c=Array(h);for(var m=0;m<h;m++)c[m]=e[m]}}d.set.call(this,b);if(c)for(b=0;b<c.length;b++)a.a(c[b])}}})}
u(Node.prototype,"insertBefore",function(b,d){if(b instanceof DocumentFragment){var c=Array.prototype.slice.apply(b.childNodes);b=Vb.call(this,b,d);if(n(this))for(d=0;d<c.length;d++)a.b(c[d]);return b}c=n(b);d=Vb.call(this,b,d);c&&a.a(b);n(this)&&a.b(b);return d});u(Node.prototype,"appendChild",function(b){if(b instanceof DocumentFragment){var c=Array.prototype.slice.apply(b.childNodes);b=qa.call(this,b);if(n(this))for(var e=0;e<c.length;e++)a.b(c[e]);return b}c=n(b);e=qa.call(this,b);c&&a.a(b);n(this)&&
a.b(b);return e});u(Node.prototype,"cloneNode",function(b){b=Nb.call(this,b);this.ownerDocument.__CE_hasRegistry?a.f(b):a.l(b);return b});u(Node.prototype,"removeChild",function(b){var c=n(b),e=Oa.call(this,b);c&&a.a(b);return e});u(Node.prototype,"replaceChild",function(b,d){if(b instanceof DocumentFragment){var c=Array.prototype.slice.apply(b.childNodes);b=Wb.call(this,b,d);if(n(this))for(a.a(d),d=0;d<c.length;d++)a.b(c[d]);return b}c=n(b);var f=Wb.call(this,b,d),g=n(this);g&&a.a(d);c&&a.a(b);g&&
a.b(b);return f});Qa&&Qa.get?b(Node.prototype,Qa):a.v(function(a){b(a,{enumerable:!0,configurable:!0,get:function(){for(var a=[],b=0;b<this.childNodes.length;b++)a.push(this.childNodes[b].textContent);return a.join("")},set:function(a){for(;this.firstChild;)Oa.call(this,this.firstChild);qa.call(this,document.createTextNode(a))}})})}function Rd(a){u(Document.prototype,"createElement",function(b){if(this.__CE_hasRegistry){var c=a.c(b);if(c)return new c.constructor}b=Na.call(this,b);a.g(b);return b});
u(Document.prototype,"importNode",function(b,c){b=Sd.call(this,b,c);this.__CE_hasRegistry?a.f(b):a.l(b);return b});u(Document.prototype,"createElementNS",function(b,c){if(this.__CE_hasRegistry&&(null===b||"http://www.w3.org/1999/xhtml"===b)){var d=a.c(c);if(d)return new d.constructor}b=Td.call(this,b,c);a.g(b);return b});Ub(a,Document.prototype,{Ka:Ud,append:Vd})}function Ub(a,b,c){b.prepend=function(b){for(var d=[],f=0;f<arguments.length;++f)d[f-0]=arguments[f];f=d.filter(function(a){return a instanceof
Node&&n(a)});c.Ka.apply(this,d);for(var g=0;g<f.length;g++)a.a(f[g]);if(n(this))for(f=0;f<d.length;f++)g=d[f],g instanceof Element&&a.b(g)};b.append=function(b){for(var d=[],f=0;f<arguments.length;++f)d[f-0]=arguments[f];f=d.filter(function(a){return a instanceof Node&&n(a)});c.append.apply(this,d);for(var g=0;g<f.length;g++)a.a(f[g]);if(n(this))for(f=0;f<d.length;f++)g=d[f],g instanceof Element&&a.b(g)}}function Wd(a){window.HTMLElement=function(){function b(){var b=this.constructor,d=a.C(b);if(!d)throw Error("The custom element being constructed was not registered with `customElements`.");
var e=d.constructionStack;if(0===e.length)return e=Na.call(document,d.localName),Object.setPrototypeOf(e,b.prototype),e.__CE_state=1,e.__CE_definition=d,a.g(e),e;d=e.length-1;var f=e[d];if(f===Xb)throw Error("The HTMLElement constructor was either called reentrantly for this constructor or called multiple times.");e[d]=Xb;Object.setPrototypeOf(f,b.prototype);a.g(f);return f}b.prototype=Xd.prototype;return b}()}function x(a){this.c=!1;this.a=a;this.h=new Map;this.f=function(a){return a()};this.b=!1;
this.g=[];this.i=new Ra(a,document)}function Yb(){var a=this;this.b=this.a=void 0;this.c=new Promise(function(b){a.b=b;a.a&&b(a.a)})}function Ra(a,b){this.b=a;this.a=b;this.N=void 0;this.b.f(this.a);"loading"===this.a.readyState&&(this.N=new MutationObserver(this.f.bind(this)),this.N.observe(this.a,{childList:!0,subtree:!0}))}function z(){this.u=new Map;this.s=new Map;this.j=[];this.h=!1}function l(a,b,c){if(a!==Zb)throw new TypeError("Illegal constructor");a=document.createDocumentFragment();a.__proto__=
l.prototype;a.D(b,c);return a}function sa(a){if(!a.__shady||void 0===a.__shady.firstChild){a.__shady=a.__shady||{};a.__shady.firstChild=Sa(a);a.__shady.lastChild=Ta(a);$b(a);for(var b=a.__shady.childNodes=U(a),c=0,d;c<b.length&&(d=b[c]);c++)d.__shady=d.__shady||{},d.__shady.parentNode=a,d.__shady.nextSibling=b[c+1]||null,d.__shady.previousSibling=b[c-1]||null,ac(d)}}function Yd(a){var b=a&&a.N;b&&(b.ba.delete(a.ab),b.ba.size||(a.fb.__shady.X=null))}function Zd(a,b){a.__shady=a.__shady||{};a.__shady.X||
(a.__shady.X=new ta);a.__shady.X.ba.add(b);var c=a.__shady.X;return{ab:b,N:c,fb:a,takeRecords:function(){return c.takeRecords()}}}function ta(){this.a=!1;this.addedNodes=[];this.removedNodes=[];this.ba=new Set}function Q(a,b){V[W]=a;V[W+1]=b;W+=2;2===W&&(Ua?Ua(X):$d())}function ae(){return function(){return process.Gb(X)}}function be(){return"undefined"!==typeof Va?function(){Va(X)}:Wa()}function ce(){var a=0,b=new bc(X),c=document.createTextNode("");b.observe(c,{characterData:!0});return function(){c.data=
a=++a%2}}function de(){var a=new MessageChannel;a.port1.onmessage=X;return function(){return a.port2.postMessage(0)}}function Wa(){var a=setTimeout;return function(){return a(X,1)}}function X(){for(var a=0;a<W;a+=2)(0,V[a])(V[a+1]),V[a]=void 0,V[a+1]=void 0;W=0}function ee(){try{var a=require("vertx");Va=a.Ib||a.Hb;return be()}catch(b){return Wa()}}function Xa(a,b){var c=this,d=new this.constructor(Y);void 0===d[ua]&&cc(d);var e=c.o;if(e){var f=arguments[e-1];Q(function(){return dc(e,d,f,c.m)})}else Ya(c,
d,a,b);return d}function Za(a){if(a&&"object"===typeof a&&a.constructor===this)return a;var b=new this(Y);ea(b,a);return b}function Y(){}function ec(a){try{return a.then}catch(b){return fa.error=b,fa}}function fe(a,b,c,d){try{a.call(b,c,d)}catch(e){return e}}function ge(a,b,c){Q(function(a){var d=!1,f=fe(c,b,function(c){d||(d=!0,b!==c?ea(a,c):K(a,c))},function(b){d||(d=!0,A(a,b))});!d&&f&&(d=!0,A(a,f))},a)}function he(a,b){1===b.o?K(a,b.m):2===b.o?A(a,b.m):Ya(b,void 0,function(b){return ea(a,b)},
function(b){return A(a,b)})}function fc(a,b,c){b.constructor===a.constructor&&c===Xa&&b.constructor.resolve===Za?he(a,b):c===fa?(A(a,fa.error),fa.error=null):void 0===c?K(a,b):"function"===typeof c?ge(a,b,c):K(a,b)}function ea(a,b){if(a===b)A(a,new TypeError("You cannot resolve a promise with itself"));else{var c=typeof b;null===b||"object"!==c&&"function"!==c?K(a,b):fc(a,b,ec(b))}}function ie(a){a.Ba&&a.Ba(a.m);$a(a)}function K(a,b){void 0===a.o&&(a.m=b,a.o=1,0!==a.U.length&&Q($a,a))}function A(a,
b){void 0===a.o&&(a.o=2,a.m=b,Q(ie,a))}function Ya(a,b,c,d){var e=a.U,f=e.length;a.Ba=null;e[f]=b;e[f+1]=c;e[f+2]=d;0===f&&a.o&&Q($a,a)}function $a(a){var b=a.U,c=a.o;if(0!==b.length){for(var d,e,f=a.m,g=0;g<b.length;g+=3)d=b[g],e=b[g+c],d?dc(c,d,e,f):e(f);a.U.length=0}}function gc(){this.error=null}function dc(a,b,c,d){var e="function"===typeof c;if(e){try{var f=c(d)}catch(H){ab.error=H,f=ab}if(f===ab){var g=!0;var h=f.error;f.error=null}else var m=!0;if(b===f){A(b,new TypeError("A promises callback cannot return that same promise."));
return}}else f=d,m=!0;void 0===b.o&&(e&&m?ea(b,f):g?A(b,h):1===a?K(b,f):2===a&&A(b,f))}function je(a,b){try{b(function(b){ea(a,b)},function(b){A(a,b)})}catch(c){A(a,c)}}function cc(a){a[ua]=hc++;a.o=void 0;a.m=void 0;a.U=[]}function ha(a,b){this.eb=a;this.J=new a(Y);this.J[ua]||cc(this.J);ic(b)?(this.$=this.length=b.length,this.m=Array(this.length),0===this.length?K(this.J,this.m):(this.length=this.length||0,this.cb(b),0===this.$&&K(this.J,this.m))):A(this.J,Error("Array Methods must be provided an Array"))}
function y(a){this[ua]=hc++;this.m=this.o=void 0;this.U=[];if(Y!==a){if("function"!==typeof a)throw new TypeError("You must pass a resolver function as the first argument to the promise constructor");if(this instanceof y)je(this,a);else throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");}}function Z(a){return a.__shady&&void 0!==a.__shady.firstChild}function I(a){return"ShadyRoot"===a.Wa}function ia(a){a=a.getRootNode();
if(I(a))return a}function bb(a,b){if(a&&b)for(var c=Object.getOwnPropertyNames(b),d=0,e;d<c.length&&(e=c[d]);d++){var f=Object.getOwnPropertyDescriptor(b,e);f&&Object.defineProperty(a,e,f)}}function cb(a,b){for(var c=[],d=1;d<arguments.length;++d)c[d-1]=arguments[d];for(d=0;d<c.length;d++)bb(a,c[d]);return a}function ke(a,b){for(var c in b)a[c]=b[c]}function jc(a){db.push(a);eb.textContent=kc++}function lc(a){fb||(fb=!0,jc(va));ja.push(a)}function va(){fb=!1;for(var a=!!ja.length;ja.length;)ja.shift()();
return a}function le(a,b){var c=b.getRootNode();return a.map(function(a){var b=c===a.target.getRootNode();if(b&&a.addedNodes){if(b=Array.from(a.addedNodes).filter(function(a){return c===a.getRootNode()}),b.length)return a=Object.create(a),Object.defineProperty(a,"addedNodes",{value:b,configurable:!0}),a}else if(b)return a}).filter(function(a){return a})}function mc(a){switch(a){case "&":return"&amp;";case "<":return"&lt;";case ">":return"&gt;";case '"':return"&quot;";case "\u00a0":return"&nbsp;"}}
function nc(a){for(var b={},c=0;c<a.length;c++)b[a[c]]=!0;return b}function gb(a,b){"template"===a.localName&&(a=a.content);for(var c="",d=b?b(a):a.childNodes,e=0,f=d.length,g;e<f&&(g=d[e]);e++){a:{var h=g;var m=a;var H=b;switch(h.nodeType){case Node.ELEMENT_NODE:for(var k=h.localName,l="<"+k,n=h.attributes,p=0;m=n[p];p++)l+=" "+m.name+'="'+m.value.replace(me,mc)+'"';l+=">";h=ne[k]?l:l+gb(h,H)+"</"+k+">";break a;case Node.TEXT_NODE:h=h.data;h=m&&oe[m.localName]?h:h.replace(pe,mc);break a;case Node.COMMENT_NODE:h=
"\x3c!--"+h.data+"--\x3e";break a;default:throw window.console.error(h),Error("not implemented");}}c+=h}return c}function aa(a){B.currentNode=a;return B.parentNode()}function Sa(a){B.currentNode=a;return B.firstChild()}function Ta(a){B.currentNode=a;return B.lastChild()}function oc(a){B.currentNode=a;return B.previousSibling()}function pc(a){B.currentNode=a;return B.nextSibling()}function U(a){var b=[];B.currentNode=a;for(a=B.firstChild();a;)b.push(a),a=B.nextSibling();return b}function qc(a){C.currentNode=
a;return C.parentNode()}function rc(a){C.currentNode=a;return C.firstChild()}function sc(a){C.currentNode=a;return C.lastChild()}function tc(a){C.currentNode=a;return C.previousSibling()}function uc(a){C.currentNode=a;return C.nextSibling()}function vc(a){var b=[];C.currentNode=a;for(a=C.firstChild();a;)b.push(a),a=C.nextSibling();return b}function wc(a){return gb(a,function(a){return U(a)})}function xc(a){switch(a.nodeType){case Node.ELEMENT_NODE:case Node.DOCUMENT_FRAGMENT_NODE:a=document.createTreeWalker(a,
NodeFilter.SHOW_TEXT,null,!1);for(var b="",c;c=a.nextNode();)b+=c.nodeValue;return b;default:return a.nodeValue}}function M(a,b,c){for(var d in b){var e=Object.getOwnPropertyDescriptor(a,d);e&&e.configurable||!e&&c?Object.defineProperty(a,d,b[d]):c&&console.warn("Could not define",d,"on",a)}}function R(a){M(a,yc);M(a,hb);M(a,ib)}function zc(a,b,c){ac(a);c=c||null;a.__shady=a.__shady||{};b.__shady=b.__shady||{};c&&(c.__shady=c.__shady||{});a.__shady.previousSibling=c?c.__shady.previousSibling:b.lastChild;
var d=a.__shady.previousSibling;d&&d.__shady&&(d.__shady.nextSibling=a);(d=a.__shady.nextSibling=c)&&d.__shady&&(d.__shady.previousSibling=a);a.__shady.parentNode=b;c?c===b.__shady.firstChild&&(b.__shady.firstChild=a):(b.__shady.lastChild=a,b.__shady.firstChild||(b.__shady.firstChild=a));b.__shady.childNodes=null}function jb(a,b,c){if(b===a)throw Error("Failed to execute 'appendChild' on 'Node': The new child element contains the parent.");if(c){var d=c.__shady&&c.__shady.parentNode;if(void 0!==d&&
d!==a||void 0===d&&aa(c)!==a)throw Error("Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node.");}if(c===b)return b;b.parentNode&&kb(b.parentNode,b);d=ia(a);var e;if(e=d)a:{if(!b.__noInsertionPoint){var f;"slot"===b.localName?f=[b]:b.querySelectorAll&&(f=b.querySelectorAll("slot"));if(f&&f.length){e=f;break a}}e=void 0}f=e;d&&("slot"===a.localName||f)&&d.M();if(Z(a)){e=c;$b(a);a.__shady=a.__shady||{};void 0!==a.__shady.firstChild&&
(a.__shady.childNodes=null);if(b.nodeType===Node.DOCUMENT_FRAGMENT_NODE){for(var g=b.childNodes,h=0;h<g.length;h++)zc(g[h],a,e);b.__shady=b.__shady||{};e=void 0!==b.__shady.firstChild?null:void 0;b.__shady.firstChild=b.__shady.lastChild=e;b.__shady.childNodes=e}else zc(b,a,e);if(lb(a)){a.__shady.root.M();var m=!0}else a.__shady.root&&(m=!0)}m||(m=I(a)?a.host:a,c?(c=Ac(c),mb.call(m,b,c)):Bc.call(m,b));Cc(a,b);f&&d.$a(f);return b}function kb(a,b){if(b.parentNode!==a)throw Error("The node to be removed is not a child of this node: "+
b);var c=ia(b);if(Z(a)){b.__shady=b.__shady||{};a.__shady=a.__shady||{};b===a.__shady.firstChild&&(a.__shady.firstChild=b.__shady.nextSibling);b===a.__shady.lastChild&&(a.__shady.lastChild=b.__shady.previousSibling);var d=b.__shady.previousSibling;var e=b.__shady.nextSibling;d&&(d.__shady=d.__shady||{},d.__shady.nextSibling=e);e&&(e.__shady=e.__shady||{},e.__shady.previousSibling=d);b.__shady.parentNode=b.__shady.previousSibling=b.__shady.nextSibling=void 0;void 0!==a.__shady.childNodes&&(a.__shady.childNodes=
null);if(lb(a)){a.__shady.root.M();var f=!0}}Dc(b);c&&((e=a&&"slot"===a.localName)&&(f=!0),((d=c.gb(b))||e)&&c.M());f||(f=I(a)?a.host:a,(!a.__shady.root&&"slot"!==b.localName||f===aa(b))&&ka.call(f,b));Cc(a,null,b);return b}function Dc(a){if(a.__shady&&void 0!==a.__shady.sa)for(var b=a.childNodes,c=0,d=b.length,e;c<d&&(e=b[c]);c++)Dc(e);a.__shady&&(a.__shady.sa=void 0)}function Ac(a){var b=a;a&&"slot"===a.localName&&(b=(b=a.__shady&&a.__shady.V)&&b.length?b[0]:Ac(a.nextSibling));return b}function lb(a){return(a=
a&&a.__shady&&a.__shady.root)&&a.Aa()}function Ec(a,b){"slot"===b?(a=a.parentNode,lb(a)&&a.__shady.root.M()):"slot"===a.localName&&"name"===b&&(b=ia(a))&&(b.ib(a),b.M())}function Cc(a,b,c){if(a=a.__shady&&a.__shady.X)b&&a.addedNodes.push(b),c&&a.removedNodes.push(c),a.vb()}function Fc(a){if(a&&a.nodeType){a.__shady=a.__shady||{};var b=a.__shady.sa;void 0===b&&(I(a)?b=a:b=(b=a.parentNode)?Fc(b):a,document.documentElement.contains(a)&&(a.__shady.sa=b));return b}}function wa(a,b,c){var d=[];Gc(a.childNodes,
b,c,d);return d}function Gc(a,b,c,d){for(var e=0,f=a.length,g;e<f&&(g=a[e]);e++){var h;if(h=g.nodeType===Node.ELEMENT_NODE){h=g;var m=b,H=c,k=d,l=m(h);l&&k.push(h);H&&H(l)?h=l:(Gc(h.childNodes,m,H,k),h=void 0)}if(h)break}}function Hc(a){a=a.getRootNode();I(a)&&a.Da()}function Ic(a,b,c){xa||(xa=window.ShadyCSS&&window.ShadyCSS.ScopingShim);xa&&"class"===b?xa.setElementClass(a,c):(Jc.call(a,b,c),Ec(a,b))}function Kc(a,b){if(a.ownerDocument!==document)return nb.call(document,a,b);var c=nb.call(document,
a,!1);if(b){a=a.childNodes;b=0;for(var d;b<a.length;b++)d=Kc(a[b],!0),c.appendChild(d)}return c}function ob(a,b){var c=[],d=a;for(a=a===window?window:a.getRootNode();d;)c.push(d),d=d.assignedSlot?d.assignedSlot:d.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&d.host&&(b||d!==a)?d.host:d.parentNode;c[c.length-1]===document&&c.push(window);return c}function Lc(a,b){if(!I)return a;a=ob(a,!0);for(var c=0,d,e,f,g;c<b.length;c++)if(d=b[c],f=d===window?window:d.getRootNode(),f!==e&&(g=a.indexOf(f),e=f),!I(f)||
-1<g)return d}function pb(a){function b(b,d){b=new a(b,d);b.ia=d&&!!d.composed;return b}ke(b,a);b.prototype=a.prototype;return b}function Mc(a,b,c){if(c=b.__handlers&&b.__handlers[a.type]&&b.__handlers[a.type][c])for(var d=0,e;(e=c[d])&&a.target!==a.relatedTarget&&(e.call(b,a),!a.Ua);d++);}function qe(a){var b=a.composedPath();Object.defineProperty(a,"currentTarget",{get:function(){return d},configurable:!0});for(var c=b.length-1;0<=c;c--){var d=b[c];Mc(a,d,"capture");if(a.ja)return}Object.defineProperty(a,
"eventPhase",{get:function(){return Event.AT_TARGET}});var e;for(c=0;c<b.length;c++){d=b[c];var f=d.__shady&&d.__shady.root;if(0===c||f&&f===e)if(Mc(a,d,"bubble"),d!==window&&(e=d.getRootNode()),a.ja)break}}function Nc(a,b,c,d,e,f){for(var g=0;g<a.length;g++){var h=a[g],m=h.type,H=h.capture,k=h.once,l=h.passive;if(b===h.node&&c===m&&d===H&&e===k&&f===l)return g}return-1}function Oc(a,b,c){if(b){if("object"===typeof c){var d=!!c.capture;var e=!!c.once;var f=!!c.passive}else d=!!c,f=e=!1;var g=c&&c.ka||
this,h=b[la];if(h){if(-1<Nc(h,g,a,d,e,f))return}else b[la]=[];h=function(d){e&&this.removeEventListener(a,b,c);d.__target||Pc(d);if(g!==this){var f=Object.getOwnPropertyDescriptor(d,"currentTarget");Object.defineProperty(d,"currentTarget",{get:function(){return g},configurable:!0})}if(d.composed||-1<d.composedPath().indexOf(g))if(d.target===d.relatedTarget)d.eventPhase===Event.BUBBLING_PHASE&&d.stopImmediatePropagation();else if(d.eventPhase===Event.CAPTURING_PHASE||d.bubbles||d.target===g){var h=
"object"===typeof b&&b.handleEvent?b.handleEvent(d):b.call(g,d);g!==this&&(f?(Object.defineProperty(d,"currentTarget",f),f=null):delete d.currentTarget);return h}};b[la].push({node:this,type:a,capture:d,once:e,passive:f,zb:h});qb[a]?(this.__handlers=this.__handlers||{},this.__handlers[a]=this.__handlers[a]||{capture:[],bubble:[]},this.__handlers[a][d?"capture":"bubble"].push(h)):(this instanceof Window?Qc:Rc).call(this,a,h,c)}}function Sc(a,b,c){if(b){if("object"===typeof c){var d=!!c.capture;var e=
!!c.once;var f=!!c.passive}else d=!!c,f=e=!1;var g=c&&c.ka||this,h=void 0;var m=null;try{m=b[la]}catch(H){}m&&(e=Nc(m,g,a,d,e,f),-1<e&&(h=m.splice(e,1)[0].zb,m.length||(b[la]=void 0)));(this instanceof Window?Tc:Uc).call(this,a,h||b,c);h&&qb[a]&&this.__handlers&&this.__handlers[a]&&(a=this.__handlers[a][d?"capture":"bubble"],h=a.indexOf(h),-1<h&&a.splice(h,1))}}function re(){for(var a in qb)window.addEventListener(a,function(a){a.__target||(Pc(a),qe(a))},!0)}function Pc(a){a.__target=a.target;a.ya=
a.relatedTarget;if(D.W){var b=Vc,c=Object.getPrototypeOf(a);if(!c.hasOwnProperty("__patchProto")){var d=Object.create(c);d.Bb=c;bb(d,b);c.__patchProto=d}a.__proto__=c.__patchProto}else bb(a,Vc)}function ma(a,b){return{index:a,Y:[],aa:b}}function se(a,b,c,d){var e=0,f=0,g=0,h=0,m=Math.min(b-e,d-f);if(0==e&&0==f)a:{for(g=0;g<m;g++)if(a[g]!==c[g])break a;g=m}if(b==a.length&&d==c.length){h=a.length;for(var k=c.length,l=0;l<m-g&&te(a[--h],c[--k]);)l++;h=l}e+=g;f+=g;b-=h;d-=h;if(0==b-e&&0==d-f)return[];
if(e==b){for(b=ma(e,0);f<d;)b.Y.push(c[f++]);return[b]}if(f==d)return[ma(e,b-e)];m=e;g=f;d=d-g+1;h=b-m+1;b=Array(d);for(k=0;k<d;k++)b[k]=Array(h),b[k][0]=k;for(k=0;k<h;k++)b[0][k]=k;for(k=1;k<d;k++)for(l=1;l<h;l++)if(a[m+l-1]===c[g+k-1])b[k][l]=b[k-1][l-1];else{var n=b[k-1][l]+1,p=b[k][l-1]+1;b[k][l]=n<p?n:p}m=b.length-1;g=b[0].length-1;d=b[m][g];for(a=[];0<m||0<g;)0==m?(a.push(2),g--):0==g?(a.push(3),m--):(h=b[m-1][g-1],k=b[m-1][g],l=b[m][g-1],n=k<l?k<h?k:h:l<h?l:h,n==h?(h==d?a.push(0):(a.push(1),
d=h),m--,g--):n==k?(a.push(3),m--,d=k):(a.push(2),g--,d=l));a.reverse();b=void 0;m=[];for(g=0;g<a.length;g++)switch(a[g]){case 0:b&&(m.push(b),b=void 0);e++;f++;break;case 1:b||(b=ma(e,0));b.aa++;e++;b.Y.push(c[f]);f++;break;case 2:b||(b=ma(e,0));b.aa++;e++;break;case 3:b||(b=ma(e,0)),b.Y.push(c[f]),f++}b&&m.push(b);return m}function te(a,b){return a===b}function Wc(a){var b=[];do b.unshift(a);while(a=a.parentNode);return b}function Xc(a){Hc(a);return a.__shady&&a.__shady.assignedSlot||null}function N(a,
b){for(var c=Object.getOwnPropertyNames(b),d=0;d<c.length;d++){var e=c[d],f=Object.getOwnPropertyDescriptor(b,e);f.value?a[e]=f.value:Object.defineProperty(a,e,f)}}function ue(){var a=window.customElements&&window.customElements.nativeHTMLElement||HTMLElement;N(window.Node.prototype,ve);N(window.Window.prototype,we);N(window.Text.prototype,xe);N(window.DocumentFragment.prototype,rb);N(window.Element.prototype,Yc);N(window.Document.prototype,Zc);window.HTMLSlotElement&&N(window.HTMLSlotElement.prototype,
$c);N(a.prototype,ye);D.W&&(R(window.Node.prototype),R(window.Text.prototype),R(window.DocumentFragment.prototype),R(window.Element.prototype),R(a.prototype),R(window.Document.prototype),window.HTMLSlotElement&&R(window.HTMLSlotElement.prototype))}function ad(a){var b=ze.has(a);a=/^[a-z][.0-9_a-z]*-[\-.0-9_a-z]*$/.test(a);return!b&&a}function n(a){var b=a.isConnected;if(void 0!==b)return b;for(;a&&!(a.__CE_isImportDocument||a instanceof Document);)a=a.parentNode||(window.ShadowRoot&&a instanceof ShadowRoot?
a.host:void 0);return!(!a||!(a.__CE_isImportDocument||a instanceof Document))}function sb(a,b){for(;b&&b!==a&&!b.nextSibling;)b=b.parentNode;return b&&b!==a?b.nextSibling:null}function P(a,b,c){c=c?c:new Set;for(var d=a;d;){if(d.nodeType===Node.ELEMENT_NODE){var e=d;b(e);var f=e.localName;if("link"===f&&"import"===e.getAttribute("rel")){d=e.import;if(d instanceof Node&&!c.has(d))for(c.add(d),d=d.firstChild;d;d=d.nextSibling)P(d,b,c);d=sb(a,e);continue}else if("template"===f){d=sb(a,e);continue}if(e=
e.__CE_shadowRoot)for(e=e.firstChild;e;e=e.nextSibling)P(e,b,c)}d=d.firstChild?d.firstChild:sb(a,d)}}function u(a,b,c){a[b]=c}function tb(a){a=a.replace(G.mb,"").replace(G.port,"");var b=bd,c=a,d=new Ka;d.start=0;d.end=c.length;for(var e=d,f=0,g=c.length;f<g;f++)if("{"===c[f]){e.rules||(e.rules=[]);var h=e,m=h.rules[h.rules.length-1]||null;e=new Ka;e.start=f+1;e.parent=h;e.previous=m;h.rules.push(e)}else"}"===c[f]&&(e.end=f+1,e=e.parent||d);return b(d,a)}function bd(a,b){var c=b.substring(a.start,
a.end-1);a.parsedCssText=a.cssText=c.trim();a.parent&&(c=b.substring(a.previous?a.previous.end:a.parent.start,a.start-1),c=Ae(c),c=c.replace(G.Ja," "),c=c.substring(c.lastIndexOf(";")+1),c=a.parsedSelector=a.selector=c.trim(),a.atRule=0===c.indexOf("@"),a.atRule?0===c.indexOf("@media")?a.type=L.MEDIA_RULE:c.match(G.rb)&&(a.type=L.ha,a.keyframesName=a.selector.split(G.Ja).pop()):a.type=0===c.indexOf("--")?L.ua:L.STYLE_RULE);if(c=a.rules)for(var d=0,e=c.length,f;d<e&&(f=c[d]);d++)bd(f,b);return a}function Ae(a){return a.replace(/\\([0-9a-f]{1,6})\s/gi,
function(a,c){a=c;for(c=6-a.length;c--;)a="0"+a;return"\\"+a})}function cd(a,b,c){c=void 0===c?"":c;var d="";if(a.cssText||a.rules){var e=a.rules,f;if(f=e)f=e[0],f=!(f&&f.selector&&0===f.selector.indexOf("--"));if(f){f=0;for(var g=e.length,h;f<g&&(h=e[f]);f++)d=cd(h,b,d)}else b?b=a.cssText:(b=a.cssText,b=b.replace(G.Ea,"").replace(G.Ia,""),b=b.replace(G.sb,"").replace(G.xb,"")),(d=b.trim())&&(d=" "+d+"\n")}d&&(a.selector&&(c+=a.selector+" {\n"),c+=d,a.selector&&(c+="}\n\n"));return c}function dd(a){v=
a&&a.shimcssproperties?!1:q||!(navigator.userAgent.match(/AppleWebKit\/601|Edge\/15/)||!window.CSS||!CSS.supports||!CSS.supports("box-shadow","0 0 0 var(--foo)"))}function ba(a,b){if(!a)return"";"string"===typeof a&&(a=tb(a));b&&ca(a,b);return cd(a,v)}function ya(a){!a.__cssRules&&a.textContent&&(a.__cssRules=tb(a.textContent));return a.__cssRules||null}function ed(a){return!!a.parent&&a.parent.type===L.ha}function ca(a,b,c,d){if(a){var e=!1,f=a.type;if(d&&f===L.MEDIA_RULE){var g=a.selector.match(Be);
g&&(window.matchMedia(g[1]).matches||(e=!0))}f===L.STYLE_RULE?b(a):c&&f===L.ha?c(a):f===L.ua&&(e=!0);if((a=a.rules)&&!e){e=0;f=a.length;for(var h;e<f&&(h=a[e]);e++)ca(h,b,c,d)}}}function ub(a,b,c,d){var e=document.createElement("style");b&&e.setAttribute("scope",b);e.textContent=a;fd(e,c,d);return e}function fd(a,b,c){b=b||document.head;b.insertBefore(a,c&&c.nextSibling||b.firstChild);S?a.compareDocumentPosition(S)===Node.DOCUMENT_POSITION_PRECEDING&&(S=a):S=a}function gd(a,b){var c=a.indexOf("var(");
if(-1===c)return b(a,"","","");a:{var d=0;var e=c+3;for(var f=a.length;e<f;e++)if("("===a[e])d++;else if(")"===a[e]&&0===--d)break a;e=-1}d=a.substring(c+4,e);c=a.substring(0,c);a=gd(a.substring(e+1),b);e=d.indexOf(",");return-1===e?b(c,d.trim(),"",a):b(c,d.substring(0,e).trim(),d.substring(e+1).trim(),a)}function za(a,b){q?a.setAttribute("class",b):window.ShadyDOM.nativeMethods.setAttribute.call(a,"class",b)}function T(a){var b=a.localName,c="";b?-1<b.indexOf("-")||(c=b,b=a.getAttribute&&a.getAttribute("is")||
""):(b=a.is,c=a.extends);return{is:b,Z:c}}function hd(a){for(var b=0;b<a.length;b++){var c=a[b];if(c.target!==document.documentElement&&c.target!==document.head)for(var d=0;d<c.addedNodes.length;d++){var e=c.addedNodes[d];if(e.nodeType===Node.ELEMENT_NODE){var f=e.getRootNode();var g=e;var h=[];g.classList?h=Array.from(g.classList):g instanceof window.SVGElement&&g.hasAttribute("class")&&(h=g.getAttribute("class").split(/\s+/));g=h;h=g.indexOf(w.c);(g=-1<h?g[h+1]:"")&&f===e.ownerDocument?w.a(e,g,
!0):f.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&(f=f.host)&&(f=T(f).is,g!==f&&(g&&w.a(e,g,!0),w.a(e,f)))}}}}function Ce(a){if(a=Aa[a])a._applyShimCurrentVersion=a._applyShimCurrentVersion||0,a._applyShimValidatingVersion=a._applyShimValidatingVersion||0,a._applyShimNextVersion=(a._applyShimNextVersion||0)+1}function id(a){return a._applyShimCurrentVersion===a._applyShimNextVersion}function De(a){a._applyShimValidatingVersion=a._applyShimNextVersion;a.b||(a.b=!0,Ee.then(function(){a._applyShimCurrentVersion=
a._applyShimNextVersion;a.b=!1}))}function Kb(a){requestAnimationFrame(function(){jd?jd(a):(vb||(vb=new Promise(function(a){wb=a}),"complete"===document.readyState?wb():document.addEventListener("readystatechange",function(){"complete"===document.readyState&&wb()})),vb.then(function(){a&&a()}))})}(function(){if(!function(){var a=document.createEvent("Event");a.initEvent("foo",!0,!0);a.preventDefault();return a.defaultPrevented}()){var a=Event.prototype.preventDefault;Event.prototype.preventDefault=
function(){this.cancelable&&(a.call(this),Object.defineProperty(this,"defaultPrevented",{get:function(){return!0},configurable:!0}))}}var b=/Trident/.test(navigator.userAgent);if(!window.CustomEvent||b&&"function"!==typeof window.CustomEvent)window.CustomEvent=function(a,b){b=b||{};var c=document.createEvent("CustomEvent");c.initCustomEvent(a,!!b.bubbles,!!b.cancelable,b.detail);return c},window.CustomEvent.prototype=window.Event.prototype;if(!window.Event||b&&"function"!==typeof window.Event){var c=
window.Event;window.Event=function(a,b){b=b||{};var c=document.createEvent("Event");c.initEvent(a,!!b.bubbles,!!b.cancelable);return c};if(c)for(var d in c)window.Event[d]=c[d];window.Event.prototype=c.prototype}if(!window.MouseEvent||b&&"function"!==typeof window.MouseEvent){b=window.MouseEvent;window.MouseEvent=function(a,b){b=b||{};var c=document.createEvent("MouseEvent");c.initMouseEvent(a,!!b.bubbles,!!b.cancelable,b.view||window,b.detail,b.screenX,b.screenY,b.clientX,b.clientY,b.ctrlKey,b.altKey,
b.shiftKey,b.metaKey,b.button,b.relatedTarget);return c};if(b)for(d in b)window.MouseEvent[d]=b[d];window.MouseEvent.prototype=b.prototype}Array.from||(Array.from=function(a){return[].slice.call(a)});Object.assign||(Object.assign=function(a,b){for(var c=[].slice.call(arguments,1),d=0,e;d<c.length;d++)if(e=c[d])for(var f=a,k=e,l=Object.getOwnPropertyNames(k),n=0;n<l.length;n++)e=l[n],f[e]=k[e];return a})})(window.WebComponents);(function(){function a(){}var b="undefined"===typeof HTMLTemplateElement;
/Trident/.test(navigator.userAgent)&&function(){var a=Document.prototype.importNode;Document.prototype.importNode=function(){var b=a.apply(this,arguments);if(b.nodeType===Node.DOCUMENT_FRAGMENT_NODE){var c=this.createDocumentFragment();c.appendChild(b);return c}return b}}();var c=Node.prototype.cloneNode,d=Document.prototype.createElement,e=Document.prototype.importNode,f=function(){if(!b){var a=document.createElement("template"),c=document.createElement("template");c.content.appendChild(document.createElement("div"));
a.content.appendChild(c);a=a.cloneNode(!0);return 0===a.content.childNodes.length||0===a.content.firstChild.content.childNodes.length||!(document.createDocumentFragment().cloneNode()instanceof DocumentFragment)}}();if(b){var g=function(a){switch(a){case "&":return"&amp;";case "<":return"&lt;";case ">":return"&gt;";case "\u00a0":return"&nbsp;"}},h=function(b){Object.defineProperty(b,"innerHTML",{get:function(){for(var a="",b=this.content.firstChild;b;b=b.nextSibling)a+=b.outerHTML||b.data.replace(r,
g);return a},set:function(b){m.body.innerHTML=b;for(a.b(m);this.content.firstChild;)this.content.removeChild(this.content.firstChild);for(;m.body.firstChild;)this.content.appendChild(m.body.firstChild)},configurable:!0})},m=document.implementation.createHTMLDocument("template"),k=!0,l=document.createElement("style");l.textContent="template{display:none;}";var n=document.head;n.insertBefore(l,n.firstElementChild);a.prototype=Object.create(HTMLElement.prototype);var p=!document.createElement("div").hasOwnProperty("innerHTML");
a.O=function(b){if(!b.content){b.content=m.createDocumentFragment();for(var c;c=b.firstChild;)b.content.appendChild(c);if(p)b.__proto__=a.prototype;else if(b.cloneNode=function(b){return a.a(this,b)},k)try{h(b)}catch(df){k=!1}a.b(b.content)}};h(a.prototype);a.b=function(b){b=b.querySelectorAll("template");for(var c=0,d=b.length,e;c<d&&(e=b[c]);c++)a.O(e)};document.addEventListener("DOMContentLoaded",function(){a.b(document)});Document.prototype.createElement=function(){var b=d.apply(this,arguments);
"template"===b.localName&&a.O(b);return b};var r=/[&\u00A0<>]/g}if(b||f)a.a=function(a,b){var d=c.call(a,!1);this.O&&this.O(d);b&&(d.content.appendChild(c.call(a.content,!0)),this.qa(d.content,a.content));return d},a.prototype.cloneNode=function(b){return a.a(this,b)},a.qa=function(a,b){if(b.querySelectorAll){b=b.querySelectorAll("template");a=a.querySelectorAll("template");for(var c=0,d=a.length,e,f;c<d;c++)f=b[c],e=a[c],this.O&&this.O(f),e.parentNode.replaceChild(f.cloneNode(!0),e)}},Node.prototype.cloneNode=
function(b){if(this instanceof DocumentFragment)if(b)var d=this.ownerDocument.importNode(this,!0);else return this.ownerDocument.createDocumentFragment();else d=c.call(this,b);b&&a.qa(d,this);return d},Document.prototype.importNode=function(b,c){if("template"===b.localName)return a.a(b,c);var d=e.call(this,b,c);c&&a.qa(d,b);return d},f&&(window.HTMLTemplateElement.prototype.cloneNode=function(b){return a.a(this,b)});b&&(window.HTMLTemplateElement=a)})();var xb;Array.isArray?xb=Array.isArray:xb=function(a){return"[object Array]"===
Object.prototype.toString.call(a)};var ic=xb,W=0,Va,Ua,kd="undefined"!==typeof window?window:void 0,ld=kd||{},bc=ld.MutationObserver||ld.WebKitMutationObserver,Fe="undefined"!==typeof Uint8ClampedArray&&"undefined"!==typeof importScripts&&"undefined"!==typeof MessageChannel,V=Array(1E3);var $d="undefined"===typeof self&&"undefined"!==typeof process&&"[object process]"==={}.toString.call(process)?ae():bc?ce():Fe?de():kd||"function"!==typeof require?Wa():ee();var ua=Math.random().toString(36).substring(16),
fa=new gc,ab=new gc,hc=0;ha.prototype.cb=function(a){for(var b=0;void 0===this.o&&b<a.length;b++)this.bb(a[b],b)};ha.prototype.bb=function(a,b){var c=this.eb,d=c.resolve;d===Za?(d=ec(a),d===Xa&&void 0!==a.o?this.na(a.o,b,a.m):"function"!==typeof d?(this.$--,this.m[b]=a):c===y?(c=new c(Y),fc(c,a,d),this.oa(c,b)):this.oa(new c(function(b){return b(a)}),b)):this.oa(d(a),b)};ha.prototype.na=function(a,b,c){var d=this.J;void 0===d.o&&(this.$--,2===a?A(d,c):this.m[b]=c);0===this.$&&K(d,this.m)};ha.prototype.oa=
function(a,b){var c=this;Ya(a,void 0,function(a){return c.na(1,b,a)},function(a){return c.na(2,b,a)})};y.g=function(a){return(new ha(this,a)).J};y.h=function(a){var b=this;return ic(a)?new b(function(c,d){for(var e=a.length,f=0;f<e;f++)b.resolve(a[f]).then(c,d)}):new b(function(a,b){return b(new TypeError("You must pass an array to race."))})};y.resolve=Za;y.i=function(a){var b=new this(Y);A(b,a);return b};y.f=function(a){Ua=a};y.c=function(a){Q=a};y.b=Q;y.prototype={constructor:y,then:Xa};y.a=function(){if("undefined"!==
typeof global)var a=global;else if("undefined"!==typeof self)a=self;else try{a=Function("return this")()}catch(d){throw Error("polyfill failed because global object is unavailable in this environment");}var b=a.Promise;if(b){var c=null;try{c=Object.prototype.toString.call(b.resolve())}catch(d){}if("[object Promise]"===c&&!b.Eb)return}a.Promise=y};y.Promise=y;y.a();(function(a){function b(a,b){if("function"===typeof window.CustomEvent)return new CustomEvent(a,b);var c=document.createEvent("CustomEvent");
c.initCustomEvent(a,!!b.bubbles,!!b.cancelable,b.detail);return c}function c(a){if(l)return a.ownerDocument!==document?a.ownerDocument:null;var b=a.__importDoc;if(!b&&a.parentNode){b=a.parentNode;if("function"===typeof b.closest)b=b.closest("link[rel=import]");else for(;!h(b)&&(b=b.parentNode););a.__importDoc=b}return b}function d(a){var b=document.querySelectorAll("link[rel=import]:not(import-dependency)"),c=b.length;c?k(b,function(b){return g(b,function(){0===--c&&a()})}):a()}function e(a){function b(){"loading"!==
document.readyState&&document.body&&(document.removeEventListener("readystatechange",b),a())}document.addEventListener("readystatechange",b);b()}function f(a){e(function(){return d(function(){return a&&a()})})}function g(a,b){if(a.__loaded)b&&b();else if("script"===a.localName&&!a.src||"style"===a.localName&&!a.firstChild)a.__loaded=!0,b&&b();else{var c=function(d){a.removeEventListener(d.type,c);a.__loaded=!0;b&&b()};a.addEventListener("load",c);x&&"style"===a.localName||a.addEventListener("error",
c)}}function h(a){return a.nodeType===Node.ELEMENT_NODE&&"link"===a.localName&&"import"===a.rel}function m(){var a=this;this.a={};this.b=0;this.f=new MutationObserver(function(b){return a.l(b)});this.f.observe(document.head,{childList:!0,subtree:!0});this.c(document)}function k(a,b,c){var d=a?a.length:0,e=c?-1:1;for(c=c?d-1:0;c<d&&0<=c;c+=e)b(a[c],c)}var l="import"in document.createElement("link"),n=null;!1==="currentScript"in document&&Object.defineProperty(document,"currentScript",{get:function(){return n||
("complete"!==document.readyState?document.scripts[document.scripts.length-1]:null)},configurable:!0});var p=/(^\/)|(^#)|(^[\w-\d]*:)/,r=/(url\()([^)]*)(\))/g,t=/(@import[\s]+(?!url\())([^;]*)(;)/g,w=/(<link[^>]*)(rel=['|"]?stylesheet['|"]?[^>]*>)/g,q={nb:function(a,b){a.href&&a.setAttribute("href",q.ta(a.getAttribute("href"),b));a.src&&a.setAttribute("src",q.ta(a.getAttribute("src"),b));if("style"===a.localName){var c=q.La(a.textContent,b,r);a.textContent=q.La(c,b,t)}},La:function(a,b,c){return a.replace(c,
function(a,c,d,e){a=d.replace(/["']/g,"");b&&(a=q.Ma(a,b));return c+"'"+a+"'"+e})},ta:function(a,b){return a&&p.test(a)?a:q.Ma(a,b)},Ma:function(a,b){if(void 0===q.la){q.la=!1;try{var c=new URL("b","http://a");c.pathname="c%20d";q.la="http://a/c%20d"===c.href}catch(ef){}}if(q.la)return(new URL(a,b)).href;c=q.Za;c||(c=document.implementation.createHTMLDocument("temp"),q.Za=c,c.wa=c.createElement("base"),c.head.appendChild(c.wa),c.va=c.createElement("a"));c.wa.href=b;c.va.href=a;return c.va.href||a}},
y={async:!0,load:function(a,b,c){if(a)if(a.match(/^data:/)){a=a.split(",");var d=a[1];d=-1<a[0].indexOf(";base64")?atob(d):decodeURIComponent(d);b(d)}else{var e=new XMLHttpRequest;e.open("GET",a,y.async);e.onload=function(){var a=e.responseURL||e.getResponseHeader("Location");a&&0===a.indexOf("/")&&(a=(location.origin||location.protocol+"//"+location.host)+a);var d=e.response||e.responseText;304===e.status||0===e.status||200<=e.status&&300>e.status?b(d,a):c(d)};e.send()}else c("error: href must be specified")}},
x=/Trident/.test(navigator.userAgent)||/Edge\/\d./i.test(navigator.userAgent);m.prototype.c=function(a){var b=this;a=a.querySelectorAll("link[rel=import]");k(a,function(a){return b.h(a)})};m.prototype.h=function(a){var b=this,c=a.href;if(void 0!==this.a[c]){var d=this.a[c];d&&d.__loaded&&(a.import=d,this.g(a))}else this.b++,this.a[c]="pending",y.load(c,function(a,d){a=b.s(a,d||c);b.a[c]=a;b.b--;b.c(a);b.i()},function(){b.a[c]=null;b.b--;b.i()})};m.prototype.s=function(a,b){if(!a)return document.createDocumentFragment();
x&&(a=a.replace(w,function(a,b,c){return-1===a.indexOf("type=")?b+" type=import-disable "+c:a}));var c=document.createElement("template");c.innerHTML=a;if(c.content)a=c.content;else for(a=document.createDocumentFragment();c.firstChild;)a.appendChild(c.firstChild);if(c=a.querySelector("base"))b=q.ta(c.getAttribute("href"),b),c.removeAttribute("href");c=a.querySelectorAll('link[rel=import], link[rel=stylesheet][href][type=import-disable],\n style:not([type]), link[rel=stylesheet][href]:not([type]),\n script:not([type]), script[type="application/javascript"],\n script[type="text/javascript"]');
var d=0;k(c,function(a){g(a);q.nb(a,b);a.setAttribute("import-dependency","");"script"===a.localName&&!a.src&&a.textContent&&(a.setAttribute("src","data:text/javascript;charset=utf-8,"+encodeURIComponent(a.textContent+("\n//# sourceURL="+b+(d?"-"+d:"")+".js\n"))),a.textContent="",d++)});return a};m.prototype.i=function(){var a=this;if(!this.b){this.f.disconnect();this.flatten(document);var b=!1,c=!1,d=function(){c&&b&&(a.c(document),a.b||(a.f.observe(document.head,{childList:!0,subtree:!0}),a.j()))};
this.v(function(){c=!0;d()});this.u(function(){b=!0;d()})}};m.prototype.flatten=function(a){var b=this;a=a.querySelectorAll("link[rel=import]");k(a,function(a){var c=b.a[a.href];(a.import=c)&&c.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&(b.a[a.href]=a,a.readyState="loading",a.import=a,b.flatten(c),a.appendChild(c))})};m.prototype.u=function(a){function b(e){if(e<d){var f=c[e],h=document.createElement("script");f.removeAttribute("import-dependency");k(f.attributes,function(a){return h.setAttribute(a.name,
a.value)});n=h;f.parentNode.replaceChild(h,f);g(h,function(){n=null;b(e+1)})}else a()}var c=document.querySelectorAll("script[import-dependency]"),d=c.length;b(0)};m.prototype.v=function(a){var b=document.querySelectorAll("style[import-dependency],\n link[rel=stylesheet][import-dependency]"),d=b.length;if(d){var e=x&&!!document.querySelector("link[rel=stylesheet][href][type=import-disable]");k(b,function(b){g(b,function(){b.removeAttribute("import-dependency");0===--d&&a()});if(e&&b.parentNode!==
document.head){var f=document.createElement(b.localName);f.__appliedElement=b;f.setAttribute("type","import-placeholder");b.parentNode.insertBefore(f,b.nextSibling);for(f=c(b);f&&c(f);)f=c(f);f.parentNode!==document.head&&(f=null);document.head.insertBefore(b,f);b.removeAttribute("type")}})}else a()};m.prototype.j=function(){var a=this,b=document.querySelectorAll("link[rel=import]");k(b,function(b){return a.g(b)},!0)};m.prototype.g=function(a){a.__loaded||(a.__loaded=!0,a.import&&(a.import.readyState=
"complete"),a.dispatchEvent(b(a.import?"load":"error",{bubbles:!1,cancelable:!1,detail:void 0})))};m.prototype.l=function(a){var b=this;k(a,function(a){return k(a.addedNodes,function(a){a&&a.nodeType===Node.ELEMENT_NODE&&(h(a)?b.h(a):b.c(a))})})};if(l){var v=document.querySelectorAll("link[rel=import]");k(v,function(a){a.import&&"loading"===a.import.readyState||(a.__loaded=!0)});v=function(a){a=a.target;h(a)&&(a.__loaded=!0)};document.addEventListener("load",v,!0);document.addEventListener("error",
v,!0)}else{var u=Object.getOwnPropertyDescriptor(Node.prototype,"baseURI");Object.defineProperty((!u||u.configurable?Node:Element).prototype,"baseURI",{get:function(){var a=h(this)?this:c(this);return a?a.href:u&&u.get?u.get.call(this):(document.querySelector("base")||window.location).href},configurable:!0,enumerable:!0});e(function(){return new m})}f(function(){return document.dispatchEvent(b("HTMLImportsLoaded",{cancelable:!0,bubbles:!0,detail:void 0}))});a.useNative=l;a.whenReady=f;a.importForElement=
c})(window.HTMLImports=window.HTMLImports||{});window.WebComponents=window.WebComponents||{flags:{}};var md=document.querySelector('script[src*="webcomponents-lite.js"]'),Ge=/wc-(.+)/,E={};if(!E.noOpts){location.search.slice(1).split("&").forEach(function(a){a=a.split("=");var b;a[0]&&(b=a[0].match(Ge))&&(E[b[1]]=a[1]||!0)});if(md)for(var nd=0,Ba;Ba=md.attributes[nd];nd++)"src"!==Ba.name&&(E[Ba.name]=Ba.value||!0);if(E.log&&E.log.split){var He=E.log.split(",");E.log={};He.forEach(function(a){E.log[a]=
!0})}else E.log={}}window.WebComponents.flags=E;var od=E.shadydom;od&&(window.ShadyDOM=window.ShadyDOM||{},window.ShadyDOM.force=od);var pd=E.register||E.ce;pd&&window.customElements&&(window.customElements.forcePolyfill=pd);var D=window.ShadyDOM||{};D.ob=!(!Element.prototype.attachShadow||!Node.prototype.getRootNode);var yb=Object.getOwnPropertyDescriptor(Node.prototype,"firstChild");D.W=!!(yb&&yb.configurable&&yb.get);D.Ha=D.force||!D.ob;var da=Element.prototype,qd=da.matches||da.matchesSelector||
da.mozMatchesSelector||da.msMatchesSelector||da.oMatchesSelector||da.webkitMatchesSelector,eb=document.createTextNode(""),kc=0,db=[];(new MutationObserver(function(){for(;db.length;)try{db.shift()()}catch(a){throw eb.textContent=kc++,a;}})).observe(eb,{characterData:!0});var ja=[],fb;va.list=ja;ta.prototype.vb=function(){var a=this;this.a||(this.a=!0,jc(function(){a.b()}))};ta.prototype.b=function(){if(this.a){this.a=!1;var a=this.takeRecords();a.length&&this.ba.forEach(function(b){b(a)})}};ta.prototype.takeRecords=
function(){if(this.addedNodes.length||this.removedNodes.length){var a=[{addedNodes:this.addedNodes,removedNodes:this.removedNodes}];this.addedNodes=[];this.removedNodes=[];return a}return[]};var Bc=Element.prototype.appendChild,mb=Element.prototype.insertBefore,ka=Element.prototype.removeChild,Jc=Element.prototype.setAttribute,rd=Element.prototype.removeAttribute,zb=Element.prototype.cloneNode,nb=Document.prototype.importNode,Rc=Element.prototype.addEventListener,Uc=Element.prototype.removeEventListener,
Qc=Window.prototype.addEventListener,Tc=Window.prototype.removeEventListener,Ab=Element.prototype.dispatchEvent,Ie=Object.freeze({appendChild:Bc,insertBefore:mb,removeChild:ka,setAttribute:Jc,removeAttribute:rd,cloneNode:zb,importNode:nb,addEventListener:Rc,removeEventListener:Uc,Jb:Qc,Kb:Tc,dispatchEvent:Ab,querySelector:Element.prototype.querySelector,querySelectorAll:Element.prototype.querySelectorAll}),me=/[&\u00A0"]/g,pe=/[&\u00A0<>]/g,ne=nc("area base br col command embed hr img input keygen link meta param source track wbr".split(" ")),
oe=nc("style script xmp iframe noembed noframes plaintext noscript".split(" ")),B=document.createTreeWalker(document,NodeFilter.SHOW_ALL,null,!1),C=document.createTreeWalker(document,NodeFilter.SHOW_ELEMENT,null,!1),Je=Object.freeze({parentNode:aa,firstChild:Sa,lastChild:Ta,previousSibling:oc,nextSibling:pc,childNodes:U,parentElement:qc,firstElementChild:rc,lastElementChild:sc,previousElementSibling:tc,nextElementSibling:uc,children:vc,innerHTML:wc,textContent:xc}),Bb=Object.getOwnPropertyDescriptor(Element.prototype,
"innerHTML")||Object.getOwnPropertyDescriptor(HTMLElement.prototype,"innerHTML"),Ca=document.implementation.createHTMLDocument("inert").createElement("div"),Cb=Object.getOwnPropertyDescriptor(Document.prototype,"activeElement"),yc={parentElement:{get:function(){var a=this.__shady&&this.__shady.parentNode;a&&a.nodeType!==Node.ELEMENT_NODE&&(a=null);return void 0!==a?a:qc(this)},configurable:!0},parentNode:{get:function(){var a=this.__shady&&this.__shady.parentNode;return void 0!==a?a:aa(this)},configurable:!0},
nextSibling:{get:function(){var a=this.__shady&&this.__shady.nextSibling;return void 0!==a?a:pc(this)},configurable:!0},previousSibling:{get:function(){var a=this.__shady&&this.__shady.previousSibling;return void 0!==a?a:oc(this)},configurable:!0},className:{get:function(){return this.getAttribute("class")||""},set:function(a){this.setAttribute("class",a)},configurable:!0},nextElementSibling:{get:function(){if(this.__shady&&void 0!==this.__shady.nextSibling){for(var a=this.nextSibling;a&&a.nodeType!==
Node.ELEMENT_NODE;)a=a.nextSibling;return a}return uc(this)},configurable:!0},previousElementSibling:{get:function(){if(this.__shady&&void 0!==this.__shady.previousSibling){for(var a=this.previousSibling;a&&a.nodeType!==Node.ELEMENT_NODE;)a=a.previousSibling;return a}return tc(this)},configurable:!0}},hb={childNodes:{get:function(){if(Z(this)){if(!this.__shady.childNodes){this.__shady.childNodes=[];for(var a=this.firstChild;a;a=a.nextSibling)this.__shady.childNodes.push(a)}var b=this.__shady.childNodes}else b=
U(this);b.item=function(a){return b[a]};return b},configurable:!0},childElementCount:{get:function(){return this.children.length},configurable:!0},firstChild:{get:function(){var a=this.__shady&&this.__shady.firstChild;return void 0!==a?a:Sa(this)},configurable:!0},lastChild:{get:function(){var a=this.__shady&&this.__shady.lastChild;return void 0!==a?a:Ta(this)},configurable:!0},textContent:{get:function(){if(Z(this)){for(var a=[],b=0,c=this.childNodes,d;d=c[b];b++)d.nodeType!==Node.COMMENT_NODE&&
a.push(d.textContent);return a.join("")}return xc(this)},set:function(a){switch(this.nodeType){case Node.ELEMENT_NODE:case Node.DOCUMENT_FRAGMENT_NODE:for(;this.firstChild;)this.removeChild(this.firstChild);(0<a.length||this.nodeType===Node.ELEMENT_NODE)&&this.appendChild(document.createTextNode(a));break;default:this.nodeValue=a}},configurable:!0},firstElementChild:{get:function(){if(this.__shady&&void 0!==this.__shady.firstChild){for(var a=this.firstChild;a&&a.nodeType!==Node.ELEMENT_NODE;)a=a.nextSibling;
return a}return rc(this)},configurable:!0},lastElementChild:{get:function(){if(this.__shady&&void 0!==this.__shady.lastChild){for(var a=this.lastChild;a&&a.nodeType!==Node.ELEMENT_NODE;)a=a.previousSibling;return a}return sc(this)},configurable:!0},children:{get:function(){var a;Z(this)?a=Array.prototype.filter.call(this.childNodes,function(a){return a.nodeType===Node.ELEMENT_NODE}):a=vc(this);a.item=function(b){return a[b]};return a},configurable:!0},innerHTML:{get:function(){var a="template"===
this.localName?this.content:this;return Z(this)?gb(a):wc(a)},set:function(a){for(var b="template"===this.localName?this.content:this;b.firstChild;)b.removeChild(b.firstChild);for(Bb&&Bb.set?Bb.set.call(Ca,a):Ca.innerHTML=a;Ca.firstChild;)b.appendChild(Ca.firstChild)},configurable:!0}},sd={shadowRoot:{get:function(){return this.__shady&&this.__shady.tb||null},configurable:!0}},ib={activeElement:{get:function(){var a=Cb&&Cb.get?Cb.get.call(document):D.W?void 0:document.activeElement;if(a&&a.nodeType){var b=
!!I(this);if(this===document||b&&this.host!==a&&this.host.contains(a)){for(b=ia(a);b&&b!==this;)a=b.host,b=ia(a);a=this===document?b?null:a:b===this?a:null}else a=null}else a=null;return a},set:function(){},configurable:!0}},ac=D.W?function(){}:function(a){a.__shady&&a.__shady.Xa||(a.__shady=a.__shady||{},a.__shady.Xa=!0,M(a,yc,!0))},$b=D.W?function(){}:function(a){a.__shady&&a.__shady.Va||(a.__shady=a.__shady||{},a.__shady.Va=!0,M(a,hb,!0),M(a,sd,!0))},xa=null,la="__eventWrappers"+Date.now(),Ke=
{blur:!0,focus:!0,focusin:!0,focusout:!0,click:!0,dblclick:!0,mousedown:!0,mouseenter:!0,mouseleave:!0,mousemove:!0,mouseout:!0,mouseover:!0,mouseup:!0,wheel:!0,beforeinput:!0,input:!0,keydown:!0,keyup:!0,compositionstart:!0,compositionupdate:!0,compositionend:!0,touchstart:!0,touchend:!0,touchmove:!0,touchcancel:!0,pointerover:!0,pointerenter:!0,pointerdown:!0,pointermove:!0,pointerup:!0,pointercancel:!0,pointerout:!0,pointerleave:!0,gotpointercapture:!0,lostpointercapture:!0,dragstart:!0,drag:!0,
dragenter:!0,dragleave:!0,dragover:!0,drop:!0,dragend:!0,DOMActivate:!0,DOMFocusIn:!0,DOMFocusOut:!0,keypress:!0},Vc={get composed(){!1!==this.isTrusted&&void 0===this.ia&&(this.ia=Ke[this.type]);return this.ia||!1},composedPath:function(){this.xa||(this.xa=ob(this.__target,this.composed));return this.xa},get target(){return Lc(this.currentTarget,this.composedPath())},get relatedTarget(){if(!this.ya)return null;this.za||(this.za=ob(this.ya,!0));return Lc(this.currentTarget,this.za)},stopPropagation:function(){Event.prototype.stopPropagation.call(this);
this.ja=!0},stopImmediatePropagation:function(){Event.prototype.stopImmediatePropagation.call(this);this.ja=this.Ua=!0}},qb={focus:!0,blur:!0},Le=pb(window.Event),Me=pb(window.CustomEvent),Ne=pb(window.MouseEvent),Zb={};l.prototype=Object.create(DocumentFragment.prototype);l.prototype.D=function(a,b){this.Wa="ShadyRoot";sa(a);sa(this);this.host=a;this.L=b&&b.mode;a.__shady=a.__shady||{};a.__shady.root=this;a.__shady.tb="closed"!==this.L?this:null;this.T=!1;this.b=[];this.a=null;b=U(a);for(var c=0,
d=b.length;c<d;c++)ka.call(a,b[c])};l.prototype.M=function(){var a=this;this.T||(this.T=!0,lc(function(){return a.Da()}))};l.prototype.C=function(){for(var a=this,b=this;b;)b.T&&(a=b),b=b.hb();return a};l.prototype.hb=function(){var a=this.host.getRootNode();if(I(a))for(var b=this.host.childNodes,c=0,d;c<b.length;c++)if(d=b[c],this.h(d))return a};l.prototype.Da=function(){this.T&&this.C()._renderRoot()};l.prototype._renderRoot=function(){this.T=!1;this.v();this.s()};l.prototype.v=function(){for(var a=
0,b;a<this.b.length;a++)b=this.b[a],this.l(b);for(b=this.host.firstChild;b;b=b.nextSibling)this.f(b);for(a=0;a<this.b.length;a++){b=this.b[a];if(!b.__shady.assignedNodes.length)for(var c=b.firstChild;c;c=c.nextSibling)this.f(c,b);c=b.parentNode;(c=c.__shady&&c.__shady.root)&&c.Aa()&&c._renderRoot();this.c(b.__shady.V,b.__shady.assignedNodes);if(c=b.__shady.Ca){for(var d=0;d<c.length;d++)c[d].__shady.ma=null;b.__shady.Ca=null;c.length>b.__shady.assignedNodes.length&&(b.__shady.pa=!0)}b.__shady.pa&&
(b.__shady.pa=!1,this.g(b))}};l.prototype.f=function(a,b){a.__shady=a.__shady||{};var c=a.__shady.ma;a.__shady.ma=null;b||(b=(b=this.a[a.slot||"__catchall"])&&b[0]);b?(b.__shady.assignedNodes.push(a),a.__shady.assignedSlot=b):a.__shady.assignedSlot=void 0;c!==a.__shady.assignedSlot&&a.__shady.assignedSlot&&(a.__shady.assignedSlot.__shady.pa=!0)};l.prototype.l=function(a){var b=a.__shady.assignedNodes;a.__shady.assignedNodes=[];a.__shady.V=[];if(a.__shady.Ca=b)for(var c=0;c<b.length;c++){var d=b[c];
d.__shady.ma=d.__shady.assignedSlot;d.__shady.assignedSlot===a&&(d.__shady.assignedSlot=null)}};l.prototype.c=function(a,b){for(var c=0,d;c<b.length&&(d=b[c]);c++)"slot"==d.localName?this.c(a,d.__shady.assignedNodes):a.push(b[c])};l.prototype.g=function(a){Ab.call(a,new Event("slotchange"));a.__shady.assignedSlot&&this.g(a.__shady.assignedSlot)};l.prototype.s=function(){for(var a=this.b,b=[],c=0;c<a.length;c++){var d=a[c].parentNode;d.__shady&&d.__shady.root||!(0>b.indexOf(d))||b.push(d)}for(a=0;a<
b.length;a++)c=b[a],this.I(c===this?this.host:c,this.u(c))};l.prototype.u=function(a){var b=[];a=a.childNodes;for(var c=0;c<a.length;c++){var d=a[c];if(this.h(d)){d=d.__shady.V;for(var e=0;e<d.length;e++)b.push(d[e])}else b.push(d)}return b};l.prototype.h=function(a){return"slot"==a.localName};l.prototype.I=function(a,b){for(var c=U(a),d=se(b,b.length,c,c.length),e=0,f=0,g;e<d.length&&(g=d[e]);e++){for(var h=0,k;h<g.Y.length&&(k=g.Y[h]);h++)aa(k)===a&&ka.call(a,k),c.splice(g.index+f,1);f-=g.aa}for(e=
0;e<d.length&&(g=d[e]);e++)for(f=c[g.index],h=g.index;h<g.index+g.aa;h++)k=b[h],mb.call(a,k,f),c.splice(h,0,k)};l.prototype.$a=function(a){this.a=this.a||{};this.b=this.b||[];for(var b=0;b<a.length;b++){var c=a[b];c.__shady=c.__shady||{};sa(c);sa(c.parentNode);var d=this.i(c);if(this.a[d]){var e=e||{};e[d]=!0;this.a[d].push(c)}else this.a[d]=[c];this.b.push(c)}if(e)for(var f in e)this.a[f]=this.j(this.a[f])};l.prototype.i=function(a){var b=a.name||a.getAttribute("name")||"__catchall";return a.Ya=
b};l.prototype.j=function(a){return a.sort(function(a,c){a=Wc(a);for(var b=Wc(c),e=0;e<a.length;e++){c=a[e];var f=b[e];if(c!==f)return a=Array.from(c.parentNode.childNodes),a.indexOf(c)-a.indexOf(f)}})};l.prototype.gb=function(a){this.a=this.a||{};this.b=this.b||[];var b=this.a,c;for(c in b)for(var d=b[c],e=0;e<d.length;e++){var f=d[e],g;a:{for(g=f;g;){if(g==a){g=!0;break a}g=g.parentNode}g=void 0}if(g){d.splice(e,1);var h=this.b.indexOf(f);0<=h&&this.b.splice(h,1);e--;this.H(f);h=!0}}return h};l.prototype.ib=
function(a){var b=a.Ya,c=this.i(a);if(c!==b){b=this.a[b];var d=b.indexOf(a);0<=d&&b.splice(d,1);b=this.a[c]||(this.a[c]=[]);b.push(a);1<b.length&&(this.a[c]=this.j(b))}};l.prototype.H=function(a){if(a=a.__shady.V)for(var b=0;b<a.length;b++){var c=a[b],d=aa(c);d&&ka.call(d,c)}};l.prototype.Aa=function(){return!!this.b.length};l.prototype.addEventListener=function(a,b,c){"object"!==typeof c&&(c={capture:!!c});c.ka=this;this.host.addEventListener(a,b,c)};l.prototype.removeEventListener=function(a,b,
c){"object"!==typeof c&&(c={capture:!!c});c.ka=this;this.host.removeEventListener(a,b,c)};l.prototype.getElementById=function(a){return wa(this,function(b){return b.id==a},function(a){return!!a})[0]||null};(function(a){M(a,hb,!0);M(a,ib,!0)})(l.prototype);var we={addEventListener:Oc.bind(window),removeEventListener:Sc.bind(window)},ve={addEventListener:Oc,removeEventListener:Sc,appendChild:function(a){return jb(this,a)},insertBefore:function(a,b){return jb(this,a,b)},removeChild:function(a){return kb(this,
a)},replaceChild:function(a,b){jb(this,a,b);kb(this,b);return a},cloneNode:function(a){if("template"==this.localName)var b=zb.call(this,a);else if(b=zb.call(this,!1),a){a=this.childNodes;for(var c=0,d;c<a.length;c++)d=a[c].cloneNode(!0),b.appendChild(d)}return b},getRootNode:function(){return Fc(this)},get isConnected(){var a=this.ownerDocument;if(a&&a.contains&&a.contains(this)||(a=a.documentElement)&&a.contains&&a.contains(this))return!0;for(a=this;a&&!(a instanceof Document);)a=a.parentNode||(a instanceof
l?a.host:void 0);return!!(a&&a instanceof Document)},dispatchEvent:function(a){va();return Ab.call(this,a)}},xe={get assignedSlot(){return Xc(this)}},rb={querySelector:function(a){return wa(this,function(b){return qd.call(b,a)},function(a){return!!a})[0]||null},querySelectorAll:function(a){return wa(this,function(b){return qd.call(b,a)})}},$c={assignedNodes:function(a){if("slot"===this.localName)return Hc(this),this.__shady?(a&&a.flatten?this.__shady.V:this.__shady.assignedNodes)||[]:[]}},Yc=cb({setAttribute:function(a,
b){Ic(this,a,b)},removeAttribute:function(a){rd.call(this,a);Ec(this,a)},attachShadow:function(a){if(!this)throw"Must provide a host.";if(!a)throw"Not enough arguments.";return new l(Zb,this,a)},get slot(){return this.getAttribute("slot")},set slot(a){Ic(this,"slot",a)},get assignedSlot(){return Xc(this)}},rb,$c);Object.defineProperties(Yc,sd);var Zc=cb({importNode:function(a,b){return Kc(a,b)},getElementById:function(a){return wa(this,function(b){return b.id==a},function(a){return!!a})[0]||null}},
rb);Object.defineProperties(Zc,{_activeElement:ib.activeElement});var Oe=HTMLElement.prototype.blur,ye=cb({blur:function(){var a=this.__shady&&this.__shady.root;(a=a&&a.activeElement)?a.blur():Oe.call(this)}});D.Ha&&(window.ShadyDOM={inUse:D.Ha,patch:function(a){return a},isShadyRoot:I,enqueue:lc,flush:va,settings:D,filterMutations:le,observeChildren:Zd,unobserveChildren:Yd,nativeMethods:Ie,nativeTree:Je},window.Event=Le,window.CustomEvent=Me,window.MouseEvent=Ne,re(),ue(),window.ShadowRoot=l);var ze=
new Set("annotation-xml color-profile font-face font-face-src font-face-uri font-face-format font-face-name missing-glyph".split(" "));z.prototype.D=function(a,b){this.u.set(a,b);this.s.set(b.constructor,b)};z.prototype.c=function(a){return this.u.get(a)};z.prototype.C=function(a){return this.s.get(a)};z.prototype.v=function(a){this.h=!0;this.j.push(a)};z.prototype.l=function(a){var b=this;this.h&&P(a,function(a){return b.g(a)})};z.prototype.g=function(a){if(this.h&&!a.__CE_patched){a.__CE_patched=
!0;for(var b=0;b<this.j.length;b++)this.j[b](a)}};z.prototype.b=function(a){var b=[];P(a,function(a){return b.push(a)});for(a=0;a<b.length;a++){var c=b[a];1===c.__CE_state?this.connectedCallback(c):this.i(c)}};z.prototype.a=function(a){var b=[];P(a,function(a){return b.push(a)});for(a=0;a<b.length;a++){var c=b[a];1===c.__CE_state&&this.disconnectedCallback(c)}};z.prototype.f=function(a,b){var c=this;b=b?b:{};var d=b.yb||new Set,e=b.Na||function(a){return c.i(a)},f=[];P(a,function(a){if("link"===a.localName&&
"import"===a.getAttribute("rel")){var b=a.import;b instanceof Node&&"complete"===b.readyState?(b.__CE_isImportDocument=!0,b.__CE_hasRegistry=!0):a.addEventListener("load",function(){var b=a.import;b.__CE_documentLoadHandled||(b.__CE_documentLoadHandled=!0,b.__CE_isImportDocument=!0,b.__CE_hasRegistry=!0,d.delete(b),c.f(b,{yb:d,Na:e}))})}else f.push(a)},d);if(this.h)for(a=0;a<f.length;a++)this.g(f[a]);for(a=0;a<f.length;a++)e(f[a])};z.prototype.i=function(a){if(void 0===a.__CE_state){var b=this.c(a.localName);
if(b){b.constructionStack.push(a);var c=b.constructor;try{try{if(new c!==a)throw Error("The custom element constructor did not produce the element being upgraded.");}finally{b.constructionStack.pop()}}catch(f){throw a.__CE_state=2,f;}a.__CE_state=1;a.__CE_definition=b;if(b.attributeChangedCallback)for(b=b.observedAttributes,c=0;c<b.length;c++){var d=b[c],e=a.getAttribute(d);null!==e&&this.attributeChangedCallback(a,d,null,e,null)}n(a)&&this.connectedCallback(a)}}};z.prototype.connectedCallback=function(a){var b=
a.__CE_definition;b.connectedCallback&&b.connectedCallback.call(a)};z.prototype.disconnectedCallback=function(a){var b=a.__CE_definition;b.disconnectedCallback&&b.disconnectedCallback.call(a)};z.prototype.attributeChangedCallback=function(a,b,c,d,e){var f=a.__CE_definition;f.attributeChangedCallback&&-1<f.observedAttributes.indexOf(b)&&f.attributeChangedCallback.call(a,b,c,d,e)};Ra.prototype.c=function(){this.N&&this.N.disconnect()};Ra.prototype.f=function(a){var b=this.a.readyState;"interactive"!==
b&&"complete"!==b||this.c();for(b=0;b<a.length;b++)for(var c=a[b].addedNodes,d=0;d<c.length;d++)this.b.f(c[d])};Yb.prototype.resolve=function(a){if(this.a)throw Error("Already resolved.");this.a=a;this.b&&this.b(a)};x.prototype.define=function(a,b){var c=this;if(!(b instanceof Function))throw new TypeError("Custom element constructors must be functions.");if(!ad(a))throw new SyntaxError("The element name '"+a+"' is not valid.");if(this.a.c(a))throw Error("A custom element with name '"+a+"' has already been defined.");
if(this.c)throw Error("A custom element is already being defined.");this.c=!0;try{var d=function(a){var b=e[a];if(void 0!==b&&!(b instanceof Function))throw Error("The '"+a+"' callback must be a function.");return b},e=b.prototype;if(!(e instanceof Object))throw new TypeError("The custom element constructor's prototype is not an object.");var f=d("connectedCallback");var g=d("disconnectedCallback");var h=d("adoptedCallback");var k=d("attributeChangedCallback");var l=b.observedAttributes||[]}catch(cf){return}finally{this.c=
!1}b={localName:a,constructor:b,connectedCallback:f,disconnectedCallback:g,adoptedCallback:h,attributeChangedCallback:k,observedAttributes:l,constructionStack:[]};this.a.D(a,b);this.g.push(b);this.b||(this.b=!0,this.f(function(){return c.j()}))};x.prototype.j=function(){var a=this;if(!1!==this.b){this.b=!1;for(var b=this.g,c=[],d=new Map,e=0;e<b.length;e++)d.set(b[e].localName,[]);this.a.f(document,{Na:function(b){if(void 0===b.__CE_state){var e=b.localName,f=d.get(e);f?f.push(b):a.a.c(e)&&c.push(b)}}});
for(e=0;e<c.length;e++)this.a.i(c[e]);for(;0<b.length;){var f=b.shift();e=f.localName;f=d.get(f.localName);for(var g=0;g<f.length;g++)this.a.i(f[g]);(e=this.h.get(e))&&e.resolve(void 0)}}};x.prototype.get=function(a){if(a=this.a.c(a))return a.constructor};x.prototype.whenDefined=function(a){if(!ad(a))return Promise.reject(new SyntaxError("'"+a+"' is not a valid custom element name."));var b=this.h.get(a);if(b)return b.c;b=new Yb;this.h.set(a,b);this.a.c(a)&&!this.g.some(function(b){return b.localName===
a})&&b.resolve(void 0);return b.c};x.prototype.l=function(a){this.i.c();var b=this.f;this.f=function(c){return a(function(){return b(c)})}};window.CustomElementRegistry=x;x.prototype.define=x.prototype.define;x.prototype.get=x.prototype.get;x.prototype.whenDefined=x.prototype.whenDefined;x.prototype.polyfillWrapFlushCallback=x.prototype.l;var Na=window.Document.prototype.createElement,Td=window.Document.prototype.createElementNS,Sd=window.Document.prototype.importNode,Ud=window.Document.prototype.prepend,
Vd=window.Document.prototype.append,Nb=window.Node.prototype.cloneNode,qa=window.Node.prototype.appendChild,Vb=window.Node.prototype.insertBefore,Oa=window.Node.prototype.removeChild,Wb=window.Node.prototype.replaceChild,Qa=Object.getOwnPropertyDescriptor(window.Node.prototype,"textContent"),Mb=window.Element.prototype.attachShadow,La=Object.getOwnPropertyDescriptor(window.Element.prototype,"innerHTML"),Pa=window.Element.prototype.getAttribute,Ob=window.Element.prototype.setAttribute,Qb=window.Element.prototype.removeAttribute,
ra=window.Element.prototype.getAttributeNS,Pb=window.Element.prototype.setAttributeNS,Rb=window.Element.prototype.removeAttributeNS,Tb=window.Element.prototype.insertAdjacentElement,Jd=window.Element.prototype.prepend,Kd=window.Element.prototype.append,Md=window.Element.prototype.before,Nd=window.Element.prototype.after,Od=window.Element.prototype.replaceWith,Pd=window.Element.prototype.remove,Xd=window.HTMLElement,Ma=Object.getOwnPropertyDescriptor(window.HTMLElement.prototype,"innerHTML"),Sb=window.HTMLElement.prototype.insertAdjacentElement,
Xb=new function(){},Da=window.customElements;if(!Da||Da.forcePolyfill||"function"!=typeof Da.define||"function"!=typeof Da.get){var na=new z;Wd(na);Rd(na);Qd(na);Id(na);document.__CE_hasRegistry=!0;var Pe=new x(na);Object.defineProperty(window,"customElements",{configurable:!0,enumerable:!0,value:Pe})}var L={STYLE_RULE:1,ha:7,MEDIA_RULE:4,ua:1E3},G={mb:/\/\*[^*]*\*+([^/*][^*]*\*+)*\//gim,port:/@import[^;]*;/gim,Ea:/(?:^[^;\-\s}]+)?--[^;{}]*?:[^{};]*?(?:[;\n]|$)/gim,Ia:/(?:^[^;\-\s}]+)?--[^;{}]*?:[^{};]*?{[^}]*?}(?:[;\n]|$)?/gim,
sb:/@apply\s*\(?[^);]*\)?\s*(?:[;\n]|$)?/gim,xb:/[^;:]*?:[^;]*?var\([^;]*\)(?:[;\n]|$)?/gim,rb:/^@[^\s]*keyframes/,Ja:/\s+/g},q=!(window.ShadyDOM&&window.ShadyDOM.inUse);if(window.ShadyCSS&&void 0!==window.ShadyCSS.nativeCss)var v=window.ShadyCSS.nativeCss;else window.ShadyCSS?(dd(window.ShadyCSS),window.ShadyCSS=void 0):dd(window.WebComponents&&window.WebComponents.flags);var Ea=/(?:^|[;\s{]\s*)(--[\w-]*?)\s*:\s*(?:((?:'(?:\\'|.)*?'|"(?:\\"|.)*?"|\([^)]*?\)|[^};{])+)|\{([^}]*)\}(?:(?=[;\s}])|$))/gi,
Fa=/(?:^|\W+)@apply\s*\(?([^);\n]*)\)?/gi,Qe=/(--[\w-]+)\s*([:,;)]|$)/gi,Re=/(animation\s*:)|(animation-name\s*:)/,Be=/@media\s(.*)/,Se=/\{[^}]*\}/g,S=null;r.prototype.a=function(a,b,c){a.__styleScoped?a.__styleScoped=null:this.i(a,b||"",c)};r.prototype.i=function(a,b,c){a.nodeType===Node.ELEMENT_NODE&&this.C(a,b,c);if(a="template"===a.localName?(a.content||a.Cb).childNodes:a.children||a.childNodes)for(var d=0;d<a.length;d++)this.i(a[d],b,c)};r.prototype.C=function(a,b,c){if(b)if(a.classList)c?(a.classList.remove("style-scope"),
a.classList.remove(b)):(a.classList.add("style-scope"),a.classList.add(b));else if(a.getAttribute){var d=a.getAttribute(Te);c?d&&(b=d.replace("style-scope","").replace(b,""),za(a,b)):za(a,(d?d+" ":"")+"style-scope "+b)}};r.prototype.b=function(a,b,c){var d=a.__cssBuild;q||"shady"===d?b=ba(b,c):(a=T(a),b=this.I(b,a.is,a.Z,c)+"\n\n");return b.trim()};r.prototype.I=function(a,b,c,d){var e=this.f(b,c);b=this.h(b);var f=this;return ba(a,function(a){a.c||(f.S(a,b,e),a.c=!0);d&&d(a,b,e)})};r.prototype.h=
function(a){return a?Ue+a:""};r.prototype.f=function(a,b){return b?"[is="+a+"]":a};r.prototype.S=function(a,b,c){this.j(a,this.g,b,c)};r.prototype.j=function(a,b,c,d){a.selector=a.A=this.l(a,b,c,d)};r.prototype.l=function(a,b,c,d){var e=a.selector.split(td);if(!ed(a)){a=0;for(var f=e.length,g;a<f&&(g=e[a]);a++)e[a]=b.call(this,g,c,d)}return e.join(td)};r.prototype.u=function(a){return a.replace(Db,function(a,c,d){-1<d.indexOf("+")?d=d.replace(/\+/g,"___"):-1<d.indexOf("___")&&(d=d.replace(/___/g,
"+"));return":"+c+"("+d+")"})};r.prototype.g=function(a,b,c){var d=this,e=!1;a=a.trim();var f=Db.test(a);f&&(a=a.replace(Db,function(a,b,c){return":"+b+"("+c.replace(/\s/g,"")+")"}),a=this.u(a));a=a.replace(Ve,Eb+" $1");a=a.replace(We,function(a,f,k){e||(a=d.D(k,f,b,c),e=e||a.stop,f=a.lb,k=a.value);return f+k});f&&(a=this.u(a));return a};r.prototype.D=function(a,b,c,d){var e=a.indexOf(Fb);0<=a.indexOf(Eb)?a=this.H(a,d):0!==e&&(a=c?this.s(a,c):a);c=!1;0<=e&&(b="",c=!0);if(c){var f=!0;c&&(a=a.replace(Xe,
function(a,b){return" > "+b}))}a=a.replace(Ye,function(a,b,c){return'[dir="'+c+'"] '+b+", "+b+'[dir="'+c+'"]'});return{value:a,lb:b,stop:f}};r.prototype.s=function(a,b){a=a.split(ud);a[0]+=b;return a.join(ud)};r.prototype.H=function(a,b){var c=a.match(vd);return(c=c&&c[2].trim()||"")?c[0].match(wd)?a.replace(vd,function(a,c,f){return b+f}):c.split(wd)[0]===b?c:Ze:a.replace(Eb,b)};r.prototype.R=function(a){a.selector=a.parsedSelector;this.v(a);this.j(a,this.L)};r.prototype.v=function(a){a.selector===
$e&&(a.selector="html")};r.prototype.L=function(a){return a.match(Fb)?this.g(a,xd):this.s(a.trim(),xd)};Jb.Object.defineProperties(r.prototype,{c:{configurable:!0,enumerable:!0,get:function(){return"style-scope"}}});var Db=/:(nth[-\w]+)\(([^)]+)\)/,xd=":not(.style-scope)",td=",",We=/(^|[\s>+~]+)((?:\[.+?\]|[^\s>+~=[])+)/g,wd=/[[.:#*]/,Eb=":host",$e=":root",Fb="::slotted",Ve=new RegExp("^("+Fb+")"),vd=/(:host)(?:\(((?:\([^)(]*\)|[^)(]*)+?)\))/,Xe=/(?:::slotted)(?:\(((?:\([^)(]*\)|[^)(]*)+?)\))/,Ye=
/(.*):dir\((?:(ltr|rtl))\)/,Ue=".",ud=":",Te="class",Ze="should_not_match",w=new r;t.get=function(a){return a?a.__styleInfo:null};t.set=function(a,b){return a.__styleInfo=b};t.prototype.c=function(){return this.G};t.prototype._getStyleRules=t.prototype.c;var yd=function(a){return a.matches||a.matchesSelector||a.mozMatchesSelector||a.msMatchesSelector||a.oMatchesSelector||a.webkitMatchesSelector}(window.Element.prototype),af=navigator.userAgent.match("Trident");p.prototype.R=function(a){var b=this,
c={},d=[],e=0;ca(a,function(a){b.c(a);a.index=e++;b.I(a.w.cssText,c)},function(a){d.push(a)});a.b=d;a=[];for(var f in c)a.push(f);return a};p.prototype.c=function(a){if(!a.w){var b={},c={};this.b(a,c)&&(b.F=c,a.rules=null);b.cssText=this.H(a);a.w=b}};p.prototype.b=function(a,b){var c=a.w;if(c){if(c.F)return Object.assign(b,c.F),!0}else{c=a.parsedCssText;for(var d;a=Ea.exec(c);){d=(a[2]||a[3]).trim();if("inherit"!==d||"unset"!==d)b[a[1].trim()]=d;d=!0}return d}};p.prototype.H=function(a){return this.L(a.parsedCssText)};
p.prototype.L=function(a){return a.replace(Se,"").replace(Ea,"")};p.prototype.I=function(a,b){for(var c;c=Qe.exec(a);){var d=c[1];":"!==c[2]&&(b[d]=!0)}};p.prototype.ea=function(a){for(var b=Object.getOwnPropertyNames(a),c=0,d;c<b.length;c++)d=b[c],a[d]=this.a(a[d],a)};p.prototype.a=function(a,b){if(a)if(0<=a.indexOf(";"))a=this.f(a,b);else{var c=this;a=gd(a,function(a,e,f,g){if(!e)return a+g;(e=c.a(b[e],b))&&"initial"!==e?"apply-shim-inherit"===e&&(e="inherit"):e=c.a(b[f]||f,b)||f;return a+(e||"")+
g})}return a&&a.trim()||""};p.prototype.f=function(a,b){a=a.split(";");for(var c=0,d,e;c<a.length;c++)if(d=a[c]){Fa.lastIndex=0;if(e=Fa.exec(d))d=this.a(b[e[1]],b);else if(e=d.indexOf(":"),-1!==e){var f=d.substring(e);f=f.trim();f=this.a(f,b)||f;d=d.substring(0,e)+f}a[c]=d&&d.lastIndexOf(";")===d.length-1?d.slice(0,-1):d||""}return a.join(";")};p.prototype.D=function(a,b){var c="";a.w||this.c(a);a.w.cssText&&(c=this.f(a.w.cssText,b));a.cssText=c};p.prototype.C=function(a,b){var c=a.cssText,d=a.cssText;
null==a.Ga&&(a.Ga=Re.test(c));if(a.Ga)if(null==a.ca){a.ca=[];for(var e in b)d=b[e],d=d(c),c!==d&&(c=d,a.ca.push(e))}else{for(e=0;e<a.ca.length;++e)d=b[a.ca[e]],c=d(c);d=c}a.cssText=d};p.prototype.da=function(a,b){var c={},d=this,e=[];ca(a,function(a){a.w||d.c(a);var f=a.A||a.parsedSelector;b&&a.w.F&&f&&yd.call(b,f)&&(d.b(a,c),a=a.index,f=parseInt(a/32,10),e[f]=(e[f]||0)|1<<a%32)},null,!0);return{F:c,key:e}};p.prototype.ga=function(a,b,c,d){b.w||this.c(b);if(b.w.F){var e=T(a);a=e.is;e=e.Z;e=a?w.f(a,
e):"html";var f=b.parsedSelector,g=":host > *"===f||"html"===f,h=0===f.indexOf(":host")&&!g;"shady"===c&&(g=f===e+" > *."+e||-1!==f.indexOf("html"),h=!g&&0===f.indexOf(e));"shadow"===c&&(g=":host > *"===f||"html"===f,h=h&&!g);if(g||h)c=e,h&&(q&&!b.A&&(b.A=w.l(b,w.g,w.h(a),e)),c=b.A||e),d({wb:c,qb:h,Fb:g})}};p.prototype.S=function(a,b){var c={},d={},e=this,f=b&&b.__cssBuild;ca(b,function(b){e.ga(a,b,f,function(f){yd.call(a.Db||a,f.wb)&&(f.qb?e.b(b,c):e.b(b,d))})},null,!0);return{ub:d,pb:c}};p.prototype.fa=
function(a,b,c){var d=this,e=T(a),f=w.f(e.is,e.Z),g=new RegExp("(?:^|[^.#[:])"+(a.extends?"\\"+f.slice(0,-1)+"\\]":f)+"($|[.:[\\s>+~])");e=t.get(a).G;var h=this.h(e,c);return w.b(a,e,function(a){d.D(a,b);q||ed(a)||!a.cssText||(d.C(a,h),d.l(a,g,f,c))})};p.prototype.h=function(a,b){a=a.b;var c={};if(!q&&a)for(var d=0,e=a[d];d<a.length;e=a[++d])this.j(e,b),c[e.keyframesName]=this.i(e);return c};p.prototype.i=function(a){return function(b){return b.replace(a.f,a.a)}};p.prototype.j=function(a,b){a.f=new RegExp(a.keyframesName,
"g");a.a=a.keyframesName+"-"+b;a.A=a.A||a.selector;a.selector=a.A.replace(a.keyframesName,a.a)};p.prototype.l=function(a,b,c,d){a.A=a.A||a.selector;d="."+d;for(var e=a.A.split(","),f=0,g=e.length,h;f<g&&(h=e[f]);f++)e[f]=h.match(b)?h.replace(c,d):d+" "+h;a.selector=e.join(",")};p.prototype.u=function(a,b,c){var d=a.getAttribute("class")||"",e=d;c&&(e=d.replace(new RegExp("\\s*x-scope\\s*"+c+"\\s*","g")," "));e+=(e?" ":"")+"x-scope "+b;d!==e&&za(a,e)};p.prototype.v=function(a,b,c,d){b=d?d.textContent||
"":this.fa(a,b,c);var e=t.get(a),f=e.a;f&&!q&&f!==d&&(f._useCount--,0>=f._useCount&&f.parentNode&&f.parentNode.removeChild(f));q?e.a?(e.a.textContent=b,d=e.a):b&&(d=ub(b,c,a.shadowRoot,e.b)):d?d.parentNode||(af&&-1<b.indexOf("@media")&&(d.textContent=b),fd(d,null,e.b)):b&&(d=ub(b,c,null,e.b));d&&(d._useCount=d._useCount||0,e.a!=d&&d._useCount++,e.a=d);return d};p.prototype.s=function(a,b){var c=ya(a),d=this;a.textContent=ba(c,function(a){var c=a.cssText=a.parsedCssText;a.w&&a.w.cssText&&(c=c.replace(G.Ea,
"").replace(G.Ia,""),a.cssText=d.f(c,b))})};Jb.Object.defineProperties(p.prototype,{g:{configurable:!0,enumerable:!0,get:function(){return"x-scope"}}});var O=new p,Gb={},Ga=window.customElements;if(Ga&&!q){var bf=Ga.define;Ga.define=function(a,b,c){var d=document.createComment(" Shady DOM styles for "+a+" "),e=document.head;e.insertBefore(d,(S?S.nextSibling:null)||e.firstChild);S=d;Gb[a]=d;return bf.call(Ga,a,b,c)}}pa.prototype.a=function(a,b,c){for(var d=0;d<c.length;d++){var e=c[d];if(a.F[e]!==
b[e])return!1}return!0};pa.prototype.b=function(a,b,c,d){var e=this.cache[a]||[];e.push({F:b,styleElement:c,B:d});e.length>this.c&&e.shift();this.cache[a]=e};pa.prototype.fetch=function(a,b,c){if(a=this.cache[a])for(var d=a.length-1;0<=d;d--){var e=a[d];if(this.a(e,b,c))return e}};if(!q){var zd=new MutationObserver(hd),Ad=function(a){zd.observe(a,{childList:!0,subtree:!0})};if(window.customElements&&!window.customElements.polyfillWrapFlushCallback)Ad(document);else{var Hb=function(){Ad(document.body)};
window.HTMLImports?window.HTMLImports.whenReady(Hb):requestAnimationFrame(function(){if("loading"===document.readyState){var a=function(){Hb();document.removeEventListener("readystatechange",a)};document.addEventListener("readystatechange",a)}else Hb()})}Lb=function(){hd(zd.takeRecords())}}var Aa={},Ee=Promise.resolve(),vb=null,jd=window.HTMLImports&&window.HTMLImports.whenReady||null,wb,Ha=null,oa=null;F.prototype.Fa=function(){!this.enqueued&&oa&&(this.enqueued=!0,Kb(oa))};F.prototype.b=function(a){a.__seenByShadyCSS||
(a.__seenByShadyCSS=!0,this.customStyles.push(a),this.Fa())};F.prototype.a=function(a){return a.__shadyCSSCachedStyle?a.__shadyCSSCachedStyle:a.getStyle?a.getStyle():a};F.prototype.c=function(){for(var a=this.customStyles,b=0;b<a.length;b++){var c=a[b];if(!c.__shadyCSSCachedStyle){var d=this.a(c);d&&(d=d.__appliedElement||d,Ha&&Ha(d),c.__shadyCSSCachedStyle=d)}}return a};F.prototype.addCustomStyle=F.prototype.b;F.prototype.getStyleForCustomStyle=F.prototype.a;F.prototype.processStyles=F.prototype.c;
Object.defineProperties(F.prototype,{transformCallback:{get:function(){return Ha},set:function(a){Ha=a}},validateCallback:{get:function(){return oa},set:function(a){var b=!1;oa||(b=!0);oa=a;b&&this.Fa()}}});var Bd=new pa;k.prototype.C=function(){Lb()};k.prototype.S=function(a){var b=this.s[a]=(this.s[a]||0)+1;return a+"-"+b};k.prototype.Ra=function(a){return ya(a)};k.prototype.Ta=function(a){return ba(a)};k.prototype.R=function(a){a=a.content.querySelectorAll("style");for(var b=[],c=0;c<a.length;c++){var d=
a[c];b.push(d.textContent);d.parentNode.removeChild(d)}return b.join("").trim()};k.prototype.ea=function(a){return(a=a.content.querySelector("style"))?a.getAttribute("css-build")||"":""};k.prototype.prepareTemplate=function(a,b,c){if(!a.f){a.f=!0;a.name=b;a.extends=c;Aa[b]=a;var d=this.ea(a),e=this.R(a);c={is:b,extends:c,Ab:d};q||w.a(a.content,b);this.c();var f=Fa.test(e)||Ea.test(e);Fa.lastIndex=0;Ea.lastIndex=0;e=tb(e);f&&v&&this.a&&this.a.transformRules(e,b);a._styleAst=e;a.g=d;d=[];v||(d=O.R(a._styleAst));
if(!d.length||v)b=this.da(c,a._styleAst,q?a.content:null,Gb[b]),a.a=b;a.c=d}};k.prototype.da=function(a,b,c,d){b=w.b(a,b);if(b.length)return ub(b,a.is,c,d)};k.prototype.ga=function(a){var b=T(a),c=b.is;b=b.Z;var d=Gb[c];c=Aa[c];if(c){var e=c._styleAst;var f=c.c}return t.set(a,new t(e,d,f,0,b))};k.prototype.H=function(){!this.a&&window.ShadyCSS&&window.ShadyCSS.ApplyShim&&(this.a=window.ShadyCSS.ApplyShim,this.a.invalidCallback=Ce)};k.prototype.I=function(){var a=this;!this.b&&window.ShadyCSS&&window.ShadyCSS.CustomStyleInterface&&
(this.b=window.ShadyCSS.CustomStyleInterface,this.b.transformCallback=function(b){a.v(b)},this.b.validateCallback=function(){requestAnimationFrame(function(){(a.b.enqueued||a.i)&&a.f()})})};k.prototype.c=function(){this.H();this.I()};k.prototype.f=function(){this.c();if(this.b){var a=this.b.processStyles();this.b.enqueued&&(v?this.Pa(a):(this.u(this.g,this.h),this.D(a)),this.b.enqueued=!1,this.i&&!v&&this.styleDocument())}};k.prototype.styleElement=function(a,b){var c=T(a).is,d=t.get(a);d||(d=this.ga(a));
this.j(a)||(this.i=!0);b&&(d.P=d.P||{},Object.assign(d.P,b));if(v){if(d.P){b=d.P;for(var e in b)null===e?a.style.removeProperty(e):a.style.setProperty(e,b[e])}if(((e=Aa[c])||this.j(a))&&e&&e.a&&!id(e)){if(id(e)||e._applyShimValidatingVersion!==e._applyShimNextVersion)this.c(),this.a&&this.a.transformRules(e._styleAst,c),e.a.textContent=w.b(a,d.G),De(e);q&&(c=a.shadowRoot)&&(c.querySelector("style").textContent=w.b(a,d.G));d.G=e._styleAst}}else this.u(a,d),d.ra&&d.ra.length&&this.L(a,d)};k.prototype.l=
function(a){return(a=a.getRootNode().host)?t.get(a)?a:this.l(a):this.g};k.prototype.j=function(a){return a===this.g};k.prototype.L=function(a,b){var c=T(a).is,d=Bd.fetch(c,b.K,b.ra),e=d?d.styleElement:null,f=b.B;b.B=d&&d.B||this.S(c);e=O.v(a,b.K,b.B,e);q||O.u(a,b.B,f);d||Bd.b(c,b.K,e,b.B)};k.prototype.u=function(a,b){var c=this.l(a),d=t.get(c);c=Object.create(d.K||null);var e=O.S(a,b.G);a=O.da(d.G,a).F;Object.assign(c,e.pb,a,e.ub);this.fa(c,b.P);O.ea(c);b.K=c};k.prototype.fa=function(a,b){for(var c in b){var d=
b[c];if(d||0===d)a[c]=d}};k.prototype.styleDocument=function(a){this.styleSubtree(this.g,a)};k.prototype.styleSubtree=function(a,b){var c=a.shadowRoot;(c||this.j(a))&&this.styleElement(a,b);if(b=c&&(c.children||c.childNodes))for(a=0;a<b.length;a++)this.styleSubtree(b[a]);else if(a=a.children||a.childNodes)for(b=0;b<a.length;b++)this.styleSubtree(a[b])};k.prototype.Pa=function(a){for(var b=0;b<a.length;b++){var c=this.b.getStyleForCustomStyle(a[b]);c&&this.Oa(c)}};k.prototype.D=function(a){for(var b=
0;b<a.length;b++){var c=this.b.getStyleForCustomStyle(a[b]);c&&O.s(c,this.h.K)}};k.prototype.v=function(a){var b=this,c=ya(a);ca(c,function(a){q?w.v(a):w.R(a);v&&(b.c(),b.a&&b.a.transformRule(a))});v?a.textContent=ba(c):this.h.G.rules.push(c)};k.prototype.Oa=function(a){if(v&&this.a){var b=ya(a);this.c();this.a.transformRules(b);a.textContent=ba(b)}};k.prototype.getComputedStyleValue=function(a,b){var c;v||(c=(t.get(a)||t.get(this.l(a))).K[b]);return(c=c||window.getComputedStyle(a).getPropertyValue(b))?
c.trim():""};k.prototype.Sa=function(a,b){var c=a.getRootNode();b=b?b.split(/\s/):[];c=c.host&&c.host.localName;if(!c){var d=a.getAttribute("class");if(d){d=d.split(/\s/);for(var e=0;e<d.length;e++)if(d[e]===w.c){c=d[e+1];break}}}c&&b.push(w.c,c);v||(c=t.get(a))&&c.B&&b.push(O.g,c.B);za(a,b.join(" "))};k.prototype.Qa=function(a){return t.get(a)};k.prototype.flush=k.prototype.C;k.prototype.prepareTemplate=k.prototype.prepareTemplate;k.prototype.styleElement=k.prototype.styleElement;k.prototype.styleDocument=
k.prototype.styleDocument;k.prototype.styleSubtree=k.prototype.styleSubtree;k.prototype.getComputedStyleValue=k.prototype.getComputedStyleValue;k.prototype.setElementClass=k.prototype.Sa;k.prototype._styleInfoForNode=k.prototype.Qa;k.prototype.transformCustomStyleForDocument=k.prototype.v;k.prototype.getStyleAst=k.prototype.Ra;k.prototype.styleAstToString=k.prototype.Ta;k.prototype.flushCustomStyles=k.prototype.f;Object.defineProperties(k.prototype,{nativeShadow:{get:function(){return q}},nativeCss:{get:function(){return v}}});
var J=new k;if(window.ShadyCSS){var Cd=window.ShadyCSS.ApplyShim;var Dd=window.ShadyCSS.CustomStyleInterface}window.ShadyCSS={ScopingShim:J,prepareTemplate:function(a,b,c){J.f();J.prepareTemplate(a,b,c)},styleSubtree:function(a,b){J.f();J.styleSubtree(a,b)},styleElement:function(a){J.f();J.styleElement(a)},styleDocument:function(a){J.f();J.styleDocument(a)},getComputedStyleValue:function(a,b){return J.getComputedStyleValue(a,b)},nativeCss:v,nativeShadow:q};Cd&&(window.ShadyCSS.ApplyShim=Cd);Dd&&(window.ShadyCSS.CustomStyleInterface=
Dd);var Ib=window.customElements,Ia=window.HTMLImports;window.WebComponents=window.WebComponents||{};if(Ib&&Ib.polyfillWrapFlushCallback){var Ja,Ed=function(){if(Ja){var a=Ja;Ja=null;a();return!0}},Fd=Ia.whenReady;Ib.polyfillWrapFlushCallback(function(a){Ja=a;Fd(Ed)});Ia.whenReady=function(a){Fd(function(){Ed()?Ia.whenReady(a):a()})}}Ia.whenReady(function(){requestAnimationFrame(function(){window.WebComponents.ready=!0;document.dispatchEvent(new CustomEvent("WebComponentsReady",{bubbles:!0}))})});
var Gd=document.createElement("style");Gd.textContent="body {transition: opacity ease-in 0.2s; } \nbody[unresolved] {opacity: 0; display: block; overflow: hidden; position: relative; } \n";var Hd=document.querySelector("head");Hd.insertBefore(Gd,Hd.firstChild)})();}).call(this);
'use strict';var N="undefined"!=typeof window&&window===this?this:"undefined"!=typeof global&&null!=global?global:this,Oa="function"==typeof Object.defineProperties?Object.defineProperty:function(g,t,R){g!=Array.prototype&&g!=Object.prototype&&(g[t]=R.value)};function Pb(){Pb=function(){};N.Symbol||(N.Symbol=Qb)}var Qb=function(){var g=0;return function(t){return"jscomp_symbol_"+(t||"")+g++}}();
function Od(){Pb();var g=N.Symbol.iterator;g||(g=N.Symbol.iterator=N.Symbol("iterator"));"function"!=typeof Array.prototype[g]&&Oa(Array.prototype,g,{configurable:!0,writable:!0,value:function(){return Pd(this)}});Od=function(){}}function Pd(g){var t=0;return Qd(function(){return t<g.length?{done:!1,value:g[t++]}:{done:!0}})}function Qd(g){Od();g={next:g};g[N.Symbol.iterator]=function(){return this};return g}function Rd(g){Od();var t=g[Symbol.iterator];return t?t.call(g):Pd(g)}
function Sd(g){for(var t,R=[];!(t=g.next()).done;)R.push(t.value);return R}
(function(){function g(){var a=this;this.s={};this.g=document.documentElement;var b=new Pa;b.rules=[];this.h=w.set(this.g,new w(b));this.i=!1;this.b=this.a=null;Rb(function(){a.c()})}function t(){this.customStyles=[];this.enqueued=!1}function R(){}function ta(a){this.cache={};this.c=void 0===a?100:a}function p(){}function w(a,b,c,d,e){this.G=a||null;this.b=b||null;this.ua=c||[];this.S=null;this.$=e||"";this.a=this.C=this.M=null}function v(){}function Pa(){this.end=this.start=0;this.rules=this.parent=
this.previous=null;this.cssText=this.parsedCssText="";this.atRule=!1;this.type=0;this.parsedSelector=this.selector=this.keyframesName=""}function Td(a){function b(b,c){Object.defineProperty(b,"innerHTML",{enumerable:c.enumerable,configurable:!0,get:c.get,set:function(b){var d=this,e=void 0;u(this)&&(e=[],S(this,function(a){a!==d&&e.push(a)}));c.set.call(this,b);if(e)for(var f=0;f<e.length;f++){var h=e[f];1===h.__CE_state&&a.disconnectedCallback(h)}this.ownerDocument.__CE_hasRegistry?a.f(this):a.l(this);
return b}})}function c(b,c){x(b,"insertAdjacentElement",function(b,d){var e=u(d);b=c.call(this,b,d);e&&a.a(d);u(b)&&a.b(d);return b})}Sb&&x(Element.prototype,"attachShadow",function(a){return this.__CE_shadowRoot=a=Sb.call(this,a)});if(Qa&&Qa.get)b(Element.prototype,Qa);else if(Ra&&Ra.get)b(HTMLElement.prototype,Ra);else{var d=Sa.call(document,"div");a.v(function(a){b(a,{enumerable:!0,configurable:!0,get:function(){return Tb.call(this,!0).innerHTML},set:function(a){var b="template"===this.localName?
this.content:this;for(d.innerHTML=a;0<b.childNodes.length;)Ta.call(b,b.childNodes[0]);for(;0<d.childNodes.length;)ua.call(b,d.childNodes[0])}})})}x(Element.prototype,"setAttribute",function(b,c){if(1!==this.__CE_state)return Ub.call(this,b,c);var d=Ua.call(this,b);Ub.call(this,b,c);c=Ua.call(this,b);a.attributeChangedCallback(this,b,d,c,null)});x(Element.prototype,"setAttributeNS",function(b,c,d){if(1!==this.__CE_state)return Vb.call(this,b,c,d);var e=va.call(this,b,c);Vb.call(this,b,c,d);d=va.call(this,
b,c);a.attributeChangedCallback(this,c,e,d,b)});x(Element.prototype,"removeAttribute",function(b){if(1!==this.__CE_state)return Wb.call(this,b);var c=Ua.call(this,b);Wb.call(this,b);null!==c&&a.attributeChangedCallback(this,b,c,null,null)});x(Element.prototype,"removeAttributeNS",function(b,c){if(1!==this.__CE_state)return Xb.call(this,b,c);var d=va.call(this,b,c);Xb.call(this,b,c);var e=va.call(this,b,c);d!==e&&a.attributeChangedCallback(this,c,d,e,b)});Yb?c(HTMLElement.prototype,Yb):Zb?c(Element.prototype,
Zb):console.warn("Custom Elements: `Element#insertAdjacentElement` was not patched.");Va(a,Element.prototype,{ea:Ud,append:Vd});Wd(a,{ra:Xd,jb:Yd,replaceWith:Zd,remove:$d})}function Wd(a,b){var c=Element.prototype;function d(b){return function(c){for(var d=[],e=0;e<arguments.length;++e)d[e-0]=arguments[e];e=[];for(var f=[],q=0;q<d.length;q++){var g=d[q];g instanceof Element&&u(g)&&f.push(g);if(g instanceof DocumentFragment)for(g=g.firstChild;g;g=g.nextSibling)e.push(g);else e.push(g)}b.apply(this,
d);for(d=0;d<f.length;d++)a.a(f[d]);if(u(this))for(d=0;d<e.length;d++)f=e[d],f instanceof Element&&a.b(f)}}void 0!==b.ra&&(c.before=d(b.ra));void 0!==b.ra&&(c.after=d(b.jb));void 0!==b.replaceWith&&x(c,"replaceWith",function(c){for(var d=[],e=0;e<arguments.length;++e)d[e-0]=arguments[e];e=[];for(var k=[],m=0;m<d.length;m++){var g=d[m];g instanceof Element&&u(g)&&k.push(g);if(g instanceof DocumentFragment)for(g=g.firstChild;g;g=g.nextSibling)e.push(g);else e.push(g)}m=u(this);b.replaceWith.apply(this,
d);for(d=0;d<k.length;d++)a.a(k[d]);if(m)for(a.a(this),d=0;d<e.length;d++)k=e[d],k instanceof Element&&a.b(k)});void 0!==b.remove&&x(c,"remove",function(){var c=u(this);b.remove.call(this);c&&a.a(this)})}function ae(a){function b(b,d){Object.defineProperty(b,"textContent",{enumerable:d.enumerable,configurable:!0,get:d.get,set:function(b){if(this.nodeType===Node.TEXT_NODE)d.set.call(this,b);else{var c=void 0;if(this.firstChild){var e=this.childNodes,k=e.length;if(0<k&&u(this)){c=Array(k);for(var m=
0;m<k;m++)c[m]=e[m]}}d.set.call(this,b);if(c)for(b=0;b<c.length;b++)a.a(c[b])}}})}x(Node.prototype,"insertBefore",function(b,d){if(b instanceof DocumentFragment){var c=Array.prototype.slice.apply(b.childNodes);b=$b.call(this,b,d);if(u(this))for(d=0;d<c.length;d++)a.b(c[d]);return b}c=u(b);d=$b.call(this,b,d);c&&a.a(b);u(this)&&a.b(b);return d});x(Node.prototype,"appendChild",function(b){if(b instanceof DocumentFragment){var c=Array.prototype.slice.apply(b.childNodes);b=ua.call(this,b);if(u(this))for(var e=
0;e<c.length;e++)a.b(c[e]);return b}c=u(b);e=ua.call(this,b);c&&a.a(b);u(this)&&a.b(b);return e});x(Node.prototype,"cloneNode",function(b){b=Tb.call(this,b);this.ownerDocument.__CE_hasRegistry?a.f(b):a.l(b);return b});x(Node.prototype,"removeChild",function(b){var c=u(b),e=Ta.call(this,b);c&&a.a(b);return e});x(Node.prototype,"replaceChild",function(b,d){if(b instanceof DocumentFragment){var c=Array.prototype.slice.apply(b.childNodes);b=ac.call(this,b,d);if(u(this))for(a.a(d),d=0;d<c.length;d++)a.b(c[d]);
return b}c=u(b);var f=ac.call(this,b,d),h=u(this);h&&a.a(d);c&&a.a(b);h&&a.b(b);return f});Wa&&Wa.get?b(Node.prototype,Wa):a.v(function(a){b(a,{enumerable:!0,configurable:!0,get:function(){for(var a=[],b=0;b<this.childNodes.length;b++)a.push(this.childNodes[b].textContent);return a.join("")},set:function(a){for(;this.firstChild;)Ta.call(this,this.firstChild);ua.call(this,document.createTextNode(a))}})})}function be(a){x(Document.prototype,"createElement",function(b){if(this.__CE_hasRegistry){var c=
a.c(b);if(c)return new c.constructor}b=Sa.call(this,b);a.g(b);return b});x(Document.prototype,"importNode",function(b,c){b=ce.call(this,b,c);this.__CE_hasRegistry?a.f(b):a.l(b);return b});x(Document.prototype,"createElementNS",function(b,c){if(this.__CE_hasRegistry&&(null===b||"http://www.w3.org/1999/xhtml"===b)){var d=a.c(c);if(d)return new d.constructor}b=de.call(this,b,c);a.g(b);return b});Va(a,Document.prototype,{ea:ee,append:fe})}function Va(a,b,c){function d(b){return function(c){for(var d=
[],e=0;e<arguments.length;++e)d[e-0]=arguments[e];e=[];for(var f=[],g=0;g<d.length;g++){var n=d[g];n instanceof Element&&u(n)&&f.push(n);if(n instanceof DocumentFragment)for(n=n.firstChild;n;n=n.nextSibling)e.push(n);else e.push(n)}b.apply(this,d);for(d=0;d<f.length;d++)a.a(f[d]);if(u(this))for(d=0;d<e.length;d++)f=e[d],f instanceof Element&&a.b(f)}}void 0!==c.ea&&(b.prepend=d(c.ea));void 0!==c.append&&(b.append=d(c.append))}function ge(a){window.HTMLElement=function(){function b(){var b=this.constructor,
d=a.B(b);if(!d)throw Error("The custom element being constructed was not registered with `customElements`.");var e=d.constructionStack;if(0===e.length)return e=Sa.call(document,d.localName),Object.setPrototypeOf(e,b.prototype),e.__CE_state=1,e.__CE_definition=d,a.g(e),e;d=e.length-1;var f=e[d];if(f===bc)throw Error("The HTMLElement constructor was either called reentrantly for this constructor or called multiple times.");e[d]=bc;Object.setPrototypeOf(f,b.prototype);a.g(f);return f}b.prototype=he.prototype;
return b}()}function y(a){this.c=!1;this.a=a;this.h=new Map;this.f=function(a){return a()};this.b=!1;this.g=[];this.i=new Xa(a,document)}function cc(){var a=this;this.b=this.a=void 0;this.c=new Promise(function(b){a.b=b;a.a&&b(a.a)})}function Xa(a,b){this.b=a;this.a=b;this.P=void 0;this.b.f(this.a);"loading"===this.a.readyState&&(this.P=new MutationObserver(this.f.bind(this)),this.P.observe(this.a,{childList:!0,subtree:!0}))}function B(){this.u=new Map;this.s=new Map;this.j=[];this.h=!1}function l(a,
b,c){if(a!==dc)throw new TypeError("Illegal constructor");a=document.createDocumentFragment();a.__proto__=l.prototype;a.H(b,c);return a}function wa(a){if(!a.__shady||void 0===a.__shady.firstChild){a.__shady=a.__shady||{};a.__shady.firstChild=Ya(a);a.__shady.lastChild=Za(a);ec(a);for(var b=a.__shady.childNodes=X(a),c=0,d;c<b.length&&(d=b[c]);c++)d.__shady=d.__shady||{},d.__shady.parentNode=a,d.__shady.nextSibling=b[c+1]||null,d.__shady.previousSibling=b[c-1]||null,fc(d)}}function ie(a){var b=a&&a.P;
b&&(b.ca.delete(a.ab),b.ca.size||(a.fb.__shady.Y=null))}function je(a,b){a.__shady=a.__shady||{};a.__shady.Y||(a.__shady.Y=new xa);a.__shady.Y.ca.add(b);var c=a.__shady.Y;return{ab:b,P:c,fb:a,takeRecords:function(){return c.takeRecords()}}}function xa(){this.a=!1;this.addedNodes=[];this.removedNodes=[];this.ca=new Set}function T(a,b){Y[Z]=a;Y[Z+1]=b;Z+=2;2===Z&&($a?$a(aa):ke())}function le(){return function(){return process.Hb(aa)}}function me(){return"undefined"!==typeof ab?function(){ab(aa)}:bb()}
function ne(){var a=0,b=new gc(aa),c=document.createTextNode("");b.observe(c,{characterData:!0});return function(){c.data=a=++a%2}}function oe(){var a=new MessageChannel;a.port1.onmessage=aa;return function(){return a.port2.postMessage(0)}}function bb(){var a=setTimeout;return function(){return a(aa,1)}}function aa(){for(var a=0;a<Z;a+=2)(0,Y[a])(Y[a+1]),Y[a]=void 0,Y[a+1]=void 0;Z=0}function pe(){try{var a=require("vertx");ab=a.Jb||a.Ib;return me()}catch(b){return bb()}}function cb(a,b){var c=this,
d=new this.constructor(ba);void 0===d[ya]&&hc(d);var e=c.o;if(e){var f=arguments[e-1];T(function(){return ic(e,d,f,c.m)})}else db(c,d,a,b);return d}function eb(a){if(a&&"object"===typeof a&&a.constructor===this)return a;var b=new this(ba);ja(b,a);return b}function ba(){}function jc(a){try{return a.then}catch(b){return ka.error=b,ka}}function qe(a,b,c,d){try{a.call(b,c,d)}catch(e){return e}}function re(a,b,c){T(function(a){var d=!1,f=qe(c,b,function(c){d||(d=!0,b!==c?ja(a,c):L(a,c))},function(b){d||
(d=!0,C(a,b))});!d&&f&&(d=!0,C(a,f))},a)}function se(a,b){1===b.o?L(a,b.m):2===b.o?C(a,b.m):db(b,void 0,function(b){return ja(a,b)},function(b){return C(a,b)})}function kc(a,b,c){b.constructor===a.constructor&&c===cb&&b.constructor.resolve===eb?se(a,b):c===ka?(C(a,ka.error),ka.error=null):void 0===c?L(a,b):"function"===typeof c?re(a,b,c):L(a,b)}function ja(a,b){if(a===b)C(a,new TypeError("You cannot resolve a promise with itself"));else{var c=typeof b;null===b||"object"!==c&&"function"!==c?L(a,b):
kc(a,b,jc(b))}}function te(a){a.Da&&a.Da(a.m);fb(a)}function L(a,b){void 0===a.o&&(a.m=b,a.o=1,0!==a.V.length&&T(fb,a))}function C(a,b){void 0===a.o&&(a.o=2,a.m=b,T(te,a))}function db(a,b,c,d){var e=a.V,f=e.length;a.Da=null;e[f]=b;e[f+1]=c;e[f+2]=d;0===f&&a.o&&T(fb,a)}function fb(a){var b=a.V,c=a.o;if(0!==b.length){for(var d,e,f=a.m,h=0;h<b.length;h+=3)d=b[h],e=b[h+c],d?ic(c,d,e,f):e(f);a.V.length=0}}function lc(){this.error=null}function ic(a,b,c,d){var e="function"===typeof c;if(e){try{var f=c(d)}catch(q){gb.error=
q,f=gb}if(f===gb){var h=!0;var k=f.error;f.error=null}else var m=!0;if(b===f){C(b,new TypeError("A promises callback cannot return that same promise."));return}}else f=d,m=!0;void 0===b.o&&(e&&m?ja(b,f):h?C(b,k):1===a?L(b,f):2===a&&C(b,f))}function ue(a,b){try{b(function(b){ja(a,b)},function(b){C(a,b)})}catch(c){C(a,c)}}function hc(a){a[ya]=mc++;a.o=void 0;a.m=void 0;a.V=[]}function la(a,b){this.eb=a;this.L=new a(ba);this.L[ya]||hc(this.L);nc(b)?(this.aa=this.length=b.length,this.m=Array(this.length),
0===this.length?L(this.L,this.m):(this.length=this.length||0,this.cb(b),0===this.aa&&L(this.L,this.m))):C(this.L,Error("Array Methods must be provided an Array"))}function D(a){this[ya]=mc++;this.m=this.o=void 0;this.V=[];if(ba!==a){if("function"!==typeof a)throw new TypeError("You must pass a resolver function as the first argument to the promise constructor");if(this instanceof D)ue(this,a);else throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");
}}function ca(a){return a.__shady&&void 0!==a.__shady.firstChild}function J(a){return"ShadyRoot"===a.Wa}function ma(a){a=a.getRootNode();if(J(a))return a}function hb(a,b){if(a&&b)for(var c=Object.getOwnPropertyNames(b),d=0,e;d<c.length&&(e=c[d]);d++){var f=Object.getOwnPropertyDescriptor(b,e);f&&Object.defineProperty(a,e,f)}}function ib(a,b){for(var c=[],d=1;d<arguments.length;++d)c[d-1]=arguments[d];for(d=0;d<c.length;d++)hb(a,c[d]);return a}function ve(a,b){for(var c in b)a[c]=b[c]}function oc(a){jb.push(a);
kb.textContent=pc++}function qc(a,b){for(;b;){if(b==a)return!0;b=b.parentNode}return!1}function rc(a){lb||(lb=!0,oc(za));na.push(a)}function za(){lb=!1;for(var a=!!na.length;na.length;)na.shift()();return a}function we(a,b){var c=b.getRootNode();return a.map(function(a){var b=c===a.target.getRootNode();if(b&&a.addedNodes){if(b=Array.from(a.addedNodes).filter(function(a){return c===a.getRootNode()}),b.length)return a=Object.create(a),Object.defineProperty(a,"addedNodes",{value:b,configurable:!0}),
a}else if(b)return a}).filter(function(a){return a})}function sc(a){switch(a){case "&":return"&amp;";case "<":return"&lt;";case ">":return"&gt;";case '"':return"&quot;";case "\u00a0":return"&nbsp;"}}function tc(a){for(var b={},c=0;c<a.length;c++)b[a[c]]=!0;return b}function mb(a,b){"template"===a.localName&&(a=a.content);for(var c="",d=b?b(a):a.childNodes,e=0,f=d.length,h;e<f&&(h=d[e]);e++){a:{var k=h;var m=a;var g=b;switch(k.nodeType){case Node.ELEMENT_NODE:for(var n=k.localName,l="<"+n,t=k.attributes,
p=0;m=t[p];p++)l+=" "+m.name+'="'+m.value.replace(xe,sc)+'"';l+=">";k=ye[n]?l:l+mb(k,g)+"</"+n+">";break a;case Node.TEXT_NODE:k=k.data;k=m&&ze[m.localName]?k:k.replace(Ae,sc);break a;case Node.COMMENT_NODE:k="\x3c!--"+k.data+"--\x3e";break a;default:throw window.console.error(k),Error("not implemented");}}c+=k}return c}function da(a){E.currentNode=a;return E.parentNode()}function Ya(a){E.currentNode=a;return E.firstChild()}function Za(a){E.currentNode=a;return E.lastChild()}function uc(a){E.currentNode=
a;return E.previousSibling()}function vc(a){E.currentNode=a;return E.nextSibling()}function X(a){var b=[];E.currentNode=a;for(a=E.firstChild();a;)b.push(a),a=E.nextSibling();return b}function wc(a){F.currentNode=a;return F.parentNode()}function xc(a){F.currentNode=a;return F.firstChild()}function yc(a){F.currentNode=a;return F.lastChild()}function zc(a){F.currentNode=a;return F.previousSibling()}function Ac(a){F.currentNode=a;return F.nextSibling()}function Bc(a){var b=[];F.currentNode=a;for(a=F.firstChild();a;)b.push(a),
a=F.nextSibling();return b}function Cc(a){return mb(a,function(a){return X(a)})}function Dc(a){switch(a.nodeType){case Node.ELEMENT_NODE:case Node.DOCUMENT_FRAGMENT_NODE:a=document.createTreeWalker(a,NodeFilter.SHOW_TEXT,null,!1);for(var b="",c;c=a.nextNode();)b+=c.nodeValue;return b;default:return a.nodeValue}}function O(a,b,c){for(var d in b){var e=Object.getOwnPropertyDescriptor(a,d);e&&e.configurable||!e&&c?Object.defineProperty(a,d,b[d]):c&&console.warn("Could not define",d,"on",a)}}function U(a){O(a,
Ec);O(a,nb);O(a,ob)}function Fc(a,b,c){fc(a);c=c||null;a.__shady=a.__shady||{};b.__shady=b.__shady||{};c&&(c.__shady=c.__shady||{});a.__shady.previousSibling=c?c.__shady.previousSibling:b.lastChild;var d=a.__shady.previousSibling;d&&d.__shady&&(d.__shady.nextSibling=a);(d=a.__shady.nextSibling=c)&&d.__shady&&(d.__shady.previousSibling=a);a.__shady.parentNode=b;c?c===b.__shady.firstChild&&(b.__shady.firstChild=a):(b.__shady.lastChild=a,b.__shady.firstChild||(b.__shady.firstChild=a));b.__shady.childNodes=
null}function pb(a,b,c){if(b===a)throw Error("Failed to execute 'appendChild' on 'Node': The new child element contains the parent.");if(c){var d=c.__shady&&c.__shady.parentNode;if(void 0!==d&&d!==a||void 0===d&&da(c)!==a)throw Error("Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node.");}if(c===b)return b;b.parentNode&&qb(b.parentNode,b);d=ma(a);var e;if(e=d)a:{if(!b.__noInsertionPoint){var f;"slot"===b.localName?f=[b]:b.querySelectorAll&&
(f=b.querySelectorAll("slot"));if(f&&f.length){e=f;break a}}e=void 0}(f=e)&&d.$a(f);d&&("slot"===a.localName||f)&&d.O();if(ca(a)){d=c;ec(a);a.__shady=a.__shady||{};void 0!==a.__shady.firstChild&&(a.__shady.childNodes=null);if(b.nodeType===Node.DOCUMENT_FRAGMENT_NODE){f=b.childNodes;for(e=0;e<f.length;e++)Fc(f[e],a,d);b.__shady=b.__shady||{};d=void 0!==b.__shady.firstChild?null:void 0;b.__shady.firstChild=b.__shady.lastChild=d;b.__shady.childNodes=d}else Fc(b,a,d);if(rb(a)){a.__shady.root.O();var h=
!0}else a.__shady.root&&(h=!0)}h||(h=J(a)?a.host:a,c?(c=Gc(c),sb.call(h,b,c)):Hc.call(h,b));Ic(a,b);return b}function qb(a,b){if(b.parentNode!==a)throw Error("The node to be removed is not a child of this node: "+b);var c=ma(b);if(ca(a)){b.__shady=b.__shady||{};a.__shady=a.__shady||{};b===a.__shady.firstChild&&(a.__shady.firstChild=b.__shady.nextSibling);b===a.__shady.lastChild&&(a.__shady.lastChild=b.__shady.previousSibling);var d=b.__shady.previousSibling;var e=b.__shady.nextSibling;d&&(d.__shady=
d.__shady||{},d.__shady.nextSibling=e);e&&(e.__shady=e.__shady||{},e.__shady.previousSibling=d);b.__shady.parentNode=b.__shady.previousSibling=b.__shady.nextSibling=void 0;void 0!==a.__shady.childNodes&&(a.__shady.childNodes=null);if(rb(a)){a.__shady.root.O();var f=!0}}Jc(b);c&&((e=a&&"slot"===a.localName)&&(f=!0),((d=c.gb(b))||e)&&c.O());f||(f=J(a)?a.host:a,(!a.__shady.root&&"slot"!==b.localName||f===da(b))&&oa.call(f,b));Ic(a,null,b);return b}function Jc(a){if(a.__shady&&void 0!==a.__shady.va)for(var b=
a.childNodes,c=0,d=b.length,e;c<d&&(e=b[c]);c++)Jc(e);a.__shady&&(a.__shady.va=void 0)}function Gc(a){var b=a;a&&"slot"===a.localName&&(b=(b=a.__shady&&a.__shady.W)&&b.length?b[0]:Gc(a.nextSibling));return b}function rb(a){return(a=a&&a.__shady&&a.__shady.root)&&a.Ca()}function Kc(a,b){"slot"===b?(a=a.parentNode,rb(a)&&a.__shady.root.O()):"slot"===a.localName&&"name"===b&&(b=ma(a))&&(b.ib(a),b.O())}function Ic(a,b,c){if(a=a.__shady&&a.__shady.Y)b&&a.addedNodes.push(b),c&&a.removedNodes.push(c),a.ub()}
function Lc(a){if(a&&a.nodeType){a.__shady=a.__shady||{};var b=a.__shady.va;void 0===b&&(J(a)?b=a:b=(b=a.parentNode)?Lc(b):a,pa.call(document.documentElement,a)&&(a.__shady.va=b));return b}}function Aa(a,b,c){var d=[];Mc(a.childNodes,b,c,d);return d}function Mc(a,b,c,d){for(var e=0,f=a.length,h;e<f&&(h=a[e]);e++){var k;if(k=h.nodeType===Node.ELEMENT_NODE){k=h;var m=b,g=c,n=d,l=m(k);l&&n.push(k);g&&g(l)?k=l:(Mc(k.childNodes,m,g,n),k=void 0)}if(k)break}}function Nc(a){a=a.getRootNode();J(a)&&a.Fa()}
function Oc(a,b,c){Ba||(Ba=window.ShadyCSS&&window.ShadyCSS.ScopingShim);Ba&&"class"===b?Ba.setElementClass(a,c):(Pc.call(a,b,c),Kc(a,b))}function Qc(a,b){if(a.ownerDocument!==document)return tb.call(document,a,b);var c=tb.call(document,a,!1);if(b){a=a.childNodes;b=0;for(var d;b<a.length;b++)d=Qc(a[b],!0),c.appendChild(d)}return c}function ub(a,b){var c=[],d=a;for(a=a===window?window:a.getRootNode();d;)c.push(d),d=d.assignedSlot?d.assignedSlot:d.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&d.host&&(b||
d!==a)?d.host:d.parentNode;c[c.length-1]===document&&c.push(window);return c}function Rc(a,b){if(!J)return a;a=ub(a,!0);for(var c=0,d,e,f,h;c<b.length;c++)if(d=b[c],f=d===window?window:d.getRootNode(),f!==e&&(h=a.indexOf(f),e=f),!J(f)||-1<h)return d}function vb(a){function b(b,d){b=new a(b,d);b.ka=d&&!!d.composed;return b}ve(b,a);b.prototype=a.prototype;return b}function Sc(a,b,c){if(c=b.__handlers&&b.__handlers[a.type]&&b.__handlers[a.type][c])for(var d=0,e;(e=c[d])&&a.target!==a.relatedTarget&&
(e.call(b,a),!a.Ua);d++);}function Be(a){var b=a.composedPath();Object.defineProperty(a,"currentTarget",{get:function(){return d},configurable:!0});for(var c=b.length-1;0<=c;c--){var d=b[c];Sc(a,d,"capture");if(a.la)return}Object.defineProperty(a,"eventPhase",{get:function(){return Event.AT_TARGET}});var e;for(c=0;c<b.length;c++){d=b[c];var f=d.__shady&&d.__shady.root;if(0===c||f&&f===e)if(Sc(a,d,"bubble"),d!==window&&(e=d.getRootNode()),a.la)break}}function Tc(a,b,c,d,e,f){for(var h=0;h<a.length;h++){var k=
a[h],m=k.type,g=k.capture,n=k.once,l=k.passive;if(b===k.node&&c===m&&d===g&&e===n&&f===l)return h}return-1}function Uc(a,b,c){if(b){if("object"===typeof c){var d=!!c.capture;var e=!!c.once;var f=!!c.passive}else d=!!c,f=e=!1;var h=c&&c.ma||this,k=b[qa];if(k){if(-1<Tc(k,h,a,d,e,f))return}else b[qa]=[];k=function(d){e&&this.removeEventListener(a,b,c);d.__target||Vc(d);if(h!==this){var f=Object.getOwnPropertyDescriptor(d,"currentTarget");Object.defineProperty(d,"currentTarget",{get:function(){return h},
configurable:!0})}if(d.composed||-1<d.composedPath().indexOf(h))if(d.target===d.relatedTarget)d.eventPhase===Event.BUBBLING_PHASE&&d.stopImmediatePropagation();else if(d.eventPhase===Event.CAPTURING_PHASE||d.bubbles||d.target===h){var k="object"===typeof b&&b.handleEvent?b.handleEvent(d):b.call(h,d);h!==this&&(f?(Object.defineProperty(d,"currentTarget",f),f=null):delete d.currentTarget);return k}};b[qa].push({node:this,type:a,capture:d,once:e,passive:f,yb:k});wb[a]?(this.__handlers=this.__handlers||
{},this.__handlers[a]=this.__handlers[a]||{capture:[],bubble:[]},this.__handlers[a][d?"capture":"bubble"].push(k)):(this instanceof Window?Wc:Xc).call(this,a,k,c)}}function Yc(a,b,c){if(b){if("object"===typeof c){var d=!!c.capture;var e=!!c.once;var f=!!c.passive}else d=!!c,f=e=!1;var h=c&&c.ma||this,k=void 0;var g=null;try{g=b[qa]}catch(q){}g&&(e=Tc(g,h,a,d,e,f),-1<e&&(k=g.splice(e,1)[0].yb,g.length||(b[qa]=void 0)));(this instanceof Window?Zc:$c).call(this,a,k||b,c);k&&wb[a]&&this.__handlers&&this.__handlers[a]&&
(a=this.__handlers[a][d?"capture":"bubble"],k=a.indexOf(k),-1<k&&a.splice(k,1))}}function Ce(){for(var a in wb)window.addEventListener(a,function(a){a.__target||(Vc(a),Be(a))},!0)}function Vc(a){a.__target=a.target;a.Aa=a.relatedTarget;if(G.X){var b=ad,c=Object.getPrototypeOf(a);if(!c.hasOwnProperty("__patchProto")){var d=Object.create(c);d.Ab=c;hb(d,b);c.__patchProto=d}a.__proto__=c.__patchProto}else hb(a,ad)}function ra(a,b){return{index:a,Z:[],ba:b}}function De(a,b,c,d){var e=0,f=0,h=0,k=0,g=Math.min(b-
e,d-f);if(0==e&&0==f)a:{for(h=0;h<g;h++)if(a[h]!==c[h])break a;h=g}if(b==a.length&&d==c.length){k=a.length;for(var q=c.length,n=0;n<g-h&&Ee(a[--k],c[--q]);)n++;k=n}e+=h;f+=h;b-=k;d-=k;if(0==b-e&&0==d-f)return[];if(e==b){for(b=ra(e,0);f<d;)b.Z.push(c[f++]);return[b]}if(f==d)return[ra(e,b-e)];g=e;h=f;d=d-h+1;k=b-g+1;b=Array(d);for(q=0;q<d;q++)b[q]=Array(k),b[q][0]=q;for(q=0;q<k;q++)b[0][q]=q;for(q=1;q<d;q++)for(n=1;n<k;n++)if(a[g+n-1]===c[h+q-1])b[q][n]=b[q-1][n-1];else{var l=b[q-1][n]+1,p=b[q][n-1]+
1;b[q][n]=l<p?l:p}g=b.length-1;h=b[0].length-1;d=b[g][h];for(a=[];0<g||0<h;)0==g?(a.push(2),h--):0==h?(a.push(3),g--):(k=b[g-1][h-1],q=b[g-1][h],n=b[g][h-1],l=q<n?q<k?q:k:n<k?n:k,l==k?(k==d?a.push(0):(a.push(1),d=k),g--,h--):l==q?(a.push(3),g--,d=q):(a.push(2),h--,d=n));a.reverse();b=void 0;g=[];for(h=0;h<a.length;h++)switch(a[h]){case 0:b&&(g.push(b),b=void 0);e++;f++;break;case 1:b||(b=ra(e,0));b.ba++;e++;b.Z.push(c[f]);f++;break;case 2:b||(b=ra(e,0));b.ba++;e++;break;case 3:b||(b=ra(e,0)),b.Z.push(c[f]),
f++}b&&g.push(b);return g}function Ee(a,b){return a===b}function bd(a){var b=[];do b.unshift(a);while(a=a.parentNode);return b}function cd(a){Nc(a);return a.__shady&&a.__shady.assignedSlot||null}function P(a,b){for(var c=Object.getOwnPropertyNames(b),d=0;d<c.length;d++){var e=c[d],f=Object.getOwnPropertyDescriptor(b,e);f.value?a[e]=f.value:Object.defineProperty(a,e,f)}}function Fe(){var a=window.customElements&&window.customElements.nativeHTMLElement||HTMLElement;P(window.Node.prototype,Ge);P(window.Window.prototype,
He);P(window.Text.prototype,Ie);P(window.DocumentFragment.prototype,xb);P(window.Element.prototype,dd);P(window.Document.prototype,ed);window.HTMLSlotElement&&P(window.HTMLSlotElement.prototype,fd);P(a.prototype,Je);G.X&&(U(window.Node.prototype),U(window.Text.prototype),U(window.DocumentFragment.prototype),U(window.Element.prototype),U(a.prototype),U(window.Document.prototype),window.HTMLSlotElement&&U(window.HTMLSlotElement.prototype))}function gd(a){var b=Ke.has(a);a=/^[a-z][.0-9_a-z]*-[\-.0-9_a-z]*$/.test(a);
return!b&&a}function u(a){var b=a.isConnected;if(void 0!==b)return b;for(;a&&!(a.__CE_isImportDocument||a instanceof Document);)a=a.parentNode||(window.ShadowRoot&&a instanceof ShadowRoot?a.host:void 0);return!(!a||!(a.__CE_isImportDocument||a instanceof Document))}function yb(a,b){for(;b&&b!==a&&!b.nextSibling;)b=b.parentNode;return b&&b!==a?b.nextSibling:null}function S(a,b,c){c=c?c:new Set;for(var d=a;d;){if(d.nodeType===Node.ELEMENT_NODE){var e=d;b(e);var f=e.localName;if("link"===f&&"import"===
e.getAttribute("rel")){d=e.import;if(d instanceof Node&&!c.has(d))for(c.add(d),d=d.firstChild;d;d=d.nextSibling)S(d,b,c);d=yb(a,e);continue}else if("template"===f){d=yb(a,e);continue}if(e=e.__CE_shadowRoot)for(e=e.firstChild;e;e=e.nextSibling)S(e,b,c)}d=d.firstChild?d.firstChild:yb(a,d)}}function x(a,b,c){a[b]=c}function zb(a){a=a.replace(I.lb,"").replace(I.port,"");var b=hd,c=a,d=new Pa;d.start=0;d.end=c.length;for(var e=d,f=0,h=c.length;f<h;f++)if("{"===c[f]){e.rules||(e.rules=[]);var k=e,g=k.rules[k.rules.length-
1]||null;e=new Pa;e.start=f+1;e.parent=k;e.previous=g;k.rules.push(e)}else"}"===c[f]&&(e.end=f+1,e=e.parent||d);return b(d,a)}function hd(a,b){var c=b.substring(a.start,a.end-1);a.parsedCssText=a.cssText=c.trim();a.parent&&(c=b.substring(a.previous?a.previous.end:a.parent.start,a.start-1),c=Le(c),c=c.replace(I.La," "),c=c.substring(c.lastIndexOf(";")+1),c=a.parsedSelector=a.selector=c.trim(),a.atRule=0===c.indexOf("@"),a.atRule?0===c.indexOf("@media")?a.type=M.MEDIA_RULE:c.match(I.qb)&&(a.type=M.ja,
a.keyframesName=a.selector.split(I.La).pop()):a.type=0===c.indexOf("--")?M.wa:M.STYLE_RULE);if(c=a.rules)for(var d=0,e=c.length,f;d<e&&(f=c[d]);d++)hd(f,b);return a}function Le(a){return a.replace(/\\([0-9a-f]{1,6})\s/gi,function(a,c){a=c;for(c=6-a.length;c--;)a="0"+a;return"\\"+a})}function id(a,b,c){c=void 0===c?"":c;var d="";if(a.cssText||a.rules){var e=a.rules,f;if(f=e)f=e[0],f=!(f&&f.selector&&0===f.selector.indexOf("--"));if(f){f=0;for(var h=e.length,k;f<h&&(k=e[f]);f++)d=id(k,b,d)}else b?b=
a.cssText:(b=a.cssText,b=b.replace(I.Ga,"").replace(I.Ka,""),b=b.replace(I.rb,"").replace(I.wb,"")),(d=b.trim())&&(d=" "+d+"\n")}d&&(a.selector&&(c+=a.selector+" {\n"),c+=d,a.selector&&(c+="}\n\n"));return c}function jd(a){A=a&&a.shimcssproperties?!1:z||!(navigator.userAgent.match(/AppleWebKit\/601|Edge\/15/)||!window.CSS||!CSS.supports||!CSS.supports("box-shadow","0 0 0 var(--foo)"))}function ea(a,b){if(!a)return"";"string"===typeof a&&(a=zb(a));b&&fa(a,b);return id(a,A)}function Ca(a){!a.__cssRules&&
a.textContent&&(a.__cssRules=zb(a.textContent));return a.__cssRules||null}function kd(a){return!!a.parent&&a.parent.type===M.ja}function fa(a,b,c,d){if(a){var e=!1,f=a.type;if(d&&f===M.MEDIA_RULE){var h=a.selector.match(Me);h&&(window.matchMedia(h[1]).matches||(e=!0))}f===M.STYLE_RULE?b(a):c&&f===M.ja?c(a):f===M.wa&&(e=!0);if((a=a.rules)&&!e){e=0;f=a.length;for(var k;e<f&&(k=a[e]);e++)fa(k,b,c,d)}}}function Ab(a,b,c,d){var e=document.createElement("style");b&&e.setAttribute("scope",b);e.textContent=
a;ld(e,c,d);return e}function ld(a,b,c){b=b||document.head;b.insertBefore(a,c&&c.nextSibling||b.firstChild);V?a.compareDocumentPosition(V)===Node.DOCUMENT_POSITION_PRECEDING&&(V=a):V=a}function md(a,b){var c=a.indexOf("var(");if(-1===c)return b(a,"","","");a:{var d=0;var e=c+3;for(var f=a.length;e<f;e++)if("("===a[e])d++;else if(")"===a[e]&&0===--d)break a;e=-1}d=a.substring(c+4,e);c=a.substring(0,c);a=md(a.substring(e+1),b);e=d.indexOf(",");return-1===e?b(c,d.trim(),"",a):b(c,d.substring(0,e).trim(),
d.substring(e+1).trim(),a)}function Da(a,b){z?a.setAttribute("class",b):window.ShadyDOM.nativeMethods.setAttribute.call(a,"class",b)}function W(a){var b=a.localName,c="";b?-1<b.indexOf("-")||(c=b,b=a.getAttribute&&a.getAttribute("is")||""):(b=a.is,c=a.extends);return{is:b,$:c}}function nd(a){for(var b=0;b<a.length;b++){var c=a[b];if(c.target!==document.documentElement&&c.target!==document.head)for(var d=0;d<c.addedNodes.length;d++){var e=c.addedNodes[d];if(e.nodeType===Node.ELEMENT_NODE){var f=e.getRootNode();
var h=e;var k=[];h.classList?k=Array.from(h.classList):h instanceof window.SVGElement&&h.hasAttribute("class")&&(k=h.getAttribute("class").split(/\s+/));h=k;k=h.indexOf(r.a);if((h=-1<k?h[k+1]:"")&&f===e.ownerDocument)r.b(e,h,!0);else if(f.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&(f=f.host))if(f=W(f).is,h===f)for(e=window.ShadyDOM.nativeMethods.querySelectorAll.call(e,":not(."+r.a+")"),f=0;f<e.length;f++)r.h(e[f],h);else h&&r.b(e,h,!0),r.b(e,f)}}}}function Ne(a){if(a=Ea[a])a._applyShimCurrentVersion=
a._applyShimCurrentVersion||0,a._applyShimValidatingVersion=a._applyShimValidatingVersion||0,a._applyShimNextVersion=(a._applyShimNextVersion||0)+1}function od(a){return a._applyShimCurrentVersion===a._applyShimNextVersion}function Oe(a){a._applyShimValidatingVersion=a._applyShimNextVersion;a.b||(a.b=!0,Pe.then(function(){a._applyShimCurrentVersion=a._applyShimNextVersion;a.b=!1}))}function Rb(a){requestAnimationFrame(function(){pd?pd(a):(Bb||(Bb=new Promise(function(a){Cb=a}),"complete"===document.readyState?
Cb():document.addEventListener("readystatechange",function(){"complete"===document.readyState&&Cb()})),Bb.then(function(){a&&a()}))})}(function(){if(!function(){var a=document.createEvent("Event");a.initEvent("foo",!0,!0);a.preventDefault();return a.defaultPrevented}()){var a=Event.prototype.preventDefault;Event.prototype.preventDefault=function(){this.cancelable&&(a.call(this),Object.defineProperty(this,"defaultPrevented",{get:function(){return!0},configurable:!0}))}}var b=/Trident/.test(navigator.userAgent);
if(!window.CustomEvent||b&&"function"!==typeof window.CustomEvent)window.CustomEvent=function(a,b){b=b||{};var c=document.createEvent("CustomEvent");c.initCustomEvent(a,!!b.bubbles,!!b.cancelable,b.detail);return c},window.CustomEvent.prototype=window.Event.prototype;if(!window.Event||b&&"function"!==typeof window.Event){var c=window.Event;window.Event=function(a,b){b=b||{};var c=document.createEvent("Event");c.initEvent(a,!!b.bubbles,!!b.cancelable);return c};if(c)for(var d in c)window.Event[d]=
c[d];window.Event.prototype=c.prototype}if(!window.MouseEvent||b&&"function"!==typeof window.MouseEvent){b=window.MouseEvent;window.MouseEvent=function(a,b){b=b||{};var c=document.createEvent("MouseEvent");c.initMouseEvent(a,!!b.bubbles,!!b.cancelable,b.view||window,b.detail,b.screenX,b.screenY,b.clientX,b.clientY,b.ctrlKey,b.altKey,b.shiftKey,b.metaKey,b.button,b.relatedTarget);return c};if(b)for(d in b)window.MouseEvent[d]=b[d];window.MouseEvent.prototype=b.prototype}Array.from||(Array.from=function(a){return[].slice.call(a)});
Object.assign||(Object.assign=function(a,b){for(var c=[].slice.call(arguments,1),d=0,e;d<c.length;d++)if(e=c[d])for(var f=a,g=e,l=Object.getOwnPropertyNames(g),p=0;p<l.length;p++)e=l[p],f[e]=g[e];return a})})(window.WebComponents);(function(){function a(){}var b="undefined"===typeof HTMLTemplateElement;/Trident/.test(navigator.userAgent)&&function(){var a=Document.prototype.importNode;Document.prototype.importNode=function(){var b=a.apply(this,arguments);if(b.nodeType===Node.DOCUMENT_FRAGMENT_NODE){var c=
this.createDocumentFragment();c.appendChild(b);return c}return b}}();var c=Node.prototype.cloneNode,d=Document.prototype.createElement,e=Document.prototype.importNode,f=function(){if(!b){var a=document.createElement("template"),c=document.createElement("template");c.content.appendChild(document.createElement("div"));a.content.appendChild(c);a=a.cloneNode(!0);return 0===a.content.childNodes.length||0===a.content.firstChild.content.childNodes.length||!(document.createDocumentFragment().cloneNode()instanceof
DocumentFragment)}}();if(b){var h=function(a){switch(a){case "&":return"&amp;";case "<":return"&lt;";case ">":return"&gt;";case "\u00a0":return"&nbsp;"}},k=function(b){Object.defineProperty(b,"innerHTML",{get:function(){for(var a="",b=this.content.firstChild;b;b=b.nextSibling)a+=b.outerHTML||b.data.replace(u,h);return a},set:function(b){g.body.innerHTML=b;for(a.b(g);this.content.firstChild;)this.content.removeChild(this.content.firstChild);for(;g.body.firstChild;)this.content.appendChild(g.body.firstChild)},
configurable:!0})},g=document.implementation.createHTMLDocument("template"),q=!0,l=document.createElement("style");l.textContent="template{display:none;}";var p=document.head;p.insertBefore(l,p.firstElementChild);a.prototype=Object.create(HTMLElement.prototype);var t=!document.createElement("div").hasOwnProperty("innerHTML");a.R=function(b){if(!b.content){b.content=g.createDocumentFragment();for(var c;c=b.firstChild;)b.content.appendChild(c);if(t)b.__proto__=a.prototype;else if(b.cloneNode=function(b){return a.a(this,
b)},q)try{k(b)}catch(rf){q=!1}a.b(b.content)}};k(a.prototype);a.b=function(b){b=b.querySelectorAll("template");for(var c=0,d=b.length,e;c<d&&(e=b[c]);c++)a.R(e)};document.addEventListener("DOMContentLoaded",function(){a.b(document)});Document.prototype.createElement=function(){var b=d.apply(this,arguments);"template"===b.localName&&a.R(b);return b};var u=/[&\u00A0<>]/g}if(b||f)a.a=function(a,b){var d=c.call(a,!1);this.R&&this.R(d);b&&(d.content.appendChild(c.call(a.content,!0)),this.ta(d.content,
a.content));return d},a.prototype.cloneNode=function(b){return a.a(this,b)},a.ta=function(a,b){if(b.querySelectorAll){b=b.querySelectorAll("template");a=a.querySelectorAll("template");for(var c=0,d=a.length,e,f;c<d;c++)f=b[c],e=a[c],this.R&&this.R(f),e.parentNode.replaceChild(f.cloneNode(!0),e)}},Node.prototype.cloneNode=function(b){if(this instanceof DocumentFragment)if(b)var d=this.ownerDocument.importNode(this,!0);else return this.ownerDocument.createDocumentFragment();else d=c.call(this,b);b&&
a.ta(d,this);return d},Document.prototype.importNode=function(b,c){if("template"===b.localName)return a.a(b,c);var d=e.call(this,b,c);c&&a.ta(d,b);return d},f&&(window.HTMLTemplateElement.prototype.cloneNode=function(b){return a.a(this,b)});b&&(window.HTMLTemplateElement=a)})();var Db;Array.isArray?Db=Array.isArray:Db=function(a){return"[object Array]"===Object.prototype.toString.call(a)};var nc=Db,Z=0,ab,$a,qd="undefined"!==typeof window?window:void 0,rd=qd||{},gc=rd.MutationObserver||rd.WebKitMutationObserver,
Qe="undefined"!==typeof Uint8ClampedArray&&"undefined"!==typeof importScripts&&"undefined"!==typeof MessageChannel,Y=Array(1E3);var ke="undefined"===typeof self&&"undefined"!==typeof process&&"[object process]"==={}.toString.call(process)?le():gc?ne():Qe?oe():qd||"function"!==typeof require?bb():pe();var ya=Math.random().toString(36).substring(16),ka=new lc,gb=new lc,mc=0;la.prototype.cb=function(a){for(var b=0;void 0===this.o&&b<a.length;b++)this.bb(a[b],b)};la.prototype.bb=function(a,b){var c=this.eb,
d=c.resolve;d===eb?(d=jc(a),d===cb&&void 0!==a.o?this.pa(a.o,b,a.m):"function"!==typeof d?(this.aa--,this.m[b]=a):c===D?(c=new c(ba),kc(c,a,d),this.qa(c,b)):this.qa(new c(function(b){return b(a)}),b)):this.qa(d(a),b)};la.prototype.pa=function(a,b,c){var d=this.L;void 0===d.o&&(this.aa--,2===a?C(d,c):this.m[b]=c);0===this.aa&&L(d,this.m)};la.prototype.qa=function(a,b){var c=this;db(a,void 0,function(a){return c.pa(1,b,a)},function(a){return c.pa(2,b,a)})};D.all=function(a){return(new la(this,a)).L};
D.race=function(a){var b=this;return nc(a)?new b(function(c,d){for(var e=a.length,f=0;f<e;f++)b.resolve(a[f]).then(c,d)}):new b(function(a,b){return b(new TypeError("You must pass an array to race."))})};D.resolve=eb;D.reject=function(a){var b=new this(ba);C(b,a);return b};D.Fb=function(a){$a=a};D.Eb=function(a){T=a};D.Bb=T;D.prototype={constructor:D,then:cb,a:function(a){return this.then(null,a)}};window.Promise||(window.Promise=D,D.prototype["catch"]=D.prototype.a);(function(a){function b(a,b){if("function"===
typeof window.CustomEvent)return new CustomEvent(a,b);var c=document.createEvent("CustomEvent");c.initCustomEvent(a,!!b.bubbles,!!b.cancelable,b.detail);return c}function c(a){if(n)return a.ownerDocument!==document?a.ownerDocument:null;var b=a.__importDoc;if(!b&&a.parentNode){b=a.parentNode;if("function"===typeof b.closest)b=b.closest("link[rel=import]");else for(;!g(b)&&(b=b.parentNode););a.__importDoc=b}return b}function d(a){var b=document.querySelectorAll("link[rel=import]:not([import-dependency])"),
c=b.length;c?l(b,function(b){return h(b,function(){0===--c&&a()})}):a()}function e(a){function b(){"loading"!==document.readyState&&document.body&&(document.removeEventListener("readystatechange",b),a())}document.addEventListener("readystatechange",b);b()}function f(a){e(function(){return d(function(){return a&&a()})})}function h(a,b){if(a.__loaded)b&&b();else if("script"===a.localName&&!a.src||"style"===a.localName&&!a.firstChild)a.__loaded=!0,b&&b();else{var c=function(d){a.removeEventListener(d.type,
c);a.__loaded=!0;b&&b()};a.addEventListener("load",c);z&&"style"===a.localName||a.addEventListener("error",c)}}function g(a){return a.nodeType===Node.ELEMENT_NODE&&"link"===a.localName&&"import"===a.rel}function m(){var a=this;this.a={};this.b=0;this.f=new MutationObserver(function(b){return a.l(b)});this.f.observe(document.head,{childList:!0,subtree:!0});this.c(document)}function l(a,b,c){var d=a?a.length:0,e=c?-1:1;for(c=c?d-1:0;c<d&&0<=c;c+=e)b(a[c],c)}var n="import"in document.createElement("link"),
p=null;!1==="currentScript"in document&&Object.defineProperty(document,"currentScript",{get:function(){return p||("complete"!==document.readyState?document.scripts[document.scripts.length-1]:null)},configurable:!0});var t=/(url\()([^)]*)(\))/g,u=/(@import[\s]+(?!url\())([^;]*)(;)/g,v=/(<link[^>]*)(rel=['|"]?stylesheet['|"]?[^>]*>)/g,r={mb:function(a,b){a.href&&a.setAttribute("href",r.fa(a.getAttribute("href"),b));a.src&&a.setAttribute("src",r.fa(a.getAttribute("src"),b));if("style"===a.localName){var c=
r.Ma(a.textContent,b,t);a.textContent=r.Ma(c,b,u)}},Ma:function(a,b,c){return a.replace(c,function(a,c,d,e){a=d.replace(/["']/g,"");b&&(a=r.fa(a,b));return c+"'"+a+"'"+e})},fa:function(a,b){if(void 0===r.na){r.na=!1;try{var c=new URL("b","http://a");c.pathname="c%20d";r.na="http://a/c%20d"===c.href}catch(sf){}}if(r.na)return(new URL(a,b)).href;c=r.Za;c||(c=document.implementation.createHTMLDocument("temp"),r.Za=c,c.ya=c.createElement("base"),c.head.appendChild(c.ya),c.xa=c.createElement("a"));c.ya.href=
b;c.xa.href=a;return c.xa.href||a}},w={async:!0,load:function(a,b,c){if(a)if(a.match(/^data:/)){a=a.split(",");var d=a[1];d=-1<a[0].indexOf(";base64")?atob(d):decodeURIComponent(d);b(d)}else{var e=new XMLHttpRequest;e.open("GET",a,w.async);e.onload=function(){var a=e.responseURL||e.getResponseHeader("Location");a&&0===a.indexOf("/")&&(a=(location.origin||location.protocol+"//"+location.host)+a);var d=e.response||e.responseText;304===e.status||0===e.status||200<=e.status&&300>e.status?b(d,a):c(d)};
e.send()}else c("error: href must be specified")}},z=/Trident/.test(navigator.userAgent)||/Edge\/\d./i.test(navigator.userAgent);m.prototype.c=function(a){var b=this;a=a.querySelectorAll("link[rel=import]");l(a,function(a){return b.h(a)})};m.prototype.h=function(a){var b=this,c=a.href;if(void 0!==this.a[c]){var d=this.a[c];d&&d.__loaded&&(a.import=d,this.g(a))}else this.b++,this.a[c]="pending",w.load(c,function(a,d){a=b.s(a,d||c);b.a[c]=a;b.b--;b.c(a);b.i()},function(){b.a[c]=null;b.b--;b.i()})};
m.prototype.s=function(a,b){if(!a)return document.createDocumentFragment();z&&(a=a.replace(v,function(a,b,c){return-1===a.indexOf("type=")?b+" type=import-disable "+c:a}));var c=document.createElement("template");c.innerHTML=a;if(c.content)a=c.content;else for(a=document.createDocumentFragment();c.firstChild;)a.appendChild(c.firstChild);if(c=a.querySelector("base"))b=r.fa(c.getAttribute("href"),b),c.removeAttribute("href");c=a.querySelectorAll('link[rel=import], link[rel=stylesheet][href][type=import-disable],\n style:not([type]), link[rel=stylesheet][href]:not([type]),\n script:not([type]), script[type="application/javascript"],\n script[type="text/javascript"]');
var d=0;l(c,function(a){h(a);r.mb(a,b);a.setAttribute("import-dependency","");"script"===a.localName&&!a.src&&a.textContent&&(a.setAttribute("src","data:text/javascript;charset=utf-8,"+encodeURIComponent(a.textContent+("\n//# sourceURL="+b+(d?"-"+d:"")+".js\n"))),a.textContent="",d++)});return a};m.prototype.i=function(){var a=this;if(!this.b){this.f.disconnect();this.flatten(document);var b=!1,c=!1,d=function(){c&&b&&(a.c(document),a.b||(a.f.observe(document.head,{childList:!0,subtree:!0}),a.j()))};
this.v(function(){c=!0;d()});this.u(function(){b=!0;d()})}};m.prototype.flatten=function(a){var b=this;a=a.querySelectorAll("link[rel=import]");l(a,function(a){var c=b.a[a.href];(a.import=c)&&c.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&(b.a[a.href]=a,a.readyState="loading",a.import=a,b.flatten(c),a.appendChild(c))})};m.prototype.u=function(a){function b(e){if(e<d){var f=c[e],g=document.createElement("script");f.removeAttribute("import-dependency");l(f.attributes,function(a){return g.setAttribute(a.name,
a.value)});p=g;f.parentNode.replaceChild(g,f);h(g,function(){p=null;b(e+1)})}else a()}var c=document.querySelectorAll("script[import-dependency]"),d=c.length;b(0)};m.prototype.v=function(a){var b=document.querySelectorAll("style[import-dependency],\n link[rel=stylesheet][import-dependency]"),d=b.length;if(d){var e=z&&!!document.querySelector("link[rel=stylesheet][href][type=import-disable]");l(b,function(b){h(b,function(){b.removeAttribute("import-dependency");0===--d&&a()});if(e&&b.parentNode!==
document.head){var f=document.createElement(b.localName);f.__appliedElement=b;f.setAttribute("type","import-placeholder");b.parentNode.insertBefore(f,b.nextSibling);for(f=c(b);f&&c(f);)f=c(f);f.parentNode!==document.head&&(f=null);document.head.insertBefore(b,f);b.removeAttribute("type")}})}else a()};m.prototype.j=function(){var a=this,b=document.querySelectorAll("link[rel=import]");l(b,function(b){return a.g(b)},!0)};m.prototype.g=function(a){a.__loaded||(a.__loaded=!0,a.import&&(a.import.readyState=
"complete"),a.dispatchEvent(b(a.import?"load":"error",{bubbles:!1,cancelable:!1,detail:void 0})))};m.prototype.l=function(a){var b=this;l(a,function(a){return l(a.addedNodes,function(a){a&&a.nodeType===Node.ELEMENT_NODE&&(g(a)?b.h(a):b.c(a))})})};if(n){var x=document.querySelectorAll("link[rel=import]");l(x,function(a){a.import&&"loading"===a.import.readyState||(a.__loaded=!0)});x=function(a){a=a.target;g(a)&&(a.__loaded=!0)};document.addEventListener("load",x,!0);document.addEventListener("error",
x,!0)}else{var y=Object.getOwnPropertyDescriptor(Node.prototype,"baseURI");Object.defineProperty((!y||y.configurable?Node:Element).prototype,"baseURI",{get:function(){var a=g(this)?this:c(this);return a?a.href:y&&y.get?y.get.call(this):(document.querySelector("base")||window.location).href},configurable:!0,enumerable:!0});e(function(){return new m})}f(function(){return document.dispatchEvent(b("HTMLImportsLoaded",{cancelable:!0,bubbles:!0,detail:void 0}))});a.useNative=n;a.whenReady=f;a.importForElement=
c})(window.HTMLImports=window.HTMLImports||{});window.WebComponents=window.WebComponents||{flags:{}};var sd=document.querySelector('script[src*="webcomponents-lite.js"]'),Re=/wc-(.+)/,H={};if(!H.noOpts){location.search.slice(1).split("&").forEach(function(a){a=a.split("=");var b;a[0]&&(b=a[0].match(Re))&&(H[b[1]]=a[1]||!0)});if(sd)for(var td=0,Fa;Fa=sd.attributes[td];td++)"src"!==Fa.name&&(H[Fa.name]=Fa.value||!0);if(H.log&&H.log.split){var Se=H.log.split(",");H.log={};Se.forEach(function(a){H.log[a]=
!0})}else H.log={}}window.WebComponents.flags=H;var ud=H.shadydom;ud&&(window.ShadyDOM=window.ShadyDOM||{},window.ShadyDOM.force=ud);var vd=H.register||H.ce;vd&&window.customElements&&(window.customElements.forcePolyfill=vd);var G=window.ShadyDOM||{};G.nb=!(!Element.prototype.attachShadow||!Node.prototype.getRootNode);var Eb=Object.getOwnPropertyDescriptor(Node.prototype,"firstChild");G.X=!!(Eb&&Eb.configurable&&Eb.get);G.Ja=G.force||!G.nb;var ha=Element.prototype,wd=ha.matches||ha.matchesSelector||
ha.mozMatchesSelector||ha.msMatchesSelector||ha.oMatchesSelector||ha.webkitMatchesSelector,kb=document.createTextNode(""),pc=0,jb=[];(new MutationObserver(function(){for(;jb.length;)try{jb.shift()()}catch(a){throw kb.textContent=pc++,a;}})).observe(kb,{characterData:!0});var Te=!!document.contains,na=[],lb;za.list=na;xa.prototype.ub=function(){var a=this;this.a||(this.a=!0,oc(function(){a.b()}))};xa.prototype.b=function(){if(this.a){this.a=!1;var a=this.takeRecords();a.length&&this.ca.forEach(function(b){b(a)})}};
xa.prototype.takeRecords=function(){if(this.addedNodes.length||this.removedNodes.length){var a=[{addedNodes:this.addedNodes,removedNodes:this.removedNodes}];this.addedNodes=[];this.removedNodes=[];return a}return[]};var Hc=Element.prototype.appendChild,sb=Element.prototype.insertBefore,oa=Element.prototype.removeChild,Pc=Element.prototype.setAttribute,xd=Element.prototype.removeAttribute,Fb=Element.prototype.cloneNode,tb=Document.prototype.importNode,Xc=Element.prototype.addEventListener,$c=Element.prototype.removeEventListener,
Wc=Window.prototype.addEventListener,Zc=Window.prototype.removeEventListener,Gb=Element.prototype.dispatchEvent,pa=Node.prototype.contains||HTMLElement.prototype.contains,Ue=Object.freeze({appendChild:Hc,insertBefore:sb,removeChild:oa,setAttribute:Pc,removeAttribute:xd,cloneNode:Fb,importNode:tb,addEventListener:Xc,removeEventListener:$c,Kb:Wc,Lb:Zc,dispatchEvent:Gb,querySelector:Element.prototype.querySelector,querySelectorAll:Element.prototype.querySelectorAll,contains:pa}),xe=/[&\u00A0"]/g,Ae=
/[&\u00A0<>]/g,ye=tc("area base br col command embed hr img input keygen link meta param source track wbr".split(" ")),ze=tc("style script xmp iframe noembed noframes plaintext noscript".split(" ")),E=document.createTreeWalker(document,NodeFilter.SHOW_ALL,null,!1),F=document.createTreeWalker(document,NodeFilter.SHOW_ELEMENT,null,!1),Ve=Object.freeze({parentNode:da,firstChild:Ya,lastChild:Za,previousSibling:uc,nextSibling:vc,childNodes:X,parentElement:wc,firstElementChild:xc,lastElementChild:yc,previousElementSibling:zc,
nextElementSibling:Ac,children:Bc,innerHTML:Cc,textContent:Dc}),Hb=Object.getOwnPropertyDescriptor(Element.prototype,"innerHTML")||Object.getOwnPropertyDescriptor(HTMLElement.prototype,"innerHTML"),Ga=document.implementation.createHTMLDocument("inert").createElement("div"),Ib=Object.getOwnPropertyDescriptor(Document.prototype,"activeElement"),Ec={parentElement:{get:function(){var a=this.__shady&&this.__shady.parentNode;a&&a.nodeType!==Node.ELEMENT_NODE&&(a=null);return void 0!==a?a:wc(this)},configurable:!0},
parentNode:{get:function(){var a=this.__shady&&this.__shady.parentNode;return void 0!==a?a:da(this)},configurable:!0},nextSibling:{get:function(){var a=this.__shady&&this.__shady.nextSibling;return void 0!==a?a:vc(this)},configurable:!0},previousSibling:{get:function(){var a=this.__shady&&this.__shady.previousSibling;return void 0!==a?a:uc(this)},configurable:!0},className:{get:function(){return this.getAttribute("class")||""},set:function(a){this.setAttribute("class",a)},configurable:!0},nextElementSibling:{get:function(){if(this.__shady&&
void 0!==this.__shady.nextSibling){for(var a=this.nextSibling;a&&a.nodeType!==Node.ELEMENT_NODE;)a=a.nextSibling;return a}return Ac(this)},configurable:!0},previousElementSibling:{get:function(){if(this.__shady&&void 0!==this.__shady.previousSibling){for(var a=this.previousSibling;a&&a.nodeType!==Node.ELEMENT_NODE;)a=a.previousSibling;return a}return zc(this)},configurable:!0}},nb={childNodes:{get:function(){if(ca(this)){if(!this.__shady.childNodes){this.__shady.childNodes=[];for(var a=this.firstChild;a;a=
a.nextSibling)this.__shady.childNodes.push(a)}var b=this.__shady.childNodes}else b=X(this);b.item=function(a){return b[a]};return b},configurable:!0},childElementCount:{get:function(){return this.children.length},configurable:!0},firstChild:{get:function(){var a=this.__shady&&this.__shady.firstChild;return void 0!==a?a:Ya(this)},configurable:!0},lastChild:{get:function(){var a=this.__shady&&this.__shady.lastChild;return void 0!==a?a:Za(this)},configurable:!0},textContent:{get:function(){if(ca(this)){for(var a=
[],b=0,c=this.childNodes,d;d=c[b];b++)d.nodeType!==Node.COMMENT_NODE&&a.push(d.textContent);return a.join("")}return Dc(this)},set:function(a){switch(this.nodeType){case Node.ELEMENT_NODE:case Node.DOCUMENT_FRAGMENT_NODE:for(;this.firstChild;)this.removeChild(this.firstChild);(0<a.length||this.nodeType===Node.ELEMENT_NODE)&&this.appendChild(document.createTextNode(a));break;default:this.nodeValue=a}},configurable:!0},firstElementChild:{get:function(){if(this.__shady&&void 0!==this.__shady.firstChild){for(var a=
this.firstChild;a&&a.nodeType!==Node.ELEMENT_NODE;)a=a.nextSibling;return a}return xc(this)},configurable:!0},lastElementChild:{get:function(){if(this.__shady&&void 0!==this.__shady.lastChild){for(var a=this.lastChild;a&&a.nodeType!==Node.ELEMENT_NODE;)a=a.previousSibling;return a}return yc(this)},configurable:!0},children:{get:function(){var a;ca(this)?a=Array.prototype.filter.call(this.childNodes,function(a){return a.nodeType===Node.ELEMENT_NODE}):a=Bc(this);a.item=function(b){return a[b]};return a},
configurable:!0},innerHTML:{get:function(){var a="template"===this.localName?this.content:this;return ca(this)?mb(a):Cc(a)},set:function(a){for(var b="template"===this.localName?this.content:this;b.firstChild;)b.removeChild(b.firstChild);for(Hb&&Hb.set?Hb.set.call(Ga,a):Ga.innerHTML=a;Ga.firstChild;)b.appendChild(Ga.firstChild)},configurable:!0}},yd={shadowRoot:{get:function(){return this.__shady&&this.__shady.sb||null},configurable:!0}},ob={activeElement:{get:function(){var a=Ib&&Ib.get?Ib.get.call(document):
G.X?void 0:document.activeElement;if(a&&a.nodeType){var b=!!J(this);if(this===document||b&&this.host!==a&&pa.call(this.host,a)){for(b=ma(a);b&&b!==this;)a=b.host,b=ma(a);a=this===document?b?null:a:b===this?a:null}else a=null}else a=null;return a},set:function(){},configurable:!0}},fc=G.X?function(){}:function(a){a.__shady&&a.__shady.Xa||(a.__shady=a.__shady||{},a.__shady.Xa=!0,O(a,Ec,!0))},ec=G.X?function(){}:function(a){a.__shady&&a.__shady.Va||(a.__shady=a.__shady||{},a.__shady.Va=!0,O(a,nb,!0),
O(a,yd,!0))},Ba=null,qa="__eventWrappers"+Date.now(),We={blur:!0,focus:!0,focusin:!0,focusout:!0,click:!0,dblclick:!0,mousedown:!0,mouseenter:!0,mouseleave:!0,mousemove:!0,mouseout:!0,mouseover:!0,mouseup:!0,wheel:!0,beforeinput:!0,input:!0,keydown:!0,keyup:!0,compositionstart:!0,compositionupdate:!0,compositionend:!0,touchstart:!0,touchend:!0,touchmove:!0,touchcancel:!0,pointerover:!0,pointerenter:!0,pointerdown:!0,pointermove:!0,pointerup:!0,pointercancel:!0,pointerout:!0,pointerleave:!0,gotpointercapture:!0,
lostpointercapture:!0,dragstart:!0,drag:!0,dragenter:!0,dragleave:!0,dragover:!0,drop:!0,dragend:!0,DOMActivate:!0,DOMFocusIn:!0,DOMFocusOut:!0,keypress:!0},ad={get composed(){!1!==this.isTrusted&&void 0===this.ka&&(this.ka=We[this.type]);return this.ka||!1},composedPath:function(){this.za||(this.za=ub(this.__target,this.composed));return this.za},get target(){return Rc(this.currentTarget,this.composedPath())},get relatedTarget(){if(!this.Aa)return null;this.Ba||(this.Ba=ub(this.Aa,!0));return Rc(this.currentTarget,
this.Ba)},stopPropagation:function(){Event.prototype.stopPropagation.call(this);this.la=!0},stopImmediatePropagation:function(){Event.prototype.stopImmediatePropagation.call(this);this.la=this.Ua=!0}},wb={focus:!0,blur:!0},Xe=vb(window.Event),Ye=vb(window.CustomEvent),Ze=vb(window.MouseEvent),dc={};l.prototype=Object.create(DocumentFragment.prototype);l.prototype.H=function(a,b){this.Wa="ShadyRoot";wa(a);wa(this);this.host=a;this.J=b&&b.mode;a.__shady=a.__shady||{};a.__shady.root=this;a.__shady.sb=
"closed"!==this.J?this:null;this.U=!1;this.b=[];this.a={};this.c=[];b=X(a);for(var c=0,d=b.length;c<d;c++)oa.call(a,b[c])};l.prototype.O=function(){var a=this;this.U||(this.U=!0,rc(function(){return a.Fa()}))};l.prototype.N=function(){for(var a=this,b=this;b;)b.U&&(a=b),b=b.hb();return a};l.prototype.hb=function(){var a=this.host.getRootNode();if(J(a))for(var b=this.host.childNodes,c=0,d;c<b.length;c++)if(d=b[c],this.j(d))return a};l.prototype.Fa=function(){this.U&&this.N()._renderRoot()};l.prototype._renderRoot=
function(){this.U=!1;this.D();this.v()};l.prototype.D=function(){this.f();for(var a=0,b;a<this.b.length;a++)b=this.b[a],this.u(b);for(b=this.host.firstChild;b;b=b.nextSibling)this.h(b);for(a=0;a<this.b.length;a++){b=this.b[a];if(!b.__shady.assignedNodes.length)for(var c=b.firstChild;c;c=c.nextSibling)this.h(c,b);c=b.parentNode;(c=c.__shady&&c.__shady.root)&&c.Ca()&&c._renderRoot();this.g(b.__shady.W,b.__shady.assignedNodes);if(c=b.__shady.Ea){for(var d=0;d<c.length;d++)c[d].__shady.oa=null;b.__shady.Ea=
null;c.length>b.__shady.assignedNodes.length&&(b.__shady.sa=!0)}b.__shady.sa&&(b.__shady.sa=!1,this.i(b))}};l.prototype.h=function(a,b){a.__shady=a.__shady||{};var c=a.__shady.oa;a.__shady.oa=null;b||(b=(b=this.a[a.slot||"__catchall"])&&b[0]);b?(b.__shady.assignedNodes.push(a),a.__shady.assignedSlot=b):a.__shady.assignedSlot=void 0;c!==a.__shady.assignedSlot&&a.__shady.assignedSlot&&(a.__shady.assignedSlot.__shady.sa=!0)};l.prototype.u=function(a){var b=a.__shady.assignedNodes;a.__shady.assignedNodes=
[];a.__shady.W=[];if(a.__shady.Ea=b)for(var c=0;c<b.length;c++){var d=b[c];d.__shady.oa=d.__shady.assignedSlot;d.__shady.assignedSlot===a&&(d.__shady.assignedSlot=null)}};l.prototype.g=function(a,b){for(var c=0,d;c<b.length&&(d=b[c]);c++)"slot"==d.localName?this.g(a,d.__shady.assignedNodes):a.push(b[c])};l.prototype.i=function(a){Gb.call(a,new Event("slotchange"));a.__shady.assignedSlot&&this.i(a.__shady.assignedSlot)};l.prototype.v=function(){for(var a=this.b,b=[],c=0;c<a.length;c++){var d=a[c].parentNode;
d.__shady&&d.__shady.root||!(0>b.indexOf(d))||b.push(d)}for(a=0;a<b.length;a++)c=b[a],this.T(c===this?this.host:c,this.B(c))};l.prototype.B=function(a){var b=[];a=a.childNodes;for(var c=0;c<a.length;c++){var d=a[c];if(this.j(d)){d=d.__shady.W;for(var e=0;e<d.length;e++)b.push(d[e])}else b.push(d)}return b};l.prototype.j=function(a){return"slot"==a.localName};l.prototype.T=function(a,b){for(var c=X(a),d=De(b,b.length,c,c.length),e=0,f=0,h;e<d.length&&(h=d[e]);e++){for(var g=0,m;g<h.Z.length&&(m=h.Z[g]);g++)da(m)===
a&&oa.call(a,m),c.splice(h.index+f,1);f-=h.ba}for(e=0;e<d.length&&(h=d[e]);e++)for(f=c[h.index],g=h.index;g<h.index+h.ba;g++)m=b[g],sb.call(a,m,f),c.splice(g,0,m)};l.prototype.$a=function(a){this.c.push.apply(this.c,[].concat(a instanceof Array?a:Sd(Rd(a))))};l.prototype.f=function(){this.c.length&&(this.I(this.c),this.c=[])};l.prototype.I=function(a){for(var b,c=0;c<a.length;c++){var d=a[c];d.__shady=d.__shady||{};wa(d);wa(d.parentNode);var e=this.l(d);this.a[e]?(b=b||{},b[e]=!0,this.a[e].push(d)):
this.a[e]=[d];this.b.push(d)}if(b)for(var f in b)this.a[f]=this.s(this.a[f])};l.prototype.l=function(a){var b=a.name||a.getAttribute("name")||"__catchall";return a.Ya=b};l.prototype.s=function(a){return a.sort(function(a,c){a=bd(a);for(var b=bd(c),e=0;e<a.length;e++){c=a[e];var f=b[e];if(c!==f)return a=Array.from(c.parentNode.childNodes),a.indexOf(c)-a.indexOf(f)}})};l.prototype.gb=function(a){this.f();var b=this.a,c;for(c in b)for(var d=b[c],e=0;e<d.length;e++){var f=d[e];if(qc(a,f)){d.splice(e,
1);var h=this.b.indexOf(f);0<=h&&this.b.splice(h,1);e--;this.K(f);h=!0}}return h};l.prototype.ib=function(a){var b=a.Ya,c=this.l(a);if(c!==b){b=this.a[b];var d=b.indexOf(a);0<=d&&b.splice(d,1);b=this.a[c]||(this.a[c]=[]);b.push(a);1<b.length&&(this.a[c]=this.s(b))}};l.prototype.K=function(a){if(a=a.__shady.W)for(var b=0;b<a.length;b++){var c=a[b],d=da(c);d&&oa.call(d,c)}};l.prototype.Ca=function(){this.f();return!!this.b.length};l.prototype.addEventListener=function(a,b,c){"object"!==typeof c&&(c=
{capture:!!c});c.ma=this;this.host.addEventListener(a,b,c)};l.prototype.removeEventListener=function(a,b,c){"object"!==typeof c&&(c={capture:!!c});c.ma=this;this.host.removeEventListener(a,b,c)};l.prototype.getElementById=function(a){return Aa(this,function(b){return b.id==a},function(a){return!!a})[0]||null};(function(a){O(a,nb,!0);O(a,ob,!0)})(l.prototype);var He={addEventListener:Uc.bind(window),removeEventListener:Yc.bind(window)},Ge={addEventListener:Uc,removeEventListener:Yc,appendChild:function(a){return pb(this,
a)},insertBefore:function(a,b){return pb(this,a,b)},removeChild:function(a){return qb(this,a)},replaceChild:function(a,b){pb(this,a,b);qb(this,b);return a},cloneNode:function(a){if("template"==this.localName)var b=Fb.call(this,a);else if(b=Fb.call(this,!1),a){a=this.childNodes;for(var c=0,d;c<a.length;c++)d=a[c].cloneNode(!0),b.appendChild(d)}return b},getRootNode:function(){return Lc(this)},contains:function(a){return qc(this,a)},get isConnected(){var a=this.ownerDocument;if(Te&&pa.call(a,this)||
a.documentElement&&pa.call(a.documentElement,this))return!0;for(a=this;a&&!(a instanceof Document);)a=a.parentNode||(a instanceof l?a.host:void 0);return!!(a&&a instanceof Document)},dispatchEvent:function(a){za();return Gb.call(this,a)}},Ie={get assignedSlot(){return cd(this)}},xb={querySelector:function(a){return Aa(this,function(b){return wd.call(b,a)},function(a){return!!a})[0]||null},querySelectorAll:function(a){return Aa(this,function(b){return wd.call(b,a)})}},fd={assignedNodes:function(a){if("slot"===
this.localName)return Nc(this),this.__shady?(a&&a.flatten?this.__shady.W:this.__shady.assignedNodes)||[]:[]}},dd=ib({setAttribute:function(a,b){Oc(this,a,b)},removeAttribute:function(a){xd.call(this,a);Kc(this,a)},attachShadow:function(a){if(!this)throw"Must provide a host.";if(!a)throw"Not enough arguments.";return new l(dc,this,a)},get slot(){return this.getAttribute("slot")},set slot(a){Oc(this,"slot",a)},get assignedSlot(){return cd(this)}},xb,fd);Object.defineProperties(dd,yd);var ed=ib({importNode:function(a,
b){return Qc(a,b)},getElementById:function(a){return Aa(this,function(b){return b.id==a},function(a){return!!a})[0]||null}},xb);Object.defineProperties(ed,{_activeElement:ob.activeElement});var $e=HTMLElement.prototype.blur,Je=ib({blur:function(){var a=this.__shady&&this.__shady.root;(a=a&&a.activeElement)?a.blur():$e.call(this)}});G.Ja&&(window.ShadyDOM={inUse:G.Ja,patch:function(a){return a},isShadyRoot:J,enqueue:rc,flush:za,settings:G,filterMutations:we,observeChildren:je,unobserveChildren:ie,
nativeMethods:Ue,nativeTree:Ve},window.Event=Xe,window.CustomEvent=Ye,window.MouseEvent=Ze,Ce(),Fe(),window.ShadowRoot=l);var Ke=new Set("annotation-xml color-profile font-face font-face-src font-face-uri font-face-format font-face-name missing-glyph".split(" "));B.prototype.D=function(a,b){this.u.set(a,b);this.s.set(b.constructor,b)};B.prototype.c=function(a){return this.u.get(a)};B.prototype.B=function(a){return this.s.get(a)};B.prototype.v=function(a){this.h=!0;this.j.push(a)};B.prototype.l=function(a){var b=
this;this.h&&S(a,function(a){return b.g(a)})};B.prototype.g=function(a){if(this.h&&!a.__CE_patched){a.__CE_patched=!0;for(var b=0;b<this.j.length;b++)this.j[b](a)}};B.prototype.b=function(a){var b=[];S(a,function(a){return b.push(a)});for(a=0;a<b.length;a++){var c=b[a];1===c.__CE_state?this.connectedCallback(c):this.i(c)}};B.prototype.a=function(a){var b=[];S(a,function(a){return b.push(a)});for(a=0;a<b.length;a++){var c=b[a];1===c.__CE_state&&this.disconnectedCallback(c)}};B.prototype.f=function(a,
b){var c=this;b=b?b:{};var d=b.xb||new Set,e=b.Na||function(a){return c.i(a)},f=[];S(a,function(a){if("link"===a.localName&&"import"===a.getAttribute("rel")){var b=a.import;b instanceof Node&&"complete"===b.readyState?(b.__CE_isImportDocument=!0,b.__CE_hasRegistry=!0):a.addEventListener("load",function(){var b=a.import;if(!b.__CE_documentLoadHandled){b.__CE_documentLoadHandled=!0;b.__CE_isImportDocument=!0;b.__CE_hasRegistry=!0;var f=new Set(d);f.delete(b);c.f(b,{xb:f,Na:e})}})}else f.push(a)},d);
if(this.h)for(a=0;a<f.length;a++)this.g(f[a]);for(a=0;a<f.length;a++)e(f[a])};B.prototype.i=function(a){if(void 0===a.__CE_state){var b=this.c(a.localName);if(b){b.constructionStack.push(a);var c=b.constructor;try{try{if(new c!==a)throw Error("The custom element constructor did not produce the element being upgraded.");}finally{b.constructionStack.pop()}}catch(f){throw a.__CE_state=2,f;}a.__CE_state=1;a.__CE_definition=b;if(b.attributeChangedCallback)for(b=b.observedAttributes,c=0;c<b.length;c++){var d=
b[c],e=a.getAttribute(d);null!==e&&this.attributeChangedCallback(a,d,null,e,null)}u(a)&&this.connectedCallback(a)}}};B.prototype.connectedCallback=function(a){var b=a.__CE_definition;b.connectedCallback&&b.connectedCallback.call(a)};B.prototype.disconnectedCallback=function(a){var b=a.__CE_definition;b.disconnectedCallback&&b.disconnectedCallback.call(a)};B.prototype.attributeChangedCallback=function(a,b,c,d,e){var f=a.__CE_definition;f.attributeChangedCallback&&-1<f.observedAttributes.indexOf(b)&&
f.attributeChangedCallback.call(a,b,c,d,e)};Xa.prototype.c=function(){this.P&&this.P.disconnect()};Xa.prototype.f=function(a){var b=this.a.readyState;"interactive"!==b&&"complete"!==b||this.c();for(b=0;b<a.length;b++)for(var c=a[b].addedNodes,d=0;d<c.length;d++)this.b.f(c[d])};cc.prototype.resolve=function(a){if(this.a)throw Error("Already resolved.");this.a=a;this.b&&this.b(a)};y.prototype.define=function(a,b){var c=this;if(!(b instanceof Function))throw new TypeError("Custom element constructors must be functions.");
if(!gd(a))throw new SyntaxError("The element name '"+a+"' is not valid.");if(this.a.c(a))throw Error("A custom element with name '"+a+"' has already been defined.");if(this.c)throw Error("A custom element is already being defined.");this.c=!0;try{var d=function(a){var b=e[a];if(void 0!==b&&!(b instanceof Function))throw Error("The '"+a+"' callback must be a function.");return b},e=b.prototype;if(!(e instanceof Object))throw new TypeError("The custom element constructor's prototype is not an object.");
var f=d("connectedCallback");var g=d("disconnectedCallback");var k=d("adoptedCallback");var l=d("attributeChangedCallback");var q=b.observedAttributes||[]}catch(n){return}finally{this.c=!1}b={localName:a,constructor:b,connectedCallback:f,disconnectedCallback:g,adoptedCallback:k,attributeChangedCallback:l,observedAttributes:q,constructionStack:[]};this.a.D(a,b);this.g.push(b);this.b||(this.b=!0,this.f(function(){return c.j()}))};y.prototype.j=function(){var a=this;if(!1!==this.b){this.b=!1;for(var b=
this.g,c=[],d=new Map,e=0;e<b.length;e++)d.set(b[e].localName,[]);this.a.f(document,{Na:function(b){if(void 0===b.__CE_state){var e=b.localName,f=d.get(e);f?f.push(b):a.a.c(e)&&c.push(b)}}});for(e=0;e<c.length;e++)this.a.i(c[e]);for(;0<b.length;){var f=b.shift();e=f.localName;f=d.get(f.localName);for(var g=0;g<f.length;g++)this.a.i(f[g]);(e=this.h.get(e))&&e.resolve(void 0)}}};y.prototype.get=function(a){if(a=this.a.c(a))return a.constructor};y.prototype.whenDefined=function(a){if(!gd(a))return Promise.reject(new SyntaxError("'"+
a+"' is not a valid custom element name."));var b=this.h.get(a);if(b)return b.c;b=new cc;this.h.set(a,b);this.a.c(a)&&!this.g.some(function(b){return b.localName===a})&&b.resolve(void 0);return b.c};y.prototype.l=function(a){this.i.c();var b=this.f;this.f=function(c){return a(function(){return b(c)})}};window.CustomElementRegistry=y;y.prototype.define=y.prototype.define;y.prototype.get=y.prototype.get;y.prototype.whenDefined=y.prototype.whenDefined;y.prototype.polyfillWrapFlushCallback=y.prototype.l;
var Sa=window.Document.prototype.createElement,de=window.Document.prototype.createElementNS,ce=window.Document.prototype.importNode,ee=window.Document.prototype.prepend,fe=window.Document.prototype.append,af=window.DocumentFragment.prototype.prepend,bf=window.DocumentFragment.prototype.append,Tb=window.Node.prototype.cloneNode,ua=window.Node.prototype.appendChild,$b=window.Node.prototype.insertBefore,Ta=window.Node.prototype.removeChild,ac=window.Node.prototype.replaceChild,Wa=Object.getOwnPropertyDescriptor(window.Node.prototype,
"textContent"),Sb=window.Element.prototype.attachShadow,Qa=Object.getOwnPropertyDescriptor(window.Element.prototype,"innerHTML"),Ua=window.Element.prototype.getAttribute,Ub=window.Element.prototype.setAttribute,Wb=window.Element.prototype.removeAttribute,va=window.Element.prototype.getAttributeNS,Vb=window.Element.prototype.setAttributeNS,Xb=window.Element.prototype.removeAttributeNS,Zb=window.Element.prototype.insertAdjacentElement,Ud=window.Element.prototype.prepend,Vd=window.Element.prototype.append,
Xd=window.Element.prototype.before,Yd=window.Element.prototype.after,Zd=window.Element.prototype.replaceWith,$d=window.Element.prototype.remove,he=window.HTMLElement,Ra=Object.getOwnPropertyDescriptor(window.HTMLElement.prototype,"innerHTML"),Yb=window.HTMLElement.prototype.insertAdjacentElement,bc=new function(){},Ha=window.customElements;if(!Ha||Ha.forcePolyfill||"function"!=typeof Ha.define||"function"!=typeof Ha.get){var ia=new B;ge(ia);be(ia);Va(ia,DocumentFragment.prototype,{ea:af,append:bf});
ae(ia);Td(ia);document.__CE_hasRegistry=!0;var cf=new y(ia);Object.defineProperty(window,"customElements",{configurable:!0,enumerable:!0,value:cf})}var M={STYLE_RULE:1,ja:7,MEDIA_RULE:4,wa:1E3},I={lb:/\/\*[^*]*\*+([^/*][^*]*\*+)*\//gim,port:/@import[^;]*;/gim,Ga:/(?:^[^;\-\s}]+)?--[^;{}]*?:[^{};]*?(?:[;\n]|$)/gim,Ka:/(?:^[^;\-\s}]+)?--[^;{}]*?:[^{};]*?{[^}]*?}(?:[;\n]|$)?/gim,rb:/@apply\s*\(?[^);]*\)?\s*(?:[;\n]|$)?/gim,wb:/[^;:]*?:[^;]*?var\([^;]*\)(?:[;\n]|$)?/gim,qb:/^@[^\s]*keyframes/,La:/\s+/g},
z=!(window.ShadyDOM&&window.ShadyDOM.inUse);if(window.ShadyCSS&&void 0!==window.ShadyCSS.nativeCss)var A=window.ShadyCSS.nativeCss;else window.ShadyCSS?(jd(window.ShadyCSS),window.ShadyCSS=void 0):jd(window.WebComponents&&window.WebComponents.flags);var Ia=/(?:^|[;\s{]\s*)(--[\w-]*?)\s*:\s*(?:((?:'(?:\\'|.)*?'|"(?:\\"|.)*?"|\([^)]*?\)|[^};{])+)|\{([^}]*)\}(?:(?=[;\s}])|$))/gi,Ja=/(?:^|\W+)@apply\s*\(?([^);\n]*)\)?/gi,df=/(--[\w-]+)\s*([:,;)]|$)/gi,ef=/(animation\s*:)|(animation-name\s*:)/,Me=/@media\s(.*)/,
ff=/\{[^}]*\}/g,V=null;v.prototype.b=function(a,b,c){a.__styleScoped?a.__styleScoped=null:this.j(a,b||"",c)};v.prototype.j=function(a,b,c){a.nodeType===Node.ELEMENT_NODE&&this.h(a,b,c);if(a="template"===a.localName?(a.content||a.Cb).childNodes:a.children||a.childNodes)for(var d=0;d<a.length;d++)this.j(a[d],b,c)};v.prototype.h=function(a,b,c){if(b)if(a.classList)c?(a.classList.remove("style-scope"),a.classList.remove(b)):(a.classList.add("style-scope"),a.classList.add(b));else if(a.getAttribute){var d=
a.getAttribute(gf);c?d&&(b=d.replace("style-scope","").replace(b,""),Da(a,b)):Da(a,(d?d+" ":"")+"style-scope "+b)}};v.prototype.c=function(a,b,c){var d=a.__cssBuild;z||"shady"===d?b=ea(b,c):(a=W(a),b=this.I(b,a.is,a.$,c)+"\n\n");return b.trim()};v.prototype.I=function(a,b,c,d){var e=this.f(b,c);b=this.i(b);var f=this;return ea(a,function(a){a.c||(f.K(a,b,e),a.c=!0);d&&d(a,b,e)})};v.prototype.i=function(a){return a?hf+a:""};v.prototype.f=function(a,b){return b?"[is="+a+"]":a};v.prototype.K=function(a,
b,c){this.l(a,this.g,b,c)};v.prototype.l=function(a,b,c,d){a.selector=a.A=this.s(a,b,c,d)};v.prototype.s=function(a,b,c,d){var e=a.selector.split(zd);if(!kd(a)){a=0;for(var f=e.length,g;a<f&&(g=e[a]);a++)e[a]=b.call(this,g,c,d)}return e.join(zd)};v.prototype.v=function(a){return a.replace(Jb,function(a,c,d){-1<d.indexOf("+")?d=d.replace(/\+/g,"___"):-1<d.indexOf("___")&&(d=d.replace(/___/g,"+"));return":"+c+"("+d+")"})};v.prototype.g=function(a,b,c){var d=this,e=!1;a=a.trim();var f=Jb.test(a);f&&
(a=a.replace(Jb,function(a,b,c){return":"+b+"("+c.replace(/\s/g,"")+")"}),a=this.v(a));a=a.replace(jf,Kb+" $1");a=a.replace(kf,function(a,f,g){e||(a=d.D(g,f,b,c),e=e||a.stop,f=a.kb,g=a.value);return f+g});f&&(a=this.v(a));return a};v.prototype.D=function(a,b,c,d){var e=a.indexOf(Lb);0<=a.indexOf(Kb)?a=this.H(a,d):0!==e&&(a=c?this.u(a,c):a);c=!1;0<=e&&(b="",c=!0);if(c){var f=!0;c&&(a=a.replace(lf,function(a,b){return" > "+b}))}a=a.replace(mf,function(a,b,c){return'[dir="'+c+'"] '+b+", "+b+'[dir="'+
c+'"]'});return{value:a,kb:b,stop:f}};v.prototype.u=function(a,b){a=a.split(Ad);a[0]+=b;return a.join(Ad)};v.prototype.H=function(a,b){var c=a.match(Bd);return(c=c&&c[2].trim()||"")?c[0].match(Cd)?a.replace(Bd,function(a,c,f){return b+f}):c.split(Cd)[0]===b?c:nf:a.replace(Kb,b)};v.prototype.J=function(a){a.selector=a.parsedSelector;this.B(a);this.l(a,this.N)};v.prototype.B=function(a){a.selector===of&&(a.selector="html")};v.prototype.N=function(a){return a.match(Lb)?this.g(a,Dd):this.u(a.trim(),Dd)};
N.Object.defineProperties(v.prototype,{a:{configurable:!0,enumerable:!0,get:function(){return"style-scope"}}});var Jb=/:(nth[-\w]+)\(([^)]+)\)/,Dd=":not(.style-scope)",zd=",",kf=/(^|[\s>+~]+)((?:\[.+?\]|[^\s>+~=[])+)/g,Cd=/[[.:#*]/,Kb=":host",of=":root",Lb="::slotted",jf=new RegExp("^("+Lb+")"),Bd=/(:host)(?:\(((?:\([^)(]*\)|[^)(]*)+?)\))/,lf=/(?:::slotted)(?:\(((?:\([^)(]*\)|[^)(]*)+?)\))/,mf=/(.*):dir\((?:(ltr|rtl))\)/,hf=".",Ad=":",gf="class",nf="should_not_match",r=new v;w.get=function(a){return a?
a.__styleInfo:null};w.set=function(a,b){return a.__styleInfo=b};w.prototype.c=function(){return this.G};w.prototype._getStyleRules=w.prototype.c;var Ed=function(a){return a.matches||a.matchesSelector||a.mozMatchesSelector||a.msMatchesSelector||a.oMatchesSelector||a.webkitMatchesSelector}(window.Element.prototype),pf=navigator.userAgent.match("Trident");p.prototype.J=function(a){var b=this,c={},d=[],e=0;fa(a,function(a){b.c(a);a.index=e++;b.I(a.w.cssText,c)},function(a){d.push(a)});a.b=d;a=[];for(var f in c)a.push(f);
return a};p.prototype.c=function(a){if(!a.w){var b={},c={};this.b(a,c)&&(b.F=c,a.rules=null);b.cssText=this.H(a);a.w=b}};p.prototype.b=function(a,b){var c=a.w;if(c){if(c.F)return Object.assign(b,c.F),!0}else{c=a.parsedCssText;for(var d;a=Ia.exec(c);){d=(a[2]||a[3]).trim();if("inherit"!==d||"unset"!==d)b[a[1].trim()]=d;d=!0}return d}};p.prototype.H=function(a){return this.N(a.parsedCssText)};p.prototype.N=function(a){return a.replace(ff,"").replace(Ia,"")};p.prototype.I=function(a,b){for(var c;c=df.exec(a);){var d=
c[1];":"!==c[2]&&(b[d]=!0)}};p.prototype.ga=function(a){for(var b=Object.getOwnPropertyNames(a),c=0,d;c<b.length;c++)d=b[c],a[d]=this.a(a[d],a)};p.prototype.a=function(a,b){if(a)if(0<=a.indexOf(";"))a=this.f(a,b);else{var c=this;a=md(a,function(a,e,f,g){if(!e)return a+g;(e=c.a(b[e],b))&&"initial"!==e?"apply-shim-inherit"===e&&(e="inherit"):e=c.a(b[f]||f,b)||f;return a+(e||"")+g})}return a&&a.trim()||""};p.prototype.f=function(a,b){a=a.split(";");for(var c=0,d,e;c<a.length;c++)if(d=a[c]){Ja.lastIndex=
0;if(e=Ja.exec(d))d=this.a(b[e[1]],b);else if(e=d.indexOf(":"),-1!==e){var f=d.substring(e);f=f.trim();f=this.a(f,b)||f;d=d.substring(0,e)+f}a[c]=d&&d.lastIndexOf(";")===d.length-1?d.slice(0,-1):d||""}return a.join(";")};p.prototype.D=function(a,b){var c="";a.w||this.c(a);a.w.cssText&&(c=this.f(a.w.cssText,b));a.cssText=c};p.prototype.B=function(a,b){var c=a.cssText,d=a.cssText;null==a.Ia&&(a.Ia=ef.test(c));if(a.Ia)if(null==a.da){a.da=[];for(var e in b)d=b[e],d=d(c),c!==d&&(c=d,a.da.push(e))}else{for(e=
0;e<a.da.length;++e)d=b[a.da[e]],c=d(c);d=c}a.cssText=d};p.prototype.T=function(a,b){var c={},d=this,e=[];fa(a,function(a){a.w||d.c(a);var f=a.A||a.parsedSelector;b&&a.w.F&&f&&Ed.call(b,f)&&(d.b(a,c),a=a.index,f=parseInt(a/32,10),e[f]=(e[f]||0)|1<<a%32)},null,!0);return{F:c,key:e}};p.prototype.ia=function(a,b,c,d){b.w||this.c(b);if(b.w.F){var e=W(a);a=e.is;e=e.$;e=a?r.f(a,e):"html";var f=b.parsedSelector,g=":host > *"===f||"html"===f,k=0===f.indexOf(":host")&&!g;"shady"===c&&(g=f===e+" > *."+e||-1!==
f.indexOf("html"),k=!g&&0===f.indexOf(e));"shadow"===c&&(g=":host > *"===f||"html"===f,k=k&&!g);if(g||k)c=e,k&&(z&&!b.A&&(b.A=r.s(b,r.g,r.i(a),e)),c=b.A||e),d({vb:c,pb:k,Gb:g})}};p.prototype.K=function(a,b){var c={},d={},e=this,f=b&&b.__cssBuild;fa(b,function(b){e.ia(a,b,f,function(f){Ed.call(a.Db||a,f.vb)&&(f.pb?e.b(b,c):e.b(b,d))})},null,!0);return{tb:d,ob:c}};p.prototype.ha=function(a,b,c){var d=this,e=W(a),f=r.f(e.is,e.$),g=new RegExp("(?:^|[^.#[:])"+(a.extends?"\\"+f.slice(0,-1)+"\\]":f)+"($|[.:[\\s>+~])");
e=w.get(a).G;var k=this.h(e,c);return r.c(a,e,function(a){d.D(a,b);z||kd(a)||!a.cssText||(d.B(a,k),d.l(a,g,f,c))})};p.prototype.h=function(a,b){a=a.b;var c={};if(!z&&a)for(var d=0,e=a[d];d<a.length;e=a[++d])this.j(e,b),c[e.keyframesName]=this.i(e);return c};p.prototype.i=function(a){return function(b){return b.replace(a.f,a.a)}};p.prototype.j=function(a,b){a.f=new RegExp(a.keyframesName,"g");a.a=a.keyframesName+"-"+b;a.A=a.A||a.selector;a.selector=a.A.replace(a.keyframesName,a.a)};p.prototype.l=function(a,
b,c,d){a.A=a.A||a.selector;d="."+d;for(var e=a.A.split(","),f=0,g=e.length,k;f<g&&(k=e[f]);f++)e[f]=k.match(b)?k.replace(c,d):d+" "+k;a.selector=e.join(",")};p.prototype.u=function(a,b,c){var d=a.getAttribute("class")||"",e=d;c&&(e=d.replace(new RegExp("\\s*x-scope\\s*"+c+"\\s*","g")," "));e+=(e?" ":"")+"x-scope "+b;d!==e&&Da(a,e)};p.prototype.v=function(a,b,c,d){b=d?d.textContent||"":this.ha(a,b,c);var e=w.get(a),f=e.a;f&&!z&&f!==d&&(f._useCount--,0>=f._useCount&&f.parentNode&&f.parentNode.removeChild(f));
z?e.a?(e.a.textContent=b,d=e.a):b&&(d=Ab(b,c,a.shadowRoot,e.b)):d?d.parentNode||(pf&&-1<b.indexOf("@media")&&(d.textContent=b),ld(d,null,e.b)):b&&(d=Ab(b,c,null,e.b));d&&(d._useCount=d._useCount||0,e.a!=d&&d._useCount++,e.a=d);return d};p.prototype.s=function(a,b){var c=Ca(a),d=this;a.textContent=ea(c,function(a){var c=a.cssText=a.parsedCssText;a.w&&a.w.cssText&&(c=c.replace(I.Ga,"").replace(I.Ka,""),a.cssText=d.f(c,b))})};N.Object.defineProperties(p.prototype,{g:{configurable:!0,enumerable:!0,get:function(){return"x-scope"}}});
var Q=new p,Mb={},Ka=window.customElements;if(Ka&&!z){var qf=Ka.define;Ka.define=function(a,b,c){var d=document.createComment(" Shady DOM styles for "+a+" "),e=document.head;e.insertBefore(d,(V?V.nextSibling:null)||e.firstChild);V=d;Mb[a]=d;return qf.call(Ka,a,b,c)}}ta.prototype.a=function(a,b,c){for(var d=0;d<c.length;d++){var e=c[d];if(a.F[e]!==b[e])return!1}return!0};ta.prototype.b=function(a,b,c,d){var e=this.cache[a]||[];e.push({F:b,styleElement:c,C:d});e.length>this.c&&e.shift();this.cache[a]=
e};ta.prototype.fetch=function(a,b,c){if(a=this.cache[a])for(var d=a.length-1;0<=d;d--){var e=a[d];if(this.a(e,b,c))return e}};if(!z){var Fd=new MutationObserver(nd),Gd=function(a){Fd.observe(a,{childList:!0,subtree:!0})};if(window.customElements&&!window.customElements.polyfillWrapFlushCallback)Gd(document);else{var Nb=function(){Gd(document.body)};window.HTMLImports?window.HTMLImports.whenReady(Nb):requestAnimationFrame(function(){if("loading"===document.readyState){var a=function(){Nb();document.removeEventListener("readystatechange",
a)};document.addEventListener("readystatechange",a)}else Nb()})}R=function(){nd(Fd.takeRecords())}}var Ea={},Pe=Promise.resolve(),Bb=null,pd=window.HTMLImports&&window.HTMLImports.whenReady||null,Cb,La=null,sa=null;t.prototype.Ha=function(){!this.enqueued&&sa&&(this.enqueued=!0,Rb(sa))};t.prototype.b=function(a){a.__seenByShadyCSS||(a.__seenByShadyCSS=!0,this.customStyles.push(a),this.Ha())};t.prototype.a=function(a){return a.__shadyCSSCachedStyle?a.__shadyCSSCachedStyle:a.getStyle?a.getStyle():a};
t.prototype.c=function(){for(var a=this.customStyles,b=0;b<a.length;b++){var c=a[b];if(!c.__shadyCSSCachedStyle){var d=this.a(c);d&&(d=d.__appliedElement||d,La&&La(d),c.__shadyCSSCachedStyle=d)}}return a};t.prototype.addCustomStyle=t.prototype.b;t.prototype.getStyleForCustomStyle=t.prototype.a;t.prototype.processStyles=t.prototype.c;Object.defineProperties(t.prototype,{transformCallback:{get:function(){return La},set:function(a){La=a}},validateCallback:{get:function(){return sa},set:function(a){var b=
!1;sa||(b=!0);sa=a;b&&this.Ha()}}});var Hd=new ta;g.prototype.B=function(){R()};g.prototype.K=function(a){var b=this.s[a]=(this.s[a]||0)+1;return a+"-"+b};g.prototype.Ra=function(a){return Ca(a)};g.prototype.Ta=function(a){return ea(a)};g.prototype.J=function(a){a=a.content.querySelectorAll("style");for(var b=[],c=0;c<a.length;c++){var d=a[c];b.push(d.textContent);d.parentNode.removeChild(d)}return b.join("").trim()};g.prototype.ga=function(a){return(a=a.content.querySelector("style"))?a.getAttribute("css-build")||
"":""};g.prototype.prepareTemplate=function(a,b,c){if(!a.f){a.f=!0;a.name=b;a.extends=c;Ea[b]=a;var d=this.ga(a),e=this.J(a);c={is:b,extends:c,zb:d};z||r.b(a.content,b);this.c();var f=Ja.test(e)||Ia.test(e);Ja.lastIndex=0;Ia.lastIndex=0;e=zb(e);f&&A&&this.a&&this.a.transformRules(e,b);a._styleAst=e;a.g=d;d=[];A||(d=Q.J(a._styleAst));if(!d.length||A)b=this.T(c,a._styleAst,z?a.content:null,Mb[b]),a.a=b;a.c=d}};g.prototype.T=function(a,b,c,d){b=r.c(a,b);if(b.length)return Ab(b,a.is,c,d)};g.prototype.ia=
function(a){var b=W(a),c=b.is;b=b.$;var d=Mb[c];c=Ea[c];if(c){var e=c._styleAst;var f=c.c}return w.set(a,new w(e,d,f,0,b))};g.prototype.H=function(){!this.a&&window.ShadyCSS&&window.ShadyCSS.ApplyShim&&(this.a=window.ShadyCSS.ApplyShim,this.a.invalidCallback=Ne)};g.prototype.I=function(){var a=this;!this.b&&window.ShadyCSS&&window.ShadyCSS.CustomStyleInterface&&(this.b=window.ShadyCSS.CustomStyleInterface,this.b.transformCallback=function(b){a.v(b)},this.b.validateCallback=function(){requestAnimationFrame(function(){(a.b.enqueued||
a.i)&&a.f()})})};g.prototype.c=function(){this.H();this.I()};g.prototype.f=function(){this.c();if(this.b){var a=this.b.processStyles();this.b.enqueued&&(A?this.Pa(a):(this.u(this.g,this.h),this.D(a)),this.b.enqueued=!1,this.i&&!A&&this.styleDocument())}};g.prototype.styleElement=function(a,b){var c=W(a).is,d=w.get(a);d||(d=this.ia(a));this.j(a)||(this.i=!0);b&&(d.S=d.S||{},Object.assign(d.S,b));if(A){if(d.S){b=d.S;for(var e in b)null===e?a.style.removeProperty(e):a.style.setProperty(e,b[e])}if(((e=
Ea[c])||this.j(a))&&e&&e.a&&!od(e)){if(od(e)||e._applyShimValidatingVersion!==e._applyShimNextVersion)this.c(),this.a&&this.a.transformRules(e._styleAst,c),e.a.textContent=r.c(a,d.G),Oe(e);z&&(c=a.shadowRoot)&&(c.querySelector("style").textContent=r.c(a,d.G));d.G=e._styleAst}}else this.u(a,d),d.ua&&d.ua.length&&this.N(a,d)};g.prototype.l=function(a){return(a=a.getRootNode().host)?w.get(a)?a:this.l(a):this.g};g.prototype.j=function(a){return a===this.g};g.prototype.N=function(a,b){var c=W(a).is,d=
Hd.fetch(c,b.M,b.ua),e=d?d.styleElement:null,f=b.C;b.C=d&&d.C||this.K(c);e=Q.v(a,b.M,b.C,e);z||Q.u(a,b.C,f);d||Hd.b(c,b.M,e,b.C)};g.prototype.u=function(a,b){var c=this.l(a),d=w.get(c);c=Object.create(d.M||null);var e=Q.K(a,b.G);a=Q.T(d.G,a).F;Object.assign(c,e.ob,a,e.tb);this.ha(c,b.S);Q.ga(c);b.M=c};g.prototype.ha=function(a,b){for(var c in b){var d=b[c];if(d||0===d)a[c]=d}};g.prototype.styleDocument=function(a){this.styleSubtree(this.g,a)};g.prototype.styleSubtree=function(a,b){var c=a.shadowRoot;
(c||this.j(a))&&this.styleElement(a,b);if(b=c&&(c.children||c.childNodes))for(a=0;a<b.length;a++)this.styleSubtree(b[a]);else if(a=a.children||a.childNodes)for(b=0;b<a.length;b++)this.styleSubtree(a[b])};g.prototype.Pa=function(a){for(var b=0;b<a.length;b++){var c=this.b.getStyleForCustomStyle(a[b]);c&&this.Oa(c)}};g.prototype.D=function(a){for(var b=0;b<a.length;b++){var c=this.b.getStyleForCustomStyle(a[b]);c&&Q.s(c,this.h.M)}};g.prototype.v=function(a){var b=this,c=Ca(a);fa(c,function(a){z?r.B(a):
r.J(a);A&&(b.c(),b.a&&b.a.transformRule(a))});A?a.textContent=ea(c):this.h.G.rules.push(c)};g.prototype.Oa=function(a){if(A&&this.a){var b=Ca(a);this.c();this.a.transformRules(b);a.textContent=ea(b)}};g.prototype.getComputedStyleValue=function(a,b){var c;A||(c=(w.get(a)||w.get(this.l(a))).M[b]);return(c=c||window.getComputedStyle(a).getPropertyValue(b))?c.trim():""};g.prototype.Sa=function(a,b){var c=a.getRootNode();b=b?b.split(/\s/):[];c=c.host&&c.host.localName;if(!c){var d=a.getAttribute("class");
if(d){d=d.split(/\s/);for(var e=0;e<d.length;e++)if(d[e]===r.a){c=d[e+1];break}}}c&&b.push(r.a,c);A||(c=w.get(a))&&c.C&&b.push(Q.g,c.C);Da(a,b.join(" "))};g.prototype.Qa=function(a){return w.get(a)};g.prototype.flush=g.prototype.B;g.prototype.prepareTemplate=g.prototype.prepareTemplate;g.prototype.styleElement=g.prototype.styleElement;g.prototype.styleDocument=g.prototype.styleDocument;g.prototype.styleSubtree=g.prototype.styleSubtree;g.prototype.getComputedStyleValue=g.prototype.getComputedStyleValue;
g.prototype.setElementClass=g.prototype.Sa;g.prototype._styleInfoForNode=g.prototype.Qa;g.prototype.transformCustomStyleForDocument=g.prototype.v;g.prototype.getStyleAst=g.prototype.Ra;g.prototype.styleAstToString=g.prototype.Ta;g.prototype.flushCustomStyles=g.prototype.f;Object.defineProperties(g.prototype,{nativeShadow:{get:function(){return z}},nativeCss:{get:function(){return A}}});var K=new g;if(window.ShadyCSS){var Id=window.ShadyCSS.ApplyShim;var Jd=window.ShadyCSS.CustomStyleInterface}window.ShadyCSS=
{ScopingShim:K,prepareTemplate:function(a,b,c){K.f();K.prepareTemplate(a,b,c)},styleSubtree:function(a,b){K.f();K.styleSubtree(a,b)},styleElement:function(a){K.f();K.styleElement(a)},styleDocument:function(a){K.f();K.styleDocument(a)},getComputedStyleValue:function(a,b){return K.getComputedStyleValue(a,b)},nativeCss:A,nativeShadow:z};Id&&(window.ShadyCSS.ApplyShim=Id);Jd&&(window.ShadyCSS.CustomStyleInterface=Jd);var Ob=window.customElements,Ma=window.HTMLImports;window.WebComponents=window.WebComponents||
{};if(Ob&&Ob.polyfillWrapFlushCallback){var Na,Kd=function(){if(Na){var a=Na;Na=null;a();return!0}},Ld=Ma.whenReady;Ob.polyfillWrapFlushCallback(function(a){Na=a;Ld(Kd)});Ma.whenReady=function(a){Ld(function(){Kd()?Ma.whenReady(a):a()})}}Ma.whenReady(function(){requestAnimationFrame(function(){window.WebComponents.ready=!0;document.dispatchEvent(new CustomEvent("WebComponentsReady",{bubbles:!0}))})});var Md=document.createElement("style");Md.textContent="body {transition: opacity ease-in 0.2s; } \nbody[unresolved] {opacity: 0; display: block; overflow: hidden; position: relative; } \n";
var Nd=document.querySelector("head");Nd.insertBefore(Md,Nd.firstChild)})();}).call(this);
//# sourceMappingURL=webcomponents-lite.js.map
+6 -6
View File
@@ -24,7 +24,7 @@ from homeassistant.helpers.event import track_time_change
from homeassistant.util import convert, dt
REQUIREMENTS = [
'google-api-python-client==1.6.2',
'google-api-python-client==1.6.4',
'oauth2client==4.0.0',
]
@@ -99,10 +99,10 @@ def do_authentication(hass, config):
from oauth2client.file import Storage
oauth = OAuth2WebServerFlow(
config[CONF_CLIENT_ID],
config[CONF_CLIENT_SECRET],
'https://www.googleapis.com/auth/calendar.readonly',
'Home-Assistant.io',
client_id=config[CONF_CLIENT_ID],
client_secret=config[CONF_CLIENT_SECRET],
scope='https://www.googleapis.com/auth/calendar.readonly',
redirect_uri='Home-Assistant.io',
)
try:
@@ -269,7 +269,7 @@ def load_config(path):
calendars = {}
try:
with open(path) as file:
data = yaml.load(file)
data = yaml.safe_load(file)
for calendar in data:
try:
calendars.update({calendar[CONF_CAL_ID]:
@@ -0,0 +1,52 @@
"""
Support for Actions on Google Assistant Smart Home Control.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/google_assistant/
"""
import asyncio
import logging
import voluptuous as vol
# Typing imports
# pylint: disable=using-constant-test,unused-import,ungrouped-imports
# if False:
from homeassistant.core import HomeAssistant # NOQA
from typing import Dict, Any # NOQA
from homeassistant.helpers import config_validation as cv
from .const import (
DOMAIN, CONF_PROJECT_ID, CONF_CLIENT_ID, CONF_ACCESS_TOKEN,
CONF_EXPOSE_BY_DEFAULT, CONF_EXPOSED_DOMAINS
)
from .auth import GoogleAssistantAuthView
from .http import GoogleAssistantView
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['http']
CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: {
vol.Required(CONF_PROJECT_ID): cv.string,
vol.Required(CONF_CLIENT_ID): cv.string,
vol.Required(CONF_ACCESS_TOKEN): cv.string,
vol.Optional(CONF_EXPOSE_BY_DEFAULT): cv.boolean,
vol.Optional(CONF_EXPOSED_DOMAINS): cv.ensure_list,
}
},
extra=vol.ALLOW_EXTRA)
@asyncio.coroutine
def async_setup(hass: HomeAssistant, yaml_config: Dict[str, Any]):
"""Activate Google Actions component."""
config = yaml_config.get(DOMAIN, {})
hass.http.register_view(GoogleAssistantAuthView(hass, config))
hass.http.register_view(GoogleAssistantView(hass, config))
return True
@@ -0,0 +1,86 @@
"""Google Assistant OAuth View."""
import asyncio
import logging
# Typing imports
# pylint: disable=using-constant-test,unused-import,ungrouped-imports
# if False:
from homeassistant.core import HomeAssistant # NOQA
from aiohttp.web import Request, Response # NOQA
from typing import Dict, Any # NOQA
from homeassistant.components.http import HomeAssistantView
from homeassistant.const import (
HTTP_BAD_REQUEST,
HTTP_UNAUTHORIZED,
HTTP_MOVED_PERMANENTLY,
)
from .const import (
GOOGLE_ASSISTANT_API_ENDPOINT,
CONF_PROJECT_ID, CONF_CLIENT_ID, CONF_ACCESS_TOKEN
)
BASE_OAUTH_URL = 'https://oauth-redirect.googleusercontent.com'
REDIRECT_TEMPLATE_URL = \
'{}/r/{}#access_token={}&token_type=bearer&state={}'
_LOGGER = logging.getLogger(__name__)
class GoogleAssistantAuthView(HomeAssistantView):
"""Handle Google Actions auth requests."""
url = GOOGLE_ASSISTANT_API_ENDPOINT + '/auth'
name = 'api:google_assistant:auth'
requires_auth = False
def __init__(self, hass: HomeAssistant, cfg: Dict[str, Any]) -> None:
"""Initialize instance of the view."""
super().__init__()
self.project_id = cfg.get(CONF_PROJECT_ID)
self.client_id = cfg.get(CONF_CLIENT_ID)
self.access_token = cfg.get(CONF_ACCESS_TOKEN)
@asyncio.coroutine
def get(self, request: Request) -> Response:
"""Handle oauth token request."""
query = request.query
redirect_uri = query.get('redirect_uri')
if not redirect_uri:
msg = 'missing redirect_uri field'
_LOGGER.warning(msg)
return self.json_message(msg, status_code=HTTP_BAD_REQUEST)
if self.project_id not in redirect_uri:
msg = 'missing project_id in redirect_uri'
_LOGGER.warning(msg)
return self.json_message(msg, status_code=HTTP_BAD_REQUEST)
state = query.get('state')
if not state:
msg = 'oauth request missing state'
_LOGGER.warning(msg)
return self.json_message(msg, status_code=HTTP_BAD_REQUEST)
client_id = query.get('client_id')
if self.client_id != client_id:
msg = 'invalid client id'
_LOGGER.warning(msg)
return self.json_message(msg, status_code=HTTP_UNAUTHORIZED)
generated_url = redirect_url(self.project_id, self.access_token, state)
_LOGGER.info('user login in from Google Assistant')
return self.json_message(
'redirect success',
status_code=HTTP_MOVED_PERMANENTLY,
headers={'Location': generated_url})
def redirect_url(project_id: str, access_token: str, state: str) -> str:
"""Generate the redirect format for the oauth request."""
return REDIRECT_TEMPLATE_URL.format(BASE_OAUTH_URL, project_id,
access_token, state)
@@ -0,0 +1,37 @@
"""Constants for Google Assistant."""
DOMAIN = 'google_assistant'
GOOGLE_ASSISTANT_API_ENDPOINT = '/api/google_assistant'
ATTR_GOOGLE_ASSISTANT = 'google_assistant'
ATTR_GOOGLE_ASSISTANT_NAME = 'google_assistant_name'
CONF_EXPOSE_BY_DEFAULT = 'expose_by_default'
CONF_EXPOSED_DOMAINS = 'exposed_domains'
CONF_PROJECT_ID = 'project_id'
CONF_ACCESS_TOKEN = 'access_token'
CONF_CLIENT_ID = 'client_id'
CONF_ALIASES = 'aliases'
DEFAULT_EXPOSE_BY_DEFAULT = True
DEFAULT_EXPOSED_DOMAINS = [
'switch', 'light', 'group', 'media_player', 'fan', 'cover'
]
PREFIX_TRAITS = 'action.devices.traits.'
TRAIT_ONOFF = PREFIX_TRAITS + 'OnOff'
TRAIT_BRIGHTNESS = PREFIX_TRAITS + 'Brightness'
TRAIT_RGB_COLOR = PREFIX_TRAITS + 'ColorSpectrum'
TRAIT_COLOR_TEMP = PREFIX_TRAITS + 'ColorTemperature'
TRAIT_SCENE = PREFIX_TRAITS + 'Scene'
PREFIX_COMMANDS = 'action.devices.commands.'
COMMAND_ONOFF = PREFIX_COMMANDS + 'OnOff'
COMMAND_BRIGHTNESS = PREFIX_COMMANDS + 'BrightnessAbsolute'
COMMAND_COLOR = PREFIX_COMMANDS + 'ColorAbsolute'
COMMAND_ACTIVATESCENE = PREFIX_COMMANDS + 'ActivateScene'
PREFIX_TYPES = 'action.devices.types.'
TYPE_LIGHT = PREFIX_TYPES + 'LIGHT'
TYPE_SWITCH = PREFIX_TYPES + 'SWITCH'
TYPE_SCENE = PREFIX_TYPES + 'SCENE'
@@ -0,0 +1,180 @@
"""
Support for Google Actions Smart Home Control.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/google_assistant/
"""
import asyncio
import logging
# Typing imports
# pylint: disable=using-constant-test,unused-import,ungrouped-imports
# if False:
from homeassistant.core import HomeAssistant # NOQA
from aiohttp.web import Request, Response # NOQA
from typing import Dict, Tuple, Any # NOQA
from homeassistant.helpers.entity import Entity # NOQA
from homeassistant.components.http import HomeAssistantView
from homeassistant.const import (HTTP_BAD_REQUEST, HTTP_UNAUTHORIZED)
from .const import (
GOOGLE_ASSISTANT_API_ENDPOINT,
CONF_ACCESS_TOKEN,
DEFAULT_EXPOSE_BY_DEFAULT,
DEFAULT_EXPOSED_DOMAINS,
CONF_EXPOSE_BY_DEFAULT,
CONF_EXPOSED_DOMAINS,
ATTR_GOOGLE_ASSISTANT)
from .smart_home import entity_to_device, query_device, determine_service
_LOGGER = logging.getLogger(__name__)
class GoogleAssistantView(HomeAssistantView):
"""Handle Google Assistant requests."""
url = GOOGLE_ASSISTANT_API_ENDPOINT
name = 'api:google_assistant'
requires_auth = False # Uses access token from oauth flow
def __init__(self, hass: HomeAssistant, cfg: Dict[str, Any]) -> None:
"""Initialize Google Assistant view."""
super().__init__()
self.access_token = cfg.get(CONF_ACCESS_TOKEN)
self.expose_by_default = cfg.get(CONF_EXPOSE_BY_DEFAULT,
DEFAULT_EXPOSE_BY_DEFAULT)
self.exposed_domains = cfg.get(CONF_EXPOSED_DOMAINS,
DEFAULT_EXPOSED_DOMAINS)
def is_entity_exposed(self, entity) -> bool:
"""Determine if an entity should be exposed to Google Assistant."""
if entity.attributes.get('view') is not None:
# Ignore entities that are views
return False
domain = entity.domain.lower()
explicit_expose = entity.attributes.get(ATTR_GOOGLE_ASSISTANT, None)
domain_exposed_by_default = \
self.expose_by_default and domain in self.exposed_domains
# Expose an entity if the entity's domain is exposed by default and
# the configuration doesn't explicitly exclude it from being
# exposed, or if the entity is explicitly exposed
is_default_exposed = \
domain_exposed_by_default and explicit_expose is not False
return is_default_exposed or explicit_expose
@asyncio.coroutine
def handle_sync(self, hass: HomeAssistant, request_id: str):
"""Handle SYNC action."""
devices = []
for entity in hass.states.async_all():
if not self.is_entity_exposed(entity):
continue
device = entity_to_device(entity)
if device is None:
_LOGGER.warning("No mapping for %s domain", entity.domain)
continue
devices.append(device)
return self.json(
make_actions_response(request_id, {'devices': devices}))
@asyncio.coroutine
def handle_query(self,
hass: HomeAssistant,
request_id: str,
requested_devices: list):
"""Handle the QUERY action."""
devices = {}
for device in requested_devices:
devid = device.get('id')
# In theory this should never happpen
if not devid:
_LOGGER.error('Device missing ID: %s', device)
continue
state = hass.states.get(devid)
if not state:
# If we can't find a state, the device is offline
devices[devid] = {'online': False}
devices[devid] = query_device(state)
return self.json(
make_actions_response(request_id, {'devices': devices}))
@asyncio.coroutine
def handle_execute(self,
hass: HomeAssistant,
request_id: str,
requested_commands: list):
"""Handle the EXECUTE action."""
commands = []
for command in requested_commands:
ent_ids = [ent.get('id') for ent in command.get('devices', [])]
execution = command.get('execution')[0]
for eid in ent_ids:
domain = eid.split('.')[0]
(service, service_data) = determine_service(
eid, execution.get('command'), execution.get('params'))
success = yield from hass.services.async_call(
domain, service, service_data, blocking=True)
result = {"ids": [eid], "states": {}}
if success:
result['status'] = 'SUCCESS'
else:
result['status'] = 'ERROR'
commands.append(result)
return self.json(
make_actions_response(request_id, {'commands': commands}))
@asyncio.coroutine
def post(self, request: Request) -> Response:
"""Handle Google Assistant requests."""
auth = request.headers.get('Authorization', None)
if 'Bearer {}'.format(self.access_token) != auth:
return self.json_message(
"missing authorization", status_code=HTTP_UNAUTHORIZED)
data = yield from request.json() # type: dict
inputs = data.get('inputs') # type: list
if len(inputs) != 1:
_LOGGER.error('Too many inputs in request %d', len(inputs))
return self.json_message(
"too many inputs", status_code=HTTP_BAD_REQUEST)
request_id = data.get('requestId') # type: str
intent = inputs[0].get('intent')
payload = inputs[0].get('payload')
hass = request.app['hass'] # type: HomeAssistant
res = None
if intent == 'action.devices.SYNC':
res = yield from self.handle_sync(hass, request_id)
elif intent == 'action.devices.QUERY':
res = yield from self.handle_query(hass, request_id,
payload.get('devices', []))
elif intent == 'action.devices.EXECUTE':
res = yield from self.handle_execute(hass, request_id,
payload.get('commands', []))
if res:
return res
return self.json_message(
"invalid intent", status_code=HTTP_BAD_REQUEST)
def make_actions_response(request_id: str, payload: dict) -> dict:
"""Helper to simplify format for response."""
return {'requestId': request_id, 'payload': payload}
@@ -0,0 +1,161 @@
"""Support for Google Assistant Smart Home API."""
import logging
# Typing imports
# pylint: disable=using-constant-test,unused-import,ungrouped-imports
# if False:
from aiohttp.web import Request, Response # NOQA
from typing import Dict, Tuple, Any # NOQA
from homeassistant.helpers.entity import Entity # NOQA
from homeassistant.core import HomeAssistant # NOQA
from homeassistant.const import (
ATTR_SUPPORTED_FEATURES, ATTR_ENTITY_ID,
CONF_FRIENDLY_NAME, STATE_OFF,
SERVICE_TURN_OFF, SERVICE_TURN_ON
)
from homeassistant.components import (
switch, light, cover, media_player, group, fan, scene
)
from .const import (
ATTR_GOOGLE_ASSISTANT_NAME,
COMMAND_BRIGHTNESS, COMMAND_ONOFF, COMMAND_ACTIVATESCENE,
TRAIT_ONOFF, TRAIT_BRIGHTNESS, TRAIT_COLOR_TEMP,
TRAIT_RGB_COLOR, TRAIT_SCENE,
TYPE_LIGHT, TYPE_SCENE, TYPE_SWITCH,
CONF_ALIASES,
)
_LOGGER = logging.getLogger(__name__)
# Mapping is [actions schema, primary trait, optional features]
# optional is SUPPORT_* = (trait, command)
MAPPING_COMPONENT = {
group.DOMAIN: [TYPE_SCENE, TRAIT_SCENE, None],
scene.DOMAIN: [TYPE_SCENE, TRAIT_SCENE, None],
switch.DOMAIN: [TYPE_SWITCH, TRAIT_ONOFF, None],
fan.DOMAIN: [TYPE_SWITCH, TRAIT_ONOFF, None],
light.DOMAIN: [
TYPE_LIGHT, TRAIT_ONOFF, {
light.SUPPORT_BRIGHTNESS: TRAIT_BRIGHTNESS,
light.SUPPORT_RGB_COLOR: TRAIT_RGB_COLOR,
light.SUPPORT_COLOR_TEMP: TRAIT_COLOR_TEMP,
}
],
cover.DOMAIN: [
TYPE_LIGHT, TRAIT_ONOFF, {
cover.SUPPORT_SET_POSITION: TRAIT_BRIGHTNESS
}
],
media_player.DOMAIN: [
TYPE_LIGHT, TRAIT_ONOFF, {
media_player.SUPPORT_VOLUME_SET: TRAIT_BRIGHTNESS
}
],
} # type: Dict[str, list]
def make_actions_response(request_id: str, payload: dict) -> dict:
"""Helper to simplify format for response."""
return {'requestId': request_id, 'payload': payload}
def entity_to_device(entity: Entity):
"""Convert a hass entity into an google actions device."""
class_data = MAPPING_COMPONENT.get(entity.domain)
if class_data is None:
return None
device = {
'id': entity.entity_id,
'name': {},
'traits': [],
'willReportState': False,
}
device['type'] = class_data[0]
device['traits'].append(class_data[1])
# handle custom names
device['name']['name'] = \
entity.attributes.get(ATTR_GOOGLE_ASSISTANT_NAME) or \
entity.attributes.get(CONF_FRIENDLY_NAME)
# use aliases
aliases = entity.attributes.get(CONF_ALIASES)
if isinstance(aliases, list):
device['name']['nicknames'] = aliases
else:
_LOGGER.warning("%s must be a list", CONF_ALIASES)
# add trait if entity supports feature
if class_data[2]:
supported = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
for feature, trait in class_data[2].items():
if feature & supported > 0:
device['traits'].append(trait)
return device
def query_device(entity: Entity) -> dict:
"""Take an entity and return a properly formatted device object."""
final_state = entity.state != STATE_OFF
final_brightness = entity.attributes.get(light.ATTR_BRIGHTNESS, 255
if final_state else 0)
if entity.domain == media_player.DOMAIN:
level = entity.attributes.get(media_player.ATTR_MEDIA_VOLUME_LEVEL, 1.0
if final_state else 0.0)
# Convert 0.0-1.0 to 0-255
final_brightness = round(min(1.0, level) * 255)
if final_brightness is None:
final_brightness = 255 if final_state else 0
final_brightness = 100 * (final_brightness / 255)
return {
"on": final_state,
"online": True,
"brightness": int(final_brightness)
}
# erroneous bug on old pythons and pylint
# https://github.com/PyCQA/pylint/issues/1212
# pylint: disable=invalid-sequence-index
def determine_service(entity_id: str, command: str,
params: dict) -> Tuple[str, dict]:
"""
Determine service and service_data.
Attempt to return a tuple of service and service_data based on the entity
and action requested.
"""
domain = entity_id.split('.')[0]
service_data = {ATTR_ENTITY_ID: entity_id} # type: Dict[str, Any]
# special media_player handling
if domain == media_player.DOMAIN and command == COMMAND_BRIGHTNESS:
brightness = params.get('brightness', 0)
service_data[media_player.ATTR_MEDIA_VOLUME_LEVEL] = brightness / 100
return (media_player.SERVICE_VOLUME_SET, service_data)
# special cover handling
if domain == cover.DOMAIN:
if command == COMMAND_BRIGHTNESS:
service_data['position'] = params.get('brightness', 0)
return (cover.SERVICE_SET_COVER_POSITION, service_data)
if command == COMMAND_ONOFF and params.get('on') is True:
return (cover.SERVICE_OPEN_COVER, service_data)
return (cover.SERVICE_CLOSE_COVER, service_data)
if command == COMMAND_BRIGHTNESS:
brightness = params.get('brightness')
service_data['brightness'] = int(brightness / 100 * 255)
return (SERVICE_TURN_ON, service_data)
if command == COMMAND_ACTIVATESCENE or (COMMAND_ONOFF == command and
params.get('on') is True):
return (SERVICE_TURN_ON, service_data)
return (SERVICE_TURN_OFF, service_data)

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