mirror of
https://github.com/home-assistant/core.git
synced 2026-01-09 09:07:16 +01:00
Compare commits
167 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3f97944f6b | ||
|
|
1d6609e386 | ||
|
|
fb15dbf3ba | ||
|
|
c20f147949 | ||
|
|
8ed4d732ac | ||
|
|
a1578e3c6e | ||
|
|
253e787a1b | ||
|
|
0d7ee9b93b | ||
|
|
d6d4ff6888 | ||
|
|
4291bdc6b2 | ||
|
|
7d590a6b93 | ||
|
|
e3e3ed42ec | ||
|
|
e7b8d2e6df | ||
|
|
9944c60311 | ||
|
|
93143384a8 | ||
|
|
8a2bc99f63 | ||
|
|
50266e9b91 | ||
|
|
7ad094b0a7 | ||
|
|
a5715c48a4 | ||
|
|
d0f9d125a7 | ||
|
|
80c77b8696 | ||
|
|
1f73840aab | ||
|
|
fbaa489533 | ||
|
|
cff9b1bf7e | ||
|
|
ce06229c42 | ||
|
|
3acb3a86cf | ||
|
|
db1bda2975 | ||
|
|
2640db1522 | ||
|
|
cf4b72e00e | ||
|
|
5bd9be6252 | ||
|
|
e1084e3953 | ||
|
|
3afc983c05 | ||
|
|
746f4ac158 | ||
|
|
e1501c83f8 | ||
|
|
3bd12fcef6 | ||
|
|
85658b6dd1 | ||
|
|
e61ac1a4a1 | ||
|
|
8fa9992589 | ||
|
|
f96aee2832 | ||
|
|
7a6facc875 | ||
|
|
7ea482cb1d | ||
|
|
a4aa30fc73 | ||
|
|
ba63a6abc0 | ||
|
|
2252f4a257 | ||
|
|
00cba29ae1 | ||
|
|
cd473643fe | ||
|
|
4063b24ddb | ||
|
|
46734b8409 | ||
|
|
b9c80a6bb3 | ||
|
|
a2a447b466 | ||
|
|
669b3458b9 | ||
|
|
bf29cbd381 | ||
|
|
1966597d5e | ||
|
|
4685a2cd97 | ||
|
|
78fcea25bb | ||
|
|
ac3700d1c4 | ||
|
|
03480dc779 | ||
|
|
a5cff9877e | ||
|
|
b29c296ced | ||
|
|
357e5eadb8 | ||
|
|
52e922171d | ||
|
|
97695a30f5 | ||
|
|
1d12c7b0e7 | ||
|
|
15ad82b9bd | ||
|
|
03fb2b32a6 | ||
|
|
87eb6cd25a | ||
|
|
3797b6b012 | ||
|
|
2b0b431a2a | ||
|
|
e75a1690d1 | ||
|
|
444df5b09a | ||
|
|
b31890c4cb | ||
|
|
a5d95dfbdc | ||
|
|
901cfef78e | ||
|
|
2c7d6ee6b5 | ||
|
|
d3791fa45d | ||
|
|
fa81385b5c | ||
|
|
7d852a985c | ||
|
|
976626d0ab | ||
|
|
d705375a9a | ||
|
|
5e8a1496d7 | ||
|
|
bc618af193 | ||
|
|
0e076fb9e7 | ||
|
|
16a58bd1cf | ||
|
|
8be7a0a9b9 | ||
|
|
232076b41d | ||
|
|
efa9c82c38 | ||
|
|
93f45779c6 | ||
|
|
26d39d39ea | ||
|
|
b43c47cb17 | ||
|
|
67d8db2c9f | ||
|
|
3cbf8e4f87 | ||
|
|
f20a3313b0 | ||
|
|
54c3f4f001 | ||
|
|
7289d5b656 | ||
|
|
e77344f029 | ||
|
|
60438067f8 | ||
|
|
9062de0704 | ||
|
|
64453638bb | ||
|
|
5f1282a4ab | ||
|
|
645c3a67d8 | ||
|
|
88f72a654a | ||
|
|
87df102772 | ||
|
|
25ee8e551c | ||
|
|
99d48795b9 | ||
|
|
5681fa8f07 | ||
|
|
867d17b03d | ||
|
|
7751dd7535 | ||
|
|
16a885824d | ||
|
|
3934f7bf3a | ||
|
|
96cf6d59a3 | ||
|
|
d46a1a266d | ||
|
|
aaa1ebeed5 | ||
|
|
18ba50bc2d | ||
|
|
3df8840fee | ||
|
|
630b5df59a | ||
|
|
e8801ee22f | ||
|
|
74c0429437 | ||
|
|
563588651c | ||
|
|
63614a477a | ||
|
|
f891d0f5be | ||
|
|
257b8b9b80 | ||
|
|
9a786e449b | ||
|
|
09dc4d663d | ||
|
|
12709ceaa3 | ||
|
|
67df162bcc | ||
|
|
a14980716d | ||
|
|
376d4e4fa0 | ||
|
|
5397c0d73a | ||
|
|
8ab31fe139 | ||
|
|
45649824ca | ||
|
|
6f0c30ff84 | ||
|
|
943260fcd6 | ||
|
|
24aa580b63 | ||
|
|
8435d2f53d | ||
|
|
c51170ef6d | ||
|
|
9d491f5322 | ||
|
|
94662620e2 | ||
|
|
f1e378bff8 | ||
|
|
2e9db1f5c4 | ||
|
|
dec2d8d5b0 | ||
|
|
a439690bd7 | ||
|
|
5d7a2f92df | ||
|
|
4da719f43c | ||
|
|
bacecb4249 | ||
|
|
47755fb1e9 | ||
|
|
69d104bcb6 | ||
|
|
b043ac0f7f | ||
|
|
499bb3f4a2 | ||
|
|
d166f2da80 | ||
|
|
3032de1dc1 | ||
|
|
5341785aae | ||
|
|
289b1802fd | ||
|
|
0da3e73765 | ||
|
|
0a7055d475 | ||
|
|
a1ce14e70f | ||
|
|
2f2bcf0058 | ||
|
|
f929c38e98 | ||
|
|
617802653f | ||
|
|
26a485d43c | ||
|
|
456aa5a2b2 | ||
|
|
97173f495c | ||
|
|
24a8d60566 | ||
|
|
69cea6001f | ||
|
|
647b3ff0fe | ||
|
|
84365cde07 | ||
|
|
e91a1529e4 | ||
|
|
e8775ba2b4 |
16
.coveragerc
16
.coveragerc
@@ -116,11 +116,14 @@ omit =
|
||||
homeassistant/components/google.py
|
||||
homeassistant/components/*/google.py
|
||||
|
||||
homeassistant/components/habitica/*
|
||||
homeassistant/components/*/habitica.py
|
||||
|
||||
homeassistant/components/hangouts/__init__.py
|
||||
homeassistant/components/hangouts/const.py
|
||||
homeassistant/components/hangouts/hangouts_bot.py
|
||||
homeassistant/components/hangouts/hangups_utils.py
|
||||
homeassistant/components/*/hangouts.py
|
||||
homeassistant/components/*/hangouts.py
|
||||
|
||||
homeassistant/components/hdmi_cec.py
|
||||
homeassistant/components/*/hdmi_cec.py
|
||||
@@ -142,12 +145,12 @@ omit =
|
||||
|
||||
homeassistant/components/ihc/*
|
||||
homeassistant/components/*/ihc.py
|
||||
|
||||
|
||||
homeassistant/components/insteon/*
|
||||
homeassistant/components/*/insteon.py
|
||||
|
||||
homeassistant/components/insteon_local.py
|
||||
|
||||
|
||||
homeassistant/components/insteon_plm.py
|
||||
|
||||
homeassistant/components/ios.py
|
||||
@@ -225,7 +228,7 @@ omit =
|
||||
homeassistant/components/opencv.py
|
||||
homeassistant/components/*/opencv.py
|
||||
|
||||
homeassistant/components/openuv.py
|
||||
homeassistant/components/openuv/__init__.py
|
||||
homeassistant/components/*/openuv.py
|
||||
|
||||
homeassistant/components/pilight.py
|
||||
@@ -374,6 +377,7 @@ omit =
|
||||
homeassistant/components/alarm_control_panel/nx584.py
|
||||
homeassistant/components/alarm_control_panel/simplisafe.py
|
||||
homeassistant/components/alarm_control_panel/totalconnect.py
|
||||
homeassistant/components/alarm_control_panel/yale_smart_alarm.py
|
||||
homeassistant/components/apiai.py
|
||||
homeassistant/components/binary_sensor/arest.py
|
||||
homeassistant/components/binary_sensor/concord232.py
|
||||
@@ -411,6 +415,7 @@ omit =
|
||||
homeassistant/components/climate/honeywell.py
|
||||
homeassistant/components/climate/knx.py
|
||||
homeassistant/components/climate/oem.py
|
||||
homeassistant/components/climate/opentherm_gw.py
|
||||
homeassistant/components/climate/proliphix.py
|
||||
homeassistant/components/climate/radiotherm.py
|
||||
homeassistant/components/climate/sensibo.py
|
||||
@@ -759,6 +764,7 @@ omit =
|
||||
homeassistant/components/sensor/uscis.py
|
||||
homeassistant/components/sensor/vasttrafik.py
|
||||
homeassistant/components/sensor/viaggiatreno.py
|
||||
homeassistant/components/sensor/volkszaehler.py
|
||||
homeassistant/components/sensor/waqi.py
|
||||
homeassistant/components/sensor/waze_travel_time.py
|
||||
homeassistant/components/sensor/whois.py
|
||||
@@ -789,6 +795,8 @@ omit =
|
||||
homeassistant/components/switch/rest.py
|
||||
homeassistant/components/switch/rpi_rf.py
|
||||
homeassistant/components/switch/snmp.py
|
||||
homeassistant/components/switch/switchbot.py
|
||||
homeassistant/components/switch/switchmate.py
|
||||
homeassistant/components/switch/telnet.py
|
||||
homeassistant/components/switch/tplink.py
|
||||
homeassistant/components/switch/transmission.py
|
||||
|
||||
@@ -52,6 +52,8 @@ homeassistant/components/cover/template.py @PhracturedBlue
|
||||
homeassistant/components/device_tracker/automatic.py @armills
|
||||
homeassistant/components/device_tracker/tile.py @bachya
|
||||
homeassistant/components/history_graph.py @andrey-git
|
||||
homeassistant/components/light/lifx.py @amelchio
|
||||
homeassistant/components/light/lifx_legacy.py @amelchio
|
||||
homeassistant/components/light/tplink.py @rytilahti
|
||||
homeassistant/components/light/yeelight.py @rytilahti
|
||||
homeassistant/components/lock/nello.py @pschmitt
|
||||
@@ -65,6 +67,7 @@ homeassistant/components/media_player/sonos.py @amelchio
|
||||
homeassistant/components/media_player/xiaomi_tv.py @fattdev
|
||||
homeassistant/components/media_player/yamaha_musiccast.py @jalmeroth
|
||||
homeassistant/components/plant.py @ChristianKuehnel
|
||||
homeassistant/components/scene/lifx_cloud.py @amelchio
|
||||
homeassistant/components/sensor/airvisual.py @bachya
|
||||
homeassistant/components/sensor/filter.py @dgomes
|
||||
homeassistant/components/sensor/gearbest.py @HerrHofrat
|
||||
|
||||
331
LICENSE.md
331
LICENSE.md
@@ -1,194 +1,201 @@
|
||||
Apache License
|
||||
==============
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
_Version 2.0, January 2004_
|
||||
_<<http://www.apache.org/licenses/>>_
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
### Terms and Conditions for use, reproduction, and distribution
|
||||
1. Definitions.
|
||||
|
||||
#### 1. Definitions
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
“License” shall mean the terms and conditions for use, reproduction, and
|
||||
distribution as defined by Sections 1 through 9 of this document.
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
“Licensor” shall mean the copyright owner or entity authorized by the copyright
|
||||
owner that is granting the License.
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
“Legal Entity” shall mean the union of the acting entity and all other entities
|
||||
that control, are controlled by, or are under common control with that entity.
|
||||
For the purposes of this definition, “control” means **(i)** the power, direct or
|
||||
indirect, to cause the direction or management of such entity, whether by
|
||||
contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or **(iii)** beneficial ownership of such entity.
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
“You” (or “Your”) shall mean an individual or Legal Entity exercising
|
||||
permissions granted by this License.
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
“Source” form shall mean the preferred form for making modifications, including
|
||||
but not limited to software source code, documentation source, and configuration
|
||||
files.
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
“Object” form shall mean any form resulting from mechanical transformation or
|
||||
translation of a Source form, including but not limited to compiled object code,
|
||||
generated documentation, and conversions to other media types.
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
“Work” shall mean the work of authorship, whether in Source or Object form, made
|
||||
available under the License, as indicated by a copyright notice that is included
|
||||
in or attached to the work (an example is provided in the Appendix below).
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
“Derivative Works” shall mean any work, whether in Source or Object form, that
|
||||
is based on (or derived from) the Work and for which the editorial revisions,
|
||||
annotations, elaborations, or other modifications represent, as a whole, an
|
||||
original work of authorship. For the purposes of this License, Derivative Works
|
||||
shall not include works that remain separable from, or merely link (or bind by
|
||||
name) to the interfaces of, the Work and Derivative Works thereof.
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
“Contribution” shall mean any work of authorship, including the original version
|
||||
of the Work and any modifications or additions to that Work or Derivative Works
|
||||
thereof, that is intentionally submitted to Licensor for inclusion in the Work
|
||||
by the copyright owner or by an individual or Legal Entity authorized to submit
|
||||
on behalf of the copyright owner. For the purposes of this definition,
|
||||
“submitted” means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems, and
|
||||
issue tracking systems that are managed by, or on behalf of, the Licensor for
|
||||
the purpose of discussing and improving the Work, but excluding communication
|
||||
that is conspicuously marked or otherwise designated in writing by the copyright
|
||||
owner as “Not a Contribution.”
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
“Contributor” shall mean Licensor and any individual or Legal Entity on behalf
|
||||
of whom a Contribution has been received by Licensor and subsequently
|
||||
incorporated within the Work.
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
#### 2. Grant of Copyright License
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby
|
||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||
irrevocable copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the Work and such
|
||||
Derivative Works in Source or Object form.
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
#### 3. Grant of Patent License
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby
|
||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||
irrevocable (except as stated in this section) patent license to make, have
|
||||
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
|
||||
such license applies only to those patent claims licensable by such Contributor
|
||||
that are necessarily infringed by their Contribution(s) alone or by combination
|
||||
of their Contribution(s) with the Work to which such Contribution(s) was
|
||||
submitted. If You institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
|
||||
Contribution incorporated within the Work constitutes direct or contributory
|
||||
patent infringement, then any patent licenses granted to You under this License
|
||||
for that Work shall terminate as of the date such litigation is filed.
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
#### 4. Redistribution
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
You may reproduce and distribute copies of the Work or Derivative Works thereof
|
||||
in any medium, with or without modifications, and in Source or Object form,
|
||||
provided that You meet the following conditions:
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
* **(a)** You must give any other recipients of the Work or Derivative Works a copy of
|
||||
this License; and
|
||||
* **(b)** You must cause any modified files to carry prominent notices stating that You
|
||||
changed the files; and
|
||||
* **(c)** You must retain, in the Source form of any Derivative Works that You distribute,
|
||||
all copyright, patent, trademark, and attribution notices from the Source form
|
||||
of the Work, excluding those notices that do not pertain to any part of the
|
||||
Derivative Works; and
|
||||
* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any
|
||||
Derivative Works that You distribute must include a readable copy of the
|
||||
attribution notices contained within such NOTICE file, excluding those notices
|
||||
that do not pertain to any part of the Derivative Works, in at least one of the
|
||||
following places: within a NOTICE text file distributed as part of the
|
||||
Derivative Works; within the Source form or documentation, if provided along
|
||||
with the Derivative Works; or, within a display generated by the Derivative
|
||||
Works, if and wherever such third-party notices normally appear. The contents of
|
||||
the NOTICE file are for informational purposes only and do not modify the
|
||||
License. You may add Your own attribution notices within Derivative Works that
|
||||
You distribute, alongside or as an addendum to the NOTICE text from the Work,
|
||||
provided that such additional attribution notices cannot be construed as
|
||||
modifying the License.
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and may provide
|
||||
additional or different license terms and conditions for use, reproduction, or
|
||||
distribution of Your modifications, or for any such Derivative Works as a whole,
|
||||
provided Your use, reproduction, and distribution of the Work otherwise complies
|
||||
with the conditions stated in this License.
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
#### 5. Submission of Contributions
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
Unless You explicitly state otherwise, any Contribution intentionally submitted
|
||||
for inclusion in the Work by You to the Licensor shall be under the terms and
|
||||
conditions of this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify the terms of
|
||||
any separate license agreement you may have executed with Licensor regarding
|
||||
such Contributions.
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
#### 6. Trademarks
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
This License does not grant permission to use the trade names, trademarks,
|
||||
service marks, or product names of the Licensor, except as required for
|
||||
reasonable and customary use in describing the origin of the Work and
|
||||
reproducing the content of the NOTICE file.
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
#### 7. Disclaimer of Warranty
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
Unless required by applicable law or agreed to in writing, Licensor provides the
|
||||
Work (and each Contributor provides its Contributions) on an “AS IS” BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
|
||||
including, without limitation, any warranties or conditions of TITLE,
|
||||
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
|
||||
solely responsible for determining the appropriateness of using or
|
||||
redistributing the Work and assume any risks associated with Your exercise of
|
||||
permissions under this License.
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
#### 8. Limitation of Liability
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
In no event and under no legal theory, whether in tort (including negligence),
|
||||
contract, or otherwise, unless required by applicable law (such as deliberate
|
||||
and grossly negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special, incidental,
|
||||
or consequential damages of any character arising as a result of this License or
|
||||
out of the use or inability to use the Work (including but not limited to
|
||||
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
|
||||
any and all other commercial damages or losses), even if such Contributor has
|
||||
been advised of the possibility of such damages.
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
#### 9. Accepting Warranty or Additional Liability
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
While redistributing the Work or Derivative Works thereof, You may choose to
|
||||
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
|
||||
other liability obligations and/or rights consistent with this License. However,
|
||||
in accepting such obligations, You may act only on Your own behalf and on Your
|
||||
sole responsibility, not on behalf of any other Contributor, and only if You
|
||||
agree to indemnify, defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason of your
|
||||
accepting any such warranty or additional liability.
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
_END OF TERMS AND CONDITIONS_
|
||||
|
||||
### APPENDIX: How to apply the Apache License to your work
|
||||
|
||||
To apply the Apache License to your work, attach the following boilerplate
|
||||
notice, with the fields enclosed by brackets `[]` replaced with your own
|
||||
identifying information. (Don't include the brackets!) The text should be
|
||||
enclosed in the appropriate comment syntax for the file format. We also
|
||||
recommend that a file or class name and description of purpose be included on
|
||||
the same “printed page” as the copyright notice for easier identification within
|
||||
third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
@@ -24,7 +24,7 @@ USER_SCHEMA = vol.Schema({
|
||||
CONFIG_SCHEMA = AUTH_PROVIDER_SCHEMA.extend({
|
||||
}, extra=vol.PREVENT_EXTRA)
|
||||
|
||||
LEGACY_USER = 'homeassistant'
|
||||
LEGACY_USER_NAME = 'Legacy API password user'
|
||||
|
||||
|
||||
class InvalidAuthError(HomeAssistantError):
|
||||
@@ -52,23 +52,21 @@ class LegacyApiPasswordAuthProvider(AuthProvider):
|
||||
|
||||
async def async_get_or_create_credentials(
|
||||
self, flow_result: Dict[str, str]) -> Credentials:
|
||||
"""Return LEGACY_USER always."""
|
||||
for credential in await self.async_credentials():
|
||||
if credential.data['username'] == LEGACY_USER:
|
||||
return credential
|
||||
"""Return credentials for this login."""
|
||||
credentials = await self.async_credentials()
|
||||
if credentials:
|
||||
return credentials[0]
|
||||
|
||||
return self.async_create_credentials({
|
||||
'username': LEGACY_USER
|
||||
})
|
||||
return self.async_create_credentials({})
|
||||
|
||||
async def async_user_meta_for_credentials(
|
||||
self, credentials: Credentials) -> UserMeta:
|
||||
"""
|
||||
Set name as LEGACY_USER always.
|
||||
Return info for the user.
|
||||
|
||||
Will be used to populate info when creating a new user.
|
||||
"""
|
||||
return UserMeta(name=LEGACY_USER, is_active=True)
|
||||
return UserMeta(name=LEGACY_USER_NAME, is_active=True)
|
||||
|
||||
|
||||
class LegacyLoginFlow(LoginFlow):
|
||||
|
||||
98
homeassistant/components/alarm_control_panel/yale_smart_alarm.py
Executable file
98
homeassistant/components/alarm_control_panel/yale_smart_alarm.py
Executable file
@@ -0,0 +1,98 @@
|
||||
"""
|
||||
Yale Smart Alarm client for interacting with the Yale Smart Alarm System API.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://www.home-assistant.io/components/alarm_control_panel.yale_smart_alarm
|
||||
"""
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.alarm_control_panel import (
|
||||
AlarmControlPanel, PLATFORM_SCHEMA)
|
||||
from homeassistant.const import (
|
||||
CONF_PASSWORD, CONF_USERNAME, CONF_NAME,
|
||||
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
REQUIREMENTS = ['yalesmartalarmclient==0.1.4']
|
||||
|
||||
CONF_AREA_ID = 'area_id'
|
||||
|
||||
DEFAULT_NAME = 'Yale Smart Alarm'
|
||||
|
||||
DEFAULT_AREA_ID = '1'
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_USERNAME): cv.string,
|
||||
vol.Required(CONF_PASSWORD): cv.string,
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_AREA_ID, default=DEFAULT_AREA_ID): cv.string,
|
||||
})
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up the alarm platform."""
|
||||
name = config[CONF_NAME]
|
||||
username = config[CONF_USERNAME]
|
||||
password = config[CONF_PASSWORD]
|
||||
area_id = config[CONF_AREA_ID]
|
||||
|
||||
from yalesmartalarmclient.client import (
|
||||
YaleSmartAlarmClient, AuthenticationError)
|
||||
try:
|
||||
client = YaleSmartAlarmClient(username, password, area_id)
|
||||
except AuthenticationError:
|
||||
_LOGGER.error("Authentication failed. Check credentials")
|
||||
return
|
||||
|
||||
add_entities([YaleAlarmDevice(name, client)], True)
|
||||
|
||||
|
||||
class YaleAlarmDevice(AlarmControlPanel):
|
||||
"""Represent a Yale Smart Alarm."""
|
||||
|
||||
def __init__(self, name, client):
|
||||
"""Initialize the Yale Alarm Device."""
|
||||
self._name = name
|
||||
self._client = client
|
||||
self._state = None
|
||||
|
||||
from yalesmartalarmclient.client import (YALE_STATE_DISARM,
|
||||
YALE_STATE_ARM_PARTIAL,
|
||||
YALE_STATE_ARM_FULL)
|
||||
self._state_map = {
|
||||
YALE_STATE_DISARM: STATE_ALARM_DISARMED,
|
||||
YALE_STATE_ARM_PARTIAL: STATE_ALARM_ARMED_HOME,
|
||||
YALE_STATE_ARM_FULL: STATE_ALARM_ARMED_AWAY
|
||||
}
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the device."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the state of the device."""
|
||||
return self._state
|
||||
|
||||
def update(self):
|
||||
"""Return the state of the device."""
|
||||
armed_status = self._client.get_armed_status()
|
||||
|
||||
self._state = self._state_map.get(armed_status)
|
||||
|
||||
def alarm_disarm(self, code=None):
|
||||
"""Send disarm command."""
|
||||
self._client.disarm()
|
||||
|
||||
def alarm_arm_home(self, code=None):
|
||||
"""Send arm home command."""
|
||||
self._client.arm_partial()
|
||||
|
||||
def alarm_arm_away(self, code=None):
|
||||
"""Send arm away command."""
|
||||
self._client.arm_full()
|
||||
@@ -15,7 +15,7 @@ import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.dispatcher import (
|
||||
async_dispatcher_connect, async_dispatcher_send)
|
||||
|
||||
REQUIREMENTS = ['asterisk_mbox==0.4.0']
|
||||
REQUIREMENTS = ['asterisk_mbox==0.5.0']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
7
homeassistant/components/auth/.translations/ar.json
Normal file
7
homeassistant/components/auth/.translations/ar.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"mfa_setup": {
|
||||
"totp": {
|
||||
"title": "TOTP"
|
||||
}
|
||||
}
|
||||
}
|
||||
16
homeassistant/components/auth/.translations/de.json
Normal file
16
homeassistant/components/auth/.translations/de.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"mfa_setup": {
|
||||
"totp": {
|
||||
"error": {
|
||||
"invalid_code": "Ung\u00fcltiger Code, bitte versuche es erneut. Wenn Sie diesen Fehler regelm\u00e4\u00dfig erhalten, stelle sicher, dass die Uhr deines Home Assistant-Systems korrekt ist."
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"description": "Um die Zwei-Faktor-Authentifizierung mit zeitbasierten Einmalpassw\u00f6rtern zu aktivieren, scanne den QR-Code mit Ihrer Authentifizierungs-App. Wenn du keine hast, empfehlen wir entweder [Google Authenticator] (https://support.google.com/accounts/answer/1066447) oder [Authy] (https://authy.com/). \n\n {qr_code} \n \nNachdem du den Code gescannt hast, gebe den sechsstelligen Code aus der App ein, um das Setup zu \u00fcberpr\u00fcfen. Wenn es Probleme beim Scannen des QR-Codes gibt, f\u00fchre ein manuelles Setup mit dem Code ** ` {code} ` ** durch.",
|
||||
"title": "Richte die Zwei-Faktor-Authentifizierung mit TOTP ein"
|
||||
}
|
||||
},
|
||||
"title": "TOTP"
|
||||
}
|
||||
}
|
||||
}
|
||||
12
homeassistant/components/auth/.translations/es-419.json
Normal file
12
homeassistant/components/auth/.translations/es-419.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"mfa_setup": {
|
||||
"totp": {
|
||||
"step": {
|
||||
"init": {
|
||||
"title": "Configurar la autenticaci\u00f3n de dos factores mediante TOTP"
|
||||
}
|
||||
},
|
||||
"title": "TOTP"
|
||||
}
|
||||
}
|
||||
}
|
||||
16
homeassistant/components/auth/.translations/fr.json
Normal file
16
homeassistant/components/auth/.translations/fr.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"mfa_setup": {
|
||||
"totp": {
|
||||
"error": {
|
||||
"invalid_code": "Code invalide. S'il vous pla\u00eet essayez \u00e0 nouveau. Si cette erreur persiste, assurez-vous que l'horloge de votre syst\u00e8me Home Assistant est correcte."
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"description": "Pour activer l'authentification \u00e0 deux facteurs \u00e0 l'aide de mots de passe \u00e0 utilisation unique bas\u00e9s sur l'heure, num\u00e9risez le code QR avec votre application d'authentification. Si vous n'en avez pas, nous vous recommandons d'utiliser [Google Authenticator] (https://support.google.com/accounts/answer/1066447) ou [Authy] (https://authy.com/). \n\n {qr_code} \n \n Apr\u00e8s avoir num\u00e9ris\u00e9 le code, entrez le code \u00e0 six chiffres de votre application pour v\u00e9rifier la configuration. Si vous rencontrez des probl\u00e8mes lors de l\u2019analyse du code QR, effectuez une configuration manuelle avec le code ** ` {code} ` **.",
|
||||
"title": "Configurer une authentification \u00e0 deux facteurs \u00e0 l'aide de TOTP"
|
||||
}
|
||||
},
|
||||
"title": "TOTP (Mot de passe \u00e0 utilisation unique bas\u00e9 sur le temps)"
|
||||
}
|
||||
}
|
||||
}
|
||||
16
homeassistant/components/auth/.translations/hu.json
Normal file
16
homeassistant/components/auth/.translations/hu.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"mfa_setup": {
|
||||
"totp": {
|
||||
"error": {
|
||||
"invalid_code": "\u00c9rv\u00e9nytelen k\u00f3d, pr\u00f3b\u00e1ld \u00fajra. Ha ez a hiba folyamatosan el\u0151fordul, akkor gy\u0151z\u0151dj meg r\u00f3la, hogy a Home Assistant rendszered \u00f3r\u00e1ja pontosan j\u00e1r."
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"description": "Ahhoz, hogy haszn\u00e1lhasd a k\u00e9tfaktoros hiteles\u00edt\u00e9st id\u0151alap\u00fa egyszeri jelszavakkal, szkenneld be a QR k\u00f3dot a hiteles\u00edt\u00e9si applik\u00e1ci\u00f3ddal. Ha m\u00e9g nincsen, akkor a [Google Hiteles\u00edt\u0151](https://support.google.com/accounts/answer/1066447)t vagy az [Authy](https://authy.com/)-t aj\u00e1nljuk.\n\n{qr_code}\n\nA k\u00f3d beolvas\u00e1sa ut\u00e1n add meg a hat sz\u00e1mjegy\u0171 k\u00f3dot az applik\u00e1ci\u00f3b\u00f3l a telep\u00edt\u00e9s ellen\u0151rz\u00e9s\u00e9hez. Ha probl\u00e9m\u00e1ba \u00fctk\u00f6z\u00f6l a QR k\u00f3d beolvas\u00e1s\u00e1n\u00e1l, akkor ind\u00edts egy k\u00e9zi be\u00e1ll\u00edt\u00e1st a **`{code}`** k\u00f3ddal.",
|
||||
"title": "K\u00e9tfaktoros hiteles\u00edt\u00e9s be\u00e1ll\u00edt\u00e1sa TOTP haszn\u00e1lat\u00e1val"
|
||||
}
|
||||
},
|
||||
"title": "TOTP"
|
||||
}
|
||||
}
|
||||
}
|
||||
13
homeassistant/components/auth/.translations/it.json
Normal file
13
homeassistant/components/auth/.translations/it.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"mfa_setup": {
|
||||
"totp": {
|
||||
"step": {
|
||||
"init": {
|
||||
"description": "Per attivare l'autenticazione a due fattori utilizzando password monouso basate sul tempo, eseguire la scansione del codice QR con l'app di autenticazione. Se non ne hai uno, ti consigliamo [Google Authenticator] (https://support.google.com/accounts/answer/1066447) o [Authy] (https://authy.com/). \n\n {qr_code} \n \n Dopo aver scansionato il codice, inserisci il codice a sei cifre dalla tua app per verificare la configurazione. Se riscontri problemi con la scansione del codice QR, esegui una configurazione manuale con codice ** ` {code} ` **.",
|
||||
"title": "Imposta l'autenticazione a due fattori usando TOTP"
|
||||
}
|
||||
},
|
||||
"title": "TOTP"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,11 @@
|
||||
"mfa_setup": {
|
||||
"totp": {
|
||||
"error": {
|
||||
"invalid_code": "\uc798\ubabb\ub41c \ucf54\ub4dc \uc785\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694. \uc774 \uc624\ub958\uac00 \uc9c0\uc18d\uc801\uc73c\ub85c \ubc1c\uc0dd\ud55c\ub2e4\uba74 Home Assistant \uc758 \uc2dc\uacc4\uac00 \uc815\ud655\ud55c\uc9c0 \ud655\uc778\ud574\ubcf4\uc138\uc694."
|
||||
"invalid_code": "\uc798\ubabb\ub41c \ucf54\ub4dc \uc785\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694. \uc774 \uc624\ub958\uac00 \uc9c0\uc18d\uc801\uc73c\ub85c \ubc1c\uc0dd\ud55c\ub2e4\uba74 Home Assistant \uc758 \uc2dc\uac04\uc124\uc815\uc774 \uc62c\ubc14\ub978\uc9c0 \ud655\uc778\ud574\ubcf4\uc138\uc694."
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"description": "\uc2dc\uac04 \uae30\ubc18\uc758 \uc77c\ud68c\uc6a9 \ube44\ubc00\ubc88\ud638\ub97c \uc0ac\uc6a9\ud558\ub294 2\ub2e8\uacc4 \uc778\uc99d\uc744 \ud558\ub824\uba74 \uc778\uc99d\uc6a9 \uc571\uc744 \uc774\uc6a9\ud574\uc11c QR \ucf54\ub4dc\ub97c \uc2a4\uce94\ud574 \uc8fc\uc138\uc694. \uc778\uc99d\uc6a9 \uc571\uc740 [Google Authenticator](https://support.google.com/accounts/answer/1066447) \ub098 [Authy](https://authy.com/) \ub97c \ucd94\ucc9c\ub4dc\ub9bd\ub2c8\ub2e4.\n\n{qr_code}\n\n\uc2a4\uce94 \ud6c4\uc5d0 \uc0dd\uc131\ub41c 6\uc790\ub9ac \ucf54\ub4dc\ub97c \uc785\ub825\ud574\uc11c \uc124\uc815\uc744 \ud655\uc778\ud558\uc138\uc694. QR \ucf54\ub4dc \uc2a4\uce94\uc5d0 \ubb38\uc81c\uac00 \uc788\ub2e4\uba74, **`{code}`** \ucf54\ub4dc\ub85c \uc9c1\uc811 \uc124\uc815\ud574\ubcf4\uc138\uc694.",
|
||||
"description": "\uc2dc\uac04 \uae30\ubc18\uc758 \uc77c\ud68c\uc6a9 \ube44\ubc00\ubc88\ud638\ub97c \uc0ac\uc6a9\ud558\ub294 2\ub2e8\uacc4 \uc778\uc99d\uc744 \ud558\ub824\uba74 \uc778\uc99d\uc6a9 \uc571\uc744 \uc774\uc6a9\ud574\uc11c QR \ucf54\ub4dc\ub97c \uc2a4\uce94\ud574 \uc8fc\uc138\uc694. \uc778\uc99d\uc6a9 \uc571\uc740 [Google OTP](https://support.google.com/accounts/answer/1066447) \ub610\ub294 [Authy](https://authy.com/) \ub97c \ucd94\ucc9c\ub4dc\ub9bd\ub2c8\ub2e4.\n\n{qr_code}\n\n\uc2a4\uce94 \ud6c4\uc5d0 \uc0dd\uc131\ub41c 6\uc790\ub9ac \ucf54\ub4dc\ub97c \uc785\ub825\ud574\uc11c \uc124\uc815\uc744 \ud655\uc778\ud558\uc138\uc694. QR \ucf54\ub4dc \uc2a4\uce94\uc5d0 \ubb38\uc81c\uac00 \uc788\ub2e4\uba74, **`{code}`** \ucf54\ub4dc\ub85c \uc9c1\uc811 \uc124\uc815\ud574\ubcf4\uc138\uc694.",
|
||||
"title": "TOTP \ub97c \uc0ac\uc6a9\ud558\uc5ec 2 \ub2e8\uacc4 \uc778\uc99d \uad6c\uc131"
|
||||
}
|
||||
},
|
||||
|
||||
16
homeassistant/components/auth/.translations/nl.json
Normal file
16
homeassistant/components/auth/.translations/nl.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"mfa_setup": {
|
||||
"totp": {
|
||||
"error": {
|
||||
"invalid_code": "Ongeldige code, probeer het opnieuw. Als u deze fout blijft krijgen, controleer dan of de klok van uw Home Assistant systeem correct is ingesteld."
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"description": "Voor het activeren van twee-factor-authenticatie via tijdgebonden eenmalige wachtwoorden: scan de QR code met uw authenticatie-app. Als u nog geen app heeft, adviseren we [Google Authenticator (https://support.google.com/accounts/answer/1066447) of [Authy](https://authy.com/).\n\n{qr_code}\n\nNa het scannen van de code voert u de zescijferige code uit uw app in om de instelling te controleren. Als u problemen heeft met het scannen van de QR-code, voert u een handmatige configuratie uit met code **`{code}`**.",
|
||||
"title": "Configureer twee-factor-authenticatie via TOTP"
|
||||
}
|
||||
},
|
||||
"title": "TOTP"
|
||||
}
|
||||
}
|
||||
}
|
||||
16
homeassistant/components/auth/.translations/no.json
Normal file
16
homeassistant/components/auth/.translations/no.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"mfa_setup": {
|
||||
"totp": {
|
||||
"error": {
|
||||
"invalid_code": "Ugyldig kode, pr\u00f8v igjen. Hvis du f\u00e5r denne feilen konsekvent, m\u00e5 du s\u00f8rge for at klokken p\u00e5 Home Assistant systemet er riktig."
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"description": "For \u00e5 aktivere tofaktorautentisering ved hjelp av tidsbaserte engangspassord, skann QR-koden med autentiseringsappen din. Hvis du ikke har en, kan vi anbefale enten [Google Authenticator](https://support.google.com/accounts/answer/1066447) eller [Authy](https://authy.com/). \n\n {qr_code} \n \nEtter at du har skannet koden, skriver du inn den seks-sifrede koden fra appen din for \u00e5 kontrollere oppsettet. Dersom du har problemer med \u00e5 skanne QR-koden kan du taste inn f\u00f8lgende kode manuelt: **`{code}`**.",
|
||||
"title": "Konfigurer tofaktorautentisering ved hjelp av TOTP"
|
||||
}
|
||||
},
|
||||
"title": "TOTP"
|
||||
}
|
||||
}
|
||||
}
|
||||
16
homeassistant/components/auth/.translations/pl.json
Normal file
16
homeassistant/components/auth/.translations/pl.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"mfa_setup": {
|
||||
"totp": {
|
||||
"error": {
|
||||
"invalid_code": "Nieprawid\u0142owy kod, spr\u00f3buj ponownie. Je\u015bli b\u0142\u0105d b\u0119dzie si\u0119 powtarza\u0142, upewnij si\u0119, \u017ce czas zegara systemu Home Assistant jest prawid\u0142owy."
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"description": "Aby aktywowa\u0107 uwierzytelnianie dwusk\u0142adnikowe przy u\u017cyciu jednorazowych hase\u0142 opartych na czasie, zeskanuj kod QR za pomoc\u0105 aplikacji uwierzytelniaj\u0105cej. Je\u015bli jej nie masz, polecamy [Google Authenticator](https://support.google.com/accounts/answer/1066447) lub [Authy](https://authy.com/).\n\n{qr_code} \n \nPo zeskanowaniu kodu wprowad\u017a sze\u015bciocyfrowy kod z aplikacji, aby zweryfikowa\u0107 konfiguracj\u0119. Je\u015bli masz problemy z zeskanowaniem kodu QR, wykonaj r\u0119czn\u0105 konfiguracj\u0119 z kodem **`{code}`**.",
|
||||
"title": "Skonfiguruj uwierzytelnianie dwusk\u0142adnikowe za pomoc\u0105 hase\u0142 jednorazowych opartych na czasie"
|
||||
}
|
||||
},
|
||||
"title": "Has\u0142a jednorazowe oparte na czasie"
|
||||
}
|
||||
}
|
||||
}
|
||||
16
homeassistant/components/auth/.translations/pt.json
Normal file
16
homeassistant/components/auth/.translations/pt.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"mfa_setup": {
|
||||
"totp": {
|
||||
"error": {
|
||||
"invalid_code": "C\u00f3digo inv\u00e1lido, por favor, tente novamente. Se receber este erro constantemente, por favor, certifique-se de que o rel\u00f3gio do sistema que hospeda o Home Assistent \u00e9 preciso."
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"description": "Para ativar a autentica\u00e7\u00e3o com dois fatores utilizando passwords unicas temporais (OTP), ler o c\u00f3digo QR com a sua aplica\u00e7\u00e3o de autentica\u00e7\u00e3o. Se voc\u00ea n\u00e3o tiver uma, recomendamos [Google Authenticator](https://support.google.com/accounts/answer/1066447) ou [Authy](https://authy.com/).\n\n{qr_code}\n\nDepois de ler o c\u00f3digo, introduza o c\u00f3digo de seis d\u00edgitos fornecido pela sua aplica\u00e7\u00e3o para verificar a configura\u00e7\u00e3o. Se tiver problemas a ler o c\u00f3digo QR, fa\u00e7a uma configura\u00e7\u00e3o manual com o c\u00f3digo **`{c\u00f3digo}`**.",
|
||||
"title": "Configurar autentica\u00e7\u00e3o com dois fatores usando TOTP"
|
||||
}
|
||||
},
|
||||
"title": "TOTP"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"description": "\u0427\u0442\u043e\u0431\u044b \u0430\u043a\u0442\u0438\u0432\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0434\u0432\u0443\u0445\u0444\u0430\u043a\u0442\u043e\u0440\u043d\u0443\u044e \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044e \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u043e\u0434\u043d\u043e\u0440\u0430\u0437\u043e\u0432\u044b\u0445 \u043f\u0430\u0440\u043e\u043b\u0435\u0439, \u043e\u0441\u043d\u043e\u0432\u0430\u043d\u043d\u044b\u0445 \u043d\u0430 \u0432\u0440\u0435\u043c\u0435\u043d\u0438, \u043e\u0442\u0441\u043a\u0430\u043d\u0438\u0440\u0443\u0439\u0442\u0435 QR-\u043a\u043e\u0434 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043f\u043e\u0434\u043b\u0438\u043d\u043d\u043e\u0441\u0442\u0438. \u0415\u0441\u043b\u0438 \u0443 \u0432\u0430\u0441 \u0435\u0433\u043e \u043d\u0435\u0442, \u043c\u044b \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043b\u0438\u0431\u043e [Google Authenticator] (https://support.google.com/accounts/answer/1066447), \u043b\u0438\u0431\u043e [Authy] (https://authy.com/). \n\n {qr_code} \n \n \u041f\u043e\u0441\u043b\u0435 \u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f QR-\u043a\u043e\u0434\u0430 \u0432\u0432\u0435\u0434\u0438\u0442\u0435 \u0448\u0435\u0441\u0442\u0438\u0437\u043d\u0430\u0447\u043d\u044b\u0439 \u043a\u043e\u0434 \u0438\u0437 \u0432\u0430\u0448\u0435\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443. \u0415\u0441\u043b\u0438 \u0443 \u0432\u0430\u0441 \u0435\u0441\u0442\u044c \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b \u0441\u043e \u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435\u043c QR-\u043a\u043e\u0434\u0430, \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u0435 \u0440\u0443\u0447\u043d\u0443\u044e \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443 \u0441 \u043a\u043e\u0434\u043e\u043c ** ` {code} ` **.",
|
||||
"description": "\u0427\u0442\u043e\u0431\u044b \u0430\u043a\u0442\u0438\u0432\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0434\u0432\u0443\u0445\u0444\u0430\u043a\u0442\u043e\u0440\u043d\u0443\u044e \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044e \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u043e\u0434\u043d\u043e\u0440\u0430\u0437\u043e\u0432\u044b\u0445 \u043f\u0430\u0440\u043e\u043b\u0435\u0439, \u043e\u0441\u043d\u043e\u0432\u0430\u043d\u043d\u044b\u0445 \u043d\u0430 \u0432\u0440\u0435\u043c\u0435\u043d\u0438, \u043e\u0442\u0441\u043a\u0430\u043d\u0438\u0440\u0443\u0439\u0442\u0435 QR-\u043a\u043e\u0434 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043f\u043e\u0434\u043b\u0438\u043d\u043d\u043e\u0441\u0442\u0438. \u0415\u0441\u043b\u0438 \u0443 \u0432\u0430\u0441 \u0435\u0433\u043e \u043d\u0435\u0442, \u043c\u044b \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043b\u0438\u0431\u043e [Google Authenticator](https://support.google.com/accounts/answer/1066447), \u043b\u0438\u0431\u043e [Authy](https://authy.com/). \n\n {qr_code} \n \n\u041f\u043e\u0441\u043b\u0435 \u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f QR-\u043a\u043e\u0434\u0430 \u0432\u0432\u0435\u0434\u0438\u0442\u0435 \u0448\u0435\u0441\u0442\u0438\u0437\u043d\u0430\u0447\u043d\u044b\u0439 \u043a\u043e\u0434 \u0438\u0437 \u0432\u0430\u0448\u0435\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443. \u0415\u0441\u043b\u0438 \u0443 \u0432\u0430\u0441 \u0435\u0441\u0442\u044c \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b \u0441\u043e \u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435\u043c QR-\u043a\u043e\u0434\u0430, \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043a\u043e\u0434\u0430 **`{code}`**.",
|
||||
"title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0434\u0432\u0443\u0445\u0444\u0430\u043a\u0442\u043e\u0440\u043d\u043e\u0439 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c TOTP"
|
||||
}
|
||||
},
|
||||
|
||||
16
homeassistant/components/auth/.translations/sl.json
Normal file
16
homeassistant/components/auth/.translations/sl.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"mfa_setup": {
|
||||
"totp": {
|
||||
"error": {
|
||||
"invalid_code": "Neveljavna koda, prosimo, poskusite znova. \u010ce dobite to sporo\u010dilo ve\u010dkrat, prosimo poskrbite, da bo ura va\u0161ega Home Assistenta to\u010dna."
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"description": "\u010ce \u017eelite aktivirati preverjanje pristnosti dveh faktorjev z enkratnimi gesli, ki temeljijo na \u010dasu, skenirajte kodo QR s svojo aplikacijo za preverjanje pristnosti. \u010ce je nimate, priporo\u010damo bodisi [Google Authenticator] (https://support.google.com/accounts/answer/1066447) ali [Authy] (https://authy.com/). \n\n {qr_code} \n \n Po skeniranju kode vnesite \u0161estmestno kodo iz aplikacije, da preverite nastavitev. \u010ce imate te\u017eave pri skeniranju kode QR, naredite ro\u010dno nastavitev s kodo ** ` {code} ` **.",
|
||||
"title": "Nastavite dvofaktorsko avtentifikacijo s pomo\u010djo TOTP-ja"
|
||||
}
|
||||
},
|
||||
"title": "TOTP"
|
||||
}
|
||||
}
|
||||
}
|
||||
12
homeassistant/components/auth/.translations/sv.json
Normal file
12
homeassistant/components/auth/.translations/sv.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"mfa_setup": {
|
||||
"totp": {
|
||||
"step": {
|
||||
"init": {
|
||||
"title": "St\u00e4ll in tv\u00e5faktorsautentisering med TOTP"
|
||||
}
|
||||
},
|
||||
"title": "TOTP"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -158,27 +158,26 @@ def async_reload(hass):
|
||||
return hass.services.async_call(DOMAIN, SERVICE_RELOAD)
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup(hass, config):
|
||||
async def async_setup(hass, config):
|
||||
"""Set up the automation."""
|
||||
component = EntityComponent(_LOGGER, DOMAIN, hass,
|
||||
group_name=GROUP_NAME_ALL_AUTOMATIONS)
|
||||
|
||||
yield from _async_process_config(hass, config, component)
|
||||
await _async_process_config(hass, config, component)
|
||||
|
||||
@asyncio.coroutine
|
||||
def trigger_service_handler(service_call):
|
||||
async def trigger_service_handler(service_call):
|
||||
"""Handle automation triggers."""
|
||||
tasks = []
|
||||
for entity in component.async_extract_from_service(service_call):
|
||||
tasks.append(entity.async_trigger(
|
||||
service_call.data.get(ATTR_VARIABLES), True))
|
||||
service_call.data.get(ATTR_VARIABLES),
|
||||
skip_condition=True,
|
||||
context=service_call.context))
|
||||
|
||||
if tasks:
|
||||
yield from asyncio.wait(tasks, loop=hass.loop)
|
||||
await asyncio.wait(tasks, loop=hass.loop)
|
||||
|
||||
@asyncio.coroutine
|
||||
def turn_onoff_service_handler(service_call):
|
||||
async def turn_onoff_service_handler(service_call):
|
||||
"""Handle automation turn on/off service calls."""
|
||||
tasks = []
|
||||
method = 'async_{}'.format(service_call.service)
|
||||
@@ -186,10 +185,9 @@ def async_setup(hass, config):
|
||||
tasks.append(getattr(entity, method)())
|
||||
|
||||
if tasks:
|
||||
yield from asyncio.wait(tasks, loop=hass.loop)
|
||||
await asyncio.wait(tasks, loop=hass.loop)
|
||||
|
||||
@asyncio.coroutine
|
||||
def toggle_service_handler(service_call):
|
||||
async def toggle_service_handler(service_call):
|
||||
"""Handle automation toggle service calls."""
|
||||
tasks = []
|
||||
for entity in component.async_extract_from_service(service_call):
|
||||
@@ -199,15 +197,14 @@ def async_setup(hass, config):
|
||||
tasks.append(entity.async_turn_on())
|
||||
|
||||
if tasks:
|
||||
yield from asyncio.wait(tasks, loop=hass.loop)
|
||||
await asyncio.wait(tasks, loop=hass.loop)
|
||||
|
||||
@asyncio.coroutine
|
||||
def reload_service_handler(service_call):
|
||||
async def reload_service_handler(service_call):
|
||||
"""Remove all automations and load new ones from config."""
|
||||
conf = yield from component.async_prepare_reload()
|
||||
conf = await component.async_prepare_reload()
|
||||
if conf is None:
|
||||
return
|
||||
yield from _async_process_config(hass, conf, component)
|
||||
await _async_process_config(hass, conf, component)
|
||||
|
||||
hass.services.async_register(
|
||||
DOMAIN, SERVICE_TRIGGER, trigger_service_handler,
|
||||
@@ -272,15 +269,14 @@ class AutomationEntity(ToggleEntity):
|
||||
"""Return True if entity is on."""
|
||||
return self._async_detach_triggers is not None
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_added_to_hass(self) -> None:
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Startup with initial state or previous state."""
|
||||
if self._initial_state is not None:
|
||||
enable_automation = self._initial_state
|
||||
_LOGGER.debug("Automation %s initial state %s from config "
|
||||
"initial_state", self.entity_id, enable_automation)
|
||||
else:
|
||||
state = yield from async_get_last_state(self.hass, self.entity_id)
|
||||
state = await async_get_last_state(self.hass, self.entity_id)
|
||||
if state:
|
||||
enable_automation = state.state == STATE_ON
|
||||
self._last_triggered = state.attributes.get('last_triggered')
|
||||
@@ -298,54 +294,50 @@ class AutomationEntity(ToggleEntity):
|
||||
|
||||
# HomeAssistant is starting up
|
||||
if self.hass.state == CoreState.not_running:
|
||||
@asyncio.coroutine
|
||||
def async_enable_automation(event):
|
||||
async def async_enable_automation(event):
|
||||
"""Start automation on startup."""
|
||||
yield from self.async_enable()
|
||||
await self.async_enable()
|
||||
|
||||
self.hass.bus.async_listen_once(
|
||||
EVENT_HOMEASSISTANT_START, async_enable_automation)
|
||||
|
||||
# HomeAssistant is running
|
||||
else:
|
||||
yield from self.async_enable()
|
||||
await self.async_enable()
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_turn_on(self, **kwargs) -> None:
|
||||
async def async_turn_on(self, **kwargs) -> None:
|
||||
"""Turn the entity on and update the state."""
|
||||
if self.is_on:
|
||||
return
|
||||
|
||||
yield from self.async_enable()
|
||||
await self.async_enable()
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_turn_off(self, **kwargs) -> None:
|
||||
async def async_turn_off(self, **kwargs) -> None:
|
||||
"""Turn the entity off."""
|
||||
if not self.is_on:
|
||||
return
|
||||
|
||||
self._async_detach_triggers()
|
||||
self._async_detach_triggers = None
|
||||
yield from self.async_update_ha_state()
|
||||
await self.async_update_ha_state()
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_trigger(self, variables, skip_condition=False):
|
||||
async def async_trigger(self, variables, skip_condition=False,
|
||||
context=None):
|
||||
"""Trigger automation.
|
||||
|
||||
This method is a coroutine.
|
||||
"""
|
||||
if skip_condition or self._cond_func(variables):
|
||||
yield from self._async_action(self.entity_id, variables)
|
||||
self.async_set_context(context)
|
||||
await self._async_action(self.entity_id, variables, context)
|
||||
self._last_triggered = utcnow()
|
||||
yield from self.async_update_ha_state()
|
||||
await self.async_update_ha_state()
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_will_remove_from_hass(self):
|
||||
async def async_will_remove_from_hass(self):
|
||||
"""Remove listeners when removing automation from HASS."""
|
||||
yield from self.async_turn_off()
|
||||
await self.async_turn_off()
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_enable(self):
|
||||
async def async_enable(self):
|
||||
"""Enable this automation entity.
|
||||
|
||||
This method is a coroutine.
|
||||
@@ -353,9 +345,9 @@ class AutomationEntity(ToggleEntity):
|
||||
if self.is_on:
|
||||
return
|
||||
|
||||
self._async_detach_triggers = yield from self._async_attach_triggers(
|
||||
self._async_detach_triggers = await self._async_attach_triggers(
|
||||
self.async_trigger)
|
||||
yield from self.async_update_ha_state()
|
||||
await self.async_update_ha_state()
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
@@ -368,8 +360,7 @@ class AutomationEntity(ToggleEntity):
|
||||
}
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def _async_process_config(hass, config, component):
|
||||
async def _async_process_config(hass, config, component):
|
||||
"""Process config and add automations.
|
||||
|
||||
This method is a coroutine.
|
||||
@@ -411,20 +402,19 @@ def _async_process_config(hass, config, component):
|
||||
entities.append(entity)
|
||||
|
||||
if entities:
|
||||
yield from component.async_add_entities(entities)
|
||||
await component.async_add_entities(entities)
|
||||
|
||||
|
||||
def _async_get_action(hass, config, name):
|
||||
"""Return an action based on a configuration."""
|
||||
script_obj = script.Script(hass, config, name)
|
||||
|
||||
@asyncio.coroutine
|
||||
def action(entity_id, variables):
|
||||
async def action(entity_id, variables, context):
|
||||
"""Execute an action."""
|
||||
_LOGGER.info('Executing %s', name)
|
||||
logbook.async_log_entry(
|
||||
hass, name, 'has been triggered', DOMAIN, entity_id)
|
||||
yield from script_obj.async_run(variables)
|
||||
await script_obj.async_run(variables, context)
|
||||
|
||||
return action
|
||||
|
||||
@@ -448,8 +438,7 @@ def _async_process_if(hass, config, p_config):
|
||||
return if_action
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def _async_process_trigger(hass, config, trigger_configs, name, action):
|
||||
async def _async_process_trigger(hass, config, trigger_configs, name, action):
|
||||
"""Set up the triggers.
|
||||
|
||||
This method is a coroutine.
|
||||
@@ -457,13 +446,13 @@ def _async_process_trigger(hass, config, trigger_configs, name, action):
|
||||
removes = []
|
||||
|
||||
for conf in trigger_configs:
|
||||
platform = yield from async_prepare_setup_platform(
|
||||
platform = await async_prepare_setup_platform(
|
||||
hass, config, DOMAIN, conf.get(CONF_PLATFORM))
|
||||
|
||||
if platform is None:
|
||||
return None
|
||||
|
||||
remove = yield from platform.async_trigger(hass, conf, action)
|
||||
remove = await platform.async_trigger(hass, conf, action)
|
||||
|
||||
if not remove:
|
||||
_LOGGER.error("Error setting up trigger %s", name)
|
||||
|
||||
@@ -45,11 +45,11 @@ def async_trigger(hass, config, action):
|
||||
# If event data doesn't match requested schema, skip event
|
||||
return
|
||||
|
||||
hass.async_run_job(action, {
|
||||
hass.async_run_job(action({
|
||||
'trigger': {
|
||||
'platform': 'event',
|
||||
'event': event,
|
||||
},
|
||||
})
|
||||
}, context=event.context))
|
||||
|
||||
return hass.bus.async_listen(event_type, handle_event)
|
||||
|
||||
@@ -32,12 +32,12 @@ def async_trigger(hass, config, action):
|
||||
@callback
|
||||
def hass_shutdown(event):
|
||||
"""Execute when Home Assistant is shutting down."""
|
||||
hass.async_run_job(action, {
|
||||
hass.async_run_job(action({
|
||||
'trigger': {
|
||||
'platform': 'homeassistant',
|
||||
'event': event,
|
||||
},
|
||||
})
|
||||
}, context=event.context))
|
||||
|
||||
return hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP,
|
||||
hass_shutdown)
|
||||
@@ -45,11 +45,11 @@ def async_trigger(hass, config, action):
|
||||
# Automation are enabled while hass is starting up, fire right away
|
||||
# Check state because a config reload shouldn't trigger it.
|
||||
if hass.state == CoreState.starting:
|
||||
hass.async_run_job(action, {
|
||||
hass.async_run_job(action({
|
||||
'trigger': {
|
||||
'platform': 'homeassistant',
|
||||
'event': event,
|
||||
},
|
||||
})
|
||||
}))
|
||||
|
||||
return lambda: None
|
||||
|
||||
@@ -66,7 +66,7 @@ def async_trigger(hass, config, action):
|
||||
@callback
|
||||
def call_action():
|
||||
"""Call action with right context."""
|
||||
hass.async_run_job(action, {
|
||||
hass.async_run_job(action({
|
||||
'trigger': {
|
||||
'platform': 'numeric_state',
|
||||
'entity_id': entity,
|
||||
@@ -75,7 +75,7 @@ def async_trigger(hass, config, action):
|
||||
'from_state': from_s,
|
||||
'to_state': to_s,
|
||||
}
|
||||
})
|
||||
}, context=to_s.context))
|
||||
|
||||
matching = check_numeric_state(entity, from_s, to_s)
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ def async_trigger(hass, config, action):
|
||||
@callback
|
||||
def call_action():
|
||||
"""Call action with right context."""
|
||||
hass.async_run_job(action, {
|
||||
hass.async_run_job(action({
|
||||
'trigger': {
|
||||
'platform': 'state',
|
||||
'entity_id': entity,
|
||||
@@ -51,7 +51,7 @@ def async_trigger(hass, config, action):
|
||||
'to_state': to_s,
|
||||
'for': time_delta,
|
||||
}
|
||||
})
|
||||
}, context=to_s.context))
|
||||
|
||||
# Ignore changes to state attributes if from/to is in use
|
||||
if (not match_all and from_s is not None and to_s is not None and
|
||||
|
||||
@@ -32,13 +32,13 @@ def async_trigger(hass, config, action):
|
||||
@callback
|
||||
def template_listener(entity_id, from_s, to_s):
|
||||
"""Listen for state changes and calls action."""
|
||||
hass.async_run_job(action, {
|
||||
hass.async_run_job(action({
|
||||
'trigger': {
|
||||
'platform': 'template',
|
||||
'entity_id': entity_id,
|
||||
'from_state': from_s,
|
||||
'to_state': to_s,
|
||||
},
|
||||
})
|
||||
}, context=to_s.context))
|
||||
|
||||
return async_track_template(hass, value_template, template_listener)
|
||||
|
||||
@@ -51,7 +51,7 @@ def async_trigger(hass, config, action):
|
||||
# pylint: disable=too-many-boolean-expressions
|
||||
if event == EVENT_ENTER and not from_match and to_match or \
|
||||
event == EVENT_LEAVE and from_match and not to_match:
|
||||
hass.async_run_job(action, {
|
||||
hass.async_run_job(action({
|
||||
'trigger': {
|
||||
'platform': 'zone',
|
||||
'entity_id': entity,
|
||||
@@ -60,7 +60,7 @@ def async_trigger(hass, config, action):
|
||||
'zone': zone_state,
|
||||
'event': event,
|
||||
},
|
||||
})
|
||||
}, context=to_s.context))
|
||||
|
||||
return async_track_state_change(hass, entity_id, zone_automation_listener,
|
||||
MATCH_ALL, MATCH_ALL)
|
||||
|
||||
@@ -54,6 +54,11 @@ class DeconzBinarySensor(BinarySensorDevice):
|
||||
self._sensor.register_async_callback(self.async_update_callback)
|
||||
self.hass.data[DATA_DECONZ_ID][self.entity_id] = self._sensor.deconz_id
|
||||
|
||||
async def async_will_remove_from_hass(self) -> None:
|
||||
"""Disconnect sensor object when removed."""
|
||||
self._sensor.remove_callback(self.async_update_callback)
|
||||
self._sensor = None
|
||||
|
||||
@callback
|
||||
def async_update_callback(self, reason):
|
||||
"""Update the sensor's state.
|
||||
|
||||
@@ -7,12 +7,11 @@ https://home-assistant.io/components/binary_sensor.openuv/
|
||||
import logging
|
||||
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
from homeassistant.const import CONF_MONITORED_CONDITIONS
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.components.openuv import (
|
||||
BINARY_SENSORS, DATA_PROTECTION_WINDOW, DOMAIN, TOPIC_UPDATE,
|
||||
TYPE_PROTECTION_WINDOW, OpenUvEntity)
|
||||
BINARY_SENSORS, DATA_OPENUV_CLIENT, DATA_PROTECTION_WINDOW, DOMAIN,
|
||||
TOPIC_UPDATE, TYPE_PROTECTION_WINDOW, OpenUvEntity)
|
||||
from homeassistant.util.dt import as_local, parse_datetime, utcnow
|
||||
|
||||
DEPENDENCIES = ['openuv']
|
||||
@@ -26,17 +25,20 @@ ATTR_PROTECTION_WINDOW_ENDING_UV = 'end_uv'
|
||||
|
||||
async def async_setup_platform(
|
||||
hass, config, async_add_entities, discovery_info=None):
|
||||
"""Set up the OpenUV binary sensor platform."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
"""Set up an OpenUV sensor based on existing config."""
|
||||
pass
|
||||
|
||||
openuv = hass.data[DOMAIN]
|
||||
|
||||
async def async_setup_entry(hass, entry, async_add_entities):
|
||||
"""Set up an OpenUV sensor based on a config entry."""
|
||||
openuv = hass.data[DOMAIN][DATA_OPENUV_CLIENT][entry.entry_id]
|
||||
|
||||
binary_sensors = []
|
||||
for sensor_type in discovery_info[CONF_MONITORED_CONDITIONS]:
|
||||
for sensor_type in openuv.binary_sensor_conditions:
|
||||
name, icon = BINARY_SENSORS[sensor_type]
|
||||
binary_sensors.append(
|
||||
OpenUvBinarySensor(openuv, sensor_type, name, icon))
|
||||
OpenUvBinarySensor(
|
||||
openuv, sensor_type, name, icon, entry.entry_id))
|
||||
|
||||
async_add_entities(binary_sensors, True)
|
||||
|
||||
@@ -44,14 +46,16 @@ async def async_setup_platform(
|
||||
class OpenUvBinarySensor(OpenUvEntity, BinarySensorDevice):
|
||||
"""Define a binary sensor for OpenUV."""
|
||||
|
||||
def __init__(self, openuv, sensor_type, name, icon):
|
||||
def __init__(self, openuv, sensor_type, name, icon, entry_id):
|
||||
"""Initialize the sensor."""
|
||||
super().__init__(openuv)
|
||||
|
||||
self._entry_id = entry_id
|
||||
self._icon = icon
|
||||
self._latitude = openuv.client.latitude
|
||||
self._longitude = openuv.client.longitude
|
||||
self._name = name
|
||||
self._dispatch_remove = None
|
||||
self._sensor_type = sensor_type
|
||||
self._state = None
|
||||
|
||||
@@ -83,8 +87,9 @@ class OpenUvBinarySensor(OpenUvEntity, BinarySensorDevice):
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Register callbacks."""
|
||||
async_dispatcher_connect(
|
||||
self._dispatch_remove = async_dispatcher_connect(
|
||||
self.hass, TOPIC_UPDATE, self._update_data)
|
||||
self.async_on_remove(self._dispatch_remove)
|
||||
|
||||
async def async_update(self):
|
||||
"""Update the state."""
|
||||
|
||||
@@ -374,11 +374,11 @@ class XiaomiCube(XiaomiBinarySensor):
|
||||
self._last_action = None
|
||||
self._state = False
|
||||
if 'proto' not in device or int(device['proto'][0:1]) == 1:
|
||||
self._data_key = 'status'
|
||||
data_key = 'status'
|
||||
else:
|
||||
self._data_key = 'cube_status'
|
||||
data_key = 'cube_status'
|
||||
XiaomiBinarySensor.__init__(self, device, 'Cube', xiaomi_hub,
|
||||
None, None)
|
||||
data_key, None)
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
|
||||
@@ -142,6 +142,68 @@ def async_snapshot(hass, filename, entity_id=None):
|
||||
@bind_hass
|
||||
async def async_get_image(hass, entity_id, timeout=10):
|
||||
"""Fetch an image from a camera entity."""
|
||||
camera = _get_camera_from_entity_id(hass, entity_id)
|
||||
|
||||
with suppress(asyncio.CancelledError, asyncio.TimeoutError):
|
||||
with async_timeout.timeout(timeout, loop=hass.loop):
|
||||
image = await camera.async_camera_image()
|
||||
|
||||
if image:
|
||||
return Image(camera.content_type, image)
|
||||
|
||||
raise HomeAssistantError('Unable to get image')
|
||||
|
||||
|
||||
@bind_hass
|
||||
async def async_get_mjpeg_stream(hass, request, entity_id):
|
||||
"""Fetch an mjpeg stream from a camera entity."""
|
||||
camera = _get_camera_from_entity_id(hass, entity_id)
|
||||
|
||||
return await camera.handle_async_mjpeg_stream(request)
|
||||
|
||||
|
||||
async def async_get_still_stream(request, image_cb, content_type, interval):
|
||||
"""Generate an HTTP MJPEG stream from camera images.
|
||||
|
||||
This method must be run in the event loop.
|
||||
"""
|
||||
response = web.StreamResponse()
|
||||
response.content_type = ('multipart/x-mixed-replace; '
|
||||
'boundary=--frameboundary')
|
||||
await response.prepare(request)
|
||||
|
||||
async def write_to_mjpeg_stream(img_bytes):
|
||||
"""Write image to stream."""
|
||||
await response.write(bytes(
|
||||
'--frameboundary\r\n'
|
||||
'Content-Type: {}\r\n'
|
||||
'Content-Length: {}\r\n\r\n'.format(
|
||||
content_type, len(img_bytes)),
|
||||
'utf-8') + img_bytes + b'\r\n')
|
||||
|
||||
last_image = None
|
||||
|
||||
while True:
|
||||
img_bytes = await image_cb()
|
||||
if not img_bytes:
|
||||
break
|
||||
|
||||
if img_bytes != last_image:
|
||||
await write_to_mjpeg_stream(img_bytes)
|
||||
|
||||
# Chrome seems to always ignore first picture,
|
||||
# print it twice.
|
||||
if last_image is None:
|
||||
await write_to_mjpeg_stream(img_bytes)
|
||||
last_image = img_bytes
|
||||
|
||||
await asyncio.sleep(interval)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
def _get_camera_from_entity_id(hass, entity_id):
|
||||
"""Get camera component from entity_id."""
|
||||
component = hass.data.get(DOMAIN)
|
||||
|
||||
if component is None:
|
||||
@@ -155,14 +217,7 @@ async def async_get_image(hass, entity_id, timeout=10):
|
||||
if not camera.is_on:
|
||||
raise HomeAssistantError('Camera is off')
|
||||
|
||||
with suppress(asyncio.CancelledError, asyncio.TimeoutError):
|
||||
with async_timeout.timeout(timeout, loop=hass.loop):
|
||||
image = await camera.async_camera_image()
|
||||
|
||||
if image:
|
||||
return Image(camera.content_type, image)
|
||||
|
||||
raise HomeAssistantError('Unable to get image')
|
||||
return camera
|
||||
|
||||
|
||||
async def async_setup(hass, config):
|
||||
@@ -290,39 +345,8 @@ class Camera(Entity):
|
||||
|
||||
This method must be run in the event loop.
|
||||
"""
|
||||
response = web.StreamResponse()
|
||||
response.content_type = ('multipart/x-mixed-replace; '
|
||||
'boundary=--frameboundary')
|
||||
await response.prepare(request)
|
||||
|
||||
async def write_to_mjpeg_stream(img_bytes):
|
||||
"""Write image to stream."""
|
||||
await response.write(bytes(
|
||||
'--frameboundary\r\n'
|
||||
'Content-Type: {}\r\n'
|
||||
'Content-Length: {}\r\n\r\n'.format(
|
||||
self.content_type, len(img_bytes)),
|
||||
'utf-8') + img_bytes + b'\r\n')
|
||||
|
||||
last_image = None
|
||||
|
||||
while True:
|
||||
img_bytes = await self.async_camera_image()
|
||||
if not img_bytes:
|
||||
break
|
||||
|
||||
if img_bytes and img_bytes != last_image:
|
||||
await write_to_mjpeg_stream(img_bytes)
|
||||
|
||||
# Chrome seems to always ignore first picture,
|
||||
# print it twice.
|
||||
if last_image is None:
|
||||
await write_to_mjpeg_stream(img_bytes)
|
||||
last_image = img_bytes
|
||||
|
||||
await asyncio.sleep(interval)
|
||||
|
||||
return response
|
||||
return await async_get_still_stream(request, self.async_camera_image,
|
||||
self.content_type, interval)
|
||||
|
||||
async def handle_async_mjpeg_stream(self, request):
|
||||
"""Serve an HTTP MJPEG stream from the camera.
|
||||
|
||||
@@ -7,17 +7,15 @@ https://www.home-assistant.io/components/camera.proxy/
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import aiohttp
|
||||
import async_timeout
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.camera import PLATFORM_SCHEMA, Camera
|
||||
from homeassistant.const import CONF_ENTITY_ID, CONF_NAME, HTTP_HEADER_HA_AUTH
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.aiohttp_client import (
|
||||
async_aiohttp_proxy_web, async_get_clientsession)
|
||||
from homeassistant.util.async_ import run_coroutine_threadsafe
|
||||
import homeassistant.util.dt as dt_util
|
||||
from . import async_get_still_stream
|
||||
|
||||
REQUIREMENTS = ['pillow==5.2.0']
|
||||
|
||||
@@ -158,22 +156,14 @@ class ProxyCamera(Camera):
|
||||
return self._last_image
|
||||
|
||||
self._last_image_time = now
|
||||
url = "{}/api/camera_proxy/{}".format(
|
||||
self.hass.config.api.base_url, self._proxied_camera)
|
||||
try:
|
||||
websession = async_get_clientsession(self.hass)
|
||||
with async_timeout.timeout(10, loop=self.hass.loop):
|
||||
response = await websession.get(url, headers=self._headers)
|
||||
image = await response.read()
|
||||
except asyncio.TimeoutError:
|
||||
_LOGGER.error("Timeout getting camera image")
|
||||
return self._last_image
|
||||
except aiohttp.ClientError as err:
|
||||
_LOGGER.error("Error getting new camera image: %s", err)
|
||||
image = await self.hass.components.camera.async_get_image(
|
||||
self._proxied_camera)
|
||||
if not image:
|
||||
_LOGGER.error("Error getting original camera image")
|
||||
return self._last_image
|
||||
|
||||
image = await self.hass.async_add_job(
|
||||
_resize_image, image, self._image_opts)
|
||||
_resize_image, image.content, self._image_opts)
|
||||
|
||||
if self._cache_images:
|
||||
self._last_image = image
|
||||
@@ -181,56 +171,28 @@ class ProxyCamera(Camera):
|
||||
|
||||
async def handle_async_mjpeg_stream(self, request):
|
||||
"""Generate an HTTP MJPEG stream from camera images."""
|
||||
websession = async_get_clientsession(self.hass)
|
||||
url = "{}/api/camera_proxy_stream/{}".format(
|
||||
self.hass.config.api.base_url, self._proxied_camera)
|
||||
stream_coro = websession.get(url, headers=self._headers)
|
||||
|
||||
if not self._stream_opts:
|
||||
return await async_aiohttp_proxy_web(
|
||||
self.hass, request, stream_coro)
|
||||
return await self.hass.components.camera.async_get_mjpeg_stream(
|
||||
request, self._proxied_camera)
|
||||
|
||||
response = aiohttp.web.StreamResponse()
|
||||
response.content_type = (
|
||||
'multipart/x-mixed-replace; boundary=--frameboundary')
|
||||
await response.prepare(request)
|
||||
|
||||
async def write(img_bytes):
|
||||
"""Write image to stream."""
|
||||
await response.write(bytes(
|
||||
'--frameboundary\r\n'
|
||||
'Content-Type: {}\r\n'
|
||||
'Content-Length: {}\r\n\r\n'.format(
|
||||
self.content_type, len(img_bytes)),
|
||||
'utf-8') + img_bytes + b'\r\n')
|
||||
|
||||
with async_timeout.timeout(10, loop=self.hass.loop):
|
||||
req = await stream_coro
|
||||
|
||||
try:
|
||||
# This would be nicer as an async generator
|
||||
# But that would only be supported for python >=3.6
|
||||
data = b''
|
||||
stream = req.content
|
||||
while True:
|
||||
chunk = await stream.read(102400)
|
||||
if not chunk:
|
||||
break
|
||||
data += chunk
|
||||
jpg_start = data.find(b'\xff\xd8')
|
||||
jpg_end = data.find(b'\xff\xd9')
|
||||
if jpg_start != -1 and jpg_end != -1:
|
||||
image = data[jpg_start:jpg_end + 2]
|
||||
image = await self.hass.async_add_job(
|
||||
_resize_image, image, self._stream_opts)
|
||||
await write(image)
|
||||
data = data[jpg_end + 2:]
|
||||
finally:
|
||||
req.close()
|
||||
|
||||
return response
|
||||
return await async_get_still_stream(
|
||||
request, self._async_stream_image,
|
||||
self.content_type, self.frame_interval)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of this camera."""
|
||||
return self._name
|
||||
|
||||
async def _async_stream_image(self):
|
||||
"""Return a still image response from the camera."""
|
||||
try:
|
||||
image = await self.hass.components.camera.async_get_image(
|
||||
self._proxied_camera)
|
||||
if not image:
|
||||
return None
|
||||
except HomeAssistantError:
|
||||
raise asyncio.CancelledError
|
||||
|
||||
return await self.hass.async_add_job(
|
||||
_resize_image, image.content, self._stream_opts)
|
||||
|
||||
15
homeassistant/components/cast/.translations/fr.json
Normal file
15
homeassistant/components/cast/.translations/fr.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"no_devices_found": "Aucun appareil Google Cast trouv\u00e9 sur le r\u00e9seau.",
|
||||
"single_instance_allowed": "Seulement une seule configuration de Google Cast est n\u00e9cessaire."
|
||||
},
|
||||
"step": {
|
||||
"confirm": {
|
||||
"description": "Voulez-vous configurer Google Cast?",
|
||||
"title": "Google Cast"
|
||||
}
|
||||
},
|
||||
"title": "Google Cast"
|
||||
}
|
||||
}
|
||||
@@ -251,6 +251,14 @@ class GenericThermostat(ClimateDevice):
|
||||
# Ensure we update the current operation after changing the mode
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
async def async_turn_on(self):
|
||||
"""Turn thermostat on."""
|
||||
await self.async_set_operation_mode(self.operation_list[0])
|
||||
|
||||
async def async_turn_off(self):
|
||||
"""Turn thermostat off."""
|
||||
await self.async_set_operation_mode(STATE_OFF)
|
||||
|
||||
async def async_set_temperature(self, **kwargs):
|
||||
"""Set new target temperature."""
|
||||
temperature = kwargs.get(ATTR_TEMPERATURE)
|
||||
|
||||
@@ -8,7 +8,8 @@ import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.nest import DATA_NEST, SIGNAL_NEST_UPDATE
|
||||
from homeassistant.components.nest import (
|
||||
DATA_NEST, SIGNAL_NEST_UPDATE, DOMAIN as NEST_DOMAIN)
|
||||
from homeassistant.components.climate import (
|
||||
STATE_AUTO, STATE_COOL, STATE_HEAT, STATE_ECO, ClimateDevice,
|
||||
PLATFORM_SCHEMA, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
|
||||
@@ -127,6 +128,19 @@ class NestThermostat(ClimateDevice):
|
||||
"""Return unique ID for this device."""
|
||||
return self.device.serial
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
"""Return information about the device."""
|
||||
return {
|
||||
'identifiers': {
|
||||
(NEST_DOMAIN, self.device.device_id),
|
||||
},
|
||||
'name': self.device.name_long,
|
||||
'manufacturer': 'Nest Labs',
|
||||
'model': "Thermostat",
|
||||
'sw_version': self.device.software_version,
|
||||
}
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the nest, if any."""
|
||||
|
||||
189
homeassistant/components/climate/opentherm_gw.py
Normal file
189
homeassistant/components/climate/opentherm_gw.py
Normal file
@@ -0,0 +1,189 @@
|
||||
"""
|
||||
Support for OpenTherm Gateway devices.
|
||||
|
||||
For more details about this component, please refer to the documentation at
|
||||
http://home-assistant.io/components/climate.opentherm_gw/
|
||||
"""
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.climate import (ClimateDevice, PLATFORM_SCHEMA,
|
||||
STATE_IDLE, STATE_HEAT,
|
||||
STATE_COOL,
|
||||
SUPPORT_TARGET_TEMPERATURE)
|
||||
from homeassistant.const import (ATTR_TEMPERATURE, CONF_DEVICE, CONF_NAME,
|
||||
PRECISION_HALVES, PRECISION_TENTHS,
|
||||
TEMP_CELSIUS, PRECISION_WHOLE)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
REQUIREMENTS = ['pyotgw==0.1b0']
|
||||
|
||||
CONF_FLOOR_TEMP = "floor_temperature"
|
||||
CONF_PRECISION = 'precision'
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_DEVICE): cv.string,
|
||||
vol.Optional(CONF_NAME, default="OpenTherm Gateway"): cv.string,
|
||||
vol.Optional(CONF_PRECISION): vol.In([PRECISION_TENTHS, PRECISION_HALVES,
|
||||
PRECISION_WHOLE]),
|
||||
vol.Optional(CONF_FLOOR_TEMP, default=False): cv.boolean,
|
||||
})
|
||||
|
||||
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE)
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
"""Set up the opentherm_gw device."""
|
||||
gateway = OpenThermGateway(config)
|
||||
async_add_entities([gateway])
|
||||
|
||||
|
||||
class OpenThermGateway(ClimateDevice):
|
||||
"""Representation of a climate device."""
|
||||
|
||||
def __init__(self, config):
|
||||
"""Initialize the sensor."""
|
||||
import pyotgw
|
||||
self.pyotgw = pyotgw
|
||||
self.gateway = self.pyotgw.pyotgw()
|
||||
self._device = config[CONF_DEVICE]
|
||||
self.friendly_name = config.get(CONF_NAME)
|
||||
self.floor_temp = config.get(CONF_FLOOR_TEMP)
|
||||
self.temp_precision = config.get(CONF_PRECISION)
|
||||
self._current_operation = STATE_IDLE
|
||||
self._current_temperature = 0.0
|
||||
self._target_temperature = 0.0
|
||||
self._away_mode_a = None
|
||||
self._away_mode_b = None
|
||||
self._away_state_a = False
|
||||
self._away_state_b = False
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Connect to the OpenTherm Gateway device."""
|
||||
await self.gateway.connect(self.hass.loop, self._device)
|
||||
self.gateway.subscribe(self.receive_report)
|
||||
_LOGGER.debug("Connected to %s on %s", self.friendly_name,
|
||||
self._device)
|
||||
|
||||
async def receive_report(self, status):
|
||||
"""Receive and handle a new report from the Gateway."""
|
||||
_LOGGER.debug("Received report: %s", status)
|
||||
ch_active = status.get(self.pyotgw.DATA_SLAVE_CH_ACTIVE)
|
||||
cooling_active = status.get(self.pyotgw.DATA_SLAVE_COOLING_ACTIVE)
|
||||
if ch_active:
|
||||
self._current_operation = STATE_HEAT
|
||||
elif cooling_active:
|
||||
self._current_operation = STATE_COOL
|
||||
else:
|
||||
self._current_operation = STATE_IDLE
|
||||
self._current_temperature = status.get(self.pyotgw.DATA_ROOM_TEMP)
|
||||
|
||||
temp = status.get(self.pyotgw.DATA_ROOM_SETPOINT_OVRD)
|
||||
if temp is None:
|
||||
temp = status.get(self.pyotgw.DATA_ROOM_SETPOINT)
|
||||
self._target_temperature = temp
|
||||
|
||||
# GPIO mode 5: 0 == Away
|
||||
# GPIO mode 6: 1 == Away
|
||||
gpio_a_state = status.get(self.pyotgw.OTGW_GPIO_A)
|
||||
if gpio_a_state == 5:
|
||||
self._away_mode_a = 0
|
||||
elif gpio_a_state == 6:
|
||||
self._away_mode_a = 1
|
||||
else:
|
||||
self._away_mode_a = None
|
||||
gpio_b_state = status.get(self.pyotgw.OTGW_GPIO_B)
|
||||
if gpio_b_state == 5:
|
||||
self._away_mode_b = 0
|
||||
elif gpio_b_state == 6:
|
||||
self._away_mode_b = 1
|
||||
else:
|
||||
self._away_mode_b = None
|
||||
if self._away_mode_a is not None:
|
||||
self._away_state_a = (status.get(self.pyotgw.OTGW_GPIO_A_STATE) ==
|
||||
self._away_mode_a)
|
||||
if self._away_mode_b is not None:
|
||||
self._away_state_b = (status.get(self.pyotgw.OTGW_GPIO_B_STATE) ==
|
||||
self._away_mode_b)
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the friendly name."""
|
||||
return self.friendly_name
|
||||
|
||||
@property
|
||||
def precision(self):
|
||||
"""Return the precision of the system."""
|
||||
if self.temp_precision is not None:
|
||||
return self.temp_precision
|
||||
if self.hass.config.units.temperature_unit == TEMP_CELSIUS:
|
||||
return PRECISION_HALVES
|
||||
return PRECISION_WHOLE
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""Disable polling for this entity."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def temperature_unit(self):
|
||||
"""Return the unit of measurement used by the platform."""
|
||||
return TEMP_CELSIUS
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
"""Return current operation ie. heat, cool, idle."""
|
||||
return self._current_operation
|
||||
|
||||
@property
|
||||
def current_temperature(self):
|
||||
"""Return the current temperature."""
|
||||
if self.floor_temp is True:
|
||||
if self.temp_precision == PRECISION_HALVES:
|
||||
return int(2 * self._current_temperature) / 2
|
||||
if self.temp_precision == PRECISION_TENTHS:
|
||||
return int(10 * self._current_temperature) / 10
|
||||
return int(self._current_temperature)
|
||||
return self._current_temperature
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
"""Return the temperature we try to reach."""
|
||||
return self._target_temperature
|
||||
|
||||
@property
|
||||
def target_temperature_step(self):
|
||||
"""Return the supported step of target temperature."""
|
||||
return self.temp_precision
|
||||
|
||||
@property
|
||||
def is_away_mode_on(self):
|
||||
"""Return true if away mode is on."""
|
||||
return self._away_state_a or self._away_state_b
|
||||
|
||||
async def async_set_temperature(self, **kwargs):
|
||||
"""Set new target temperature."""
|
||||
if ATTR_TEMPERATURE in kwargs:
|
||||
temp = float(kwargs[ATTR_TEMPERATURE])
|
||||
self._target_temperature = await self.gateway.set_target_temp(
|
||||
temp)
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Return the list of supported features."""
|
||||
return SUPPORT_FLAGS
|
||||
|
||||
@property
|
||||
def min_temp(self):
|
||||
"""Return the minimum temperature."""
|
||||
return 1
|
||||
|
||||
@property
|
||||
def max_temp(self):
|
||||
"""Return the maximum temperature."""
|
||||
return 30
|
||||
@@ -174,8 +174,8 @@ class RadioThermostat(ClimateDevice):
|
||||
def device_state_attributes(self):
|
||||
"""Return the device specific state attributes."""
|
||||
return {
|
||||
ATTR_FAN: self._fmode,
|
||||
ATTR_MODE: self._tmode,
|
||||
ATTR_FAN: self._fstate,
|
||||
ATTR_MODE: self._tstate,
|
||||
}
|
||||
|
||||
@property
|
||||
|
||||
@@ -7,9 +7,6 @@ from homeassistant.helpers.data_entry_flow import (
|
||||
FlowManagerIndexView, FlowManagerResourceView)
|
||||
|
||||
|
||||
REQUIREMENTS = ['voluptuous-serialize==2.0.0']
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup(hass):
|
||||
"""Enable the Home Assistant views."""
|
||||
|
||||
73
homeassistant/components/cover/insteon.py
Normal file
73
homeassistant/components/cover/insteon.py
Normal file
@@ -0,0 +1,73 @@
|
||||
"""
|
||||
Support for Insteon covers via PowerLinc Modem.
|
||||
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/cover.insteon/
|
||||
"""
|
||||
import logging
|
||||
import math
|
||||
|
||||
from homeassistant.components.insteon import InsteonEntity
|
||||
from homeassistant.components.cover import (CoverDevice, ATTR_POSITION,
|
||||
SUPPORT_OPEN, SUPPORT_CLOSE,
|
||||
SUPPORT_SET_POSITION)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEPENDENCIES = ['insteon']
|
||||
SUPPORTED_FEATURES = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION
|
||||
|
||||
|
||||
async def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
"""Set up the Insteon platform."""
|
||||
if not discovery_info:
|
||||
return
|
||||
|
||||
insteon_modem = hass.data['insteon'].get('modem')
|
||||
|
||||
address = discovery_info['address']
|
||||
device = insteon_modem.devices[address]
|
||||
state_key = discovery_info['state_key']
|
||||
|
||||
_LOGGER.debug('Adding device %s entity %s to Cover platform',
|
||||
device.address.hex, device.states[state_key].name)
|
||||
|
||||
new_entity = InsteonCoverDevice(device, state_key)
|
||||
|
||||
async_add_entities([new_entity])
|
||||
|
||||
|
||||
class InsteonCoverDevice(InsteonEntity, CoverDevice):
|
||||
"""A Class for an Insteon device."""
|
||||
|
||||
@property
|
||||
def current_cover_position(self):
|
||||
"""Return the current cover position."""
|
||||
return int(math.ceil(self._insteon_device_state.value*100/255))
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Return the supported features for this entity."""
|
||||
return SUPPORTED_FEATURES
|
||||
|
||||
@property
|
||||
def is_closed(self):
|
||||
"""Return the boolean response if the node is on."""
|
||||
return bool(self.current_cover_position)
|
||||
|
||||
async def async_open_cover(self, **kwargs):
|
||||
"""Open device."""
|
||||
self._insteon_device_state.open()
|
||||
|
||||
async def async_close_cover(self, **kwargs):
|
||||
"""Close device."""
|
||||
self._insteon_device_state.close()
|
||||
|
||||
async def async_set_cover_position(self, **kwargs):
|
||||
"""Set the cover position."""
|
||||
position = int(kwargs[ATTR_POSITION]*255/100)
|
||||
if position == 0:
|
||||
self._insteon_device_state.close()
|
||||
else:
|
||||
self._insteon_device_state.set_position(position)
|
||||
@@ -8,17 +8,25 @@ import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.cover import CoverDevice
|
||||
from homeassistant.components.cover import (
|
||||
CoverDevice, SUPPORT_CLOSE, SUPPORT_OPEN)
|
||||
from homeassistant.const import (
|
||||
CONF_USERNAME, CONF_PASSWORD, CONF_TYPE, STATE_CLOSED)
|
||||
CONF_PASSWORD, CONF_TYPE, CONF_USERNAME, STATE_CLOSED, STATE_CLOSING,
|
||||
STATE_OPENING)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
REQUIREMENTS = ['pymyq==0.0.11']
|
||||
REQUIREMENTS = ['pymyq==0.0.15']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_NAME = 'myq'
|
||||
|
||||
MYQ_TO_HASS = {
|
||||
'closed': STATE_CLOSED,
|
||||
'closing': STATE_CLOSING,
|
||||
'opening': STATE_OPENING
|
||||
}
|
||||
|
||||
NOTIFICATION_ID = 'myq_notification'
|
||||
NOTIFICATION_TITLE = 'MyQ Cover Setup'
|
||||
|
||||
@@ -87,7 +95,17 @@ class MyQDevice(CoverDevice):
|
||||
@property
|
||||
def is_closed(self):
|
||||
"""Return true if cover is closed, else False."""
|
||||
return self._status == STATE_CLOSED
|
||||
return MYQ_TO_HASS[self._status] == STATE_CLOSED
|
||||
|
||||
@property
|
||||
def is_closing(self):
|
||||
"""Return if the cover is closing or not."""
|
||||
return MYQ_TO_HASS[self._status] == STATE_CLOSING
|
||||
|
||||
@property
|
||||
def is_opening(self):
|
||||
"""Return if the cover is opening or not."""
|
||||
return MYQ_TO_HASS[self._status] == STATE_OPENING
|
||||
|
||||
def close_cover(self, **kwargs):
|
||||
"""Issue close command to cover."""
|
||||
@@ -97,6 +115,16 @@ class MyQDevice(CoverDevice):
|
||||
"""Issue open command to cover."""
|
||||
self.myq.open_device(self.device_id)
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Flag supported features."""
|
||||
return SUPPORT_OPEN | SUPPORT_CLOSE
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique, HASS-friendly identifier for this entity."""
|
||||
return self.device_id
|
||||
|
||||
def update(self):
|
||||
"""Update status of cover."""
|
||||
self._status = self.myq.get_status(self.device_id)
|
||||
|
||||
@@ -92,9 +92,9 @@ class RflinkCover(RflinkCommand, CoverDevice):
|
||||
self.cancel_queued_send_commands()
|
||||
|
||||
command = event['command']
|
||||
if command in ['on', 'allon']:
|
||||
if command in ['on', 'allon', 'up']:
|
||||
self._state = True
|
||||
elif command in ['off', 'alloff']:
|
||||
elif command in ['off', 'alloff', 'down']:
|
||||
self._state = False
|
||||
|
||||
@property
|
||||
@@ -105,7 +105,12 @@ class RflinkCover(RflinkCommand, CoverDevice):
|
||||
@property
|
||||
def is_closed(self):
|
||||
"""Return if the cover is closed."""
|
||||
return None
|
||||
return not self._state
|
||||
|
||||
@property
|
||||
def assumed_state(self):
|
||||
"""Return True because covers can be stopped midway."""
|
||||
return True
|
||||
|
||||
def async_close_cover(self, **kwargs):
|
||||
"""Turn the device close."""
|
||||
|
||||
@@ -22,7 +22,8 @@
|
||||
},
|
||||
"options": {
|
||||
"data": {
|
||||
"allow_clip_sensor": "Autoriser l'importation de capteurs virtuels"
|
||||
"allow_clip_sensor": "Autoriser l'importation de capteurs virtuels",
|
||||
"allow_deconz_groups": "Autoriser l'importation des groupes deCONZ"
|
||||
},
|
||||
"title": "Options de configuration suppl\u00e9mentaires pour deCONZ"
|
||||
}
|
||||
|
||||
@@ -28,6 +28,6 @@
|
||||
"title": "Extra configuratieopties voor deCONZ"
|
||||
}
|
||||
},
|
||||
"title": "deCONZ"
|
||||
"title": "deCONZ Zigbee gateway"
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,6 @@
|
||||
"title": "Ekstra konfigurasjonsalternativer for deCONZ"
|
||||
}
|
||||
},
|
||||
"title": "deCONZ"
|
||||
"title": "deCONZ Zigbee gateway"
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ from .const import (
|
||||
CONF_ALLOW_CLIP_SENSOR, CONFIG_FILE, DATA_DECONZ_EVENT,
|
||||
DATA_DECONZ_ID, DATA_DECONZ_UNSUB, DOMAIN, _LOGGER)
|
||||
|
||||
REQUIREMENTS = ['pydeconz==44']
|
||||
REQUIREMENTS = ['pydeconz==47']
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
DOMAIN: vol.Schema({
|
||||
@@ -46,6 +46,8 @@ SERVICE_SCHEMA = vol.Schema({
|
||||
vol.Required(SERVICE_DATA): dict,
|
||||
})
|
||||
|
||||
SERVICE_DEVICE_REFRESH = 'device_refresh'
|
||||
|
||||
|
||||
async def async_setup(hass, config):
|
||||
"""Load configuration for deCONZ component.
|
||||
@@ -84,15 +86,17 @@ async def async_setup_entry(hass, config_entry):
|
||||
@callback
|
||||
def async_add_device_callback(device_type, device):
|
||||
"""Handle event of new device creation in deCONZ."""
|
||||
if not isinstance(device, list):
|
||||
device = [device]
|
||||
async_dispatcher_send(
|
||||
hass, 'deconz_new_{}'.format(device_type), [device])
|
||||
hass, 'deconz_new_{}'.format(device_type), device)
|
||||
|
||||
session = aiohttp_client.async_get_clientsession(hass)
|
||||
deconz = DeconzSession(hass.loop, session, **config_entry.data,
|
||||
async_add_device=async_add_device_callback)
|
||||
result = await deconz.async_load_parameters()
|
||||
|
||||
if result is False:
|
||||
_LOGGER.error("Failed to communicate with deCONZ")
|
||||
return False
|
||||
|
||||
hass.data[DOMAIN] = deconz
|
||||
@@ -149,16 +153,60 @@ async def async_setup_entry(hass, config_entry):
|
||||
data = call.data.get(SERVICE_DATA)
|
||||
deconz = hass.data[DOMAIN]
|
||||
if entity_id:
|
||||
|
||||
entities = hass.data.get(DATA_DECONZ_ID)
|
||||
|
||||
if entities:
|
||||
field = entities.get(entity_id)
|
||||
|
||||
if field is None:
|
||||
_LOGGER.error('Could not find the entity %s', entity_id)
|
||||
return
|
||||
|
||||
await deconz.async_put_state(field, data)
|
||||
|
||||
hass.services.async_register(
|
||||
DOMAIN, SERVICE_DECONZ, async_configure, schema=SERVICE_SCHEMA)
|
||||
|
||||
async def async_refresh_devices(call):
|
||||
"""Refresh available devices from deCONZ."""
|
||||
deconz = hass.data[DOMAIN]
|
||||
|
||||
groups = list(deconz.groups.keys())
|
||||
lights = list(deconz.lights.keys())
|
||||
scenes = list(deconz.scenes.keys())
|
||||
sensors = list(deconz.sensors.keys())
|
||||
|
||||
if not await deconz.async_load_parameters():
|
||||
return
|
||||
|
||||
async_add_device_callback(
|
||||
'group', [group
|
||||
for group_id, group in deconz.groups.items()
|
||||
if group_id not in groups]
|
||||
)
|
||||
|
||||
async_add_device_callback(
|
||||
'light', [light
|
||||
for light_id, light in deconz.lights.items()
|
||||
if light_id not in lights]
|
||||
)
|
||||
|
||||
async_add_device_callback(
|
||||
'scene', [scene
|
||||
for scene_id, scene in deconz.scenes.items()
|
||||
if scene_id not in scenes]
|
||||
)
|
||||
|
||||
async_add_device_callback(
|
||||
'sensor', [sensor
|
||||
for sensor_id, sensor in deconz.sensors.items()
|
||||
if sensor_id not in sensors]
|
||||
)
|
||||
|
||||
hass.services.async_register(
|
||||
DOMAIN, SERVICE_DEVICE_REFRESH, async_refresh_devices)
|
||||
|
||||
@callback
|
||||
def deconz_shutdown(event):
|
||||
"""
|
||||
@@ -179,15 +227,22 @@ async def async_unload_entry(hass, config_entry):
|
||||
deconz = hass.data.pop(DOMAIN)
|
||||
hass.services.async_remove(DOMAIN, SERVICE_DECONZ)
|
||||
deconz.close()
|
||||
for component in ['binary_sensor', 'light', 'scene', 'sensor']:
|
||||
|
||||
for component in ['binary_sensor', 'light', 'scene', 'sensor', 'switch']:
|
||||
await hass.config_entries.async_forward_entry_unload(
|
||||
config_entry, component)
|
||||
|
||||
dispatchers = hass.data[DATA_DECONZ_UNSUB]
|
||||
for unsub_dispatcher in dispatchers:
|
||||
unsub_dispatcher()
|
||||
hass.data[DATA_DECONZ_UNSUB] = []
|
||||
hass.data[DATA_DECONZ_EVENT] = []
|
||||
|
||||
for event in hass.data[DATA_DECONZ_EVENT]:
|
||||
event.async_will_remove_from_hass()
|
||||
hass.data[DATA_DECONZ_EVENT].remove(event)
|
||||
|
||||
hass.data[DATA_DECONZ_ID] = []
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@@ -206,6 +261,12 @@ class DeconzEvent:
|
||||
self._event = 'deconz_{}'.format(CONF_EVENT)
|
||||
self._id = slugify(self._device.name)
|
||||
|
||||
@callback
|
||||
def async_will_remove_from_hass(self) -> None:
|
||||
"""Disconnect event object when removed."""
|
||||
self._device.remove_callback(self.async_update_callback)
|
||||
self._device = None
|
||||
|
||||
@callback
|
||||
def async_update_callback(self, reason):
|
||||
"""Fire the event if reason is that state is updated."""
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
configure:
|
||||
description: Set attribute of device in deCONZ. See https://home-assistant.io/components/deconz/#device-services for details.
|
||||
fields:
|
||||
@@ -11,3 +10,6 @@ configure:
|
||||
data:
|
||||
description: Data is a json object with what data you want to alter.
|
||||
example: '{"on": true}'
|
||||
|
||||
device_refresh:
|
||||
description: Refresh device lists from deCONZ.
|
||||
@@ -12,7 +12,8 @@ import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.event import track_point_in_utc_time
|
||||
from homeassistant.components.device_tracker import (
|
||||
YAML_DEVICES, CONF_TRACK_NEW, CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL,
|
||||
load_config, PLATFORM_SCHEMA, DEFAULT_TRACK_NEW, SOURCE_TYPE_BLUETOOTH)
|
||||
load_config, PLATFORM_SCHEMA, DEFAULT_TRACK_NEW, SOURCE_TYPE_BLUETOOTH,
|
||||
DOMAIN)
|
||||
import homeassistant.util.dt as dt_util
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -79,7 +80,13 @@ def setup_scanner(hass, config, see, discovery_info=None):
|
||||
|
||||
request_rssi = config.get(CONF_REQUEST_RSSI, False)
|
||||
|
||||
def update_bluetooth(now):
|
||||
def update_bluetooth():
|
||||
"""Update Bluetooth and set timer for the next update."""
|
||||
update_bluetooth_once()
|
||||
track_point_in_utc_time(
|
||||
hass, update_bluetooth, dt_util.utcnow() + interval)
|
||||
|
||||
def update_bluetooth_once():
|
||||
"""Lookup Bluetooth device and update status."""
|
||||
try:
|
||||
if track_new:
|
||||
@@ -99,9 +106,14 @@ def setup_scanner(hass, config, see, discovery_info=None):
|
||||
see_device(mac, result, rssi)
|
||||
except bluetooth.BluetoothError:
|
||||
_LOGGER.exception("Error looking up Bluetooth device")
|
||||
track_point_in_utc_time(
|
||||
hass, update_bluetooth, dt_util.utcnow() + interval)
|
||||
|
||||
update_bluetooth(dt_util.utcnow())
|
||||
def handle_update_bluetooth(call):
|
||||
"""Update bluetooth devices on demand."""
|
||||
update_bluetooth_once()
|
||||
|
||||
update_bluetooth()
|
||||
|
||||
hass.services.register(
|
||||
DOMAIN, "bluetooth_tracker_update", handle_update_bluetooth)
|
||||
|
||||
return True
|
||||
|
||||
@@ -15,7 +15,7 @@ from homeassistant.const import ATTR_ID, CONF_PASSWORD, CONF_USERNAME
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.event import track_time_interval
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
from homeassistant.util import slugify
|
||||
from homeassistant.util import slugify, dt as dt_util
|
||||
|
||||
REQUIREMENTS = ['locationsharinglib==2.0.11']
|
||||
|
||||
@@ -92,7 +92,7 @@ class GoogleMapsScanner:
|
||||
ATTR_ADDRESS: person.address,
|
||||
ATTR_FULL_NAME: person.full_name,
|
||||
ATTR_ID: person.id,
|
||||
ATTR_LAST_SEEN: person.datetime,
|
||||
ATTR_LAST_SEEN: dt_util.as_utc(person.datetime),
|
||||
ATTR_NICKNAME: person.nickname,
|
||||
}
|
||||
self.see(
|
||||
|
||||
@@ -143,7 +143,7 @@ class FeedManager:
|
||||
else:
|
||||
self._has_published_parsed = False
|
||||
_LOGGER.debug("No published_parsed info available for entry %s",
|
||||
entry.title)
|
||||
entry)
|
||||
entry.update({'feed_url': self._url})
|
||||
self._hass.bus.fire(self._event_type, entry)
|
||||
|
||||
@@ -164,7 +164,7 @@ class FeedManager:
|
||||
self._update_and_fire_entry(entry)
|
||||
new_entries = True
|
||||
else:
|
||||
_LOGGER.debug("Entry %s already processed", entry.title)
|
||||
_LOGGER.debug("Entry %s already processed", entry)
|
||||
if not new_entries:
|
||||
self._log_no_entries()
|
||||
self._firstrun = False
|
||||
|
||||
@@ -26,7 +26,7 @@ from homeassistant.helpers.translation import async_get_translations
|
||||
from homeassistant.loader import bind_hass
|
||||
from homeassistant.util.yaml import load_yaml
|
||||
|
||||
REQUIREMENTS = ['home-assistant-frontend==20180829.0']
|
||||
REQUIREMENTS = ['home-assistant-frontend==20180910.0']
|
||||
|
||||
DOMAIN = 'frontend'
|
||||
DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log',
|
||||
|
||||
68
homeassistant/components/geo_location/__init__.py
Normal file
68
homeassistant/components/geo_location/__init__.py
Normal file
@@ -0,0 +1,68 @@
|
||||
"""
|
||||
Geo Location component.
|
||||
|
||||
This component covers platforms that deal with external events that contain
|
||||
a geo location related to the installed HA instance.
|
||||
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/geo_location/
|
||||
"""
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
from typing import Optional
|
||||
|
||||
from homeassistant.const import ATTR_LATITUDE, ATTR_LONGITUDE
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
ATTR_DISTANCE = 'distance'
|
||||
DOMAIN = 'geo_location'
|
||||
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
||||
GROUP_NAME_ALL_EVENTS = 'All Geo Location Events'
|
||||
SCAN_INTERVAL = timedelta(seconds=60)
|
||||
|
||||
|
||||
async def async_setup(hass, config):
|
||||
"""Set up this component."""
|
||||
component = EntityComponent(
|
||||
_LOGGER, DOMAIN, hass, SCAN_INTERVAL, GROUP_NAME_ALL_EVENTS)
|
||||
await component.async_setup(config)
|
||||
return True
|
||||
|
||||
|
||||
class GeoLocationEvent(Entity):
|
||||
"""This represents an external event with an associated geo location."""
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the state of the sensor."""
|
||||
if self.distance is not None:
|
||||
return round(self.distance, 1)
|
||||
return None
|
||||
|
||||
@property
|
||||
def distance(self) -> Optional[float]:
|
||||
"""Return distance value of this external event."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def latitude(self) -> Optional[float]:
|
||||
"""Return latitude value of this external event."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def longitude(self) -> Optional[float]:
|
||||
"""Return longitude value of this external event."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def state_attributes(self):
|
||||
"""Return the state attributes of this external event."""
|
||||
data = {}
|
||||
if self.latitude is not None:
|
||||
data[ATTR_LATITUDE] = round(self.latitude, 5)
|
||||
if self.longitude is not None:
|
||||
data[ATTR_LONGITUDE] = round(self.longitude, 5)
|
||||
return data
|
||||
132
homeassistant/components/geo_location/demo.py
Normal file
132
homeassistant/components/geo_location/demo.py
Normal file
@@ -0,0 +1,132 @@
|
||||
"""
|
||||
Demo platform for the geo location component.
|
||||
|
||||
For more details about this platform, please refer to the documentation
|
||||
https://home-assistant.io/components/demo/
|
||||
"""
|
||||
import logging
|
||||
import random
|
||||
from datetime import timedelta
|
||||
from math import pi, cos, sin, radians
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from homeassistant.components.geo_location import GeoLocationEvent
|
||||
from homeassistant.helpers.event import track_time_interval
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
AVG_KM_PER_DEGREE = 111.0
|
||||
DEFAULT_UNIT_OF_MEASUREMENT = "km"
|
||||
DEFAULT_UPDATE_INTERVAL = timedelta(minutes=1)
|
||||
MAX_RADIUS_IN_KM = 50
|
||||
NUMBER_OF_DEMO_DEVICES = 5
|
||||
|
||||
EVENT_NAMES = ["Bushfire", "Hazard Reduction", "Grass Fire", "Burn off",
|
||||
"Structure Fire", "Fire Alarm", "Thunderstorm", "Tornado",
|
||||
"Cyclone", "Waterspout", "Dust Storm", "Blizzard", "Ice Storm",
|
||||
"Earthquake", "Tsunami"]
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up the Demo geo locations."""
|
||||
DemoManager(hass, add_entities)
|
||||
|
||||
|
||||
class DemoManager:
|
||||
"""Device manager for demo geo location events."""
|
||||
|
||||
def __init__(self, hass, add_entities):
|
||||
"""Initialise the demo geo location event manager."""
|
||||
self._hass = hass
|
||||
self._add_entities = add_entities
|
||||
self._managed_devices = []
|
||||
self._update(count=NUMBER_OF_DEMO_DEVICES)
|
||||
self._init_regular_updates()
|
||||
|
||||
def _generate_random_event(self):
|
||||
"""Generate a random event in vicinity of this HA instance."""
|
||||
home_latitude = self._hass.config.latitude
|
||||
home_longitude = self._hass.config.longitude
|
||||
|
||||
# Approx. 111km per degree (north-south).
|
||||
radius_in_degrees = random.random() * MAX_RADIUS_IN_KM / \
|
||||
AVG_KM_PER_DEGREE
|
||||
radius_in_km = radius_in_degrees * AVG_KM_PER_DEGREE
|
||||
angle = random.random() * 2 * pi
|
||||
# Compute coordinates based on radius and angle. Adjust longitude value
|
||||
# based on HA's latitude.
|
||||
latitude = home_latitude + radius_in_degrees * sin(angle)
|
||||
longitude = home_longitude + radius_in_degrees * cos(angle) / \
|
||||
cos(radians(home_latitude))
|
||||
|
||||
event_name = random.choice(EVENT_NAMES)
|
||||
return DemoGeoLocationEvent(event_name, radius_in_km, latitude,
|
||||
longitude, DEFAULT_UNIT_OF_MEASUREMENT)
|
||||
|
||||
def _init_regular_updates(self):
|
||||
"""Schedule regular updates based on configured time interval."""
|
||||
track_time_interval(self._hass, lambda now: self._update(),
|
||||
DEFAULT_UPDATE_INTERVAL)
|
||||
|
||||
def _update(self, count=1):
|
||||
"""Remove events and add new random events."""
|
||||
# Remove devices.
|
||||
for _ in range(1, count + 1):
|
||||
if self._managed_devices:
|
||||
device = random.choice(self._managed_devices)
|
||||
if device:
|
||||
_LOGGER.debug("Removing %s", device)
|
||||
self._managed_devices.remove(device)
|
||||
self._hass.add_job(device.async_remove())
|
||||
# Generate new devices from events.
|
||||
new_devices = []
|
||||
for _ in range(1, count + 1):
|
||||
new_device = self._generate_random_event()
|
||||
_LOGGER.debug("Adding %s", new_device)
|
||||
new_devices.append(new_device)
|
||||
self._managed_devices.append(new_device)
|
||||
self._add_entities(new_devices)
|
||||
|
||||
|
||||
class DemoGeoLocationEvent(GeoLocationEvent):
|
||||
"""This represents a demo geo location event."""
|
||||
|
||||
def __init__(self, name, distance, latitude, longitude,
|
||||
unit_of_measurement):
|
||||
"""Initialize entity with data provided."""
|
||||
self._name = name
|
||||
self._distance = distance
|
||||
self._latitude = latitude
|
||||
self._longitude = longitude
|
||||
self._unit_of_measurement = unit_of_measurement
|
||||
|
||||
@property
|
||||
def name(self) -> Optional[str]:
|
||||
"""Return the name of the event."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""No polling needed for a demo geo location event."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def distance(self) -> Optional[float]:
|
||||
"""Return distance value of this external event."""
|
||||
return self._distance
|
||||
|
||||
@property
|
||||
def latitude(self) -> Optional[float]:
|
||||
"""Return latitude value of this external event."""
|
||||
return self._latitude
|
||||
|
||||
@property
|
||||
def longitude(self) -> Optional[float]:
|
||||
"""Return longitude value of this external event."""
|
||||
return self._longitude
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
"""Return the unit of measurement."""
|
||||
return self._unit_of_measurement
|
||||
158
homeassistant/components/habitica/__init__.py
Normal file
158
homeassistant/components/habitica/__init__.py
Normal file
@@ -0,0 +1,158 @@
|
||||
"""
|
||||
The Habitica API component.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/habitica/
|
||||
"""
|
||||
|
||||
import logging
|
||||
from collections import namedtuple
|
||||
|
||||
import voluptuous as vol
|
||||
from homeassistant.const import \
|
||||
CONF_NAME, CONF_URL, CONF_SENSORS, CONF_PATH, CONF_API_KEY
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers import \
|
||||
config_validation as cv, discovery
|
||||
|
||||
REQUIREMENTS = ['habitipy==0.2.0']
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
DOMAIN = "habitica"
|
||||
|
||||
CONF_API_USER = "api_user"
|
||||
|
||||
ST = SensorType = namedtuple('SensorType', [
|
||||
"name", "icon", "unit", "path"
|
||||
])
|
||||
|
||||
SENSORS_TYPES = {
|
||||
'name': ST('Name', None, '', ["profile", "name"]),
|
||||
'hp': ST('HP', 'mdi:heart', 'HP', ["stats", "hp"]),
|
||||
'maxHealth': ST('max HP', 'mdi:heart', 'HP', ["stats", "maxHealth"]),
|
||||
'mp': ST('Mana', 'mdi:auto-fix', 'MP', ["stats", "mp"]),
|
||||
'maxMP': ST('max Mana', 'mdi:auto-fix', 'MP', ["stats", "maxMP"]),
|
||||
'exp': ST('EXP', 'mdi:star', 'EXP', ["stats", "exp"]),
|
||||
'toNextLevel': ST(
|
||||
'Next Lvl', 'mdi:star', 'EXP', ["stats", "toNextLevel"]),
|
||||
'lvl': ST(
|
||||
'Lvl', 'mdi:arrow-up-bold-circle-outline', 'Lvl', ["stats", "lvl"]),
|
||||
'gp': ST('Gold', 'mdi:coin', 'Gold', ["stats", "gp"]),
|
||||
'class': ST('Class', 'mdi:sword', '', ["stats", "class"])
|
||||
}
|
||||
|
||||
INSTANCE_SCHEMA = vol.Schema({
|
||||
vol.Optional(CONF_URL, default='https://habitica.com'): cv.url,
|
||||
vol.Optional(CONF_NAME): cv.string,
|
||||
vol.Required(CONF_API_USER): cv.string,
|
||||
vol.Required(CONF_API_KEY): cv.string,
|
||||
vol.Optional(CONF_SENSORS, default=list(SENSORS_TYPES)):
|
||||
vol.All(
|
||||
cv.ensure_list,
|
||||
vol.Unique(),
|
||||
[vol.In(list(SENSORS_TYPES))])
|
||||
})
|
||||
|
||||
has_unique_values = vol.Schema(vol.Unique()) # pylint: disable=invalid-name
|
||||
# because we want a handy alias
|
||||
|
||||
|
||||
def has_all_unique_users(value):
|
||||
"""Validate that all `api_user`s are unique."""
|
||||
api_users = [user[CONF_API_USER] for user in value]
|
||||
has_unique_values(api_users)
|
||||
return value
|
||||
|
||||
|
||||
def has_all_unique_users_names(value):
|
||||
"""Validate that all user's names are unique and set if any is set."""
|
||||
names = [user.get(CONF_NAME) for user in value]
|
||||
if None in names and any(name is not None for name in names):
|
||||
raise vol.Invalid(
|
||||
'user names of all users must be set if any is set')
|
||||
if not all(name is None for name in names):
|
||||
has_unique_values(names)
|
||||
return value
|
||||
|
||||
|
||||
INSTANCE_LIST_SCHEMA = vol.All(
|
||||
cv.ensure_list,
|
||||
has_all_unique_users,
|
||||
has_all_unique_users_names,
|
||||
[INSTANCE_SCHEMA])
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
DOMAIN: INSTANCE_LIST_SCHEMA
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
SERVICE_API_CALL = 'api_call'
|
||||
ATTR_NAME = CONF_NAME
|
||||
ATTR_PATH = CONF_PATH
|
||||
ATTR_ARGS = "args"
|
||||
EVENT_API_CALL_SUCCESS = "{0}_{1}_{2}".format(
|
||||
DOMAIN, SERVICE_API_CALL, "success")
|
||||
|
||||
SERVICE_API_CALL_SCHEMA = vol.Schema({
|
||||
vol.Required(ATTR_NAME): str,
|
||||
vol.Required(ATTR_PATH): vol.All(cv.ensure_list, [str]),
|
||||
vol.Optional(ATTR_ARGS): dict
|
||||
})
|
||||
|
||||
|
||||
async def async_setup(hass, config):
|
||||
"""Set up the habitica service."""
|
||||
conf = config[DOMAIN]
|
||||
data = hass.data[DOMAIN] = {}
|
||||
websession = async_get_clientsession(hass)
|
||||
from habitipy.aio import HabitipyAsync
|
||||
|
||||
class HAHabitipyAsync(HabitipyAsync):
|
||||
"""Closure API class to hold session."""
|
||||
|
||||
def __call__(self, **kwargs):
|
||||
return super().__call__(websession, **kwargs)
|
||||
|
||||
for instance in conf:
|
||||
url = instance[CONF_URL]
|
||||
username = instance[CONF_API_USER]
|
||||
password = instance[CONF_API_KEY]
|
||||
name = instance.get(CONF_NAME)
|
||||
config_dict = {"url": url, "login": username, "password": password}
|
||||
api = HAHabitipyAsync(config_dict)
|
||||
user = await api.user.get()
|
||||
if name is None:
|
||||
name = user['profile']['name']
|
||||
data[name] = api
|
||||
if CONF_SENSORS in instance:
|
||||
hass.async_create_task(
|
||||
discovery.async_load_platform(
|
||||
hass, "sensor", DOMAIN,
|
||||
{"name": name, "sensors": instance[CONF_SENSORS]},
|
||||
config))
|
||||
|
||||
async def handle_api_call(call):
|
||||
name = call.data[ATTR_NAME]
|
||||
path = call.data[ATTR_PATH]
|
||||
api = hass.data[DOMAIN].get(name)
|
||||
if api is None:
|
||||
_LOGGER.error(
|
||||
"API_CALL: User '%s' not configured", name)
|
||||
return
|
||||
try:
|
||||
for element in path:
|
||||
api = api[element]
|
||||
except KeyError:
|
||||
_LOGGER.error(
|
||||
"API_CALL: Path %s is invalid"
|
||||
" for api on '{%s}' element", path, element)
|
||||
return
|
||||
kwargs = call.data.get(ATTR_ARGS, {})
|
||||
data = await api(**kwargs)
|
||||
hass.bus.async_fire(EVENT_API_CALL_SUCCESS, {
|
||||
"name": name, "path": path, "data": data
|
||||
})
|
||||
|
||||
hass.services.async_register(
|
||||
DOMAIN, SERVICE_API_CALL,
|
||||
handle_api_call,
|
||||
schema=SERVICE_API_CALL_SCHEMA)
|
||||
return True
|
||||
15
homeassistant/components/habitica/services.yaml
Normal file
15
homeassistant/components/habitica/services.yaml
Normal file
@@ -0,0 +1,15 @@
|
||||
# Describes the format for Habitica service
|
||||
|
||||
---
|
||||
api_call:
|
||||
description: Call Habitica api
|
||||
fields:
|
||||
name:
|
||||
description: Habitica's username to call for
|
||||
example: 'xxxNotAValidNickxxx'
|
||||
path:
|
||||
description: "Items from API URL in form of an array with method attached at the end. Consult https://habitica.com/apidoc/. Example uses https://habitica.com/apidoc/#api-Task-CreateUserTasks"
|
||||
example: '["tasks", "user", "post"]'
|
||||
args:
|
||||
description: Any additional json or url parameter arguments. See apidoc mentioned for path. Example uses same api endpoint
|
||||
example: '{"text": "Use API from Home Assistant", "type": "todo"}'
|
||||
@@ -5,6 +5,7 @@
|
||||
"unknown": "Ein unbekannter Fehler ist aufgetreten."
|
||||
},
|
||||
"error": {
|
||||
"invalid_2fa": "Ung\u00fcltige 2-Faktor Authentifizierung, bitte versuche es erneut.",
|
||||
"invalid_2fa_method": "Ung\u00fcltige 2FA Methode (mit Telefon verifizieren)",
|
||||
"invalid_login": "Ung\u00fcltige Daten, bitte erneut versuchen."
|
||||
},
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"unknown": "Unknown error occurred."
|
||||
},
|
||||
"error": {
|
||||
"invalid_2fa": "Invalid 2 Factor Authorization, please try again.",
|
||||
"invalid_2fa": "Invalid 2 Factor Authentication, please try again.",
|
||||
"invalid_2fa_method": "Invalid 2FA Method (Verify on Phone).",
|
||||
"invalid_login": "Invalid Login, please try again."
|
||||
},
|
||||
@@ -14,7 +14,7 @@
|
||||
"data": {
|
||||
"2fa": "2FA Pin"
|
||||
},
|
||||
"title": "2-Factor-Authorization"
|
||||
"title": "2-Factor-Authentication"
|
||||
},
|
||||
"user": {
|
||||
"data": {
|
||||
|
||||
18
homeassistant/components/hangouts/.translations/es-419.json
Normal file
18
homeassistant/components/hangouts/.translations/es-419.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Google Hangouts ya est\u00e1 configurado",
|
||||
"unknown": "Se produjo un error desconocido."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"email": "Direcci\u00f3n de correo electr\u00f3nico",
|
||||
"password": "Contrase\u00f1a"
|
||||
},
|
||||
"title": "Inicio de sesi\u00f3n de Google Hangouts"
|
||||
}
|
||||
},
|
||||
"title": "Google Hangouts"
|
||||
}
|
||||
}
|
||||
24
homeassistant/components/hangouts/.translations/fr.json
Normal file
24
homeassistant/components/hangouts/.translations/fr.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Google Hangouts est d\u00e9j\u00e0 configur\u00e9",
|
||||
"unknown": "Une erreur inconnue s'est produite"
|
||||
},
|
||||
"error": {
|
||||
"invalid_login": "Login invalide, veuillez r\u00e9essayer."
|
||||
},
|
||||
"step": {
|
||||
"2fa": {
|
||||
"title": "Authentification \u00e0 2 facteurs"
|
||||
},
|
||||
"user": {
|
||||
"data": {
|
||||
"email": "Adresse e-mail",
|
||||
"password": "Mot de passe"
|
||||
},
|
||||
"title": "Connexion \u00e0 Google Hangouts"
|
||||
}
|
||||
},
|
||||
"title": "Google Hangouts"
|
||||
}
|
||||
}
|
||||
29
homeassistant/components/hangouts/.translations/hu.json
Normal file
29
homeassistant/components/hangouts/.translations/hu.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "A Google Hangouts m\u00e1r konfigur\u00e1lva van",
|
||||
"unknown": "Ismeretlen hiba t\u00f6rt\u00e9nt."
|
||||
},
|
||||
"error": {
|
||||
"invalid_2fa": "\u00c9rv\u00e9nytelen K\u00e9tfaktoros hiteles\u00edt\u00e9s, pr\u00f3b\u00e1ld \u00fajra.",
|
||||
"invalid_2fa_method": "\u00c9rv\u00e9nytelen 2FA M\u00f3dszer (Ellen\u0151rz\u00e9s a Telefonon).",
|
||||
"invalid_login": "\u00c9rv\u00e9nytelen bejelentkez\u00e9s, pr\u00f3b\u00e1ld \u00fajra."
|
||||
},
|
||||
"step": {
|
||||
"2fa": {
|
||||
"data": {
|
||||
"2fa": "2FA Pin"
|
||||
},
|
||||
"title": "K\u00e9tfaktoros Hiteles\u00edt\u00e9s"
|
||||
},
|
||||
"user": {
|
||||
"data": {
|
||||
"email": "E-Mail C\u00edm",
|
||||
"password": "Jelsz\u00f3"
|
||||
},
|
||||
"title": "Google Hangouts Bejelentkez\u00e9s"
|
||||
}
|
||||
},
|
||||
"title": "Google Hangouts"
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,29 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Google Hangouts \u00e8 gi\u00e0 configurato",
|
||||
"unknown": "Si \u00e8 verificato un errore sconosciuto."
|
||||
},
|
||||
"error": {
|
||||
"invalid_2fa": "Autenticazione a 2 fattori non valida, riprovare.",
|
||||
"invalid_2fa_method": "Metodo 2FA non valido (verifica sul telefono).",
|
||||
"invalid_login": "Accesso non valido, si prega di riprovare."
|
||||
},
|
||||
"step": {
|
||||
"2fa": {
|
||||
"data": {
|
||||
"2fa": "2FA Pin"
|
||||
},
|
||||
"title": "Autenticazione a due fattori"
|
||||
},
|
||||
"user": {
|
||||
"data": {
|
||||
"email": "Indirizzo email",
|
||||
"password": "Password"
|
||||
},
|
||||
"title": "Accesso a Google Hangouts"
|
||||
}
|
||||
},
|
||||
"title": "Google Hangouts"
|
||||
}
|
||||
}
|
||||
29
homeassistant/components/hangouts/.translations/nl.json
Normal file
29
homeassistant/components/hangouts/.translations/nl.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Google Hangouts is al geconfigureerd",
|
||||
"unknown": "Onbekende fout opgetreden."
|
||||
},
|
||||
"error": {
|
||||
"invalid_2fa": "Ongeldige twee-factor-authenticatie, probeer het opnieuw.",
|
||||
"invalid_2fa_method": "Ongeldige 2FA-methode (verifi\u00ebren op telefoon).",
|
||||
"invalid_login": "Ongeldige aanmelding, probeer het opnieuw."
|
||||
},
|
||||
"step": {
|
||||
"2fa": {
|
||||
"data": {
|
||||
"2fa": "2FA pin"
|
||||
},
|
||||
"title": "Twee-factor-authenticatie"
|
||||
},
|
||||
"user": {
|
||||
"data": {
|
||||
"email": "E-mailadres",
|
||||
"password": "Wachtwoord"
|
||||
},
|
||||
"title": "Google Hangouts inlog"
|
||||
}
|
||||
},
|
||||
"title": "Google Hangouts"
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,27 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Google Hangouts er allerede konfigurert",
|
||||
"unknown": "Ukjent feil oppstod."
|
||||
},
|
||||
"error": {
|
||||
"invalid_2fa": "Ugyldig tofaktorautentisering, vennligst pr\u00f8v igjen.",
|
||||
"invalid_2fa_method": "Ugyldig 2FA-metode (Bekreft p\u00e5 telefon).",
|
||||
"invalid_login": "Ugyldig innlogging, vennligst pr\u00f8v igjen."
|
||||
},
|
||||
"step": {
|
||||
"2fa": {
|
||||
"data": {
|
||||
"2fa": "2FA Pin"
|
||||
},
|
||||
"title": "Tofaktorautentisering"
|
||||
},
|
||||
"user": {
|
||||
"data": {
|
||||
"email": "E-postadresse",
|
||||
"password": "Passord"
|
||||
}
|
||||
},
|
||||
"title": "Google Hangouts p\u00e5logging"
|
||||
}
|
||||
},
|
||||
"title": "Google Hangouts"
|
||||
|
||||
@@ -5,19 +5,14 @@
|
||||
"unknown": "Ocorreu um erro desconhecido."
|
||||
},
|
||||
"error": {
|
||||
"invalid_2fa_method": "M\u00e9todo 2FA inv\u00e1lido (verificar no telefone).",
|
||||
"invalid_login": "Login inv\u00e1lido, por favor, tente novamente."
|
||||
"invalid_2fa": "Autentica\u00e7\u00e3o de 2 fatores inv\u00e1lida, por favor, tente novamente."
|
||||
},
|
||||
"step": {
|
||||
"2fa": {
|
||||
"data": {
|
||||
"2fa": "Pin 2FA"
|
||||
},
|
||||
"title": ""
|
||||
},
|
||||
"user": {
|
||||
"data": {
|
||||
"email": "Endere\u00e7o de e-mail",
|
||||
"password": "Senha"
|
||||
},
|
||||
"title": "Login do Hangouts do Google"
|
||||
|
||||
31
homeassistant/components/hangouts/.translations/pt.json
Normal file
31
homeassistant/components/hangouts/.translations/pt.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Google Hangouts j\u00e1 est\u00e1 configurado",
|
||||
"unknown": "Ocorreu um erro desconhecido."
|
||||
},
|
||||
"error": {
|
||||
"invalid_2fa": "Autoriza\u00e7\u00e3o por 2 factores inv\u00e1lida, por favor, tente novamente.",
|
||||
"invalid_2fa_method": "M\u00e9todo 2FA inv\u00e1lido (verificar no telefone).",
|
||||
"invalid_login": "Login inv\u00e1lido, por favor, tente novamente."
|
||||
},
|
||||
"step": {
|
||||
"2fa": {
|
||||
"data": {
|
||||
"2fa": "Pin 2FA"
|
||||
},
|
||||
"description": "Vazio",
|
||||
"title": ""
|
||||
},
|
||||
"user": {
|
||||
"data": {
|
||||
"email": "Endere\u00e7o de e-mail",
|
||||
"password": "Palavra-passe"
|
||||
},
|
||||
"description": "Vazio",
|
||||
"title": "Login Google Hangouts"
|
||||
}
|
||||
},
|
||||
"title": ""
|
||||
}
|
||||
}
|
||||
21
homeassistant/components/hangouts/.translations/sv.json
Normal file
21
homeassistant/components/hangouts/.translations/sv.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Google Hangouts \u00e4r redan inst\u00e4llt",
|
||||
"unknown": "Ett ok\u00e4nt fel intr\u00e4ffade"
|
||||
},
|
||||
"step": {
|
||||
"2fa": {
|
||||
"title": "Tv\u00e5faktorsautentisering"
|
||||
},
|
||||
"user": {
|
||||
"data": {
|
||||
"email": "E-postadress",
|
||||
"password": "L\u00f6senord"
|
||||
},
|
||||
"title": "Google Hangouts-inloggning"
|
||||
}
|
||||
},
|
||||
"title": "Google Hangouts"
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
},
|
||||
"error": {
|
||||
"invalid_login": "Invalid Login, please try again.",
|
||||
"invalid_2fa": "Invalid 2 Factor Authorization, please try again.",
|
||||
"invalid_2fa": "Invalid 2 Factor Authentication, please try again.",
|
||||
"invalid_2fa_method": "Invalid 2FA Method (Verify on Phone)."
|
||||
},
|
||||
"step": {
|
||||
@@ -23,7 +23,7 @@
|
||||
"2fa": "2FA Pin"
|
||||
},
|
||||
"description": "",
|
||||
"title": "2-Factor-Authorization"
|
||||
"title": "2-Factor-Authentication"
|
||||
}
|
||||
},
|
||||
"title": "Google Hangouts"
|
||||
|
||||
@@ -77,7 +77,7 @@ HM_DEVICE_TYPES = {
|
||||
'FillingLevel', 'ValveDrive', 'EcoLogic', 'IPThermostatWall',
|
||||
'IPSmoke', 'RFSiren', 'PresenceIP', 'IPAreaThermostat',
|
||||
'IPWeatherSensor', 'RotaryHandleSensorIP', 'IPPassageSensor',
|
||||
'IPKeySwitchPowermeter'],
|
||||
'IPKeySwitchPowermeter', 'IPThermostatWall230V'],
|
||||
DISCOVER_CLIMATE: [
|
||||
'Thermostat', 'ThermostatWall', 'MAXThermostat', 'ThermostatWall2',
|
||||
'MAXWallThermostat', 'IPThermostat', 'IPThermostatWall',
|
||||
|
||||
@@ -2,6 +2,13 @@
|
||||
"config": {
|
||||
"error": {
|
||||
"invalid_pin": "Ugyldig PIN, pr\u00f8v igen."
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"pin": "Pin kode (valgfri)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
"abort": {
|
||||
"already_configured": "Accesspoint ya est\u00e1 configurado",
|
||||
"conection_aborted": "No se pudo conectar al servidor HMIP",
|
||||
"connection_aborted": "No se pudo conectar al servidor HMIP",
|
||||
"unknown": "Se produjo un error desconocido."
|
||||
},
|
||||
"error": {
|
||||
@@ -18,6 +19,7 @@
|
||||
"pin": "C\u00f3digo PIN (opcional)"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "HomematicIP Cloud"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Le point d'acc\u00e8s est d\u00e9j\u00e0 configur\u00e9",
|
||||
"conection_aborted": "Impossible de se connecter au serveur HMIP",
|
||||
"connection_aborted": "Impossible de se connecter au serveur HMIP",
|
||||
"unknown": "Une erreur inconnue s'est produite"
|
||||
},
|
||||
"error": {
|
||||
"invalid_pin": "Code PIN invalide, veuillez r\u00e9essayer.",
|
||||
"press_the_button": "Veuillez appuyer sur le bouton bleu.",
|
||||
"register_failed": "\u00c9chec d'enregistrement. Veuillez r\u00e9essayer."
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"hapid": "ID du point d'acc\u00e8s (SGTIN)",
|
||||
"name": "Nom (facultatif, utilis\u00e9 comme pr\u00e9fixe de nom pour tous les p\u00e9riph\u00e9riques)",
|
||||
"pin": "Code PIN (facultatif)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,14 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Il punto di accesso \u00e8 gi\u00e0 configurato",
|
||||
"connection_aborted": "Impossibile connettersi al server HMIP"
|
||||
},
|
||||
"error": {
|
||||
"invalid_pin": "PIN non valido, riprova.",
|
||||
"press_the_button": "Si prega di premere il pulsante blu.",
|
||||
"register_failed": "Registrazione fallita, si prega di riprovare."
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Accesspoint is reeds geconfigureerd",
|
||||
"already_configured": "Accesspoint is al geconfigureerd",
|
||||
"conection_aborted": "Kon geen verbinding maken met de HMIP-server",
|
||||
"connection_aborted": "Kon geen verbinding maken met de HMIP-server",
|
||||
"unknown": "Er is een onbekende fout opgetreden."
|
||||
@@ -19,11 +19,11 @@
|
||||
"name": "Naam (optioneel, gebruikt als naamprefix voor alle apparaten)",
|
||||
"pin": "Pin-Code (optioneel)"
|
||||
},
|
||||
"title": "Kies HomematicIP Accesspoint"
|
||||
"title": "Kies HomematicIP accesspoint"
|
||||
},
|
||||
"link": {
|
||||
"description": "Druk op de blauwe knop op de accesspoint en de verzendknop om HomematicIP met de Home Assistant te registreren. \n\n",
|
||||
"title": "Link Accesspoint"
|
||||
"description": "Druk op de blauwe knop op het accesspoint en de verzendknop om HomematicIP bij Home Assistant te registreren. \n\n",
|
||||
"title": "Link accesspoint"
|
||||
}
|
||||
},
|
||||
"title": "HomematicIP Cloud"
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
"title": "Velg HomematicIP tilgangspunkt"
|
||||
},
|
||||
"link": {
|
||||
"description": "Trykk p\u00e5 den bl\u00e5 knappen p\u00e5 tilgangspunktet og send knappen for \u00e5 registrere HomematicIP med Home Assistant. \n\n",
|
||||
"description": "Trykk p\u00e5 den bl\u00e5 knappen p\u00e5 tilgangspunktet og p\u00e5 send knappen for \u00e5 registrere HomematicIP med Home Assistant. \n\n",
|
||||
"title": "Link tilgangspunkt"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"abort": {
|
||||
"already_configured": "O ponto de acesso j\u00e1 se encontra configurado",
|
||||
"conection_aborted": "N\u00e3o foi poss\u00edvel ligar ao servidor HMIP",
|
||||
"connection_aborted": "N\u00e3o foi poss\u00edvel ligar ao servidor HMIP",
|
||||
"unknown": "Ocorreu um erro desconhecido."
|
||||
},
|
||||
"error": {
|
||||
|
||||
@@ -24,6 +24,6 @@
|
||||
"title": "Link Hub"
|
||||
}
|
||||
},
|
||||
"title": "Philips Hue Bridge"
|
||||
"title": "Philips Hue"
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,8 @@ import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.const import CONF_FILENAME, CONF_HOST
|
||||
from homeassistant.helpers import aiohttp_client, config_validation as cv
|
||||
from homeassistant.helpers import (
|
||||
aiohttp_client, config_validation as cv, device_registry as dr)
|
||||
|
||||
from .const import DOMAIN, API_NUPNP
|
||||
from .bridge import HueBridge
|
||||
@@ -132,7 +133,28 @@ async def async_setup_entry(hass, entry):
|
||||
|
||||
bridge = HueBridge(hass, entry, allow_unreachable, allow_groups)
|
||||
hass.data[DOMAIN][host] = bridge
|
||||
return await bridge.async_setup()
|
||||
|
||||
if not await bridge.async_setup():
|
||||
return False
|
||||
|
||||
config = bridge.api.config
|
||||
device_registry = await dr.async_get_registry(hass)
|
||||
device_registry.async_get_or_create(
|
||||
config_entry=entry.entry_id,
|
||||
connections={
|
||||
(dr.CONNECTION_NETWORK_MAC, config.mac)
|
||||
},
|
||||
identifiers={
|
||||
(DOMAIN, config.bridgeid)
|
||||
},
|
||||
manufacturer='Signify',
|
||||
name=config.name,
|
||||
# Not yet exposed as properties in aiohue
|
||||
model=config.raw['modelid'],
|
||||
sw_version=config.raw['swversion'],
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass, entry):
|
||||
|
||||
@@ -18,7 +18,7 @@ import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers import discovery
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
REQUIREMENTS = ['insteonplm==0.12.3']
|
||||
REQUIREMENTS = ['insteonplm==0.13.1']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -358,6 +358,8 @@ class IPDB:
|
||||
|
||||
def __init__(self):
|
||||
"""Create the INSTEON Product Database (IPDB)."""
|
||||
from insteonplm.states.cover import Cover
|
||||
|
||||
from insteonplm.states.onOff import (OnOffSwitch,
|
||||
OnOffSwitch_OutletTop,
|
||||
OnOffSwitch_OutletBottom,
|
||||
@@ -383,7 +385,9 @@ class IPDB:
|
||||
X10AllLightsOnSensor,
|
||||
X10AllLightsOffSensor)
|
||||
|
||||
self.states = [State(OnOffSwitch_OutletTop, 'switch'),
|
||||
self.states = [State(Cover, 'cover'),
|
||||
|
||||
State(OnOffSwitch_OutletTop, 'switch'),
|
||||
State(OnOffSwitch_OutletBottom, 'switch'),
|
||||
State(OpenClosedRelay, 'switch'),
|
||||
State(OnOffSwitch, 'switch'),
|
||||
@@ -470,11 +474,10 @@ class InsteonEntity(Entity):
|
||||
return attributes
|
||||
|
||||
@callback
|
||||
def async_entity_update(self, deviceid, statename, val):
|
||||
def async_entity_update(self, deviceid, group, val):
|
||||
"""Receive notification from transport that new data exists."""
|
||||
_LOGGER.debug('Received update for device %s group %d statename %s',
|
||||
self.address, self.group,
|
||||
self._insteon_device_state.name)
|
||||
_LOGGER.debug('Received update for device %s group %d value %s',
|
||||
deviceid.human, group, val)
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
@asyncio.coroutine
|
||||
|
||||
@@ -82,6 +82,11 @@ class DeconzLight(Light):
|
||||
self._light.register_async_callback(self.async_update_callback)
|
||||
self.hass.data[DATA_DECONZ_ID][self.entity_id] = self._light.deconz_id
|
||||
|
||||
async def async_will_remove_from_hass(self) -> None:
|
||||
"""Disconnect light object when removed."""
|
||||
self._light.remove_callback(self.async_update_callback)
|
||||
self._light = None
|
||||
|
||||
@callback
|
||||
def async_update_callback(self, reason):
|
||||
"""Update the light's state."""
|
||||
|
||||
@@ -285,6 +285,25 @@ class HueLight(Light):
|
||||
"""Return the list of supported effects."""
|
||||
return [EFFECT_COLORLOOP, EFFECT_RANDOM]
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
"""Return the device info."""
|
||||
if self.light.type in ('LightGroup', 'Room'):
|
||||
return None
|
||||
|
||||
return {
|
||||
'identifiers': {
|
||||
(hue.DOMAIN, self.unique_id)
|
||||
},
|
||||
'name': self.name,
|
||||
'manufacturer': self.light.manufacturername,
|
||||
# productname added in Hue Bridge API 1.24
|
||||
# (published 03/05/2018)
|
||||
'model': self.light.productname or self.light.modelid,
|
||||
# Not yet exposed as properties in aiohue
|
||||
'sw_version': self.light.raw['swversion'],
|
||||
}
|
||||
|
||||
async def async_turn_on(self, **kwargs):
|
||||
"""Turn the specified or all lights on."""
|
||||
command = {'on': True}
|
||||
|
||||
@@ -167,9 +167,9 @@ async def async_setup_platform(hass,
|
||||
return True
|
||||
|
||||
|
||||
def lifx_features(device):
|
||||
"""Return a feature map for this device, or a default map if unknown."""
|
||||
return aiolifx().products.features_map.get(device.product) or \
|
||||
def lifx_features(bulb):
|
||||
"""Return a feature map for this bulb, or a default map if unknown."""
|
||||
return aiolifx().products.features_map.get(bulb.product) or \
|
||||
aiolifx().products.features_map.get(1)
|
||||
|
||||
|
||||
@@ -256,7 +256,7 @@ class LIFXManager:
|
||||
|
||||
async def start_effect(self, entities, service, **kwargs):
|
||||
"""Start a light effect on entities."""
|
||||
devices = [light.device for light in entities]
|
||||
bulbs = [light.bulb for light in entities]
|
||||
|
||||
if service == SERVICE_EFFECT_PULSE:
|
||||
effect = aiolifx_effects().EffectPulse(
|
||||
@@ -266,7 +266,7 @@ class LIFXManager:
|
||||
mode=kwargs.get(ATTR_MODE),
|
||||
hsbk=find_hsbk(**kwargs),
|
||||
)
|
||||
await self.effects_conductor.start(effect, devices)
|
||||
await self.effects_conductor.start(effect, bulbs)
|
||||
elif service == SERVICE_EFFECT_COLORLOOP:
|
||||
preprocess_turn_on_alternatives(kwargs)
|
||||
|
||||
@@ -282,12 +282,12 @@ class LIFXManager:
|
||||
transition=kwargs.get(ATTR_TRANSITION),
|
||||
brightness=brightness,
|
||||
)
|
||||
await self.effects_conductor.start(effect, devices)
|
||||
await self.effects_conductor.start(effect, bulbs)
|
||||
elif service == SERVICE_EFFECT_STOP:
|
||||
await self.effects_conductor.stop(devices)
|
||||
await self.effects_conductor.stop(bulbs)
|
||||
|
||||
def service_to_entities(self, service):
|
||||
"""Return the known devices that a service call mentions."""
|
||||
"""Return the known entities that a service call mentions."""
|
||||
entity_ids = extract_entity_ids(self.hass, service)
|
||||
if entity_ids:
|
||||
entities = [entity for entity in self.entities.values()
|
||||
@@ -298,50 +298,50 @@ class LIFXManager:
|
||||
return entities
|
||||
|
||||
@callback
|
||||
def register(self, device):
|
||||
def register(self, bulb):
|
||||
"""Handle aiolifx detected bulb."""
|
||||
self.hass.async_add_job(self.register_new_device(device))
|
||||
self.hass.async_add_job(self.register_new_bulb(bulb))
|
||||
|
||||
async def register_new_device(self, device):
|
||||
async def register_new_bulb(self, bulb):
|
||||
"""Handle newly detected bulb."""
|
||||
if device.mac_addr in self.entities:
|
||||
entity = self.entities[device.mac_addr]
|
||||
if bulb.mac_addr in self.entities:
|
||||
entity = self.entities[bulb.mac_addr]
|
||||
entity.registered = True
|
||||
_LOGGER.debug("%s register AGAIN", entity.who)
|
||||
await entity.update_hass()
|
||||
else:
|
||||
_LOGGER.debug("%s register NEW", device.ip_addr)
|
||||
_LOGGER.debug("%s register NEW", bulb.ip_addr)
|
||||
|
||||
# Read initial state
|
||||
ack = AwaitAioLIFX().wait
|
||||
color_resp = await ack(device.get_color)
|
||||
color_resp = await ack(bulb.get_color)
|
||||
if color_resp:
|
||||
version_resp = await ack(device.get_version)
|
||||
version_resp = await ack(bulb.get_version)
|
||||
|
||||
if color_resp is None or version_resp is None:
|
||||
_LOGGER.error("Failed to initialize %s", device.ip_addr)
|
||||
device.registered = False
|
||||
_LOGGER.error("Failed to initialize %s", bulb.ip_addr)
|
||||
bulb.registered = False
|
||||
else:
|
||||
device.timeout = MESSAGE_TIMEOUT
|
||||
device.retry_count = MESSAGE_RETRIES
|
||||
device.unregister_timeout = UNAVAILABLE_GRACE
|
||||
bulb.timeout = MESSAGE_TIMEOUT
|
||||
bulb.retry_count = MESSAGE_RETRIES
|
||||
bulb.unregister_timeout = UNAVAILABLE_GRACE
|
||||
|
||||
if lifx_features(device)["multizone"]:
|
||||
entity = LIFXStrip(device, self.effects_conductor)
|
||||
elif lifx_features(device)["color"]:
|
||||
entity = LIFXColor(device, self.effects_conductor)
|
||||
if lifx_features(bulb)["multizone"]:
|
||||
entity = LIFXStrip(bulb, self.effects_conductor)
|
||||
elif lifx_features(bulb)["color"]:
|
||||
entity = LIFXColor(bulb, self.effects_conductor)
|
||||
else:
|
||||
entity = LIFXWhite(device, self.effects_conductor)
|
||||
entity = LIFXWhite(bulb, self.effects_conductor)
|
||||
|
||||
_LOGGER.debug("%s register READY", entity.who)
|
||||
self.entities[device.mac_addr] = entity
|
||||
self.entities[bulb.mac_addr] = entity
|
||||
self.async_add_entities([entity], True)
|
||||
|
||||
@callback
|
||||
def unregister(self, device):
|
||||
def unregister(self, bulb):
|
||||
"""Handle aiolifx disappearing bulbs."""
|
||||
if device.mac_addr in self.entities:
|
||||
entity = self.entities[device.mac_addr]
|
||||
if bulb.mac_addr in self.entities:
|
||||
entity = self.entities[bulb.mac_addr]
|
||||
_LOGGER.debug("%s unregister", entity.who)
|
||||
entity.registered = False
|
||||
self.hass.async_add_job(entity.async_update_ha_state())
|
||||
@@ -352,20 +352,17 @@ class AwaitAioLIFX:
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the wrapper."""
|
||||
self.device = None
|
||||
self.message = None
|
||||
self.event = asyncio.Event()
|
||||
|
||||
@callback
|
||||
def callback(self, device, message):
|
||||
def callback(self, bulb, message):
|
||||
"""Handle responses."""
|
||||
self.device = device
|
||||
self.message = message
|
||||
self.event.set()
|
||||
|
||||
async def wait(self, method):
|
||||
"""Call an aiolifx method and wait for its response."""
|
||||
self.device = None
|
||||
self.message = None
|
||||
self.event.clear()
|
||||
method(callb=self.callback)
|
||||
@@ -387,9 +384,9 @@ def convert_16_to_8(value):
|
||||
class LIFXLight(Light):
|
||||
"""Representation of a LIFX light."""
|
||||
|
||||
def __init__(self, device, effects_conductor):
|
||||
def __init__(self, bulb, effects_conductor):
|
||||
"""Initialize the light."""
|
||||
self.light = device
|
||||
self.bulb = bulb
|
||||
self.effects_conductor = effects_conductor
|
||||
self.registered = True
|
||||
self.postponed_update = None
|
||||
@@ -397,34 +394,34 @@ class LIFXLight(Light):
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
"""Return the availability of the device."""
|
||||
"""Return the availability of the bulb."""
|
||||
return self.registered
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique ID."""
|
||||
return self.light.mac_addr
|
||||
return self.bulb.mac_addr
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the device."""
|
||||
return self.light.label
|
||||
"""Return the name of the bulb."""
|
||||
return self.bulb.label
|
||||
|
||||
@property
|
||||
def who(self):
|
||||
"""Return a string identifying the device."""
|
||||
return "%s (%s)" % (self.light.ip_addr, self.name)
|
||||
"""Return a string identifying the bulb."""
|
||||
return "%s (%s)" % (self.bulb.ip_addr, self.name)
|
||||
|
||||
@property
|
||||
def min_mireds(self):
|
||||
"""Return the coldest color_temp that this light supports."""
|
||||
kelvin = lifx_features(self.light)['max_kelvin']
|
||||
kelvin = lifx_features(self.bulb)['max_kelvin']
|
||||
return math.floor(color_util.color_temperature_kelvin_to_mired(kelvin))
|
||||
|
||||
@property
|
||||
def max_mireds(self):
|
||||
"""Return the warmest color_temp that this light supports."""
|
||||
kelvin = lifx_features(self.light)['min_kelvin']
|
||||
kelvin = lifx_features(self.bulb)['min_kelvin']
|
||||
return math.ceil(color_util.color_temperature_kelvin_to_mired(kelvin))
|
||||
|
||||
@property
|
||||
@@ -432,8 +429,8 @@ class LIFXLight(Light):
|
||||
"""Flag supported features."""
|
||||
support = SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION | SUPPORT_EFFECT
|
||||
|
||||
device_features = lifx_features(self.light)
|
||||
if device_features['min_kelvin'] != device_features['max_kelvin']:
|
||||
bulb_features = lifx_features(self.bulb)
|
||||
if bulb_features['min_kelvin'] != bulb_features['max_kelvin']:
|
||||
support |= SUPPORT_COLOR_TEMP
|
||||
|
||||
return support
|
||||
@@ -441,25 +438,25 @@ class LIFXLight(Light):
|
||||
@property
|
||||
def brightness(self):
|
||||
"""Return the brightness of this light between 0..255."""
|
||||
return convert_16_to_8(self.light.color[2])
|
||||
return convert_16_to_8(self.bulb.color[2])
|
||||
|
||||
@property
|
||||
def color_temp(self):
|
||||
"""Return the color temperature."""
|
||||
_, sat, _, kelvin = self.light.color
|
||||
_, sat, _, kelvin = self.bulb.color
|
||||
if sat:
|
||||
return None
|
||||
return color_util.color_temperature_kelvin_to_mired(kelvin)
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if device is on."""
|
||||
return self.light.power_level != 0
|
||||
"""Return true if light is on."""
|
||||
return self.bulb.power_level != 0
|
||||
|
||||
@property
|
||||
def effect(self):
|
||||
"""Return the name of the currently running effect."""
|
||||
effect = self.effects_conductor.effect(self.light)
|
||||
effect = self.effects_conductor.effect(self.bulb)
|
||||
if effect:
|
||||
return 'lifx_effect_' + effect.name
|
||||
return None
|
||||
@@ -485,19 +482,19 @@ class LIFXLight(Light):
|
||||
util.dt.utcnow() + timedelta(milliseconds=when))
|
||||
|
||||
async def async_turn_on(self, **kwargs):
|
||||
"""Turn the device on."""
|
||||
"""Turn the light on."""
|
||||
kwargs[ATTR_POWER] = True
|
||||
self.hass.async_add_job(self.set_state(**kwargs))
|
||||
|
||||
async def async_turn_off(self, **kwargs):
|
||||
"""Turn the device off."""
|
||||
"""Turn the light off."""
|
||||
kwargs[ATTR_POWER] = False
|
||||
self.hass.async_add_job(self.set_state(**kwargs))
|
||||
|
||||
async def set_state(self, **kwargs):
|
||||
"""Set a color on the light and turn it on/off."""
|
||||
async with self.lock:
|
||||
bulb = self.light
|
||||
bulb = self.bulb
|
||||
|
||||
await self.effects_conductor.stop([bulb])
|
||||
|
||||
@@ -544,13 +541,13 @@ class LIFXLight(Light):
|
||||
await self.update_during_transition(fade)
|
||||
|
||||
async def set_power(self, ack, pwr, duration=0):
|
||||
"""Send a power change to the device."""
|
||||
await ack(partial(self.light.set_power, pwr, duration=duration))
|
||||
"""Send a power change to the bulb."""
|
||||
await ack(partial(self.bulb.set_power, pwr, duration=duration))
|
||||
|
||||
async def set_color(self, ack, hsbk, kwargs, duration=0):
|
||||
"""Send a color change to the device."""
|
||||
hsbk = merge_hsbk(self.light.color, hsbk)
|
||||
await ack(partial(self.light.set_color, hsbk, duration=duration))
|
||||
"""Send a color change to the bulb."""
|
||||
hsbk = merge_hsbk(self.bulb.color, hsbk)
|
||||
await ack(partial(self.bulb.set_color, hsbk, duration=duration))
|
||||
|
||||
async def default_effect(self, **kwargs):
|
||||
"""Start an effect with default parameters."""
|
||||
@@ -563,7 +560,7 @@ class LIFXLight(Light):
|
||||
async def async_update(self):
|
||||
"""Update bulb status."""
|
||||
if self.available and not self.lock.locked():
|
||||
await AwaitAioLIFX().wait(self.light.get_color)
|
||||
await AwaitAioLIFX().wait(self.bulb.get_color)
|
||||
|
||||
|
||||
class LIFXWhite(LIFXLight):
|
||||
@@ -600,7 +597,7 @@ class LIFXColor(LIFXLight):
|
||||
@property
|
||||
def hs_color(self):
|
||||
"""Return the hs value."""
|
||||
hue, sat, _, _ = self.light.color
|
||||
hue, sat, _, _ = self.bulb.color
|
||||
hue = hue / 65535 * 360
|
||||
sat = sat / 65535 * 100
|
||||
return (hue, sat) if sat else None
|
||||
@@ -610,8 +607,8 @@ class LIFXStrip(LIFXColor):
|
||||
"""Representation of a LIFX light strip with multiple zones."""
|
||||
|
||||
async def set_color(self, ack, hsbk, kwargs, duration=0):
|
||||
"""Send a color change to the device."""
|
||||
bulb = self.light
|
||||
"""Send a color change to the bulb."""
|
||||
bulb = self.bulb
|
||||
num_zones = len(bulb.color_zones)
|
||||
|
||||
zones = kwargs.get(ATTR_ZONES)
|
||||
@@ -659,7 +656,7 @@ class LIFXStrip(LIFXColor):
|
||||
while self.available and zone < top:
|
||||
# Each get_color_zones can update 8 zones at once
|
||||
resp = await AwaitAioLIFX().wait(partial(
|
||||
self.light.get_color_zones,
|
||||
self.bulb.get_color_zones,
|
||||
start_index=zone))
|
||||
if resp:
|
||||
zone += 8
|
||||
|
||||
@@ -54,6 +54,7 @@ CONF_WHITE_VALUE_SCALE = 'white_value_scale'
|
||||
CONF_WHITE_VALUE_STATE_TOPIC = 'white_value_state_topic'
|
||||
CONF_WHITE_VALUE_TEMPLATE = 'white_value_template'
|
||||
CONF_ON_COMMAND_TYPE = 'on_command_type'
|
||||
CONF_UNIQUE_ID = 'unique_id'
|
||||
|
||||
DEFAULT_BRIGHTNESS_SCALE = 255
|
||||
DEFAULT_NAME = 'MQTT Light'
|
||||
@@ -79,6 +80,7 @@ PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
|
||||
vol.Optional(CONF_EFFECT_STATE_TOPIC): mqtt.valid_subscribe_topic,
|
||||
vol.Optional(CONF_EFFECT_VALUE_TEMPLATE): cv.template,
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_UNIQUE_ID): cv.string,
|
||||
vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
|
||||
vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string,
|
||||
vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string,
|
||||
@@ -111,6 +113,7 @@ async def async_setup_platform(hass, config, async_add_entities,
|
||||
|
||||
async_add_entities([MqttLight(
|
||||
config.get(CONF_NAME),
|
||||
config.get(CONF_UNIQUE_ID),
|
||||
config.get(CONF_EFFECT_LIST),
|
||||
{
|
||||
key: config.get(key) for key in (
|
||||
@@ -159,14 +162,15 @@ async def async_setup_platform(hass, config, async_add_entities,
|
||||
class MqttLight(MqttAvailability, Light):
|
||||
"""Representation of a MQTT light."""
|
||||
|
||||
def __init__(self, name, effect_list, topic, templates, qos,
|
||||
retain, payload, optimistic, brightness_scale,
|
||||
def __init__(self, name, unique_id, effect_list, topic, templates,
|
||||
qos, retain, payload, optimistic, brightness_scale,
|
||||
white_value_scale, on_command_type, availability_topic,
|
||||
payload_available, payload_not_available):
|
||||
"""Initialize MQTT light."""
|
||||
super().__init__(availability_topic, qos, payload_available,
|
||||
payload_not_available)
|
||||
self._name = name
|
||||
self._unique_id = unique_id
|
||||
self._effect_list = effect_list
|
||||
self._topic = topic
|
||||
self._qos = qos
|
||||
@@ -392,6 +396,11 @@ class MqttLight(MqttAvailability, Light):
|
||||
"""Return the name of the device if any."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique ID."""
|
||||
return self._unique_id
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if device is on."""
|
||||
|
||||
@@ -19,7 +19,7 @@ from homeassistant.util.color import \
|
||||
from homeassistant.util.color import (
|
||||
color_temperature_kelvin_to_mired as kelvin_to_mired)
|
||||
|
||||
REQUIREMENTS = ['pyHS100==0.3.2']
|
||||
REQUIREMENTS = ['pyHS100==0.3.3']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -4,31 +4,33 @@ Provides functionality for mailboxes.
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/mailbox/
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from contextlib import suppress
|
||||
from datetime import timedelta
|
||||
|
||||
import async_timeout
|
||||
import logging
|
||||
|
||||
from aiohttp import web
|
||||
from aiohttp.web_exceptions import HTTPNotFound
|
||||
import async_timeout
|
||||
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers import config_per_platform, discovery
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.components.http import HomeAssistantView
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import config_per_platform, discovery
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.setup import async_prepare_setup_platform
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONTENT_TYPE_MPEG = 'audio/mpeg'
|
||||
|
||||
DEPENDENCIES = ['http']
|
||||
DOMAIN = 'mailbox'
|
||||
|
||||
EVENT = 'mailbox_updated'
|
||||
CONTENT_TYPE_MPEG = 'audio/mpeg'
|
||||
|
||||
SCAN_INTERVAL = timedelta(seconds=30)
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
|
||||
@@ -7,17 +7,18 @@ https://home-assistant.io/components/mailbox.asteriskvm/
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.components.asterisk_mbox import DOMAIN
|
||||
from homeassistant.components.mailbox import (Mailbox, CONTENT_TYPE_MPEG,
|
||||
StreamError)
|
||||
from homeassistant.components.mailbox import (
|
||||
CONTENT_TYPE_MPEG, Mailbox, StreamError)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
|
||||
DEPENDENCIES = ['asterisk_mbox']
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
SIGNAL_MESSAGE_UPDATE = 'asterisk_mbox.message_updated'
|
||||
DEPENDENCIES = ['asterisk_mbox']
|
||||
|
||||
SIGNAL_MESSAGE_REQUEST = 'asterisk_mbox.message_request'
|
||||
SIGNAL_MESSAGE_UPDATE = 'asterisk_mbox.message_updated'
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
|
||||
@@ -5,16 +5,16 @@ For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/mailbox.asteriskvm/
|
||||
"""
|
||||
import asyncio
|
||||
from hashlib import sha1
|
||||
import logging
|
||||
import os
|
||||
from hashlib import sha1
|
||||
|
||||
from homeassistant.components.mailbox import (
|
||||
CONTENT_TYPE_MPEG, Mailbox, StreamError)
|
||||
from homeassistant.util import dt
|
||||
|
||||
from homeassistant.components.mailbox import (Mailbox, CONTENT_TYPE_MPEG,
|
||||
StreamError)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DOMAIN = "DemoMailbox"
|
||||
|
||||
|
||||
@@ -38,11 +38,15 @@ class DemoMailbox(Mailbox):
|
||||
msgtxt = "Message {}. {}".format(
|
||||
idx + 1, txt * (1 + idx * (idx % 2)))
|
||||
msgsha = sha1(msgtxt.encode('utf-8')).hexdigest()
|
||||
msg = {"info": {"origtime": msgtime,
|
||||
"callerid": "John Doe <212-555-1212>",
|
||||
"duration": "10"},
|
||||
"text": msgtxt,
|
||||
"sha": msgsha}
|
||||
msg = {
|
||||
'info': {
|
||||
'origtime': msgtime,
|
||||
'callerid': 'John Doe <212-555-1212>',
|
||||
'duration': '10',
|
||||
},
|
||||
'text': msgtxt,
|
||||
'sha': msgsha,
|
||||
}
|
||||
self._messages[msgsha] = msg
|
||||
|
||||
@property
|
||||
|
||||
@@ -14,7 +14,7 @@ from homeassistant.components.media_player import (
|
||||
SERVICE_PLAY_MEDIA)
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
|
||||
REQUIREMENTS = ['youtube_dl==2018.08.22']
|
||||
REQUIREMENTS = ['youtube_dl==2018.09.10']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -15,33 +15,32 @@ from random import SystemRandom
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from aiohttp import web
|
||||
from aiohttp.hdrs import CONTENT_TYPE, CACHE_CONTROL
|
||||
from aiohttp.hdrs import CACHE_CONTROL, CONTENT_TYPE
|
||||
import async_timeout
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.components import websocket_api
|
||||
from homeassistant.components.http import KEY_AUTHENTICATED, HomeAssistantView
|
||||
from homeassistant.const import (
|
||||
STATE_OFF, STATE_IDLE, STATE_PLAYING, STATE_UNKNOWN, ATTR_ENTITY_ID,
|
||||
SERVICE_TOGGLE, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_VOLUME_UP,
|
||||
SERVICE_MEDIA_PLAY, SERVICE_MEDIA_SEEK, SERVICE_MEDIA_STOP,
|
||||
SERVICE_VOLUME_SET, SERVICE_MEDIA_PAUSE, SERVICE_SHUFFLE_SET,
|
||||
SERVICE_VOLUME_DOWN, SERVICE_VOLUME_MUTE, SERVICE_MEDIA_NEXT_TRACK,
|
||||
SERVICE_MEDIA_PLAY_PAUSE, SERVICE_MEDIA_PREVIOUS_TRACK)
|
||||
ATTR_ENTITY_ID, SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PAUSE,
|
||||
SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PLAY_PAUSE, SERVICE_MEDIA_PREVIOUS_TRACK,
|
||||
SERVICE_MEDIA_SEEK, SERVICE_MEDIA_STOP, SERVICE_SHUFFLE_SET,
|
||||
SERVICE_TOGGLE, SERVICE_TURN_OFF, SERVICE_TURN_ON, SERVICE_VOLUME_DOWN,
|
||||
SERVICE_VOLUME_MUTE, SERVICE_VOLUME_SET, SERVICE_VOLUME_UP, STATE_IDLE,
|
||||
STATE_OFF, STATE_PLAYING, STATE_UNKNOWN)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.loader import bind_hass
|
||||
from homeassistant.components import websocket_api
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
_RND = SystemRandom()
|
||||
|
||||
DOMAIN = 'media_player'
|
||||
DEPENDENCIES = ['http']
|
||||
SCAN_INTERVAL = timedelta(seconds=10)
|
||||
|
||||
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
||||
|
||||
@@ -97,6 +96,8 @@ MEDIA_TYPE_CHANNEL = 'channel'
|
||||
MEDIA_TYPE_PLAYLIST = 'playlist'
|
||||
MEDIA_TYPE_URL = 'url'
|
||||
|
||||
SCAN_INTERVAL = timedelta(seconds=10)
|
||||
|
||||
SUPPORT_PAUSE = 1
|
||||
SUPPORT_SEEK = 2
|
||||
SUPPORT_VOLUME_SET = 4
|
||||
|
||||
@@ -4,17 +4,17 @@ Support for Anthem Network Receivers and Processors.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/media_player.anthemav/
|
||||
"""
|
||||
import logging
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.media_player import (
|
||||
PLATFORM_SCHEMA, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_SELECT_SOURCE,
|
||||
PLATFORM_SCHEMA, SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, SUPPORT_TURN_ON,
|
||||
SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, MediaPlayerDevice)
|
||||
from homeassistant.const import (
|
||||
CONF_NAME, CONF_HOST, CONF_PORT, STATE_OFF, STATE_ON, STATE_UNKNOWN,
|
||||
EVENT_HOMEASSISTANT_STOP)
|
||||
CONF_HOST, CONF_NAME, CONF_PORT, EVENT_HOMEASSISTANT_STOP, STATE_OFF,
|
||||
STATE_ON, STATE_UNKNOWN)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
REQUIREMENTS = ['anthemav==1.1.8']
|
||||
@@ -32,7 +32,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_HOST): cv.string,
|
||||
vol.Optional(CONF_NAME): cv.string,
|
||||
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
|
||||
@@ -7,20 +7,19 @@ https://home-assistant.io/components/media_player.apple_tv/
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.components.apple_tv import (
|
||||
ATTR_ATV, ATTR_POWER, DATA_APPLE_TV, DATA_ENTITIES)
|
||||
from homeassistant.components.media_player import (
|
||||
SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK,
|
||||
SUPPORT_STOP, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_TURN_ON,
|
||||
SUPPORT_TURN_OFF, MediaPlayerDevice, MEDIA_TYPE_MUSIC,
|
||||
MEDIA_TYPE_VIDEO, MEDIA_TYPE_TVSHOW)
|
||||
MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO, SUPPORT_NEXT_TRACK,
|
||||
SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK,
|
||||
SUPPORT_SEEK, SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON,
|
||||
MediaPlayerDevice)
|
||||
from homeassistant.const import (
|
||||
STATE_IDLE, STATE_PAUSED, STATE_PLAYING, STATE_STANDBY, CONF_HOST,
|
||||
STATE_OFF, CONF_NAME, EVENT_HOMEASSISTANT_STOP)
|
||||
CONF_HOST, CONF_NAME, EVENT_HOMEASSISTANT_STOP, STATE_IDLE, STATE_OFF,
|
||||
STATE_PAUSED, STATE_PLAYING, STATE_STANDBY)
|
||||
from homeassistant.core import callback
|
||||
import homeassistant.util.dt as dt_util
|
||||
|
||||
|
||||
DEPENDENCIES = ['apple_tv']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -31,8 +30,8 @@ SUPPORT_APPLE_TV = SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_PLAY_MEDIA | \
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
def async_setup_platform(
|
||||
hass, config, async_add_entities, discovery_info=None):
|
||||
"""Set up the Apple TV platform."""
|
||||
if not discovery_info:
|
||||
return
|
||||
|
||||
@@ -9,16 +9,13 @@ import logging
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.media_player import (
|
||||
SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK,
|
||||
SUPPORT_TURN_ON, SUPPORT_TURN_OFF, SUPPORT_SELECT_SOURCE,
|
||||
SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP, SUPPORT_PLAY,
|
||||
SUPPORT_VOLUME_SET, MediaPlayerDevice, PLATFORM_SCHEMA)
|
||||
|
||||
PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY,
|
||||
SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF,
|
||||
SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
|
||||
SUPPORT_VOLUME_STEP, MediaPlayerDevice)
|
||||
from homeassistant.const import (
|
||||
CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON, STATE_UNKNOWN,
|
||||
CONF_PORT, CONF_USERNAME, CONF_PASSWORD, CONF_TIMEOUT)
|
||||
|
||||
|
||||
CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_PORT, CONF_TIMEOUT,
|
||||
CONF_USERNAME, STATE_OFF, STATE_ON, STATE_UNKNOWN)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
REQUIREMENTS = ['sharp_aquos_rc==0.3.2']
|
||||
|
||||
@@ -13,15 +13,15 @@ from homeassistant.components.media_player import (
|
||||
DOMAIN, MEDIA_PLAYER_SCHEMA, PLATFORM_SCHEMA, SUPPORT_SELECT_SOURCE,
|
||||
SUPPORT_TURN_OFF, SUPPORT_TURN_ON, MediaPlayerDevice)
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID, CONF_NAME, CONF_HOST, CONF_PORT, STATE_OFF, STATE_ON)
|
||||
ATTR_ENTITY_ID, CONF_HOST, CONF_NAME, CONF_PORT, CONF_TYPE, STATE_OFF,
|
||||
STATE_ON)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
REQUIREMENTS = ['pyblackbird==0.5']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
SUPPORT_BLACKBIRD = SUPPORT_TURN_ON | SUPPORT_TURN_OFF | \
|
||||
SUPPORT_SELECT_SOURCE
|
||||
SUPPORT_BLACKBIRD = SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_SELECT_SOURCE
|
||||
|
||||
ZONE_SCHEMA = vol.Schema({
|
||||
vol.Required(CONF_NAME): cv.string,
|
||||
@@ -33,7 +33,6 @@ SOURCE_SCHEMA = vol.Schema({
|
||||
|
||||
CONF_ZONES = 'zones'
|
||||
CONF_SOURCES = 'sources'
|
||||
CONF_TYPE = 'type'
|
||||
|
||||
DATA_BLACKBIRD = 'blackbird'
|
||||
|
||||
|
||||
@@ -24,8 +24,8 @@ from homeassistant.components.media_player import (
|
||||
MediaPlayerDevice)
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID, CONF_HOST, CONF_HOSTS, CONF_NAME, CONF_PORT,
|
||||
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, STATE_IDLE,
|
||||
STATE_OFF, STATE_PAUSED, STATE_PLAYING)
|
||||
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, STATE_IDLE, STATE_OFF,
|
||||
STATE_PAUSED, STATE_PLAYING)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
@@ -10,11 +10,11 @@ import re
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.media_player import (
|
||||
SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK, SUPPORT_TURN_ON,
|
||||
SUPPORT_TURN_OFF, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP, SUPPORT_PLAY,
|
||||
SUPPORT_VOLUME_SET, SUPPORT_SELECT_SOURCE, MediaPlayerDevice,
|
||||
PLATFORM_SCHEMA)
|
||||
from homeassistant.const import (CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON)
|
||||
PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY,
|
||||
SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF,
|
||||
SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
|
||||
SUPPORT_VOLUME_STEP, MediaPlayerDevice)
|
||||
from homeassistant.const import CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.util.json import load_json, save_json
|
||||
|
||||
|
||||
@@ -7,26 +7,27 @@ https://home-assistant.io/components/media_player.cast/
|
||||
import asyncio
|
||||
import logging
|
||||
import threading
|
||||
|
||||
from typing import Optional, Tuple
|
||||
|
||||
import voluptuous as vol
|
||||
import attr
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.exceptions import PlatformNotReady
|
||||
from homeassistant.helpers.typing import HomeAssistantType, ConfigType
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import (dispatcher_send,
|
||||
async_dispatcher_connect)
|
||||
from homeassistant.components.cast import DOMAIN as CAST_DOMAIN
|
||||
from homeassistant.components.media_player import (
|
||||
MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_MOVIE, SUPPORT_NEXT_TRACK,
|
||||
SUPPORT_PAUSE, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK,
|
||||
SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
|
||||
SUPPORT_STOP, SUPPORT_PLAY, MediaPlayerDevice, PLATFORM_SCHEMA)
|
||||
MEDIA_TYPE_MOVIE, MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, PLATFORM_SCHEMA,
|
||||
SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA,
|
||||
SUPPORT_PREVIOUS_TRACK, SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON,
|
||||
SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, MediaPlayerDevice)
|
||||
from homeassistant.const import (
|
||||
CONF_HOST, STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING,
|
||||
EVENT_HOMEASSISTANT_STOP)
|
||||
CONF_HOST, EVENT_HOMEASSISTANT_STOP, STATE_IDLE, STATE_OFF, STATE_PAUSED,
|
||||
STATE_PLAYING)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.exceptions import PlatformNotReady
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.dispatcher import (
|
||||
async_dispatcher_connect, dispatcher_send)
|
||||
from homeassistant.helpers.typing import ConfigType, HomeAssistantType
|
||||
import homeassistant.util.dt as dt_util
|
||||
|
||||
DEPENDENCIES = ('cast',)
|
||||
@@ -57,8 +58,8 @@ SIGNAL_CAST_DISCOVERED = 'cast_discovered'
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Optional(CONF_HOST): cv.string,
|
||||
vol.Optional(CONF_IGNORE_CEC, default=[]): vol.All(cv.ensure_list,
|
||||
[cv.string])
|
||||
vol.Optional(CONF_IGNORE_CEC, default=[]):
|
||||
vol.All(cv.ensure_list, [cv.string]),
|
||||
})
|
||||
|
||||
|
||||
@@ -73,7 +74,8 @@ class ChromecastInfo:
|
||||
port = attr.ib(type=int)
|
||||
uuid = attr.ib(type=Optional[str], converter=attr.converters.optional(str),
|
||||
default=None) # always convert UUID to string if not None
|
||||
model_name = attr.ib(type=str, default='') # needed for cast type
|
||||
manufacturer = attr.ib(type=str, default='')
|
||||
model_name = attr.ib(type=str, default='')
|
||||
friendly_name = attr.ib(type=Optional[str], default=None)
|
||||
|
||||
@property
|
||||
@@ -111,6 +113,7 @@ def _fill_out_missing_chromecast_info(info: ChromecastInfo) -> ChromecastInfo:
|
||||
host=info.host, port=info.port,
|
||||
uuid=(info.uuid or http_device_status.uuid),
|
||||
friendly_name=(info.friendly_name or http_device_status.friendly_name),
|
||||
manufacturer=(info.manufacturer or http_device_status.manufacturer),
|
||||
model_name=(info.model_name or http_device_status.model_name)
|
||||
)
|
||||
|
||||
@@ -148,7 +151,13 @@ def _setup_internal_discovery(hass: HomeAssistantType) -> None:
|
||||
def internal_callback(name):
|
||||
"""Handle zeroconf discovery of a new chromecast."""
|
||||
mdns = listener.services[name]
|
||||
_discover_chromecast(hass, ChromecastInfo(*mdns))
|
||||
_discover_chromecast(hass, ChromecastInfo(
|
||||
host=mdns[0],
|
||||
port=mdns[1],
|
||||
uuid=mdns[2],
|
||||
model_name=mdns[3],
|
||||
friendly_name=mdns[4],
|
||||
))
|
||||
|
||||
_LOGGER.debug("Starting internal pychromecast discovery.")
|
||||
listener, browser = pychromecast.start_discovery(internal_callback)
|
||||
@@ -365,7 +374,10 @@ class CastDevice(MediaPlayerDevice):
|
||||
# pylint: disable=protected-access
|
||||
_LOGGER.debug("Connecting to cast device %s", cast_info)
|
||||
chromecast = await self.hass.async_add_job(
|
||||
pychromecast._get_chromecast_from_host, attr.astuple(cast_info))
|
||||
pychromecast._get_chromecast_from_host, (
|
||||
cast_info.host, cast_info.port, cast_info.uuid,
|
||||
cast_info.model_name, cast_info.friendly_name
|
||||
))
|
||||
self._chromecast = chromecast
|
||||
self._status_listener = CastStatusListener(self, chromecast)
|
||||
# Initialise connection status as connected because we can only
|
||||
@@ -494,6 +506,23 @@ class CastDevice(MediaPlayerDevice):
|
||||
"""Return the name of the device."""
|
||||
return self._cast_info.friendly_name
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
"""Return information about the device."""
|
||||
cast_info = self._cast_info
|
||||
|
||||
if cast_info.model_name == "Google Cast Group":
|
||||
return None
|
||||
|
||||
return {
|
||||
'name': cast_info.friendly_name,
|
||||
'identifiers': {
|
||||
(CAST_DOMAIN, cast_info.uuid.replace('-', ''))
|
||||
},
|
||||
'model': cast_info.model_name,
|
||||
'manufacturer': cast_info.manufacturer,
|
||||
}
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the state of the player."""
|
||||
|
||||
@@ -9,18 +9,18 @@ import logging
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.media_player import (
|
||||
MEDIA_TYPE_CHANNEL, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_EPISODE,
|
||||
MEDIA_TYPE_MOVIE, SUPPORT_PLAY, SUPPORT_PAUSE, SUPPORT_STOP,
|
||||
SUPPORT_VOLUME_MUTE, SUPPORT_NEXT_TRACK, SUPPORT_PREVIOUS_TRACK,
|
||||
SUPPORT_PLAY_MEDIA, SUPPORT_SELECT_SOURCE, DOMAIN, PLATFORM_SCHEMA,
|
||||
DOMAIN, MEDIA_TYPE_CHANNEL, MEDIA_TYPE_EPISODE, MEDIA_TYPE_MOVIE,
|
||||
MEDIA_TYPE_TVSHOW, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE,
|
||||
SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK,
|
||||
SUPPORT_SELECT_SOURCE, SUPPORT_STOP, SUPPORT_VOLUME_MUTE,
|
||||
MediaPlayerDevice)
|
||||
|
||||
from homeassistant.const import (
|
||||
CONF_HOST, CONF_PORT, CONF_NAME, STATE_IDLE, STATE_PAUSED, STATE_PLAYING,
|
||||
ATTR_ENTITY_ID)
|
||||
|
||||
ATTR_ENTITY_ID, CONF_HOST, CONF_NAME, CONF_PORT, STATE_IDLE, STATE_PAUSED,
|
||||
STATE_PLAYING)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
REQUIREMENTS = ['pychannels==1.0.0']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DATA_CHANNELS = 'channels'
|
||||
@@ -52,16 +52,11 @@ CHANNELS_SEEK_BY_SCHEMA = CHANNELS_SCHEMA.extend({
|
||||
vol.Required(ATTR_SECONDS): vol.Coerce(int),
|
||||
})
|
||||
|
||||
REQUIREMENTS = ['pychannels==1.0.0']
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up the Channels platform."""
|
||||
device = ChannelsPlayer(
|
||||
config.get('name'),
|
||||
config.get(CONF_HOST),
|
||||
config.get(CONF_PORT)
|
||||
)
|
||||
config.get(CONF_NAME), config.get(CONF_HOST), config.get(CONF_PORT))
|
||||
|
||||
if DATA_CHANNELS not in hass.data:
|
||||
hass.data[DATA_CHANNELS] = []
|
||||
@@ -77,8 +72,8 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
device.entity_id == entity_id), None)
|
||||
|
||||
if device is None:
|
||||
_LOGGER.warning("Unable to find Channels with entity_id: %s",
|
||||
entity_id)
|
||||
_LOGGER.warning(
|
||||
"Unable to find Channels with entity_id: %s", entity_id)
|
||||
return
|
||||
|
||||
if service.service == SERVICE_SEEK_FORWARD:
|
||||
@@ -90,12 +85,10 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
device.seek_by(seconds)
|
||||
|
||||
hass.services.register(
|
||||
DOMAIN, SERVICE_SEEK_FORWARD, service_handler,
|
||||
schema=CHANNELS_SCHEMA)
|
||||
DOMAIN, SERVICE_SEEK_FORWARD, service_handler, schema=CHANNELS_SCHEMA)
|
||||
|
||||
hass.services.register(
|
||||
DOMAIN, SERVICE_SEEK_BACKWARD, service_handler,
|
||||
schema=CHANNELS_SCHEMA)
|
||||
DOMAIN, SERVICE_SEEK_BACKWARD, service_handler, schema=CHANNELS_SCHEMA)
|
||||
|
||||
hass.services.register(
|
||||
DOMAIN, SERVICE_SEEK_BY, service_handler,
|
||||
|
||||
@@ -4,7 +4,6 @@ Support for Clementine Music Player as media player.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/media_player.clementine/
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
@@ -12,24 +11,24 @@ import time
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.components.media_player import (
|
||||
SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK, PLATFORM_SCHEMA,
|
||||
SUPPORT_VOLUME_STEP, SUPPORT_SELECT_SOURCE, SUPPORT_PLAY, MEDIA_TYPE_MUSIC,
|
||||
SUPPORT_VOLUME_SET, MediaPlayerDevice)
|
||||
MEDIA_TYPE_MUSIC, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE,
|
||||
SUPPORT_PLAY, SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE,
|
||||
SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP, MediaPlayerDevice)
|
||||
from homeassistant.const import (
|
||||
CONF_HOST, CONF_NAME, CONF_PORT, CONF_ACCESS_TOKEN,
|
||||
STATE_OFF, STATE_PLAYING, STATE_PAUSED, STATE_UNKNOWN)
|
||||
CONF_ACCESS_TOKEN, CONF_HOST, CONF_NAME, CONF_PORT, STATE_OFF,
|
||||
STATE_PAUSED, STATE_PLAYING)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
REQUIREMENTS = ['python-clementine-remote==1.0.1']
|
||||
|
||||
SCAN_INTERVAL = timedelta(seconds=5)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_NAME = 'Clementine Remote'
|
||||
DEFAULT_PORT = 5500
|
||||
|
||||
SCAN_INTERVAL = timedelta(seconds=5)
|
||||
|
||||
SUPPORT_CLEMENTINE = SUPPORT_PAUSE | SUPPORT_VOLUME_STEP | \
|
||||
SUPPORT_PREVIOUS_TRACK | SUPPORT_VOLUME_SET | \
|
||||
SUPPORT_NEXT_TRACK | \
|
||||
@@ -69,7 +68,7 @@ class ClementineDevice(MediaPlayerDevice):
|
||||
self._track_name = ''
|
||||
self._track_artist = ''
|
||||
self._track_album_name = ''
|
||||
self._state = STATE_UNKNOWN
|
||||
self._state = None
|
||||
|
||||
def update(self):
|
||||
"""Retrieve the latest data from the Clementine Player."""
|
||||
|
||||
@@ -8,15 +8,14 @@ import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
|
||||
from homeassistant.components.media_player import (
|
||||
MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE,
|
||||
SUPPORT_PREVIOUS_TRACK, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_PLAY,
|
||||
SUPPORT_VOLUME_SET, SUPPORT_PLAY_MEDIA, SUPPORT_SEEK, PLATFORM_SCHEMA,
|
||||
MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK,
|
||||
SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK,
|
||||
SUPPORT_SEEK, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_SET,
|
||||
MediaPlayerDevice)
|
||||
from homeassistant.const import (
|
||||
STATE_OFF, STATE_PAUSED, STATE_PLAYING, CONF_HOST, CONF_NAME, CONF_PORT,
|
||||
CONF_PASSWORD)
|
||||
CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_PORT, STATE_OFF, STATE_PAUSED,
|
||||
STATE_PLAYING)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
REQUIREMENTS = ['pycmus==0.1.1']
|
||||
|
||||
@@ -5,11 +5,12 @@ For more details about this platform, please refer to the documentation
|
||||
https://home-assistant.io/components/demo/
|
||||
"""
|
||||
from homeassistant.components.media_player import (
|
||||
MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_MOVIE, SUPPORT_NEXT_TRACK,
|
||||
SUPPORT_PAUSE, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK,
|
||||
SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
|
||||
SUPPORT_SELECT_SOURCE, SUPPORT_SELECT_SOUND_MODE, SUPPORT_CLEAR_PLAYLIST,
|
||||
SUPPORT_PLAY, SUPPORT_SHUFFLE_SET, MediaPlayerDevice)
|
||||
MEDIA_TYPE_MOVIE, MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW,
|
||||
SUPPORT_CLEAR_PLAYLIST, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY,
|
||||
SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOUND_MODE,
|
||||
SUPPORT_SELECT_SOURCE, SUPPORT_SHUFFLE_SET, SUPPORT_TURN_OFF,
|
||||
SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
|
||||
MediaPlayerDevice)
|
||||
from homeassistant.const import STATE_OFF, STATE_PAUSED, STATE_PLAYING
|
||||
import homeassistant.util.dt as dt_util
|
||||
|
||||
@@ -20,8 +21,8 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
DemoYoutubePlayer(
|
||||
'Living Room', 'eyU3bRy2x44',
|
||||
'♥♥ The Best Fireplace Video (3 hours)', 300),
|
||||
DemoYoutubePlayer('Bedroom', 'kxopViU98Xo', 'Epic sax guy 10 hours',
|
||||
360000),
|
||||
DemoYoutubePlayer(
|
||||
'Bedroom', 'kxopViU98Xo', 'Epic sax guy 10 hours', 360000),
|
||||
DemoMusicPlayer(), DemoTVShowPlayer(),
|
||||
])
|
||||
|
||||
|
||||
@@ -10,10 +10,10 @@ import telnetlib
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.media_player import (
|
||||
PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_SELECT_SOURCE,
|
||||
SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK, SUPPORT_TURN_OFF,
|
||||
SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
|
||||
SUPPORT_STOP, SUPPORT_PLAY, MediaPlayerDevice)
|
||||
PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY,
|
||||
SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE, SUPPORT_STOP,
|
||||
SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
|
||||
MediaPlayerDevice)
|
||||
from homeassistant.const import (
|
||||
CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON, STATE_UNKNOWN)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
@@ -5,35 +5,37 @@ For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/media_player.denon/
|
||||
"""
|
||||
|
||||
import logging
|
||||
from collections import namedtuple
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.media_player import (
|
||||
SUPPORT_PAUSE, SUPPORT_NEXT_TRACK, SUPPORT_PREVIOUS_TRACK,
|
||||
SUPPORT_TURN_OFF, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP,
|
||||
SUPPORT_SELECT_SOURCE, SUPPORT_SELECT_SOUND_MODE,
|
||||
SUPPORT_PLAY_MEDIA, MEDIA_TYPE_CHANNEL, MediaPlayerDevice,
|
||||
PLATFORM_SCHEMA, SUPPORT_TURN_ON, MEDIA_TYPE_MUSIC,
|
||||
SUPPORT_VOLUME_SET, SUPPORT_PLAY)
|
||||
MEDIA_TYPE_CHANNEL, MEDIA_TYPE_MUSIC, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK,
|
||||
SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK,
|
||||
SUPPORT_SELECT_SOUND_MODE, SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF,
|
||||
SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
|
||||
SUPPORT_VOLUME_STEP, MediaPlayerDevice)
|
||||
from homeassistant.const import (
|
||||
CONF_HOST, STATE_OFF, STATE_PLAYING, STATE_PAUSED,
|
||||
CONF_NAME, STATE_ON, CONF_ZONE, CONF_TIMEOUT)
|
||||
CONF_HOST, CONF_NAME, CONF_TIMEOUT, CONF_ZONE, STATE_OFF, STATE_ON,
|
||||
STATE_PAUSED, STATE_PLAYING)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
REQUIREMENTS = ['denonavr==0.7.5']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
ATTR_SOUND_MODE_RAW = 'sound_mode_raw'
|
||||
|
||||
CONF_INVALID_ZONES_ERR = 'Invalid Zone (expected Zone2 or Zone3)'
|
||||
CONF_SHOW_ALL_SOURCES = 'show_all_sources'
|
||||
CONF_VALID_ZONES = ['Zone2', 'Zone3']
|
||||
CONF_ZONES = 'zones'
|
||||
|
||||
DEFAULT_SHOW_SOURCES = False
|
||||
DEFAULT_TIMEOUT = 2
|
||||
CONF_SHOW_ALL_SOURCES = 'show_all_sources'
|
||||
CONF_ZONES = 'zones'
|
||||
CONF_VALID_ZONES = ['Zone2', 'Zone3']
|
||||
CONF_INVALID_ZONES_ERR = 'Invalid Zone (expected Zone2 or Zone3)'
|
||||
KEY_DENON_CACHE = 'denonavr_hosts'
|
||||
|
||||
ATTR_SOUND_MODE_RAW = 'sound_mode_raw'
|
||||
KEY_DENON_CACHE = 'denonavr_hosts'
|
||||
|
||||
SUPPORT_DENON = SUPPORT_VOLUME_STEP | SUPPORT_VOLUME_MUTE | \
|
||||
SUPPORT_TURN_ON | SUPPORT_TURN_OFF | \
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user