mirror of
https://github.com/home-assistant/core.git
synced 2026-01-09 09:07:16 +01:00
Compare commits
315 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1892ae5bfc | ||
|
|
8439329b04 | ||
|
|
3551c39bad | ||
|
|
66405d5651 | ||
|
|
120c8bad50 | ||
|
|
88154074c1 | ||
|
|
884591a105 | ||
|
|
9f08e2b718 | ||
|
|
e3f25eb730 | ||
|
|
1af5d20601 | ||
|
|
1a9b4b82f5 | ||
|
|
e7d5e08780 | ||
|
|
ef9b3321c1 | ||
|
|
46a55ed723 | ||
|
|
7d525ff2f3 | ||
|
|
5b0cbad953 | ||
|
|
bc7ff8323c | ||
|
|
4a30c1023c | ||
|
|
e9fe90a873 | ||
|
|
7d79d281c1 | ||
|
|
d8ccc7751f | ||
|
|
6853f99e30 | ||
|
|
1b57ea51be | ||
|
|
8d44e0cc0c | ||
|
|
fe8ff200bc | ||
|
|
36ab3d3421 | ||
|
|
a1a44d47b9 | ||
|
|
ffee50bd7a | ||
|
|
dfcffa14fb | ||
|
|
2ec86a2349 | ||
|
|
0221d136de | ||
|
|
0f204b34fa | ||
|
|
df390bc9ab | ||
|
|
3fbdc89db1 | ||
|
|
182bf1edef | ||
|
|
adaa200935 | ||
|
|
6eeb01edc4 | ||
|
|
2b30f47f4b | ||
|
|
1a73e6b44e | ||
|
|
7dfdec531c | ||
|
|
f53fcacf49 | ||
|
|
faeb95581a | ||
|
|
c31efe50ca | ||
|
|
f3fa073045 | ||
|
|
e6ecabd6e1 | ||
|
|
702a524b55 | ||
|
|
02466ed8ab | ||
|
|
53a3f2e83d | ||
|
|
c680c07c65 | ||
|
|
bee566f893 | ||
|
|
1cea3a6abc | ||
|
|
4f3a2c0443 | ||
|
|
6f27c5ae46 | ||
|
|
7468cc21be | ||
|
|
a7830bc2d2 | ||
|
|
fbc3376c32 | ||
|
|
b321ed2fdb | ||
|
|
d746035a91 | ||
|
|
adf6852acc | ||
|
|
9df5c0ab86 | ||
|
|
5d5102e1a2 | ||
|
|
a4fd991ab5 | ||
|
|
051639b6ad | ||
|
|
f3123ee0ca | ||
|
|
30fb4ddc98 | ||
|
|
3c629db096 | ||
|
|
ff136a19d9 | ||
|
|
702e63e6e8 | ||
|
|
7d71976e01 | ||
|
|
f167914951 | ||
|
|
02ded7a4a8 | ||
|
|
b14b14c3c9 | ||
|
|
5f13cdf760 | ||
|
|
fec6706bf7 | ||
|
|
28beebac61 | ||
|
|
0983367abe | ||
|
|
036e0ade1f | ||
|
|
4390ccfd4d | ||
|
|
7614f9f3fb | ||
|
|
3544f3d7e0 | ||
|
|
e204d22a9e | ||
|
|
28b9416b0c | ||
|
|
da88be3827 | ||
|
|
5237bd3fd1 | ||
|
|
ece023bfee | ||
|
|
33e1b44b3a | ||
|
|
c07227a53f | ||
|
|
6ad87e52a8 | ||
|
|
c1671bbb28 | ||
|
|
5b3004c7b0 | ||
|
|
078a72d102 | ||
|
|
0b1f389c76 | ||
|
|
a5ccb03e2e | ||
|
|
48dea59517 | ||
|
|
0abb2f3eb8 | ||
|
|
a72d9da9f4 | ||
|
|
f9445c9488 | ||
|
|
b3e574d5b2 | ||
|
|
9e8f4a589f | ||
|
|
c847cc20fc | ||
|
|
f540d74b65 | ||
|
|
a202afcac2 | ||
|
|
815e7a70e9 | ||
|
|
50cec91cf0 | ||
|
|
b870980456 | ||
|
|
f23ab2af8c | ||
|
|
5994f82fc5 | ||
|
|
3714cdaa6d | ||
|
|
518d2c31bb | ||
|
|
b1c2a5fa08 | ||
|
|
23fdc04554 | ||
|
|
2cd845fb25 | ||
|
|
a000125729 | ||
|
|
f7dc537275 | ||
|
|
c50faaef3c | ||
|
|
1cbb895d20 | ||
|
|
a85f89c5a6 | ||
|
|
1bd22a129b | ||
|
|
6acfede512 | ||
|
|
0df1b4c7a1 | ||
|
|
4004879ae0 | ||
|
|
79045f2da1 | ||
|
|
fba06049d2 | ||
|
|
c4c21d3e99 | ||
|
|
2c65e02491 | ||
|
|
6bef5a98fe | ||
|
|
d1bc0c1dd9 | ||
|
|
c191551091 | ||
|
|
3b1a4a52e9 | ||
|
|
e59eea3044 | ||
|
|
9e9859a959 | ||
|
|
6ae2aacdb2 | ||
|
|
90aaa36206 | ||
|
|
7995bf9e66 | ||
|
|
b8f9319cb0 | ||
|
|
860843ada1 | ||
|
|
7bccbcbcc3 | ||
|
|
8cf02e0b22 | ||
|
|
93e4cd6bb2 | ||
|
|
d4905477b8 | ||
|
|
a980eedd22 | ||
|
|
a74bb3fd5e | ||
|
|
b50ac6f486 | ||
|
|
4661f2a6df | ||
|
|
d5c61be651 | ||
|
|
525a434511 | ||
|
|
60ef41cc69 | ||
|
|
92f8362883 | ||
|
|
b9923ca109 | ||
|
|
22d3cf4117 | ||
|
|
b4058b5c7f | ||
|
|
ca97bba4b4 | ||
|
|
2f0eb07624 | ||
|
|
2dc90be94f | ||
|
|
2f9de2a5a5 | ||
|
|
4e2fcdb9a3 | ||
|
|
53720c5c48 | ||
|
|
b968b53e38 | ||
|
|
f7a58cc19e | ||
|
|
757482ee85 | ||
|
|
9035efee10 | ||
|
|
b6cd5ab27b | ||
|
|
e7ccb6f047 | ||
|
|
dae6895a95 | ||
|
|
445c741b30 | ||
|
|
7203027cbf | ||
|
|
ef0e9431b6 | ||
|
|
cde09062c4 | ||
|
|
1c5e0123c9 | ||
|
|
330ae0d885 | ||
|
|
7d1e3af701 | ||
|
|
7d27b4d2ab | ||
|
|
09a4a81d09 | ||
|
|
fcbc2fda49 | ||
|
|
12d470331c | ||
|
|
09a350ba26 | ||
|
|
f9edec19ad | ||
|
|
13bb2ea35a | ||
|
|
fa79ef1220 | ||
|
|
6a24d893c8 | ||
|
|
105461edb5 | ||
|
|
ad51615718 | ||
|
|
3534b8a977 | ||
|
|
617133e465 | ||
|
|
950f8343d3 | ||
|
|
c2a752e34f | ||
|
|
245450a402 | ||
|
|
0cffd61481 | ||
|
|
df9703d814 | ||
|
|
c6d839f8ae | ||
|
|
6a5f7bd8e4 | ||
|
|
df90e9c4fd | ||
|
|
85473d2c98 | ||
|
|
64465f0fbe | ||
|
|
ed5d3dba0e | ||
|
|
ecaadfed3a | ||
|
|
8ae5ece6bb | ||
|
|
f6cf4c38e7 | ||
|
|
85a1726e69 | ||
|
|
d1e3fbd622 | ||
|
|
309d401e47 | ||
|
|
795d5405db | ||
|
|
732855e86c | ||
|
|
47e76fcd5e | ||
|
|
7a171dae33 | ||
|
|
385a496944 | ||
|
|
1b13c49541 | ||
|
|
aa7513bc5c | ||
|
|
3aa2729716 | ||
|
|
79488daddf | ||
|
|
1617c2cd64 | ||
|
|
b5426761f4 | ||
|
|
5b77a357e6 | ||
|
|
597cd3e886 | ||
|
|
6102eb9f1c | ||
|
|
a80d26f0dc | ||
|
|
f91dd4f5f8 | ||
|
|
298aafc79d | ||
|
|
5ba436e3d8 | ||
|
|
fade2e991b | ||
|
|
b31fde6255 | ||
|
|
baa30aec9d | ||
|
|
d9ef92f6d2 | ||
|
|
922522b089 | ||
|
|
9df2c3f8c9 | ||
|
|
46b5b0cac7 | ||
|
|
d1874d148a | ||
|
|
614cf74225 | ||
|
|
944b544b2e | ||
|
|
7b05ede297 | ||
|
|
37a3d5fd85 | ||
|
|
f01e106e6d | ||
|
|
2f6bdc8643 | ||
|
|
299695ca24 | ||
|
|
b074337b9c | ||
|
|
4d08e73e3e | ||
|
|
62338dd28e | ||
|
|
ad6ede9ef7 | ||
|
|
d1219d0b41 | ||
|
|
1ca2f1906a | ||
|
|
0d7326168e | ||
|
|
5413cbd195 | ||
|
|
015adbbac0 | ||
|
|
6a02fd51b8 | ||
|
|
66b905776b | ||
|
|
5676f6fb86 | ||
|
|
bb52e17364 | ||
|
|
069e762da0 | ||
|
|
5f850a7dc7 | ||
|
|
25961df548 | ||
|
|
24a4a42664 | ||
|
|
04d2dbb573 | ||
|
|
36312bdef1 | ||
|
|
955bed8df4 | ||
|
|
16fff16082 | ||
|
|
789ad38c38 | ||
|
|
ec3d83c0cc | ||
|
|
688f5b7698 | ||
|
|
69ddca6f68 | ||
|
|
d652bb23de | ||
|
|
1c473487b1 | ||
|
|
bbc5049816 | ||
|
|
d156648c55 | ||
|
|
1e61d50fc5 | ||
|
|
cf3bb300e6 | ||
|
|
907ffdb762 | ||
|
|
e69953fe2d | ||
|
|
33bd9c83fb | ||
|
|
49ad527a37 | ||
|
|
a28e644def | ||
|
|
3c07a9b4c7 | ||
|
|
6525f8704a | ||
|
|
1f2e0d3949 | ||
|
|
d9ae63e239 | ||
|
|
9dc40197e9 | ||
|
|
c185c015ef | ||
|
|
8e5d272b5f | ||
|
|
03cfe7247b | ||
|
|
d7df61f980 | ||
|
|
193881c4d1 | ||
|
|
efacfa3696 | ||
|
|
055eb69e2d | ||
|
|
54d85fa3dd | ||
|
|
6f2ac705eb | ||
|
|
7a111bf863 | ||
|
|
0c49c82015 | ||
|
|
bde572c91a | ||
|
|
2db9542338 | ||
|
|
248619a036 | ||
|
|
7bfb365f62 | ||
|
|
d4bd5a180c | ||
|
|
7238eb9bac | ||
|
|
059ae2bb68 | ||
|
|
691e3f6141 | ||
|
|
3deeac6bf7 | ||
|
|
decf13b948 | ||
|
|
1efa29d6ff | ||
|
|
55031e6ea4 | ||
|
|
17750a604e | ||
|
|
2b6c5eeb1d | ||
|
|
432f6569ad | ||
|
|
f704a8e90e | ||
|
|
a4eeaac24c | ||
|
|
aa56b4dd30 | ||
|
|
bff5b00a09 | ||
|
|
bc17170f95 | ||
|
|
aff151c90a | ||
|
|
2d432da14c | ||
|
|
82b1b10c28 | ||
|
|
bc5cec97f4 | ||
|
|
be0739626b | ||
|
|
2b78bfaf78 | ||
|
|
f793c71f52 | ||
|
|
b3ae6a20ba | ||
|
|
9a16b7b0f6 |
19
.coveragerc
19
.coveragerc
@@ -31,7 +31,6 @@ omit =
|
||||
homeassistant/components/amcrest/*
|
||||
homeassistant/components/ampio/*
|
||||
homeassistant/components/android_ip_webcam/*
|
||||
homeassistant/components/androidtv/*
|
||||
homeassistant/components/anel_pwrctrl/switch.py
|
||||
homeassistant/components/anthemav/media_player.py
|
||||
homeassistant/components/apache_kafka/*
|
||||
@@ -51,6 +50,7 @@ omit =
|
||||
homeassistant/components/asterisk_cdr/mailbox.py
|
||||
homeassistant/components/asterisk_mbox/*
|
||||
homeassistant/components/asuswrt/device_tracker.py
|
||||
homeassistant/components/atome/*
|
||||
homeassistant/components/august/*
|
||||
homeassistant/components/aurora_abb_powerone/sensor.py
|
||||
homeassistant/components/automatic/device_tracker.py
|
||||
@@ -58,6 +58,7 @@ omit =
|
||||
homeassistant/components/avion/light.py
|
||||
homeassistant/components/azure_event_hub/*
|
||||
homeassistant/components/baidu/tts.py
|
||||
homeassistant/components/beewi_smartclim/sensor.py
|
||||
homeassistant/components/bbb_gpio/*
|
||||
homeassistant/components/bbox/device_tracker.py
|
||||
homeassistant/components/bbox/sensor.py
|
||||
@@ -93,6 +94,7 @@ omit =
|
||||
homeassistant/components/canary/camera.py
|
||||
homeassistant/components/cast/*
|
||||
homeassistant/components/cert_expiry/sensor.py
|
||||
homeassistant/components/cert_expiry/helper.py
|
||||
homeassistant/components/channels/media_player.py
|
||||
homeassistant/components/cisco_ios/device_tracker.py
|
||||
homeassistant/components/cisco_mobility_express/device_tracker.py
|
||||
@@ -247,6 +249,7 @@ omit =
|
||||
homeassistant/components/greeneye_monitor/sensor.py
|
||||
homeassistant/components/greenwave/light.py
|
||||
homeassistant/components/group/notify.py
|
||||
homeassistant/components/growatt_server/sensor.py
|
||||
homeassistant/components/gstreamer/media_player.py
|
||||
homeassistant/components/gtfs/sensor.py
|
||||
homeassistant/components/gtt/sensor.py
|
||||
@@ -285,6 +288,10 @@ omit =
|
||||
homeassistant/components/hydrawise/*
|
||||
homeassistant/components/hyperion/light.py
|
||||
homeassistant/components/ialarm/alarm_control_panel.py
|
||||
homeassistant/components/iaqualink/climate.py
|
||||
homeassistant/components/iaqualink/light.py
|
||||
homeassistant/components/iaqualink/sensor.py
|
||||
homeassistant/components/iaqualink/switch.py
|
||||
homeassistant/components/icloud/device_tracker.py
|
||||
homeassistant/components/idteck_prox/*
|
||||
homeassistant/components/ifttt/*
|
||||
@@ -338,6 +345,7 @@ omit =
|
||||
homeassistant/components/limitlessled/light.py
|
||||
homeassistant/components/linksys_ap/device_tracker.py
|
||||
homeassistant/components/linksys_smart/device_tracker.py
|
||||
homeassistant/components/linky/__init__.py
|
||||
homeassistant/components/linky/sensor.py
|
||||
homeassistant/components/linode/*
|
||||
homeassistant/components/linux_battery/sensor.py
|
||||
@@ -427,6 +435,7 @@ omit =
|
||||
homeassistant/components/nut/sensor.py
|
||||
homeassistant/components/nx584/alarm_control_panel.py
|
||||
homeassistant/components/nzbget/sensor.py
|
||||
homeassistant/components/obihai/*
|
||||
homeassistant/components/octoprint/*
|
||||
homeassistant/components/oem/climate.py
|
||||
homeassistant/components/oasa_telematics/sensor.py
|
||||
@@ -467,8 +476,7 @@ omit =
|
||||
homeassistant/components/pioneer/media_player.py
|
||||
homeassistant/components/pjlink/media_player.py
|
||||
homeassistant/components/plaato/*
|
||||
homeassistant/components/plex/media_player.py
|
||||
homeassistant/components/plex/sensor.py
|
||||
homeassistant/components/plex/*
|
||||
homeassistant/components/plugwise/*
|
||||
homeassistant/components/plum_lightpad/*
|
||||
homeassistant/components/pocketcasts/sensor.py
|
||||
@@ -563,6 +571,7 @@ omit =
|
||||
homeassistant/components/skybeacon/sensor.py
|
||||
homeassistant/components/skybell/*
|
||||
homeassistant/components/slack/notify.py
|
||||
homeassistant/components/slide/*
|
||||
homeassistant/components/sma/sensor.py
|
||||
homeassistant/components/smappee/*
|
||||
homeassistant/components/smarty/*
|
||||
@@ -572,6 +581,7 @@ omit =
|
||||
homeassistant/components/snmp/*
|
||||
homeassistant/components/sochain/sensor.py
|
||||
homeassistant/components/socialblade/sensor.py
|
||||
homeassistant/components/solaredge/__init__.py
|
||||
homeassistant/components/solaredge/sensor.py
|
||||
homeassistant/components/solaredge_local/sensor.py
|
||||
homeassistant/components/solax/sensor.py
|
||||
@@ -667,6 +677,7 @@ omit =
|
||||
homeassistant/components/ue_smart_radio/media_player.py
|
||||
homeassistant/components/upcloud/*
|
||||
homeassistant/components/upnp/*
|
||||
homeassistant/components/upc_connect/*
|
||||
homeassistant/components/ups/sensor.py
|
||||
homeassistant/components/uptimerobot/binary_sensor.py
|
||||
homeassistant/components/uscis/sensor.py
|
||||
@@ -689,6 +700,8 @@ omit =
|
||||
homeassistant/components/vesync/const.py
|
||||
homeassistant/components/vesync/switch.py
|
||||
homeassistant/components/viaggiatreno/sensor.py
|
||||
homeassistant/components/vicare/*
|
||||
homeassistant/components/vivotek/camera.py
|
||||
homeassistant/components/vizio/media_player.py
|
||||
homeassistant/components/vlc/media_player.py
|
||||
homeassistant/components/vlc_telnet/media_player.py
|
||||
|
||||
@@ -1,35 +1,33 @@
|
||||
// See https://aka.ms/vscode-remote/devcontainer.json for format details.
|
||||
{
|
||||
"name": "Home Assistant Dev",
|
||||
"context": "..",
|
||||
"dockerFile": "../Dockerfile.dev",
|
||||
"postCreateCommand": "pip3 install -e .",
|
||||
"appPort": 8123,
|
||||
"runArgs": [
|
||||
"-e",
|
||||
"GIT_EDITOR=\"code --wait\""
|
||||
],
|
||||
"extensions": [
|
||||
"ms-python.python",
|
||||
"ms-azure-devops.azure-pipelines",
|
||||
"redhat.vscode-yaml"
|
||||
],
|
||||
"settings": {
|
||||
"python.pythonPath": "/usr/local/bin/python",
|
||||
"python.linting.pylintEnabled": true,
|
||||
"python.linting.enabled": true,
|
||||
"python.formatting.provider": "black",
|
||||
"editor.formatOnPaste": false,
|
||||
"editor.formatOnSave": true,
|
||||
"editor.formatOnType": true,
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"terminal.integrated.shell.linux": "/bin/bash",
|
||||
"yaml.customTags": [
|
||||
"!secret scalar",
|
||||
"!include_dir_named scalar",
|
||||
"!include_dir_list scalar",
|
||||
"!include_dir_merge_list scalar",
|
||||
"!include_dir_merge_named scalar"
|
||||
]
|
||||
}
|
||||
}
|
||||
"name": "Home Assistant Dev",
|
||||
"context": "..",
|
||||
"dockerFile": "../Dockerfile.dev",
|
||||
"postCreateCommand": "mkdir -p config && pip3 install -e .",
|
||||
"appPort": 8123,
|
||||
"runArgs": ["-e", "GIT_EDITOR=\"code --wait\""],
|
||||
"extensions": [
|
||||
"ms-python.python",
|
||||
"ms-azure-devops.azure-pipelines",
|
||||
"redhat.vscode-yaml",
|
||||
"esbenp.prettier-vscode"
|
||||
],
|
||||
"settings": {
|
||||
"python.pythonPath": "/usr/local/bin/python",
|
||||
"python.linting.pylintEnabled": true,
|
||||
"python.linting.enabled": true,
|
||||
"python.formatting.provider": "black",
|
||||
"editor.formatOnPaste": false,
|
||||
"editor.formatOnSave": true,
|
||||
"editor.formatOnType": true,
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"terminal.integrated.shell.linux": "/bin/bash",
|
||||
"yaml.customTags": [
|
||||
"!secret scalar",
|
||||
"!include_dir_named scalar",
|
||||
"!include_dir_list scalar",
|
||||
"!include_dir_merge_list scalar",
|
||||
"!include_dir_merge_named scalar"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -64,6 +64,7 @@ nosetests.xml
|
||||
htmlcov/
|
||||
test-reports/
|
||||
test-results.xml
|
||||
test-output.xml
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
|
||||
12
.travis.yml
12
.travis.yml
@@ -16,18 +16,14 @@ addons:
|
||||
matrix:
|
||||
fast_finish: true
|
||||
include:
|
||||
- python: "3.6.0"
|
||||
- python: "3.6.1"
|
||||
env: TOXENV=lint
|
||||
dist: trusty
|
||||
- python: "3.6.0"
|
||||
- python: "3.6.1"
|
||||
env: TOXENV=pylint
|
||||
dist: trusty
|
||||
- python: "3.6.0"
|
||||
- python: "3.6.1"
|
||||
env: TOXENV=typing
|
||||
dist: trusty
|
||||
- python: "3.6.0"
|
||||
- python: "3.6.1"
|
||||
env: TOXENV=py36
|
||||
dist: trusty
|
||||
- python: "3.7"
|
||||
env: TOXENV=py37
|
||||
|
||||
|
||||
193
.vscode/tasks.json
vendored
193
.vscode/tasks.json
vendored
@@ -1,92 +1,105 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "Preview",
|
||||
"type": "shell",
|
||||
"command": "hass -c ./config",
|
||||
"group": {
|
||||
"kind": "test",
|
||||
"isDefault": true,
|
||||
},
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "new"
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Pytest",
|
||||
"type": "shell",
|
||||
"command": "pytest --timeout=10 tests",
|
||||
"group": {
|
||||
"kind": "test",
|
||||
"isDefault": true,
|
||||
},
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "new"
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Flake8",
|
||||
"type": "shell",
|
||||
"command": "flake8 homeassistant tests",
|
||||
"group": {
|
||||
"kind": "test",
|
||||
"isDefault": true,
|
||||
},
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "new"
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Pylint",
|
||||
"type": "shell",
|
||||
"command": "pylint homeassistant",
|
||||
"dependsOn": [
|
||||
"Install all Requirements"
|
||||
],
|
||||
"group": {
|
||||
"kind": "test",
|
||||
"isDefault": true,
|
||||
},
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "new"
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Generate Requirements",
|
||||
"type": "shell",
|
||||
"command": "./script/gen_requirements_all.py",
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "new"
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Install all Requirements",
|
||||
"type": "shell",
|
||||
"command": "pip3 install -r requirements_all.txt -c homeassistant/package_constraints.txt",
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "new"
|
||||
},
|
||||
"problemMatcher": []
|
||||
}
|
||||
]
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "Preview",
|
||||
"type": "shell",
|
||||
"command": "hass -c ./config",
|
||||
"group": {
|
||||
"kind": "test",
|
||||
"isDefault": true
|
||||
},
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "new"
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Pytest",
|
||||
"type": "shell",
|
||||
"command": "pytest --timeout=10 tests",
|
||||
"dependsOn": ["Install all Test Requirements"],
|
||||
"group": {
|
||||
"kind": "test",
|
||||
"isDefault": true
|
||||
},
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "new"
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Flake8",
|
||||
"type": "shell",
|
||||
"command": "flake8 homeassistant tests",
|
||||
"group": {
|
||||
"kind": "test",
|
||||
"isDefault": true
|
||||
},
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "new"
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Pylint",
|
||||
"type": "shell",
|
||||
"command": "pylint homeassistant",
|
||||
"dependsOn": ["Install all Requirements"],
|
||||
"group": {
|
||||
"kind": "test",
|
||||
"isDefault": true
|
||||
},
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "new"
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Generate Requirements",
|
||||
"type": "shell",
|
||||
"command": "./script/gen_requirements_all.py",
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "new"
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Install all Requirements",
|
||||
"type": "shell",
|
||||
"command": "pip3 install -r requirements_all.txt -c homeassistant/package_constraints.txt",
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "new"
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Install all Test Requirements",
|
||||
"type": "shell",
|
||||
"command": "pip3 install -r requirements_test_all.txt -c homeassistant/package_constraints.txt",
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "new"
|
||||
},
|
||||
"problemMatcher": []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
19
CODEOWNERS
19
CODEOWNERS
@@ -28,6 +28,7 @@ homeassistant/components/arcam_fmj/* @elupus
|
||||
homeassistant/components/arduino/* @fabaff
|
||||
homeassistant/components/arest/* @fabaff
|
||||
homeassistant/components/asuswrt/* @kennedyshead
|
||||
homeassistant/components/atome/* @baqs
|
||||
homeassistant/components/aurora_abb_powerone/* @davet2001
|
||||
homeassistant/components/auth/* @home-assistant/core
|
||||
homeassistant/components/automatic/* @armills
|
||||
@@ -37,6 +38,7 @@ homeassistant/components/awair/* @danielsjf
|
||||
homeassistant/components/aws/* @awarecan @robbiet480
|
||||
homeassistant/components/axis/* @kane610
|
||||
homeassistant/components/azure_event_hub/* @eavanvalkenburg
|
||||
homeassistant/components/beewi_smartclim/* @alemuro
|
||||
homeassistant/components/bitcoin/* @fabaff
|
||||
homeassistant/components/bizkaibus/* @UgaitzEtxebarria
|
||||
homeassistant/components/blink/* @fronzbot
|
||||
@@ -46,6 +48,7 @@ homeassistant/components/broadlink/* @danielhiversen
|
||||
homeassistant/components/brunt/* @eavanvalkenburg
|
||||
homeassistant/components/bt_smarthub/* @jxwolstenholme
|
||||
homeassistant/components/buienradar/* @mjj4791 @ties
|
||||
homeassistant/components/cert_expiry/* @cereal2nd
|
||||
homeassistant/components/cisco_ios/* @fbradyirl
|
||||
homeassistant/components/cisco_mobility_express/* @fbradyirl
|
||||
homeassistant/components/cisco_webex_teams/* @fbradyirl
|
||||
@@ -107,6 +110,7 @@ homeassistant/components/google_translate/* @awarecan
|
||||
homeassistant/components/google_travel_time/* @robbiet480
|
||||
homeassistant/components/gpsd/* @fabaff
|
||||
homeassistant/components/group/* @home-assistant/core
|
||||
homeassistant/components/growatt_server/* @indykoning
|
||||
homeassistant/components/gtfs/* @robbiet480
|
||||
homeassistant/components/harmony/* @ehendrix23
|
||||
homeassistant/components/hassio/* @home-assistant/hass-io
|
||||
@@ -119,12 +123,14 @@ homeassistant/components/hive/* @Rendili @KJonline
|
||||
homeassistant/components/homeassistant/* @home-assistant/core
|
||||
homeassistant/components/homekit_controller/* @Jc2k
|
||||
homeassistant/components/homematic/* @pvizeli @danielperna84
|
||||
homeassistant/components/homematicip_cloud/* @SukramJ
|
||||
homeassistant/components/honeywell/* @zxdavb
|
||||
homeassistant/components/html5/* @robbiet480
|
||||
homeassistant/components/http/* @home-assistant/core
|
||||
homeassistant/components/huawei_lte/* @scop
|
||||
homeassistant/components/huawei_router/* @abmantis
|
||||
homeassistant/components/hue/* @balloob
|
||||
homeassistant/components/iaqualink/* @flz
|
||||
homeassistant/components/ign_sismologia/* @exxamalte
|
||||
homeassistant/components/incomfort/* @zxdavb
|
||||
homeassistant/components/influxdb/* @fabaff
|
||||
@@ -150,7 +156,7 @@ homeassistant/components/life360/* @pnbruckner
|
||||
homeassistant/components/lifx/* @amelchio
|
||||
homeassistant/components/lifx_cloud/* @amelchio
|
||||
homeassistant/components/lifx_legacy/* @amelchio
|
||||
homeassistant/components/linky/* @tiste @Quentame
|
||||
homeassistant/components/linky/* @Quentame
|
||||
homeassistant/components/linux_battery/* @fabaff
|
||||
homeassistant/components/liveboxplaytv/* @pschmitt
|
||||
homeassistant/components/logger/* @home-assistant/core
|
||||
@@ -188,7 +194,10 @@ homeassistant/components/no_ip/* @fabaff
|
||||
homeassistant/components/notify/* @home-assistant/core
|
||||
homeassistant/components/notion/* @bachya
|
||||
homeassistant/components/nsw_fuel_station/* @nickw444
|
||||
homeassistant/components/nuki/* @pschmitt
|
||||
homeassistant/components/nsw_rural_fire_service_feed/* @exxamalte
|
||||
homeassistant/components/nuki/* @pvizeli
|
||||
homeassistant/components/nws/* @MatthewFlamm
|
||||
homeassistant/components/obihai/* @dshokouhi
|
||||
homeassistant/components/ohmconnect/* @robbiet480
|
||||
homeassistant/components/onboarding/* @home-assistant/core
|
||||
homeassistant/components/opentherm_gw/* @mvn23
|
||||
@@ -203,6 +212,7 @@ homeassistant/components/philips_js/* @elupus
|
||||
homeassistant/components/pi_hole/* @fabaff
|
||||
homeassistant/components/plaato/* @JohNan
|
||||
homeassistant/components/plant/* @ChristianKuehnel
|
||||
homeassistant/components/plex/* @jjlawren
|
||||
homeassistant/components/plugwise/* @laetificat @CoMPaTech
|
||||
homeassistant/components/point/* @fredrike
|
||||
homeassistant/components/ps4/* @ktnrg45
|
||||
@@ -232,6 +242,7 @@ homeassistant/components/shell_command/* @home-assistant/core
|
||||
homeassistant/components/shiftr/* @fabaff
|
||||
homeassistant/components/shodan/* @fabaff
|
||||
homeassistant/components/simplisafe/* @bachya
|
||||
homeassistant/components/slide/* @ualex73
|
||||
homeassistant/components/sma/* @kellerza
|
||||
homeassistant/components/smarthab/* @outadoc
|
||||
homeassistant/components/smartthings/* @andrewsayre
|
||||
@@ -281,15 +292,18 @@ homeassistant/components/twentemilieu/* @frenck
|
||||
homeassistant/components/twilio_call/* @robbiet480
|
||||
homeassistant/components/twilio_sms/* @robbiet480
|
||||
homeassistant/components/unifi/* @kane610
|
||||
homeassistant/components/upc_connect/* @pvizeli
|
||||
homeassistant/components/upcloud/* @scop
|
||||
homeassistant/components/updater/* @home-assistant/core
|
||||
homeassistant/components/upnp/* @robbiet480
|
||||
homeassistant/components/uptimerobot/* @ludeeus
|
||||
homeassistant/components/usgs_earthquakes_feed/* @exxamalte
|
||||
homeassistant/components/utility_meter/* @dgomes
|
||||
homeassistant/components/velbus/* @cereal2nd
|
||||
homeassistant/components/velux/* @Julius2342
|
||||
homeassistant/components/version/* @fabaff
|
||||
homeassistant/components/vesync/* @markperdue @webdjoe
|
||||
homeassistant/components/vicare/* @oischinger
|
||||
homeassistant/components/vizio/* @raman325
|
||||
homeassistant/components/vlc_telnet/* @rodripf
|
||||
homeassistant/components/waqi/* @andrey-git
|
||||
@@ -298,6 +312,7 @@ homeassistant/components/weather/* @fabaff
|
||||
homeassistant/components/weblink/* @home-assistant/core
|
||||
homeassistant/components/websocket_api/* @home-assistant/core
|
||||
homeassistant/components/wemo/* @sqldiablo
|
||||
homeassistant/components/withings/* @vangorra
|
||||
homeassistant/components/worldclock/* @fabaff
|
||||
homeassistant/components/wwlln/* @bachya
|
||||
homeassistant/components/xfinity/* @cisasteelersfan
|
||||
|
||||
@@ -23,9 +23,10 @@ RUN git clone --depth 1 https://github.com/home-assistant/hass-release \
|
||||
|
||||
WORKDIR /workspaces
|
||||
|
||||
# Install Python dependencies from requirements.txt if it exists
|
||||
COPY requirements_test_all.txt homeassistant/package_constraints.txt /workspaces/
|
||||
RUN pip3 install -r requirements_test_all.txt -c package_constraints.txt
|
||||
# Install Python dependencies from requirements
|
||||
COPY requirements_test.txt homeassistant/package_constraints.txt ./
|
||||
RUN pip3 install -r requirements_test.txt -c package_constraints.txt \
|
||||
&& rm -f requirements_test.txt package_constraints.txt
|
||||
|
||||
# Set the default shell to bash instead of sh
|
||||
ENV SHELL /bin/bash
|
||||
|
||||
@@ -113,7 +113,7 @@ stages:
|
||||
pip uninstall -y typing
|
||||
- script: |
|
||||
. venv/bin/activate
|
||||
pytest --timeout=9 --durations=10 --junitxml=test-results.xml -qq -o console_output_style=count -p no:sugar tests
|
||||
pytest --timeout=9 --durations=10 -qq -o console_output_style=count -p no:sugar tests
|
||||
script/check_dirty
|
||||
displayName: 'Run pytest for python $(python.container)'
|
||||
condition: and(succeeded(), ne(variables['python.container'], variables['PythonMain']))
|
||||
@@ -121,22 +121,11 @@ stages:
|
||||
set -e
|
||||
|
||||
. venv/bin/activate
|
||||
pytest --timeout=9 --durations=10 --junitxml=test-results.xml --cov --cov-report=xml -qq -o console_output_style=count -p no:sugar tests
|
||||
pytest --timeout=9 --durations=10 --cov homeassistant --cov-report html -qq -o console_output_style=count -p no:sugar tests
|
||||
codecov --token $(codecovToken)
|
||||
script/check_dirty
|
||||
displayName: 'Run pytest for python $(python.container) / coverage'
|
||||
condition: and(succeeded(), eq(variables['python.container'], variables['PythonMain']))
|
||||
- task: PublishTestResults@2
|
||||
condition: succeededOrFailed()
|
||||
inputs:
|
||||
testResultsFiles: 'test-results.xml'
|
||||
testRunTitle: 'Publish test results for Python $(python.container)'
|
||||
- task: PublishCodeCoverageResults@1
|
||||
inputs:
|
||||
codeCoverageTool: cobertura
|
||||
summaryFileLocation: coverage.xml
|
||||
displayName: 'publish coverage artifact'
|
||||
condition: and(succeeded(), eq(variables['python.container'], variables['PythonMain']))
|
||||
|
||||
- stage: 'FullCheck'
|
||||
dependsOn:
|
||||
|
||||
@@ -43,7 +43,7 @@ stages:
|
||||
release="$(Build.SourceBranchName)"
|
||||
created_by="$(curl -s https://api.github.com/repos/home-assistant/home-assistant/releases/tags/${release} | jq --raw-output '.author.login')"
|
||||
|
||||
if [[ "${created_by}" =~ ^(balloob|pvizeli|fabaff|robbiet480)$ ]]; then
|
||||
if [[ "${created_by}" =~ ^(balloob|pvizeli|fabaff|robbiet480|bramkragten)$ ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
@@ -97,14 +97,15 @@ stages:
|
||||
buildMachine: 'qemuarm-64,raspberrypi3-64,raspberrypi4-64,odroid-c2,orangepi-prime'
|
||||
steps:
|
||||
- template: templates/azp-step-ha-version.yaml@azure
|
||||
- script: sudo docker login -u $(dockerUser) -p $(dockerPassword)
|
||||
- script: |
|
||||
docker login -u $(dockerUser) -p $(dockerPassword)
|
||||
displayName: 'Docker hub login'
|
||||
- script: sudo docker pull homeassistant/amd64-builder:$(versionBuilder)
|
||||
- script: docker pull homeassistant/amd64-builder:$(versionBuilder)
|
||||
displayName: 'Install Builder'
|
||||
- script: |
|
||||
set -e
|
||||
|
||||
sudo docker run --rm --privileged \
|
||||
docker run --rm --privileged \
|
||||
-v ~/.docker:/root/.docker:rw \
|
||||
-v /run/docker.sock:/run/docker.sock:rw \
|
||||
-v $(pwd):/homeassistant:ro \
|
||||
@@ -113,7 +114,7 @@ stages:
|
||||
-r https://github.com/home-assistant/hassio-homeassistant \
|
||||
-t generic --docker-hub homeassistant
|
||||
|
||||
sudo docker run --rm --privileged \
|
||||
docker run --rm --privileged \
|
||||
-v ~/.docker:/root/.docker \
|
||||
-v /run/docker.sock:/run/docker.sock:rw \
|
||||
homeassistant/amd64-builder:$(versionBuilder) \
|
||||
@@ -169,61 +170,59 @@ stages:
|
||||
steps:
|
||||
- template: templates/azp-step-ha-version.yaml@azure
|
||||
- script: |
|
||||
mkdir -p ~/.docker
|
||||
echo '{ "experimental": "enabled" }' > .docker/config.json
|
||||
|
||||
sudo docker login -u $(dockerUser) -p $(dockerPassword)
|
||||
displayName: 'Enable manifest / Docker login'
|
||||
docker login -u $(dockerUser) -p $(dockerPassword)
|
||||
displayName: 'Docker login'
|
||||
- script: |
|
||||
set -e
|
||||
export DOCKER_CLI_EXPERIMENTAL=enabled
|
||||
|
||||
function create_manifest() {
|
||||
local tag_l=$1
|
||||
local tag_r=$2
|
||||
|
||||
sudo docker --config .docker manifest create homeassistant/home-assistant:${tag_l} \
|
||||
docker manifest create homeassistant/home-assistant:${tag_l} \
|
||||
homeassistant/amd64-homeassistant:${tag_r} \
|
||||
homeassistant/i386-homeassistant:${tag_r} \
|
||||
homeassistant/armhf-homeassistant:${tag_r} \
|
||||
homeassistant/armv7-homeassistant:${tag_r} \
|
||||
homeassistant/aarch64-homeassistant:${tag_r}
|
||||
|
||||
sudo docker --config .docker manifest annotate homeassistant/home-assistant:${tag_l} \
|
||||
docker manifest annotate homeassistant/home-assistant:${tag_l} \
|
||||
homeassistant/amd64-homeassistant:${tag_r} \
|
||||
--os linux --arch amd64
|
||||
|
||||
sudo docker --config .docker manifest annotate homeassistant/home-assistant:${tag_l} \
|
||||
docker manifest annotate homeassistant/home-assistant:${tag_l} \
|
||||
homeassistant/i386-homeassistant:${tag_r} \
|
||||
--os linux --arch 386
|
||||
|
||||
sudo docker --config .docker manifest annotate homeassistant/home-assistant:${tag_l} \
|
||||
docker manifest annotate homeassistant/home-assistant:${tag_l} \
|
||||
homeassistant/armhf-homeassistant:${tag_r} \
|
||||
--os linux --arch arm --variant=v6
|
||||
|
||||
sudo docker --config .docker manifest annotate homeassistant/home-assistant:${tag_l} \
|
||||
docker manifest annotate homeassistant/home-assistant:${tag_l} \
|
||||
homeassistant/armv7-homeassistant:${tag_r} \
|
||||
--os linux --arch arm --variant=v7
|
||||
|
||||
sudo docker --config .docker manifest annotate homeassistant/home-assistant:${tag_l} \
|
||||
docker manifest annotate homeassistant/home-assistant:${tag_l} \
|
||||
homeassistant/aarch64-homeassistant:${tag_r} \
|
||||
--os linux --arch arm64 --variant=v8
|
||||
|
||||
sudo docker --config .docker manifest push --purge homeassistant/home-assistant:${tag_l}
|
||||
docker manifest push --purge homeassistant/home-assistant:${tag_l}
|
||||
}
|
||||
|
||||
sudo docker pull homeassistant/amd64-homeassistant:$(homeassistantRelease)
|
||||
sudo docker pull homeassistant/i386-homeassistant:$(homeassistantRelease)
|
||||
sudo docker pull homeassistant/armhf-homeassistant:$(homeassistantRelease)
|
||||
sudo docker pull homeassistant/armv7-homeassistant:$(homeassistantRelease)
|
||||
sudo docker pull homeassistant/aarch64-homeassistant:$(homeassistantRelease)
|
||||
docker pull homeassistant/amd64-homeassistant:$(homeassistantRelease)
|
||||
docker pull homeassistant/i386-homeassistant:$(homeassistantRelease)
|
||||
docker pull homeassistant/armhf-homeassistant:$(homeassistantRelease)
|
||||
docker pull homeassistant/armv7-homeassistant:$(homeassistantRelease)
|
||||
docker pull homeassistant/aarch64-homeassistant:$(homeassistantRelease)
|
||||
|
||||
# Create version tag
|
||||
create_manifest "$(homeassistantRelease)" "$(homeassistantRelease)"
|
||||
|
||||
# Create general tags
|
||||
if [[ "$version" =~ d ]]; then
|
||||
if [[ "$(homeassistantRelease)" =~ d ]]; then
|
||||
create_manifest "dev" "$(homeassistantRelease)"
|
||||
elif [[ "$version" =~ b ]]; then
|
||||
elif [[ "$(homeassistantRelease)" =~ b ]]; then
|
||||
create_manifest "beta" "$(homeassistantRelease)"
|
||||
create_manifest "rc" "$(homeassistantRelease)"
|
||||
else
|
||||
|
||||
66
azure-pipelines-translation.yml
Normal file
66
azure-pipelines-translation.yml
Normal file
@@ -0,0 +1,66 @@
|
||||
# https://dev.azure.com/home-assistant
|
||||
|
||||
trigger:
|
||||
batch: true
|
||||
branches:
|
||||
include:
|
||||
- dev
|
||||
pr: none
|
||||
schedules:
|
||||
- cron: "30 0 * * *"
|
||||
displayName: "translation update"
|
||||
branches:
|
||||
include:
|
||||
- dev
|
||||
always: true
|
||||
variables:
|
||||
- group: translation
|
||||
resources:
|
||||
repositories:
|
||||
- repository: azure
|
||||
type: github
|
||||
name: 'home-assistant/ci-azure'
|
||||
endpoint: 'home-assistant'
|
||||
|
||||
|
||||
jobs:
|
||||
|
||||
- job: 'Upload'
|
||||
pool:
|
||||
vmImage: 'ubuntu-latest'
|
||||
steps:
|
||||
- task: UsePythonVersion@0
|
||||
displayName: 'Use Python 3.7'
|
||||
inputs:
|
||||
versionSpec: '3.7'
|
||||
- script: |
|
||||
export LOKALISE_TOKEN="$(lokaliseToken)"
|
||||
export AZURE_BRANCH="$(Build.SourceBranchName)"
|
||||
|
||||
./script/translations_upload
|
||||
displayName: 'Upload Translation'
|
||||
|
||||
- job: 'Download'
|
||||
dependsOn:
|
||||
- 'Upload'
|
||||
condition: or(eq(variables['Build.Reason'], 'Schedule'), eq(variables['Build.Reason'], 'Manual'))
|
||||
pool:
|
||||
vmImage: 'ubuntu-latest'
|
||||
steps:
|
||||
- task: UsePythonVersion@0
|
||||
displayName: 'Use Python 3.7'
|
||||
inputs:
|
||||
versionSpec: '3.7'
|
||||
- template: templates/azp-step-git-init.yaml@azure
|
||||
- script: |
|
||||
export LOKALISE_TOKEN="$(lokaliseToken)"
|
||||
export AZURE_BRANCH="$(Build.SourceBranchName)"
|
||||
|
||||
./script/translations_download
|
||||
displayName: 'Download Translation'
|
||||
- script: |
|
||||
git checkout dev
|
||||
git add homeassistant
|
||||
git commit -am "[ci skip] Translation update"
|
||||
git push
|
||||
displayName: 'Update translation'
|
||||
@@ -10,7 +10,7 @@ trigger:
|
||||
- requirements_all.txt
|
||||
pr: none
|
||||
schedules:
|
||||
- cron: '0 */8 * * *'
|
||||
- cron: '0 */4 * * *'
|
||||
displayName: 'daily builds'
|
||||
branches:
|
||||
include:
|
||||
@@ -18,7 +18,7 @@ schedules:
|
||||
always: true
|
||||
variables:
|
||||
- name: versionWheels
|
||||
value: '1.1-3.7-alpine3.10'
|
||||
value: '1.3-3.7-alpine3.10'
|
||||
resources:
|
||||
repositories:
|
||||
- repository: azure
|
||||
@@ -30,7 +30,8 @@ jobs:
|
||||
- template: templates/azp-job-wheels.yaml@azure
|
||||
parameters:
|
||||
builderVersion: '$(versionWheels)'
|
||||
builderApk: 'build-base;cmake;git;linux-headers;bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;autoconf;automake;cups-dev;linux-headers;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev'
|
||||
builderApk: 'build-base;cmake;git;linux-headers;bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;autoconf;automake;cups-dev;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev'
|
||||
builderPip: 'Cython;numpy'
|
||||
wheelsRequirement: 'requirements_wheels.txt'
|
||||
wheelsRequirementDiff: 'requirements_diff.txt'
|
||||
preBuild:
|
||||
@@ -65,5 +66,6 @@ jobs:
|
||||
sed -i "s|# PySwitchbot|PySwitchbot|g" ${requirement_file}
|
||||
sed -i "s|# pySwitchmate|pySwitchmate|g" ${requirement_file}
|
||||
sed -i "s|# face_recognition|face_recognition|g" ${requirement_file}
|
||||
sed -i "s|# py_noaa|py_noaa|g" ${requirement_file}
|
||||
done
|
||||
displayName: 'Prepare requirements files for Hass.io'
|
||||
|
||||
@@ -7,7 +7,7 @@ import platform
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
from typing import List, Dict, Any, TYPE_CHECKING # noqa pylint: disable=unused-import
|
||||
from typing import List, Dict, Any, TYPE_CHECKING
|
||||
|
||||
from homeassistant import monkey_patch
|
||||
from homeassistant.const import __version__, REQUIRED_PYTHON_VER, RESTART_EXIT_CODE
|
||||
@@ -168,7 +168,7 @@ def get_arguments() -> argparse.Namespace:
|
||||
parser.add_argument(
|
||||
"--runner",
|
||||
action="store_true",
|
||||
help="On restart exit with code {}".format(RESTART_EXIT_CODE),
|
||||
help=f"On restart exit with code {RESTART_EXIT_CODE}",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--script", nargs=argparse.REMAINDER, help="Run one of the embedded scripts"
|
||||
@@ -216,7 +216,7 @@ def check_pid(pid_file: str) -> None:
|
||||
try:
|
||||
with open(pid_file, "r") as file:
|
||||
pid = int(file.readline())
|
||||
except IOError:
|
||||
except OSError:
|
||||
# PID File does not exist
|
||||
return
|
||||
|
||||
@@ -239,8 +239,8 @@ def write_pid(pid_file: str) -> None:
|
||||
try:
|
||||
with open(pid_file, "w") as file:
|
||||
file.write(str(pid))
|
||||
except IOError:
|
||||
print("Fatal Error: Unable to write pid file {}".format(pid_file))
|
||||
except OSError:
|
||||
print(f"Fatal Error: Unable to write pid file {pid_file}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
@@ -258,7 +258,7 @@ def closefds_osx(min_fd: int, max_fd: int) -> None:
|
||||
val = fcntl(_fd, F_GETFD)
|
||||
if not val & FD_CLOEXEC:
|
||||
fcntl(_fd, F_SETFD, val | FD_CLOEXEC)
|
||||
except IOError:
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
@@ -280,7 +280,7 @@ async def setup_and_run_hass(config_dir: str, args: argparse.Namespace) -> int:
|
||||
hass = core.HomeAssistant()
|
||||
|
||||
if args.demo_mode:
|
||||
config = {"frontend": {}, "demo": {}} # type: Dict[str, Any]
|
||||
config: Dict[str, Any] = {"frontend": {}, "demo": {}}
|
||||
bootstrap.async_from_config_dict(
|
||||
config,
|
||||
hass,
|
||||
@@ -326,7 +326,7 @@ def try_to_restart() -> None:
|
||||
thread.is_alive() and not thread.daemon for thread in threading.enumerate()
|
||||
)
|
||||
if nthreads > 1:
|
||||
sys.stderr.write("Found {} non-daemonic threads.\n".format(nthreads))
|
||||
sys.stderr.write(f"Found {nthreads} non-daemonic threads.\n")
|
||||
|
||||
# Somehow we sometimes seem to trigger an assertion in the python threading
|
||||
# module. It seems we find threads that have no associated OS level thread
|
||||
|
||||
@@ -47,7 +47,7 @@ async def auth_manager_from_config(
|
||||
else:
|
||||
providers = ()
|
||||
# So returned auth providers are in same order as config
|
||||
provider_hash = OrderedDict() # type: _ProviderDict
|
||||
provider_hash: _ProviderDict = OrderedDict()
|
||||
for provider in providers:
|
||||
key = (provider.type, provider.id)
|
||||
provider_hash[key] = provider
|
||||
@@ -59,7 +59,7 @@ async def auth_manager_from_config(
|
||||
else:
|
||||
modules = ()
|
||||
# So returned auth modules are in same order as config
|
||||
module_hash = OrderedDict() # type: _MfaModuleDict
|
||||
module_hash: _MfaModuleDict = OrderedDict()
|
||||
for module in modules:
|
||||
module_hash[module.id] = module
|
||||
|
||||
@@ -168,11 +168,11 @@ class AuthManager:
|
||||
|
||||
async def async_create_user(self, name: str) -> models.User:
|
||||
"""Create a user."""
|
||||
kwargs = {
|
||||
kwargs: Dict[str, Any] = {
|
||||
"name": name,
|
||||
"is_active": True,
|
||||
"group_ids": [GROUP_ID_ADMIN],
|
||||
} # type: Dict[str, Any]
|
||||
}
|
||||
|
||||
if await self._user_should_be_owner():
|
||||
kwargs["is_owner"] = True
|
||||
@@ -238,7 +238,7 @@ class AuthManager:
|
||||
group_ids: Optional[List[str]] = None,
|
||||
) -> None:
|
||||
"""Update a user."""
|
||||
kwargs = {} # type: Dict[str,Any]
|
||||
kwargs: Dict[str, Any] = {}
|
||||
if name is not None:
|
||||
kwargs["name"] = name
|
||||
if group_ids is not None:
|
||||
@@ -278,9 +278,7 @@ class AuthManager:
|
||||
|
||||
module = self.get_auth_mfa_module(mfa_module_id)
|
||||
if module is None:
|
||||
raise ValueError(
|
||||
"Unable find multi-factor auth module: {}".format(mfa_module_id)
|
||||
)
|
||||
raise ValueError(f"Unable find multi-factor auth module: {mfa_module_id}")
|
||||
|
||||
await module.async_setup_user(user.id, data)
|
||||
|
||||
@@ -295,15 +293,13 @@ class AuthManager:
|
||||
|
||||
module = self.get_auth_mfa_module(mfa_module_id)
|
||||
if module is None:
|
||||
raise ValueError(
|
||||
"Unable find multi-factor auth module: {}".format(mfa_module_id)
|
||||
)
|
||||
raise ValueError(f"Unable find multi-factor auth module: {mfa_module_id}")
|
||||
|
||||
await module.async_depose_user(user.id)
|
||||
|
||||
async def async_get_enabled_mfa(self, user: models.User) -> Dict[str, str]:
|
||||
"""List enabled mfa modules for user."""
|
||||
modules = OrderedDict() # type: Dict[str, str]
|
||||
modules: Dict[str, str] = OrderedDict()
|
||||
for module_id, module in self._mfa_modules.items():
|
||||
if await module.async_is_user_setup(user.id):
|
||||
modules[module_id] = module.name
|
||||
@@ -356,7 +352,7 @@ class AuthManager:
|
||||
):
|
||||
# Each client_name can only have one
|
||||
# long_lived_access_token type of refresh token
|
||||
raise ValueError("{} already exists".format(client_name))
|
||||
raise ValueError(f"{client_name} already exists")
|
||||
|
||||
return await self._store.async_create_refresh_token(
|
||||
user,
|
||||
|
||||
@@ -4,7 +4,7 @@ from collections import OrderedDict
|
||||
from datetime import timedelta
|
||||
import hmac
|
||||
from logging import getLogger
|
||||
from typing import Any, Dict, List, Optional # noqa: F401
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from homeassistant.auth.const import ACCESS_TOKEN_EXPIRATION
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
@@ -13,7 +13,7 @@ from homeassistant.util import dt as dt_util
|
||||
from . import models
|
||||
from .const import GROUP_ID_ADMIN, GROUP_ID_USER, GROUP_ID_READ_ONLY
|
||||
from .permissions import PermissionLookup, system_policies
|
||||
from .permissions.types import PolicyType # noqa: F401
|
||||
from .permissions.types import PolicyType
|
||||
|
||||
STORAGE_VERSION = 1
|
||||
STORAGE_KEY = "auth"
|
||||
@@ -34,9 +34,9 @@ class AuthStore:
|
||||
def __init__(self, hass: HomeAssistant) -> None:
|
||||
"""Initialize the auth store."""
|
||||
self.hass = hass
|
||||
self._users = None # type: Optional[Dict[str, models.User]]
|
||||
self._groups = None # type: Optional[Dict[str, models.Group]]
|
||||
self._perm_lookup = None # type: Optional[PermissionLookup]
|
||||
self._users: Optional[Dict[str, models.User]] = None
|
||||
self._groups: Optional[Dict[str, models.Group]] = None
|
||||
self._perm_lookup: Optional[PermissionLookup] = None
|
||||
self._store = hass.helpers.storage.Store(
|
||||
STORAGE_VERSION, STORAGE_KEY, private=True
|
||||
)
|
||||
@@ -94,16 +94,16 @@ class AuthStore:
|
||||
for group_id in group_ids or []:
|
||||
group = self._groups.get(group_id)
|
||||
if group is None:
|
||||
raise ValueError("Invalid group specified {}".format(group_id))
|
||||
raise ValueError(f"Invalid group specified {group_id}")
|
||||
groups.append(group)
|
||||
|
||||
kwargs = {
|
||||
kwargs: Dict[str, Any] = {
|
||||
"name": name,
|
||||
# Until we get group management, we just put everyone in the
|
||||
# same group.
|
||||
"groups": groups,
|
||||
"perm_lookup": self._perm_lookup,
|
||||
} # type: Dict[str, Any]
|
||||
}
|
||||
|
||||
if is_owner is not None:
|
||||
kwargs["is_owner"] = is_owner
|
||||
@@ -210,12 +210,12 @@ class AuthStore:
|
||||
access_token_expiration: timedelta = ACCESS_TOKEN_EXPIRATION,
|
||||
) -> models.RefreshToken:
|
||||
"""Create a new token for a user."""
|
||||
kwargs = {
|
||||
kwargs: Dict[str, Any] = {
|
||||
"user": user,
|
||||
"client_id": client_id,
|
||||
"token_type": token_type,
|
||||
"access_token_expiration": access_token_expiration,
|
||||
} # type: Dict[str, Any]
|
||||
}
|
||||
if client_name:
|
||||
kwargs["client_name"] = client_name
|
||||
if client_icon:
|
||||
@@ -307,8 +307,8 @@ class AuthStore:
|
||||
self._set_defaults()
|
||||
return
|
||||
|
||||
users = OrderedDict() # type: Dict[str, models.User]
|
||||
groups = OrderedDict() # type: Dict[str, models.Group]
|
||||
users: Dict[str, models.User] = OrderedDict()
|
||||
groups: Dict[str, models.Group] = OrderedDict()
|
||||
|
||||
# Soft-migrating data as we load. We are going to make sure we have a
|
||||
# read only group and an admin group. There are two states that we can
|
||||
@@ -325,7 +325,7 @@ class AuthStore:
|
||||
# was added.
|
||||
|
||||
for group_dict in data.get("groups", []):
|
||||
policy = None # type: Optional[PolicyType]
|
||||
policy: Optional[PolicyType] = None
|
||||
|
||||
if group_dict["id"] == GROUP_ID_ADMIN:
|
||||
has_admin_group = True
|
||||
@@ -503,11 +503,11 @@ class AuthStore:
|
||||
|
||||
groups = []
|
||||
for group in self._groups.values():
|
||||
g_dict = {
|
||||
g_dict: Dict[str, Any] = {
|
||||
"id": group.id,
|
||||
# Name not read for sys groups. Kept here for backwards compat
|
||||
"name": group.name,
|
||||
} # type: Dict[str, Any]
|
||||
}
|
||||
|
||||
if not group.system_generated:
|
||||
g_dict["policy"] = group.policy
|
||||
@@ -558,7 +558,7 @@ class AuthStore:
|
||||
"""Set default values for auth store."""
|
||||
self._users = OrderedDict()
|
||||
|
||||
groups = OrderedDict() # type: Dict[str, models.Group]
|
||||
groups: Dict[str, models.Group] = OrderedDict()
|
||||
admin_group = _system_admin_group()
|
||||
groups[admin_group.id] = admin_group
|
||||
user_group = _system_user_group()
|
||||
|
||||
@@ -109,7 +109,7 @@ class SetupFlow(data_entry_flow.FlowHandler):
|
||||
Return self.async_show_form(step_id='init') if user_input is None.
|
||||
Return self.async_create_entry(data={'result': result}) if finish.
|
||||
"""
|
||||
errors = {} # type: Dict[str, str]
|
||||
errors: Dict[str, str] = {}
|
||||
|
||||
if user_input:
|
||||
result = await self._auth_module.async_setup_user(self._user_id, user_input)
|
||||
@@ -144,15 +144,13 @@ async def auth_mfa_module_from_config(
|
||||
|
||||
async def _load_mfa_module(hass: HomeAssistant, module_name: str) -> types.ModuleType:
|
||||
"""Load an mfa auth module."""
|
||||
module_path = "homeassistant.auth.mfa_modules.{}".format(module_name)
|
||||
module_path = f"homeassistant.auth.mfa_modules.{module_name}"
|
||||
|
||||
try:
|
||||
module = importlib.import_module(module_path)
|
||||
except ImportError as err:
|
||||
_LOGGER.error("Unable to load mfa module %s: %s", module_name, err)
|
||||
raise HomeAssistantError(
|
||||
"Unable to load mfa module {}: {}".format(module_name, err)
|
||||
)
|
||||
raise HomeAssistantError(f"Unable to load mfa module {module_name}: {err}")
|
||||
|
||||
if hass.config.skip_pip or not hasattr(module, "REQUIREMENTS"):
|
||||
return module
|
||||
|
||||
@@ -95,7 +95,7 @@ class NotifyAuthModule(MultiFactorAuthModule):
|
||||
def __init__(self, hass: HomeAssistant, config: Dict[str, Any]) -> None:
|
||||
"""Initialize the user data store."""
|
||||
super().__init__(hass, config)
|
||||
self._user_settings = None # type: Optional[_UsersDict]
|
||||
self._user_settings: Optional[_UsersDict] = None
|
||||
self._user_store = hass.helpers.storage.Store(
|
||||
STORAGE_VERSION, STORAGE_KEY, private=True
|
||||
)
|
||||
@@ -279,18 +279,18 @@ class NotifySetupFlow(SetupFlow):
|
||||
"""Initialize the setup flow."""
|
||||
super().__init__(auth_module, setup_schema, user_id)
|
||||
# to fix typing complaint
|
||||
self._auth_module = auth_module # type: NotifyAuthModule
|
||||
self._auth_module: NotifyAuthModule = auth_module
|
||||
self._available_notify_services = available_notify_services
|
||||
self._secret = None # type: Optional[str]
|
||||
self._count = None # type: Optional[int]
|
||||
self._notify_service = None # type: Optional[str]
|
||||
self._target = None # type: Optional[str]
|
||||
self._secret: Optional[str] = None
|
||||
self._count: Optional[int] = None
|
||||
self._notify_service: Optional[str] = None
|
||||
self._target: Optional[str] = None
|
||||
|
||||
async def async_step_init(
|
||||
self, user_input: Optional[Dict[str, str]] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""Let user select available notify services."""
|
||||
errors = {} # type: Dict[str, str]
|
||||
errors: Dict[str, str] = {}
|
||||
|
||||
hass = self._auth_module.hass
|
||||
if user_input:
|
||||
@@ -304,7 +304,7 @@ class NotifySetupFlow(SetupFlow):
|
||||
if not self._available_notify_services:
|
||||
return self.async_abort(reason="no_available_service")
|
||||
|
||||
schema = OrderedDict() # type: Dict[str, Any]
|
||||
schema: Dict[str, Any] = OrderedDict()
|
||||
schema["notify_service"] = vol.In(self._available_notify_services)
|
||||
schema["target"] = vol.Optional(str)
|
||||
|
||||
@@ -316,7 +316,7 @@ class NotifySetupFlow(SetupFlow):
|
||||
self, user_input: Optional[Dict[str, str]] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""Verify user can recevie one-time password."""
|
||||
errors = {} # type: Dict[str, str]
|
||||
errors: Dict[str, str] = {}
|
||||
|
||||
hass = self._auth_module.hass
|
||||
if user_input:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import asyncio
|
||||
import logging
|
||||
from io import BytesIO
|
||||
from typing import Any, Dict, Optional, Tuple # noqa: F401
|
||||
from typing import Any, Dict, Optional, Tuple
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
@@ -75,7 +75,7 @@ class TotpAuthModule(MultiFactorAuthModule):
|
||||
def __init__(self, hass: HomeAssistant, config: Dict[str, Any]) -> None:
|
||||
"""Initialize the user data store."""
|
||||
super().__init__(hass, config)
|
||||
self._users = None # type: Optional[Dict[str, str]]
|
||||
self._users: Optional[Dict[str, str]] = None
|
||||
self._user_store = hass.helpers.storage.Store(
|
||||
STORAGE_VERSION, STORAGE_KEY, private=True
|
||||
)
|
||||
@@ -107,7 +107,7 @@ class TotpAuthModule(MultiFactorAuthModule):
|
||||
"""Create a ota_secret for user."""
|
||||
import pyotp
|
||||
|
||||
ota_secret = secret or pyotp.random_base32() # type: str
|
||||
ota_secret: str = secret or pyotp.random_base32()
|
||||
|
||||
self._users[user_id] = ota_secret # type: ignore
|
||||
return ota_secret
|
||||
@@ -181,9 +181,9 @@ class TotpSetupFlow(SetupFlow):
|
||||
"""Initialize the setup flow."""
|
||||
super().__init__(auth_module, setup_schema, user.id)
|
||||
# to fix typing complaint
|
||||
self._auth_module = auth_module # type: TotpAuthModule
|
||||
self._auth_module: TotpAuthModule = auth_module
|
||||
self._user = user
|
||||
self._ota_secret = None # type: Optional[str]
|
||||
self._ota_secret: Optional[str] = None
|
||||
self._url = None # type Optional[str]
|
||||
self._image = None # type Optional[str]
|
||||
|
||||
@@ -197,7 +197,7 @@ class TotpSetupFlow(SetupFlow):
|
||||
"""
|
||||
import pyotp
|
||||
|
||||
errors = {} # type: Dict[str, str]
|
||||
errors: Dict[str, str] = {}
|
||||
|
||||
if user_input:
|
||||
verified = await self.hass.async_add_executor_job( # type: ignore
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Auth models."""
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Dict, List, NamedTuple, Optional # noqa: F401
|
||||
from typing import Dict, List, NamedTuple, Optional
|
||||
import uuid
|
||||
|
||||
import attr
|
||||
@@ -20,7 +20,7 @@ TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN = "long_lived_access_token"
|
||||
class Group:
|
||||
"""A group."""
|
||||
|
||||
name = attr.ib(type=str) # type: Optional[str]
|
||||
name = attr.ib(type=Optional[str])
|
||||
policy = attr.ib(type=perm_mdl.PolicyType)
|
||||
id = attr.ib(type=str, factory=lambda: uuid.uuid4().hex)
|
||||
system_generated = attr.ib(type=bool, default=False)
|
||||
@@ -30,24 +30,20 @@ class Group:
|
||||
class User:
|
||||
"""A user."""
|
||||
|
||||
name = attr.ib(type=str) # type: Optional[str]
|
||||
perm_lookup = attr.ib(
|
||||
type=perm_mdl.PermissionLookup, cmp=False
|
||||
) # type: perm_mdl.PermissionLookup
|
||||
name = attr.ib(type=Optional[str])
|
||||
perm_lookup = attr.ib(type=perm_mdl.PermissionLookup, cmp=False)
|
||||
id = attr.ib(type=str, factory=lambda: uuid.uuid4().hex)
|
||||
is_owner = attr.ib(type=bool, default=False)
|
||||
is_active = attr.ib(type=bool, default=False)
|
||||
system_generated = attr.ib(type=bool, default=False)
|
||||
|
||||
groups = attr.ib(type=List, factory=list, cmp=False) # type: List[Group]
|
||||
groups = attr.ib(type=List[Group], factory=list, cmp=False)
|
||||
|
||||
# List of credentials of a user.
|
||||
credentials = attr.ib(type=list, factory=list, cmp=False) # type: List[Credentials]
|
||||
credentials = attr.ib(type=List["Credentials"], factory=list, cmp=False)
|
||||
|
||||
# Tokens associated with a user.
|
||||
refresh_tokens = attr.ib(
|
||||
type=dict, factory=dict, cmp=False
|
||||
) # type: Dict[str, RefreshToken]
|
||||
refresh_tokens = attr.ib(type=Dict[str, "RefreshToken"], factory=dict, cmp=False)
|
||||
|
||||
_permissions = attr.ib(
|
||||
type=Optional[perm_mdl.PolicyPermissions], init=False, cmp=False, default=None
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Entity permissions."""
|
||||
from collections import OrderedDict
|
||||
from typing import Callable, Optional # noqa: F401
|
||||
from typing import Callable, Optional
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
@@ -8,8 +8,7 @@ from .const import SUBCAT_ALL, POLICY_READ, POLICY_CONTROL, POLICY_EDIT
|
||||
from .models import PermissionLookup
|
||||
from .types import CategoryType, SubCategoryDict, ValueType
|
||||
|
||||
# pylint: disable=unused-import
|
||||
from .util import SubCatLookupType, lookup_all, compile_policy # noqa
|
||||
from .util import SubCatLookupType, lookup_all, compile_policy
|
||||
|
||||
SINGLE_ENTITY_SCHEMA = vol.Any(
|
||||
True,
|
||||
@@ -90,7 +89,7 @@ def compile_entities(
|
||||
policy: CategoryType, perm_lookup: PermissionLookup
|
||||
) -> Callable[[str, str], bool]:
|
||||
"""Compile policy into a function that tests policy."""
|
||||
subcategories = OrderedDict() # type: SubCatLookupType
|
||||
subcategories: SubCatLookupType = OrderedDict()
|
||||
subcategories[ENTITY_ENTITY_IDS] = _lookup_entity_id
|
||||
subcategories[ENTITY_DEVICE_IDS] = _lookup_device
|
||||
subcategories[ENTITY_AREAS] = _lookup_area
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
"""Merging of policies."""
|
||||
from typing import cast, Dict, List, Set # noqa: F401
|
||||
from typing import cast, Dict, List, Set
|
||||
|
||||
from .types import PolicyType, CategoryType
|
||||
|
||||
|
||||
def merge_policies(policies: List[PolicyType]) -> PolicyType:
|
||||
"""Merge policies."""
|
||||
new_policy = {} # type: Dict[str, CategoryType]
|
||||
seen = set() # type: Set[str]
|
||||
new_policy: Dict[str, CategoryType] = {}
|
||||
seen: Set[str] = set()
|
||||
for policy in policies:
|
||||
for category in policy:
|
||||
if category in seen:
|
||||
@@ -33,8 +33,8 @@ def _merge_policies(sources: List[CategoryType]) -> CategoryType:
|
||||
# If there are multiple sources with a dict as policy, we recursively
|
||||
# merge each key in the source.
|
||||
|
||||
policy = None # type: CategoryType
|
||||
seen = set() # type: Set[str]
|
||||
policy: CategoryType = None
|
||||
seen: Set[str] = set()
|
||||
for source in sources:
|
||||
if source is None:
|
||||
continue
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Helpers to deal with permissions."""
|
||||
from functools import wraps
|
||||
|
||||
from typing import Callable, Dict, List, Optional, cast # noqa: F401
|
||||
from typing import Callable, Dict, List, Optional, cast
|
||||
|
||||
from .const import SUBCAT_ALL
|
||||
from .models import PermissionLookup
|
||||
@@ -45,7 +45,7 @@ def compile_policy(
|
||||
|
||||
assert isinstance(policy, dict)
|
||||
|
||||
funcs = [] # type: List[Callable[[str, str], Optional[bool]]]
|
||||
funcs: List[Callable[[str, str], Optional[bool]]] = []
|
||||
|
||||
for key, lookup_func in subcategories.items():
|
||||
lookup_value = policy.get(key)
|
||||
@@ -85,7 +85,7 @@ def _gen_dict_test_func(
|
||||
|
||||
def test_value(object_id: str, key: str) -> Optional[bool]:
|
||||
"""Test if permission is allowed based on the keys."""
|
||||
schema = lookup_func(perm_lookup, lookup_dict, object_id) # type: ValueType
|
||||
schema: ValueType = lookup_func(perm_lookup, lookup_dict, object_id)
|
||||
|
||||
if schema is None or isinstance(schema, bool):
|
||||
return schema
|
||||
|
||||
@@ -16,7 +16,7 @@ from homeassistant.util.decorator import Registry
|
||||
|
||||
from ..auth_store import AuthStore
|
||||
from ..const import MFA_SESSION_EXPIRATION
|
||||
from ..models import Credentials, User, UserMeta # noqa: F401
|
||||
from ..models import Credentials, User, UserMeta
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
DATA_REQS = "auth_prov_reqs_processed"
|
||||
@@ -144,14 +144,10 @@ async def load_auth_provider_module(
|
||||
) -> types.ModuleType:
|
||||
"""Load an auth provider."""
|
||||
try:
|
||||
module = importlib.import_module(
|
||||
"homeassistant.auth.providers.{}".format(provider)
|
||||
)
|
||||
module = importlib.import_module(f"homeassistant.auth.providers.{provider}")
|
||||
except ImportError as err:
|
||||
_LOGGER.error("Unable to load auth provider %s: %s", provider, err)
|
||||
raise HomeAssistantError(
|
||||
"Unable to load auth provider {}: {}".format(provider, err)
|
||||
)
|
||||
raise HomeAssistantError(f"Unable to load auth provider {provider}: {err}")
|
||||
|
||||
if hass.config.skip_pip or not hasattr(module, "REQUIREMENTS"):
|
||||
return module
|
||||
@@ -166,7 +162,7 @@ async def load_auth_provider_module(
|
||||
# https://github.com/python/mypy/issues/1424
|
||||
reqs = module.REQUIREMENTS # type: ignore
|
||||
await requirements.async_process_requirements(
|
||||
hass, "auth provider {}".format(provider), reqs
|
||||
hass, f"auth provider {provider}", reqs
|
||||
)
|
||||
|
||||
processed.add(provider)
|
||||
@@ -179,12 +175,12 @@ class LoginFlow(data_entry_flow.FlowHandler):
|
||||
def __init__(self, auth_provider: AuthProvider) -> None:
|
||||
"""Initialize the login flow."""
|
||||
self._auth_provider = auth_provider
|
||||
self._auth_module_id = None # type: Optional[str]
|
||||
self._auth_module_id: Optional[str] = None
|
||||
self._auth_manager = auth_provider.hass.auth # type: ignore
|
||||
self.available_mfa_modules = {} # type: Dict[str, str]
|
||||
self.available_mfa_modules: Dict[str, str] = {}
|
||||
self.created_at = dt_util.utcnow()
|
||||
self.invalid_mfa_times = 0
|
||||
self.user = None # type: Optional[User]
|
||||
self.user: Optional[User] = None
|
||||
|
||||
async def async_step_init(
|
||||
self, user_input: Optional[Dict[str, str]] = None
|
||||
@@ -259,10 +255,10 @@ class LoginFlow(data_entry_flow.FlowHandler):
|
||||
if not errors:
|
||||
return await self.async_finish(self.user)
|
||||
|
||||
description_placeholders = {
|
||||
description_placeholders: Dict[str, Optional[str]] = {
|
||||
"mfa_module_name": auth_module.name,
|
||||
"mfa_module_id": auth_module.id,
|
||||
} # type: Dict[str, Optional[str]]
|
||||
}
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="mfa",
|
||||
|
||||
@@ -53,7 +53,7 @@ class CommandLineAuthProvider(AuthProvider):
|
||||
attributes provided by external programs.
|
||||
"""
|
||||
super().__init__(*args, **kwargs)
|
||||
self._user_meta = {} # type: Dict[str, Dict[str, Any]]
|
||||
self._user_meta: Dict[str, Dict[str, Any]] = {}
|
||||
|
||||
async def async_login_flow(self, context: Optional[dict]) -> LoginFlow:
|
||||
"""Return a flow to login."""
|
||||
@@ -85,7 +85,7 @@ class CommandLineAuthProvider(AuthProvider):
|
||||
raise InvalidAuthError
|
||||
|
||||
if self.config[CONF_META]:
|
||||
meta = {} # type: Dict[str, str]
|
||||
meta: Dict[str, str] = {}
|
||||
for _line in stdout.splitlines():
|
||||
try:
|
||||
line = _line.decode().lstrip()
|
||||
@@ -146,7 +146,7 @@ class CommandLineLoginFlow(LoginFlow):
|
||||
user_input.pop("password")
|
||||
return await self.async_finish(user_input)
|
||||
|
||||
schema = collections.OrderedDict() # type: Dict[str, type]
|
||||
schema: Dict[str, type] = collections.OrderedDict()
|
||||
schema["username"] = str
|
||||
schema["password"] = str
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import base64
|
||||
from collections import OrderedDict
|
||||
import logging
|
||||
|
||||
from typing import Any, Dict, List, Optional, Set, cast # noqa: F401
|
||||
from typing import Any, Dict, List, Optional, Set, cast
|
||||
|
||||
import bcrypt
|
||||
import voluptuous as vol
|
||||
@@ -53,7 +53,7 @@ class Data:
|
||||
self._store = hass.helpers.storage.Store(
|
||||
STORAGE_VERSION, STORAGE_KEY, private=True
|
||||
)
|
||||
self._data = None # type: Optional[Dict[str, Any]]
|
||||
self._data: Optional[Dict[str, Any]] = None
|
||||
# Legacy mode will allow usernames to start/end with whitespace
|
||||
# and will compare usernames case-insensitive.
|
||||
# Remove in 2020 or when we launch 1.0.
|
||||
@@ -74,7 +74,7 @@ class Data:
|
||||
if data is None:
|
||||
data = {"users": []}
|
||||
|
||||
seen = set() # type: Set[str]
|
||||
seen: Set[str] = set()
|
||||
|
||||
for user in data["users"]:
|
||||
username = user["username"]
|
||||
@@ -210,7 +210,7 @@ class HassAuthProvider(AuthProvider):
|
||||
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
||||
"""Initialize an Home Assistant auth provider."""
|
||||
super().__init__(*args, **kwargs)
|
||||
self.data = None # type: Optional[Data]
|
||||
self.data: Optional[Data] = None
|
||||
self._init_lock = asyncio.Lock()
|
||||
|
||||
async def async_initialize(self) -> None:
|
||||
@@ -296,7 +296,7 @@ class HassLoginFlow(LoginFlow):
|
||||
user_input.pop("password")
|
||||
return await self.async_finish(user_input)
|
||||
|
||||
schema = OrderedDict() # type: Dict[str, type]
|
||||
schema: Dict[str, type] = OrderedDict()
|
||||
schema["username"] = str
|
||||
schema["password"] = str
|
||||
|
||||
|
||||
@@ -112,7 +112,7 @@ class ExampleLoginFlow(LoginFlow):
|
||||
user_input.pop("password")
|
||||
return await self.async_finish(user_input)
|
||||
|
||||
schema = OrderedDict() # type: Dict[str, type]
|
||||
schema: Dict[str, type] = OrderedDict()
|
||||
schema["username"] = str
|
||||
schema["password"] = str
|
||||
|
||||
|
||||
@@ -97,6 +97,17 @@ async def async_from_config_dict(
|
||||
stop = time()
|
||||
_LOGGER.info("Home Assistant initialized in %.2fs", stop - start)
|
||||
|
||||
if sys.version_info[:3] < (3, 6, 1):
|
||||
msg = (
|
||||
"Python 3.6.0 support is deprecated and will "
|
||||
"be removed in the first release after October 2. Please "
|
||||
"upgrade Python to 3.6.1 or higher."
|
||||
)
|
||||
_LOGGER.warning(msg)
|
||||
hass.components.persistent_notification.async_create(
|
||||
msg, "Python version", "python_version"
|
||||
)
|
||||
|
||||
return hass
|
||||
|
||||
|
||||
@@ -163,7 +174,7 @@ def async_enable_logging(
|
||||
# ensure that the handlers it sets up wraps the correct streams.
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
colorfmt = "%(log_color)s{}%(reset)s".format(fmt)
|
||||
colorfmt = f"%(log_color)s{fmt}%(reset)s"
|
||||
logging.getLogger().handlers[0].setFormatter(
|
||||
ColoredFormatter(
|
||||
colorfmt,
|
||||
@@ -206,9 +217,9 @@ def async_enable_logging(
|
||||
):
|
||||
|
||||
if log_rotate_days:
|
||||
err_handler = logging.handlers.TimedRotatingFileHandler(
|
||||
err_handler: logging.FileHandler = logging.handlers.TimedRotatingFileHandler(
|
||||
err_log_path, when="midnight", backupCount=log_rotate_days
|
||||
) # type: logging.FileHandler
|
||||
)
|
||||
else:
|
||||
err_handler = logging.FileHandler(err_log_path, mode="w", delay=True)
|
||||
|
||||
@@ -335,7 +346,7 @@ async def _async_set_up_integrations(
|
||||
)
|
||||
|
||||
# Load all integrations
|
||||
after_dependencies = {} # type: Dict[str, Set[str]]
|
||||
after_dependencies: Dict[str, Set[str]] = {}
|
||||
|
||||
for int_or_exc in await asyncio.gather(
|
||||
*(loader.async_get_integration(hass, domain) for domain in stage_2_domains),
|
||||
|
||||
@@ -20,7 +20,8 @@
|
||||
"username": "Nombre de usuario",
|
||||
"verify_ssl": "AdGuard Home utiliza un certificado adecuado"
|
||||
},
|
||||
"description": "Configure su instancia de AdGuard Home para permitir la supervisi\u00f3n y el control."
|
||||
"description": "Configure su instancia de AdGuard Home para permitir la supervisi\u00f3n y el control.",
|
||||
"title": "Enlace su AdGuard Home."
|
||||
}
|
||||
},
|
||||
"title": "AdGuard Home"
|
||||
|
||||
@@ -1,7 +1,30 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"existing_instance_updated": "Se ha actualizado la configuraci\u00f3n existente."
|
||||
}
|
||||
"existing_instance_updated": "Se ha actualizado la configuraci\u00f3n existente.",
|
||||
"single_instance_allowed": "S\u00f3lo se permite una \u00fanica configuraci\u00f3n de AdGuard Home."
|
||||
},
|
||||
"error": {
|
||||
"connection_error": "No se conect\u00f3."
|
||||
},
|
||||
"step": {
|
||||
"hassio_confirm": {
|
||||
"description": "\u00bfDesea configurar Home Assistant para conectarse al AdGuard Home proporcionado por el complemento Hass.io: {addon} ?",
|
||||
"title": "AdGuard Home a trav\u00e9s del complemento Hass.io"
|
||||
},
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "Host",
|
||||
"password": "Contrase\u00f1a",
|
||||
"port": "Puerto",
|
||||
"ssl": "AdGuard Home utiliza un certificado SSL",
|
||||
"username": "Nombre de usuario",
|
||||
"verify_ssl": "AdGuard Home utiliza un certificado apropiado"
|
||||
},
|
||||
"description": "Configure su instancia de AdGuard Home para permitir la supervisi\u00f3n y el control.",
|
||||
"title": "Enlace su AdGuard Home."
|
||||
}
|
||||
},
|
||||
"title": "AdGuard Home"
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,15 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"existing_instance_updated": "La configuration existante a \u00e9t\u00e9 mise \u00e0 jour."
|
||||
"existing_instance_updated": "La configuration existante a \u00e9t\u00e9 mise \u00e0 jour.",
|
||||
"single_instance_allowed": "Une seule configuration d'AdGuard Home est autoris\u00e9e."
|
||||
},
|
||||
"error": {
|
||||
"connection_error": "\u00c9chec de connexion."
|
||||
},
|
||||
"step": {
|
||||
"hassio_confirm": {
|
||||
"description": "Voulez-vous configurer Home Assistant pour qu'il se connecte \u00e0 AdGuard Home fourni par le module compl\u00e9mentaire Hass.io: {addon} ?",
|
||||
"title": "AdGuard Home via le module compl\u00e9mentaire Hass.io"
|
||||
},
|
||||
"user": {
|
||||
@@ -16,8 +18,11 @@
|
||||
"password": "Mot de passe",
|
||||
"port": "Port",
|
||||
"ssl": "AdGuard Home utilise un certificat SSL",
|
||||
"username": "Nom d'utilisateur"
|
||||
}
|
||||
"username": "Nom d'utilisateur",
|
||||
"verify_ssl": "AdGuard Home utilise un certificat appropri\u00e9"
|
||||
},
|
||||
"description": "Configurez votre instance AdGuard Home pour permettre la surveillance et le contr\u00f4le.",
|
||||
"title": "Liez votre AdGuard Home."
|
||||
}
|
||||
},
|
||||
"title": "AdGuard Home"
|
||||
|
||||
7
homeassistant/components/adguard/.translations/hr.json
Normal file
7
homeassistant/components/adguard/.translations/hr.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"existing_instance_updated": "Postoje\u0107a konfiguracija je a\u017eurirana."
|
||||
}
|
||||
}
|
||||
}
|
||||
15
homeassistant/components/adguard/.translations/id.json
Normal file
15
homeassistant/components/adguard/.translations/id.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"config": {
|
||||
"error": {
|
||||
"connection_error": "Gagal terhubung."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"password": "Kata sandi",
|
||||
"port": "Port"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,30 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"existing_instance_updated": "Configurazione esistente aggiornata.",
|
||||
"single_instance_allowed": "\u00c8 consentita solo una singola configurazione di AdGuard Home."
|
||||
},
|
||||
"error": {
|
||||
"connection_error": "Impossibile connettersi."
|
||||
},
|
||||
"step": {
|
||||
"hassio_confirm": {
|
||||
"description": "Vuoi configurare Home Assistant per connettersi alla AdGuard Home fornita dal componente aggiuntivo di Hass.io: {addon} ?",
|
||||
"title": "AdGuard Home tramite il componente aggiuntivo di Hass.io"
|
||||
},
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "Host",
|
||||
"password": "Password",
|
||||
"port": "Porta",
|
||||
"ssl": "AdGuard Home utilizza un certificato SSL",
|
||||
"username": "Nome utente"
|
||||
}
|
||||
"username": "Nome utente",
|
||||
"verify_ssl": "AdGuard Home utilizza un certificato appropriato"
|
||||
},
|
||||
"description": "Configura l'istanza di AdGuard Home per consentire il monitoraggio e il controllo.",
|
||||
"title": "Collega la tua AdGuard Home."
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "AdGuard Home"
|
||||
}
|
||||
}
|
||||
@@ -5,11 +5,11 @@
|
||||
"single_instance_allowed": "Dozwolona jest tylko jedna konfiguracja AdGuard Home."
|
||||
},
|
||||
"error": {
|
||||
"connection_error": "Po\u0142\u0105czenie nieudane."
|
||||
"connection_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia."
|
||||
},
|
||||
"step": {
|
||||
"hassio_confirm": {
|
||||
"description": "Czy chcesz skonfigurowa\u0107 Home Assistant'a, aby po\u0142\u0105czy\u0142 si\u0119 z AdGuard Home przez dodatek Hass.io {addon}?",
|
||||
"description": "Czy chcesz skonfigurowa\u0107 Home Assistant, aby po\u0142\u0105czy\u0142 si\u0119 z AdGuard Home przez dodatek Hass.io {addon}?",
|
||||
"title": "AdGuard Home przez dodatek Hass.io"
|
||||
},
|
||||
"user": {
|
||||
@@ -21,7 +21,7 @@
|
||||
"username": "Nazwa u\u017cytkownika",
|
||||
"verify_ssl": "AdGuard Home u\u017cywa odpowiedniego certyfikatu."
|
||||
},
|
||||
"description": "Skonfiguruj swoj\u0105 instancj\u0119 AdGuard Home, aby umo\u017cliwi\u0107 monitorowanie i nadz\u00f3r sieci.",
|
||||
"description": "Skonfiguruj instancj\u0119 AdGuard Home, aby umo\u017cliwi\u0107 monitorowanie i kontrol\u0119.",
|
||||
"title": "Po\u0142\u0105cz sw\u00f3j AdGuard Home"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -132,7 +132,7 @@ class AdGuardHomePercentageBlockedSensor(AdGuardHomeSensor):
|
||||
async def _adguard_update(self) -> None:
|
||||
"""Update AdGuard Home entity."""
|
||||
percentage = await self.adguard.stats.blocked_percentage()
|
||||
self._state = "{:.2f}".format(percentage)
|
||||
self._state = f"{percentage:.2f}"
|
||||
|
||||
|
||||
class AdGuardHomeReplacedParentalSensor(AdGuardHomeSensor):
|
||||
@@ -205,7 +205,7 @@ class AdGuardHomeAverageProcessingTimeSensor(AdGuardHomeSensor):
|
||||
async def _adguard_update(self) -> None:
|
||||
"""Update AdGuard Home entity."""
|
||||
average = await self.adguard.stats.avg_processing_time()
|
||||
self._state = "{:.2f}".format(average)
|
||||
self._state = f"{average:.2f}"
|
||||
|
||||
|
||||
class AdGuardHomeRulesCountSensor(AdGuardHomeSensor):
|
||||
|
||||
@@ -194,7 +194,7 @@ class AirVisualSensor(Entity):
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique, HASS-friendly identifier for this entity."""
|
||||
return "{0}_{1}_{2}".format(self._location_id, self._locale, self._type)
|
||||
return f"{self._location_id}_{self._locale}_{self._type}"
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
@@ -210,7 +210,7 @@ class AirVisualSensor(Entity):
|
||||
return
|
||||
|
||||
if self._type == SENSOR_TYPE_LEVEL:
|
||||
aqi = data["aqi{0}".format(self._locale)]
|
||||
aqi = data[f"aqi{self._locale}"]
|
||||
[level] = [
|
||||
i
|
||||
for i in POLLUTANT_LEVEL_MAPPING
|
||||
@@ -219,9 +219,9 @@ class AirVisualSensor(Entity):
|
||||
self._state = level["label"]
|
||||
self._icon = level["icon"]
|
||||
elif self._type == SENSOR_TYPE_AQI:
|
||||
self._state = data["aqi{0}".format(self._locale)]
|
||||
self._state = data[f"aqi{self._locale}"]
|
||||
elif self._type == SENSOR_TYPE_POLLUTANT:
|
||||
symbol = data["main{0}".format(self._locale)]
|
||||
symbol = data[f"main{self._locale}"]
|
||||
self._state = POLLUTANT_MAPPING[symbol]["label"]
|
||||
self._attrs.update(
|
||||
{
|
||||
|
||||
@@ -85,7 +85,7 @@ class AladdinDevice(CoverDevice):
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique ID."""
|
||||
return "{}-{}".format(self._device_id, self._number)
|
||||
return f"{self._device_id}-{self._number}"
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
|
||||
@@ -13,13 +13,17 @@ from homeassistant.const import (
|
||||
)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
from . import DATA_AD, SIGNAL_PANEL_MESSAGE
|
||||
from . import DATA_AD, DOMAIN as DOMAIN_ALARMDECODER, SIGNAL_PANEL_MESSAGE
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
SERVICE_ALARM_TOGGLE_CHIME = "alarmdecoder_alarm_toggle_chime"
|
||||
ALARM_TOGGLE_CHIME_SCHEMA = vol.Schema({vol.Required(ATTR_CODE): cv.string})
|
||||
|
||||
SERVICE_ALARM_KEYPRESS = "alarm_keypress"
|
||||
ATTR_KEYPRESS = "keypress"
|
||||
ALARM_KEYPRESS_SCHEMA = vol.Schema({vol.Required(ATTR_KEYPRESS): cv.string})
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up for AlarmDecoder alarm panels."""
|
||||
@@ -38,6 +42,18 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
schema=ALARM_TOGGLE_CHIME_SCHEMA,
|
||||
)
|
||||
|
||||
def alarm_keypress_handler(service):
|
||||
"""Register keypress handler."""
|
||||
keypress = service.data[ATTR_KEYPRESS]
|
||||
device.alarm_keypress(keypress)
|
||||
|
||||
hass.services.register(
|
||||
DOMAIN_ALARMDECODER,
|
||||
SERVICE_ALARM_KEYPRESS,
|
||||
alarm_keypress_handler,
|
||||
schema=ALARM_KEYPRESS_SCHEMA,
|
||||
)
|
||||
|
||||
|
||||
class AlarmDecoderAlarmPanel(alarm.AlarmControlPanel):
|
||||
"""Representation of an AlarmDecoder-based alarm panel."""
|
||||
@@ -124,24 +140,29 @@ class AlarmDecoderAlarmPanel(alarm.AlarmControlPanel):
|
||||
def alarm_disarm(self, code=None):
|
||||
"""Send disarm command."""
|
||||
if code:
|
||||
self.hass.data[DATA_AD].send("{!s}1".format(code))
|
||||
self.hass.data[DATA_AD].send(f"{code!s}1")
|
||||
|
||||
def alarm_arm_away(self, code=None):
|
||||
"""Send arm away command."""
|
||||
if code:
|
||||
self.hass.data[DATA_AD].send("{!s}2".format(code))
|
||||
self.hass.data[DATA_AD].send(f"{code!s}2")
|
||||
|
||||
def alarm_arm_home(self, code=None):
|
||||
"""Send arm home command."""
|
||||
if code:
|
||||
self.hass.data[DATA_AD].send("{!s}3".format(code))
|
||||
self.hass.data[DATA_AD].send(f"{code!s}3")
|
||||
|
||||
def alarm_arm_night(self, code=None):
|
||||
"""Send arm night command."""
|
||||
if code:
|
||||
self.hass.data[DATA_AD].send("{!s}33".format(code))
|
||||
self.hass.data[DATA_AD].send(f"{code!s}33")
|
||||
|
||||
def alarm_toggle_chime(self, code=None):
|
||||
"""Send toggle chime command."""
|
||||
if code:
|
||||
self.hass.data[DATA_AD].send("{!s}9".format(code))
|
||||
self.hass.data[DATA_AD].send(f"{code!s}9")
|
||||
|
||||
def alarm_keypress(self, keypress):
|
||||
"""Send custom keypresses."""
|
||||
if keypress:
|
||||
self.hass.data[DATA_AD].send(keypress)
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
alarm_keypress:
|
||||
description: Send custom keypresses to the alarm.
|
||||
fields:
|
||||
entity_id:
|
||||
description: Name of the alarm control panel to trigger.
|
||||
example: 'alarm_control_panel.downstairs'
|
||||
keypress:
|
||||
description: 'String to send to the alarm panel.'
|
||||
example: '*71'
|
||||
|
||||
@@ -56,6 +56,11 @@ class Auth:
|
||||
|
||||
return await self._async_request_new_token(lwa_params)
|
||||
|
||||
@callback
|
||||
def async_invalidate_access_token(self):
|
||||
"""Invalidate access token."""
|
||||
self._prefs[STORAGE_ACCESS_TOKEN] = None
|
||||
|
||||
async def async_get_access_token(self):
|
||||
"""Perform access token or token refresh request."""
|
||||
async with self._get_token_lock:
|
||||
|
||||
@@ -11,6 +11,7 @@ from homeassistant.const import (
|
||||
STATE_ON,
|
||||
STATE_UNAVAILABLE,
|
||||
STATE_UNLOCKED,
|
||||
STATE_UNKNOWN,
|
||||
)
|
||||
import homeassistant.components.climate.const as climate
|
||||
from homeassistant.components import light, fan, cover
|
||||
@@ -443,7 +444,17 @@ class AlexaTemperatureSensor(AlexaCapibility):
|
||||
if self.entity.domain == climate.DOMAIN:
|
||||
unit = self.hass.config.units.temperature_unit
|
||||
temp = self.entity.attributes.get(climate.ATTR_CURRENT_TEMPERATURE)
|
||||
return {"value": float(temp), "scale": API_TEMP_UNITS[unit]}
|
||||
|
||||
if temp in (STATE_UNAVAILABLE, STATE_UNKNOWN):
|
||||
return None
|
||||
|
||||
try:
|
||||
temp = float(temp)
|
||||
except ValueError:
|
||||
_LOGGER.warning("Invalid temp value %s for %s", temp, self.entity.entity_id)
|
||||
return None
|
||||
|
||||
return {"value": temp, "scale": API_TEMP_UNITS[unit]}
|
||||
|
||||
|
||||
class AlexaContactSensor(AlexaCapibility):
|
||||
@@ -591,4 +602,12 @@ class AlexaThermostatController(AlexaCapibility):
|
||||
if temp is None:
|
||||
return None
|
||||
|
||||
return {"value": float(temp), "scale": API_TEMP_UNITS[unit]}
|
||||
try:
|
||||
temp = float(temp)
|
||||
except ValueError:
|
||||
_LOGGER.warning(
|
||||
"Invalid temp value %s for %s in %s", temp, name, self.entity.entity_id
|
||||
)
|
||||
return None
|
||||
|
||||
return {"value": temp, "scale": API_TEMP_UNITS[unit]}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
"""Config helpers for Alexa."""
|
||||
from homeassistant.core import callback
|
||||
|
||||
from .state_report import async_enable_proactive_mode
|
||||
|
||||
|
||||
@@ -55,11 +57,17 @@ class AbstractConfig:
|
||||
unsub_func()
|
||||
self._unsub_proactive_report = None
|
||||
|
||||
@callback
|
||||
def should_expose(self, entity_id):
|
||||
"""If an entity should be exposed."""
|
||||
# pylint: disable=no-self-use
|
||||
return False
|
||||
|
||||
@callback
|
||||
def async_invalidate_access_token(self):
|
||||
"""Invalidate access token."""
|
||||
raise NotImplementedError
|
||||
|
||||
async def async_get_access_token(self):
|
||||
"""Get an access token."""
|
||||
raise NotImplementedError
|
||||
|
||||
@@ -40,7 +40,7 @@ class AlexaInvalidEndpointError(AlexaError):
|
||||
|
||||
def __init__(self, endpoint_id):
|
||||
"""Initialize invalid endpoint error."""
|
||||
msg = "The endpoint {} does not exist".format(endpoint_id)
|
||||
msg = f"The endpoint {endpoint_id} does not exist"
|
||||
AlexaError.__init__(self, msg)
|
||||
self.endpoint_id = endpoint_id
|
||||
|
||||
@@ -73,7 +73,7 @@ class AlexaTempRangeError(AlexaError):
|
||||
"maximumValue": {"value": max_temp, "scale": API_TEMP_UNITS[unit]},
|
||||
}
|
||||
payload = {"validRange": temp_range}
|
||||
msg = "The requested temperature {} is out of range".format(temp)
|
||||
msg = f"The requested temperature {temp} is out of range"
|
||||
|
||||
AlexaError.__init__(self, msg, payload)
|
||||
|
||||
|
||||
@@ -744,7 +744,7 @@ async def async_api_set_thermostat_mode(hass, config, directive, context):
|
||||
presets = entity.attributes.get(climate.ATTR_PRESET_MODES, [])
|
||||
|
||||
if ha_preset not in presets:
|
||||
msg = "The requested thermostat mode {} is not supported".format(ha_preset)
|
||||
msg = f"The requested thermostat mode {ha_preset} is not supported"
|
||||
raise AlexaUnsupportedThermostatModeError(msg)
|
||||
|
||||
service = climate.SERVICE_SET_PRESET_MODE
|
||||
@@ -754,7 +754,7 @@ async def async_api_set_thermostat_mode(hass, config, directive, context):
|
||||
operation_list = entity.attributes.get(climate.ATTR_HVAC_MODES)
|
||||
ha_mode = next((k for k, v in API_THERMOSTAT_MODES.items() if v == mode), None)
|
||||
if ha_mode not in operation_list:
|
||||
msg = "The requested thermostat mode {} is not supported".format(mode)
|
||||
msg = f"The requested thermostat mode {mode} is not supported"
|
||||
raise AlexaUnsupportedThermostatModeError(msg)
|
||||
|
||||
service = climate.SERVICE_SET_HVAC_MODE
|
||||
|
||||
@@ -113,7 +113,7 @@ async def async_handle_message(hass, message):
|
||||
handler = HANDLERS.get(req_type)
|
||||
|
||||
if not handler:
|
||||
raise UnknownRequest("Received unknown request {}".format(req_type))
|
||||
raise UnknownRequest(f"Received unknown request {req_type}")
|
||||
|
||||
return await handler(hass, message)
|
||||
|
||||
|
||||
@@ -57,6 +57,11 @@ class AlexaConfig(AbstractConfig):
|
||||
"""If an entity should be exposed."""
|
||||
return self._config[CONF_FILTER](entity_id)
|
||||
|
||||
@core.callback
|
||||
def async_invalidate_access_token(self):
|
||||
"""Invalidate access token."""
|
||||
self._auth.async_invalidate_access_token()
|
||||
|
||||
async def async_get_access_token(self):
|
||||
"""Get an access token."""
|
||||
return await self._auth.async_get_access_token()
|
||||
|
||||
@@ -51,14 +51,16 @@ async def async_enable_proactive_mode(hass, smart_home_config):
|
||||
)
|
||||
|
||||
|
||||
async def async_send_changereport_message(hass, config, alexa_entity):
|
||||
async def async_send_changereport_message(
|
||||
hass, config, alexa_entity, *, invalidate_access_token=True
|
||||
):
|
||||
"""Send a ChangeReport message for an Alexa entity.
|
||||
|
||||
https://developer.amazon.com/docs/smarthome/state-reporting-for-a-smart-home-skill.html#report-state-with-changereport-events
|
||||
"""
|
||||
token = await config.async_get_access_token()
|
||||
|
||||
headers = {"Authorization": "Bearer {}".format(token)}
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
|
||||
endpoint = alexa_entity.alexa_id()
|
||||
|
||||
@@ -88,21 +90,33 @@ async def async_send_changereport_message(hass, config, alexa_entity):
|
||||
|
||||
except (asyncio.TimeoutError, aiohttp.ClientError):
|
||||
_LOGGER.error("Timeout sending report to Alexa.")
|
||||
return None
|
||||
return
|
||||
|
||||
response_text = await response.text()
|
||||
|
||||
_LOGGER.debug("Sent: %s", json.dumps(message_serialized))
|
||||
_LOGGER.debug("Received (%s): %s", response.status, response_text)
|
||||
|
||||
if response.status != 202:
|
||||
response_json = json.loads(response_text)
|
||||
_LOGGER.error(
|
||||
"Error when sending ChangeReport to Alexa: %s: %s",
|
||||
response_json["payload"]["code"],
|
||||
response_json["payload"]["description"],
|
||||
if response.status == 202:
|
||||
return
|
||||
|
||||
response_json = json.loads(response_text)
|
||||
|
||||
if (
|
||||
response_json["payload"]["code"] == "INVALID_ACCESS_TOKEN_EXCEPTION"
|
||||
and not invalidate_access_token
|
||||
):
|
||||
config.async_invalidate_access_token()
|
||||
return await async_send_changereport_message(
|
||||
hass, config, alexa_entity, invalidate_access_token=False
|
||||
)
|
||||
|
||||
_LOGGER.error(
|
||||
"Error when sending ChangeReport to Alexa: %s: %s",
|
||||
response_json["payload"]["code"],
|
||||
response_json["payload"]["description"],
|
||||
)
|
||||
|
||||
|
||||
async def async_send_add_or_update_message(hass, config, entity_ids):
|
||||
"""Send an AddOrUpdateReport message for entities.
|
||||
@@ -111,7 +125,7 @@ async def async_send_add_or_update_message(hass, config, entity_ids):
|
||||
"""
|
||||
token = await config.async_get_access_token()
|
||||
|
||||
headers = {"Authorization": "Bearer {}".format(token)}
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
|
||||
endpoints = []
|
||||
|
||||
@@ -141,7 +155,7 @@ async def async_send_delete_message(hass, config, entity_ids):
|
||||
"""
|
||||
token = await config.async_get_access_token()
|
||||
|
||||
headers = {"Authorization": "Bearer {}".format(token)}
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
|
||||
endpoints = []
|
||||
|
||||
|
||||
@@ -168,7 +168,7 @@ class AlphaVantageForeignExchange(Entity):
|
||||
if CONF_NAME in config:
|
||||
self._name = config.get(CONF_NAME)
|
||||
else:
|
||||
self._name = "{}/{}".format(self._to_currency, self._from_currency)
|
||||
self._name = f"{self._to_currency}/{self._from_currency}"
|
||||
self._unit_of_measurement = self._to_currency
|
||||
self._icon = ICONS.get(self._from_currency, "USD")
|
||||
self.values = None
|
||||
|
||||
@@ -7,6 +7,17 @@
|
||||
},
|
||||
"create_entry": {
|
||||
"default": "Autenticaci\u00f3n exitosa con Ambiclimate"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"follow_link": "Por favor, siga el enlace y autent\u00edquese antes de presionar Enviar",
|
||||
"no_token": "No autenticado con Ambiclimate"
|
||||
},
|
||||
"step": {
|
||||
"auth": {
|
||||
"description": "Por favor, siga este [link]('authorization_url') y <b>Permitir</b> acceso a su cuenta de Ambiclimate, luego vuelva y presione <b>Enviar</b> a continuaci\u00f3n.\n(Aseg\u00farese de que la url de devoluci\u00f3n de llamada especificada es {cb_url})",
|
||||
"title": "Autenticaci\u00f3n de Ambiclimate"
|
||||
}
|
||||
},
|
||||
"title": "Ambiclimate"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,22 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_setup": "L'account Ambiclimate \u00e8 configurato."
|
||||
"access_token": "Errore sconosciuto durante la generazione di un token di accesso.",
|
||||
"already_setup": "L'account Ambiclimate \u00e8 configurato.",
|
||||
"no_config": "\u00c8 necessario configurare Ambiclimate prima di poter eseguire l'autenticazione con esso. [Leggere le istruzioni] (https://www.home-assistant.io/components/ambiclimate/)."
|
||||
},
|
||||
"create_entry": {
|
||||
"default": "Autenticato con successo con Ambiclimate"
|
||||
},
|
||||
"error": {
|
||||
"follow_link": "Si prega di seguire il link e di autenticarsi prima di premere Invia",
|
||||
"no_token": "Non autenticato con Ambiclimate"
|
||||
},
|
||||
"step": {
|
||||
"auth": {
|
||||
"description": "Segui questo [link]({authorization_url}) e <b>Consenti</b> accesso al tuo account Ambiclimate, quindi torna indietro e premi <b>Invia</b> qui sotto. \n (Assicurati che l'URL di richiamata specificato sia {cb_url})",
|
||||
"title": "Autenticare Ambiclimate"
|
||||
}
|
||||
},
|
||||
"title": "Ambiclimate"
|
||||
}
|
||||
|
||||
@@ -3,18 +3,18 @@
|
||||
"abort": {
|
||||
"access_token": "Nieznany b\u0142\u0105d podczas generowania tokena dost\u0119pu.",
|
||||
"already_setup": "Konto Ambiclimate jest skonfigurowane.",
|
||||
"no_config": "Musisz skonfigurowa\u0107 Ambiclimate, zanim b\u0119dziesz m\u00f3g\u0142 si\u0119 z nim uwierzytelni\u0107. [Przeczytaj instrukcj\u0119](https://www.home-assistant.io/components/ambiclimate/)."
|
||||
"no_config": "Musisz skonfigurowa\u0107 Ambiclimate, zanim b\u0119dziesz m\u00f3g\u0142 si\u0119 w nim uwierzytelni\u0107. [Przeczytaj instrukcj\u0119](https://www.home-assistant.io/components/ambiclimate/)."
|
||||
},
|
||||
"create_entry": {
|
||||
"default": "Pomy\u015blnie uwierzytelniono z Ambiclimate"
|
||||
},
|
||||
"error": {
|
||||
"follow_link": "Prosz\u0119 klikn\u0105\u0107 link i uwierzytelni\u0107 przed naci\u015bni\u0119ciem przycisku Prze\u015blij",
|
||||
"no_token": "Nie uwierzytelniony z Ambiclimate"
|
||||
"no_token": "Nieuwierzytelniony z Ambiclimate"
|
||||
},
|
||||
"step": {
|
||||
"auth": {
|
||||
"description": "Kliknij poni\u017cszy [link]({authorization_url}) i <b>Zezw\u00f3l</b> na dost\u0119p do swojego konta Ambiclimate, a nast\u0119pnie wr\u00f3\u0107 i naci\u015bnij <b>Prze\u015blij</b> poni\u017cej. \n(Upewnij si\u0119, \u017ce podany adres URL to {cb_url})",
|
||||
"description": "Kliknij poni\u017cszy [link]({authorization_url}) i <b>Zezw\u00f3l</b> na dost\u0119p do konta Ambiclimate, a nast\u0119pnie wr\u00f3\u0107 i naci\u015bnij <b>Prze\u015blij</b> poni\u017cej. \n(Upewnij si\u0119, \u017ce podany adres URL to {cb_url})",
|
||||
"title": "Uwierzytelnienie Ambiclimate"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"abort": {
|
||||
"access_token": "\u041f\u0440\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 \u0442\u043e\u043a\u0435\u043d\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u043f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u0430 \u043e\u0448\u0438\u0431\u043a\u0430.",
|
||||
"already_setup": "\u0423\u0447\u0435\u0442\u043d\u0430\u044f \u0437\u0430\u043f\u0438\u0441\u044c Ambi Climate \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d\u0430.",
|
||||
"no_config": "\u0412\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c Ambi Climate \u043f\u0435\u0440\u0435\u0434 \u043f\u0440\u043e\u0445\u043e\u0436\u0434\u0435\u043d\u0438\u0435\u043c \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438. [\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c\u0438](https://www.home-assistant.io/components/ambiclimate/)."
|
||||
"no_config": "\u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443 Ambi Climate \u043f\u0435\u0440\u0435\u0434 \u043f\u0440\u043e\u0445\u043e\u0436\u0434\u0435\u043d\u0438\u0435\u043c \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 [\u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c\u0438](https://www.home-assistant.io/components/ambiclimate/)."
|
||||
},
|
||||
"create_entry": {
|
||||
"default": "\u0410\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u043f\u0440\u043e\u0439\u0434\u0435\u043d\u0430 \u0443\u0441\u043f\u0435\u0448\u043d\u043e."
|
||||
|
||||
@@ -130,7 +130,7 @@ class AmbiclimateFlowHandler(config_entries.ConfigFlow):
|
||||
return oauth
|
||||
|
||||
def _cb_url(self):
|
||||
return "{}{}".format(self.hass.config.api.base_url, AUTH_CALLBACK_PATH)
|
||||
return f"{self.hass.config.api.base_url}{AUTH_CALLBACK_PATH}"
|
||||
|
||||
async def _get_authorize_url(self):
|
||||
oauth = self._generate_oauth()
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/components/ambiclimate",
|
||||
"requirements": [
|
||||
"ambiclimate==0.2.0"
|
||||
"ambiclimate==0.2.1"
|
||||
],
|
||||
"dependencies": [],
|
||||
"codeowners": [
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
},
|
||||
"title": "Inserisci i tuoi dati"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "PWS ambientale"
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@
|
||||
"api_key": "Klucz API",
|
||||
"app_key": "Klucz aplikacji"
|
||||
},
|
||||
"title": "Wprowad\u017a swoje dane"
|
||||
"title": "Wprowad\u017a dane"
|
||||
}
|
||||
},
|
||||
"title": "Ambient PWS"
|
||||
|
||||
@@ -492,7 +492,7 @@ class AmbientWeatherEntity(Entity):
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the sensor."""
|
||||
return "{0}_{1}".format(self._station_name, self._sensor_name)
|
||||
return f"{self._station_name}_{self._sensor_name}"
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
@@ -502,7 +502,7 @@ class AmbientWeatherEntity(Entity):
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique, unchanging string that represents this sensor."""
|
||||
return "{0}_{1}".format(self._mac_address, self._sensor_type)
|
||||
return f"{self._mac_address}_{self._sensor_type}"
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Register callbacks."""
|
||||
|
||||
@@ -490,7 +490,7 @@ class AmcrestCam(Camera):
|
||||
self._api.go_to_preset(action="start", preset_point_number=preset)
|
||||
except AmcrestError as error:
|
||||
log_update_error(
|
||||
_LOGGER, "move", self.name, "camera to preset {}".format(preset), error
|
||||
_LOGGER, "move", self.name, f"camera to preset {preset}", error
|
||||
)
|
||||
|
||||
def _set_color_bw(self, cbw):
|
||||
@@ -499,7 +499,7 @@ class AmcrestCam(Camera):
|
||||
self._api.day_night_color = _CBW.index(cbw)
|
||||
except AmcrestError as error:
|
||||
log_update_error(
|
||||
_LOGGER, "set", self.name, "camera color mode to {}".format(cbw), error
|
||||
_LOGGER, "set", self.name, f"camera color mode to {cbw}", error
|
||||
)
|
||||
else:
|
||||
self._color_bw = cbw
|
||||
|
||||
@@ -4,7 +4,7 @@ from .const import DOMAIN
|
||||
|
||||
def service_signal(service, ident=None):
|
||||
"""Encode service and identifier into signal."""
|
||||
signal = "{}_{}".format(DOMAIN, service)
|
||||
signal = f"{DOMAIN}_{service}"
|
||||
if ident:
|
||||
signal += "_{}".format(ident.replace(".", "_"))
|
||||
return signal
|
||||
|
||||
@@ -57,7 +57,7 @@ class AmpioSmogQuality(AirQualityEntity):
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return unique_name."""
|
||||
return "ampio_smog_{}".format(self._station_id)
|
||||
return f"ampio_smog_{self._station_id}"
|
||||
|
||||
@property
|
||||
def particulate_matter_2_5(self):
|
||||
|
||||
@@ -25,7 +25,7 @@ class IPWebcamBinarySensor(AndroidIPCamEntity, BinarySensorDevice):
|
||||
|
||||
self._sensor = sensor
|
||||
self._mapped_name = KEY_MAP.get(self._sensor, self._sensor)
|
||||
self._name = "{} {}".format(name, self._mapped_name)
|
||||
self._name = f"{name} {self._mapped_name}"
|
||||
self._state = None
|
||||
self._unit = None
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ class IPWebcamSensor(AndroidIPCamEntity):
|
||||
|
||||
self._sensor = sensor
|
||||
self._mapped_name = KEY_MAP.get(self._sensor, self._sensor)
|
||||
self._name = "{} {}".format(name, self._mapped_name)
|
||||
self._name = f"{name} {self._mapped_name}"
|
||||
self._state = None
|
||||
self._unit = None
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ class IPWebcamSettingsSwitch(AndroidIPCamEntity, SwitchDevice):
|
||||
|
||||
self._setting = setting
|
||||
self._mapped_name = KEY_MAP.get(self._setting, self._setting)
|
||||
self._name = "{} {}".format(name, self._mapped_name)
|
||||
self._name = f"{name} {self._mapped_name}"
|
||||
self._state = False
|
||||
|
||||
@property
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"name": "Androidtv",
|
||||
"documentation": "https://www.home-assistant.io/components/androidtv",
|
||||
"requirements": [
|
||||
"androidtv==0.0.24"
|
||||
"androidtv==0.0.27"
|
||||
],
|
||||
"dependencies": [],
|
||||
"codeowners": ["@JeffLIrion"]
|
||||
|
||||
@@ -392,7 +392,7 @@ class ADBDevice(MediaPlayerDevice):
|
||||
"""Send an ADB command to an Android TV / Fire TV device."""
|
||||
key = self._keys.get(cmd)
|
||||
if key:
|
||||
self.aftv.adb_shell("input keyevent {}".format(key))
|
||||
self.aftv.adb_shell(f"input keyevent {key}")
|
||||
self._adb_response = None
|
||||
self.schedule_update_ha_state()
|
||||
return
|
||||
@@ -431,8 +431,10 @@ class AndroidTVDevice(ADBDevice):
|
||||
# Try to connect
|
||||
self._available = self.aftv.connect(always_log_errors=False)
|
||||
|
||||
# To be safe, wait until the next update to run ADB commands.
|
||||
return
|
||||
# To be safe, wait until the next update to run ADB commands if
|
||||
# using the Python ADB implementation.
|
||||
if not self.aftv.adb_server_ip:
|
||||
return
|
||||
|
||||
# If the ADB connection is not intact, don't update.
|
||||
if not self._available:
|
||||
@@ -443,7 +445,9 @@ class AndroidTVDevice(ADBDevice):
|
||||
self.aftv.update()
|
||||
)
|
||||
|
||||
self._state = ANDROIDTV_STATES[state]
|
||||
self._state = ANDROIDTV_STATES.get(state)
|
||||
if self._state is None:
|
||||
self._available = False
|
||||
|
||||
@property
|
||||
def is_volume_muted(self):
|
||||
@@ -506,8 +510,10 @@ class FireTVDevice(ADBDevice):
|
||||
# Try to connect
|
||||
self._available = self.aftv.connect(always_log_errors=False)
|
||||
|
||||
# To be safe, wait until the next update to run ADB commands.
|
||||
return
|
||||
# To be safe, wait until the next update to run ADB commands if
|
||||
# using the Python ADB implementation.
|
||||
if not self.aftv.adb_server_ip:
|
||||
return
|
||||
|
||||
# If the ADB connection is not intact, don't update.
|
||||
if not self._available:
|
||||
@@ -518,7 +524,9 @@ class FireTVDevice(ADBDevice):
|
||||
self._get_sources
|
||||
)
|
||||
|
||||
self._state = ANDROIDTV_STATES[state]
|
||||
self._state = ANDROIDTV_STATES.get(state)
|
||||
if self._state is None:
|
||||
self._available = False
|
||||
|
||||
@property
|
||||
def source(self):
|
||||
|
||||
@@ -81,7 +81,7 @@ class KafkaManager:
|
||||
self._hass = hass
|
||||
self._producer = AIOKafkaProducer(
|
||||
loop=hass.loop,
|
||||
bootstrap_servers="{0}:{1}".format(ip_address, port),
|
||||
bootstrap_servers=f"{ip_address}:{port}",
|
||||
compression_type="gzip",
|
||||
)
|
||||
self._topic = topic
|
||||
|
||||
@@ -138,7 +138,7 @@ class APIEventStream(HomeAssistantView):
|
||||
if payload is stop_obj:
|
||||
break
|
||||
|
||||
msg = "data: {}\n\n".format(payload)
|
||||
msg = f"data: {payload}\n\n"
|
||||
_LOGGER.debug("STREAM %s WRITING %s", id(stop_obj), msg.strip())
|
||||
await response.write(msg.encode("UTF-8"))
|
||||
except asyncio.TimeoutError:
|
||||
@@ -316,7 +316,7 @@ class APIEventView(HomeAssistantView):
|
||||
event_type, event_data, ha.EventOrigin.remote, self.context(request)
|
||||
)
|
||||
|
||||
return self.json_message("Event {} fired.".format(event_type))
|
||||
return self.json_message(f"Event {event_type} fired.")
|
||||
|
||||
|
||||
class APIServicesView(HomeAssistantView):
|
||||
@@ -388,7 +388,7 @@ class APITemplateView(HomeAssistantView):
|
||||
return tpl.async_render(data.get("variables"))
|
||||
except (ValueError, TemplateError) as ex:
|
||||
return self.json_message(
|
||||
"Error rendering template: {}".format(ex), HTTP_BAD_REQUEST
|
||||
f"Error rendering template: {ex}", HTTP_BAD_REQUEST
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ def get_service(hass, config, discovery_info=None):
|
||||
|
||||
service = ApnsNotificationService(hass, name, topic, sandbox, cert_file)
|
||||
hass.services.register(
|
||||
DOMAIN, "apns_{}".format(name), service.register, schema=REGISTER_SERVICE_SCHEMA
|
||||
DOMAIN, f"apns_{name}", service.register, schema=REGISTER_SERVICE_SCHEMA
|
||||
)
|
||||
return service
|
||||
|
||||
@@ -98,7 +98,7 @@ class ApnsDevice:
|
||||
The full id of a device that is tracked by the device
|
||||
tracking component.
|
||||
"""
|
||||
return "{}.{}".format(DEVICE_TRACKER_DOMAIN, self.tracking_id)
|
||||
return f"{DEVICE_TRACKER_DOMAIN}.{self.tracking_id}"
|
||||
|
||||
@property
|
||||
def disabled(self):
|
||||
@@ -124,9 +124,9 @@ def _write_device(out, device):
|
||||
"""Write a single device to file."""
|
||||
attributes = []
|
||||
if device.name is not None:
|
||||
attributes.append("name: {}".format(device.name))
|
||||
attributes.append(f"name: {device.name}")
|
||||
if device.tracking_device_id is not None:
|
||||
attributes.append("tracking_device_id: {}".format(device.tracking_device_id))
|
||||
attributes.append(f"tracking_device_id: {device.tracking_device_id}")
|
||||
if device.disabled:
|
||||
attributes.append("disabled: True")
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"name": "Apple tv",
|
||||
"documentation": "https://www.home-assistant.io/components/apple_tv",
|
||||
"requirements": [
|
||||
"pyatv==0.3.12"
|
||||
"pyatv==0.3.13"
|
||||
],
|
||||
"dependencies": ["configurator"],
|
||||
"codeowners": []
|
||||
|
||||
@@ -127,6 +127,7 @@ class AppleTvDevice(MediaPlayerDevice):
|
||||
const.PLAY_STATE_PAUSED,
|
||||
const.PLAY_STATE_FAST_FORWARD,
|
||||
const.PLAY_STATE_FAST_BACKWARD,
|
||||
const.PLAY_STATE_STOPPED,
|
||||
):
|
||||
# Catch fast forward/backward here so "play" is default action
|
||||
return STATE_PAUSED
|
||||
@@ -212,7 +213,7 @@ class AppleTvDevice(MediaPlayerDevice):
|
||||
title = self._playing.title
|
||||
return title if title else "No title"
|
||||
|
||||
return "Establishing a connection to {0}...".format(self._name)
|
||||
return f"Establishing a connection to {self._name}..."
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
|
||||
@@ -70,7 +70,7 @@ def gps_accuracy(gps, posambiguity: int) -> int:
|
||||
|
||||
accuracy = round(dist_m)
|
||||
else:
|
||||
message = "APRS position ambiguity must be 0-4, not '{0}'.".format(posambiguity)
|
||||
message = f"APRS position ambiguity must be 0-4, not '{posambiguity}'."
|
||||
raise ValueError(message)
|
||||
|
||||
return accuracy
|
||||
@@ -147,8 +147,7 @@ class AprsListenerThread(threading.Thread):
|
||||
)
|
||||
self.ais.connect()
|
||||
self.start_complete(
|
||||
True,
|
||||
"Connected to {0} with callsign {1}.".format(self.host, self.callsign),
|
||||
True, f"Connected to {self.host} with callsign {self.callsign}."
|
||||
)
|
||||
self.ais.consumer(callback=self.rx_msg, immortal=True)
|
||||
except (AprsConnectionError, LoginError) as err:
|
||||
|
||||
5
homeassistant/components/arcam_fmj/.translations/ca.json
Normal file
5
homeassistant/components/arcam_fmj/.translations/ca.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"config": {
|
||||
"title": "Arcam FMJ"
|
||||
}
|
||||
}
|
||||
5
homeassistant/components/arcam_fmj/.translations/da.json
Normal file
5
homeassistant/components/arcam_fmj/.translations/da.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"config": {
|
||||
"title": "Arcam FMJ"
|
||||
}
|
||||
}
|
||||
5
homeassistant/components/arcam_fmj/.translations/de.json
Normal file
5
homeassistant/components/arcam_fmj/.translations/de.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"config": {
|
||||
"title": "Arcam FMJ"
|
||||
}
|
||||
}
|
||||
5
homeassistant/components/arcam_fmj/.translations/es.json
Normal file
5
homeassistant/components/arcam_fmj/.translations/es.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"config": {
|
||||
"title": "Arcam FMJ"
|
||||
}
|
||||
}
|
||||
5
homeassistant/components/arcam_fmj/.translations/fr.json
Normal file
5
homeassistant/components/arcam_fmj/.translations/fr.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"config": {
|
||||
"title": "Arcam FMJ"
|
||||
}
|
||||
}
|
||||
5
homeassistant/components/arcam_fmj/.translations/it.json
Normal file
5
homeassistant/components/arcam_fmj/.translations/it.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"config": {
|
||||
"title": "Arcam FMJ"
|
||||
}
|
||||
}
|
||||
5
homeassistant/components/arcam_fmj/.translations/ko.json
Normal file
5
homeassistant/components/arcam_fmj/.translations/ko.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"config": {
|
||||
"title": "Arcam FMJ"
|
||||
}
|
||||
}
|
||||
5
homeassistant/components/arcam_fmj/.translations/lb.json
Normal file
5
homeassistant/components/arcam_fmj/.translations/lb.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"config": {
|
||||
"title": "Arcam FMJ"
|
||||
}
|
||||
}
|
||||
5
homeassistant/components/arcam_fmj/.translations/nl.json
Normal file
5
homeassistant/components/arcam_fmj/.translations/nl.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"config": {
|
||||
"title": "Arcam FMJ"
|
||||
}
|
||||
}
|
||||
5
homeassistant/components/arcam_fmj/.translations/no.json
Normal file
5
homeassistant/components/arcam_fmj/.translations/no.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"config": {
|
||||
"title": "Arcam FMJ"
|
||||
}
|
||||
}
|
||||
5
homeassistant/components/arcam_fmj/.translations/pl.json
Normal file
5
homeassistant/components/arcam_fmj/.translations/pl.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"config": {
|
||||
"title": "Arcam FMJ"
|
||||
}
|
||||
}
|
||||
5
homeassistant/components/arcam_fmj/.translations/ru.json
Normal file
5
homeassistant/components/arcam_fmj/.translations/ru.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"config": {
|
||||
"title": "Arcam FMJ"
|
||||
}
|
||||
}
|
||||
5
homeassistant/components/arcam_fmj/.translations/sl.json
Normal file
5
homeassistant/components/arcam_fmj/.translations/sl.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"config": {
|
||||
"title": "Arcam FMJ"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"config": {
|
||||
"title": "Arcam FMJ"
|
||||
}
|
||||
}
|
||||
@@ -9,5 +9,5 @@ DEFAULT_PORT = 50000
|
||||
DEFAULT_NAME = "Arcam FMJ"
|
||||
DEFAULT_SCAN_INTERVAL = 5
|
||||
|
||||
DOMAIN_DATA_ENTRIES = "{}.entries".format(DOMAIN)
|
||||
DOMAIN_DATA_CONFIG = "{}.config".format(DOMAIN)
|
||||
DOMAIN_DATA_ENTRIES = f"{DOMAIN}.entries"
|
||||
DOMAIN_DATA_CONFIG = f"{DOMAIN}.config"
|
||||
|
||||
@@ -319,7 +319,7 @@ class ArcamFmj(MediaPlayerDevice):
|
||||
channel = self.media_channel
|
||||
|
||||
if channel:
|
||||
value = "{} - {}".format(source.name, channel)
|
||||
value = f"{source.name} - {channel}"
|
||||
else:
|
||||
value = source.name
|
||||
return value
|
||||
|
||||
@@ -73,9 +73,7 @@ class ArestBinarySensor(BinarySensorDevice):
|
||||
self._pin = pin
|
||||
|
||||
if self._pin is not None:
|
||||
request = requests.get(
|
||||
"{}/mode/{}/i".format(self._resource, self._pin), timeout=10
|
||||
)
|
||||
request = requests.get(f"{self._resource}/mode/{self._pin}/i", timeout=10)
|
||||
if request.status_code != 200:
|
||||
_LOGGER.error("Can't set mode of %s", self._resource)
|
||||
|
||||
@@ -112,9 +110,7 @@ class ArestData:
|
||||
def update(self):
|
||||
"""Get the latest data from aREST device."""
|
||||
try:
|
||||
response = requests.get(
|
||||
"{}/digital/{}".format(self._resource, self._pin), timeout=10
|
||||
)
|
||||
response = requests.get(f"{self._resource}/digital/{self._pin}", timeout=10)
|
||||
self.data = {"state": response.json()["return_value"]}
|
||||
except requests.exceptions.ConnectionError:
|
||||
_LOGGER.error("No route to device '%s'", self._resource)
|
||||
|
||||
@@ -148,9 +148,7 @@ class ArestSensor(Entity):
|
||||
self._renderer = renderer
|
||||
|
||||
if self._pin is not None:
|
||||
request = requests.get(
|
||||
"{}/mode/{}/i".format(self._resource, self._pin), timeout=10
|
||||
)
|
||||
request = requests.get(f"{self._resource}/mode/{self._pin}/i", timeout=10)
|
||||
if request.status_code != 200:
|
||||
_LOGGER.error("Can't set mode of %s", self._resource)
|
||||
|
||||
@@ -212,7 +210,7 @@ class ArestData:
|
||||
self.data = {"value": response.json()["return_value"]}
|
||||
except TypeError:
|
||||
response = requests.get(
|
||||
"{}/digital/{}".format(self._resource, self._pin), timeout=10
|
||||
f"{self._resource}/digital/{self._pin}", timeout=10
|
||||
)
|
||||
self.data = {"value": response.json()["return_value"]}
|
||||
self.available = True
|
||||
|
||||
@@ -114,7 +114,7 @@ class ArestSwitchFunction(ArestSwitchBase):
|
||||
super().__init__(resource, location, name)
|
||||
self._func = func
|
||||
|
||||
request = requests.get("{}/{}".format(self._resource, self._func), timeout=10)
|
||||
request = requests.get(f"{self._resource}/{self._func}", timeout=10)
|
||||
|
||||
if request.status_code != 200:
|
||||
_LOGGER.error("Can't find function")
|
||||
@@ -130,9 +130,7 @@ class ArestSwitchFunction(ArestSwitchBase):
|
||||
def turn_on(self, **kwargs):
|
||||
"""Turn the device on."""
|
||||
request = requests.get(
|
||||
"{}/{}".format(self._resource, self._func),
|
||||
timeout=10,
|
||||
params={"params": "1"},
|
||||
f"{self._resource}/{self._func}", timeout=10, params={"params": "1"}
|
||||
)
|
||||
|
||||
if request.status_code == 200:
|
||||
@@ -143,9 +141,7 @@ class ArestSwitchFunction(ArestSwitchBase):
|
||||
def turn_off(self, **kwargs):
|
||||
"""Turn the device off."""
|
||||
request = requests.get(
|
||||
"{}/{}".format(self._resource, self._func),
|
||||
timeout=10,
|
||||
params={"params": "0"},
|
||||
f"{self._resource}/{self._func}", timeout=10, params={"params": "0"}
|
||||
)
|
||||
|
||||
if request.status_code == 200:
|
||||
@@ -158,9 +154,7 @@ class ArestSwitchFunction(ArestSwitchBase):
|
||||
def update(self):
|
||||
"""Get the latest data from aREST API and update the state."""
|
||||
try:
|
||||
request = requests.get(
|
||||
"{}/{}".format(self._resource, self._func), timeout=10
|
||||
)
|
||||
request = requests.get(f"{self._resource}/{self._func}", timeout=10)
|
||||
self._state = request.json()["return_value"] != 0
|
||||
self._available = True
|
||||
except requests.exceptions.ConnectionError:
|
||||
@@ -177,9 +171,7 @@ class ArestSwitchPin(ArestSwitchBase):
|
||||
self._pin = pin
|
||||
self.invert = invert
|
||||
|
||||
request = requests.get(
|
||||
"{}/mode/{}/o".format(self._resource, self._pin), timeout=10
|
||||
)
|
||||
request = requests.get(f"{self._resource}/mode/{self._pin}/o", timeout=10)
|
||||
if request.status_code != 200:
|
||||
_LOGGER.error("Can't set mode")
|
||||
self._available = False
|
||||
@@ -188,8 +180,7 @@ class ArestSwitchPin(ArestSwitchBase):
|
||||
"""Turn the device on."""
|
||||
turn_on_payload = int(not self.invert)
|
||||
request = requests.get(
|
||||
"{}/digital/{}/{}".format(self._resource, self._pin, turn_on_payload),
|
||||
timeout=10,
|
||||
f"{self._resource}/digital/{self._pin}/{turn_on_payload}", timeout=10
|
||||
)
|
||||
if request.status_code == 200:
|
||||
self._state = True
|
||||
@@ -200,8 +191,7 @@ class ArestSwitchPin(ArestSwitchBase):
|
||||
"""Turn the device off."""
|
||||
turn_off_payload = int(self.invert)
|
||||
request = requests.get(
|
||||
"{}/digital/{}/{}".format(self._resource, self._pin, turn_off_payload),
|
||||
timeout=10,
|
||||
f"{self._resource}/digital/{self._pin}/{turn_off_payload}", timeout=10
|
||||
)
|
||||
if request.status_code == 200:
|
||||
self._state = False
|
||||
@@ -211,9 +201,7 @@ class ArestSwitchPin(ArestSwitchBase):
|
||||
def update(self):
|
||||
"""Get the latest data from aREST API and update the state."""
|
||||
try:
|
||||
request = requests.get(
|
||||
"{}/digital/{}".format(self._resource, self._pin), timeout=10
|
||||
)
|
||||
request = requests.get(f"{self._resource}/digital/{self._pin}", timeout=10)
|
||||
status_value = int(self.invert)
|
||||
self._state = request.json()["return_value"] != status_value
|
||||
self._available = True
|
||||
|
||||
1
homeassistant/components/atome/__init__.py
Normal file
1
homeassistant/components/atome/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Support for Atome devices connected to a Linky Energy Meter."""
|
||||
8
homeassistant/components/atome/manifest.json
Normal file
8
homeassistant/components/atome/manifest.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"domain": "atome",
|
||||
"name": "Atome",
|
||||
"documentation": "https://www.home-assistant.io/components/atome",
|
||||
"dependencies": [],
|
||||
"codeowners": ["@baqs"],
|
||||
"requirements": ["pyatome==0.1.1"]
|
||||
}
|
||||
279
homeassistant/components/atome/sensor.py
Normal file
279
homeassistant/components/atome/sensor.py
Normal file
@@ -0,0 +1,279 @@
|
||||
"""Linky Atome."""
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
|
||||
import voluptuous as vol
|
||||
from pyatome.client import AtomeClient
|
||||
from pyatome.client import PyAtomeError
|
||||
|
||||
from homeassistant.const import (
|
||||
CONF_PASSWORD,
|
||||
CONF_USERNAME,
|
||||
CONF_NAME,
|
||||
DEVICE_CLASS_POWER,
|
||||
POWER_WATT,
|
||||
ENERGY_KILO_WATT_HOUR,
|
||||
)
|
||||
from homeassistant.components.sensor import PLATFORM_SCHEMA
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.util import Throttle
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_NAME = "atome"
|
||||
|
||||
LIVE_SCAN_INTERVAL = timedelta(seconds=30)
|
||||
DAILY_SCAN_INTERVAL = timedelta(seconds=150)
|
||||
WEEKLY_SCAN_INTERVAL = timedelta(hours=1)
|
||||
MONTHLY_SCAN_INTERVAL = timedelta(hours=1)
|
||||
YEARLY_SCAN_INTERVAL = timedelta(days=1)
|
||||
|
||||
LIVE_NAME = "Atome Live Power"
|
||||
DAILY_NAME = "Atome Daily"
|
||||
WEEKLY_NAME = "Atome Weekly"
|
||||
MONTHLY_NAME = "Atome Monthly"
|
||||
YEARLY_NAME = "Atome Yearly"
|
||||
|
||||
LIVE_TYPE = "live"
|
||||
DAILY_TYPE = "day"
|
||||
WEEKLY_TYPE = "week"
|
||||
MONTHLY_TYPE = "month"
|
||||
YEARLY_TYPE = "year"
|
||||
|
||||
ICON = "mdi:flash"
|
||||
|
||||
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,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up the Atome sensor."""
|
||||
username = config[CONF_USERNAME]
|
||||
password = config[CONF_PASSWORD]
|
||||
|
||||
try:
|
||||
atome_client = AtomeClient(username, password)
|
||||
atome_client.login()
|
||||
except PyAtomeError as exp:
|
||||
_LOGGER.error(exp)
|
||||
return
|
||||
|
||||
data = AtomeData(atome_client)
|
||||
|
||||
sensors = []
|
||||
sensors.append(AtomeSensor(data, LIVE_NAME, LIVE_TYPE))
|
||||
sensors.append(AtomeSensor(data, DAILY_NAME, DAILY_TYPE))
|
||||
sensors.append(AtomeSensor(data, WEEKLY_NAME, WEEKLY_TYPE))
|
||||
sensors.append(AtomeSensor(data, MONTHLY_NAME, MONTHLY_TYPE))
|
||||
sensors.append(AtomeSensor(data, YEARLY_NAME, YEARLY_TYPE))
|
||||
|
||||
add_entities(sensors, True)
|
||||
|
||||
|
||||
class AtomeData:
|
||||
"""Stores data retrieved from Neurio sensor."""
|
||||
|
||||
def __init__(self, client: AtomeClient):
|
||||
"""Initialize the data."""
|
||||
self.atome_client = client
|
||||
self._live_power = None
|
||||
self._subscribed_power = None
|
||||
self._is_connected = None
|
||||
self._day_usage = None
|
||||
self._day_price = None
|
||||
self._week_usage = None
|
||||
self._week_price = None
|
||||
self._month_usage = None
|
||||
self._month_price = None
|
||||
self._year_usage = None
|
||||
self._year_price = None
|
||||
|
||||
@property
|
||||
def live_power(self):
|
||||
"""Return latest active power value."""
|
||||
return self._live_power
|
||||
|
||||
@property
|
||||
def subscribed_power(self):
|
||||
"""Return latest active power value."""
|
||||
return self._subscribed_power
|
||||
|
||||
@property
|
||||
def is_connected(self):
|
||||
"""Return latest active power value."""
|
||||
return self._is_connected
|
||||
|
||||
@Throttle(LIVE_SCAN_INTERVAL)
|
||||
def update_live_usage(self):
|
||||
"""Return current power value."""
|
||||
try:
|
||||
values = self.atome_client.get_live()
|
||||
self._live_power = values["last"]
|
||||
self._subscribed_power = values["subscribed"]
|
||||
self._is_connected = values["isConnected"]
|
||||
_LOGGER.debug(
|
||||
"Updating Atome live data. Got: %d, isConnected: %s, subscribed: %d",
|
||||
self._live_power,
|
||||
self._is_connected,
|
||||
self._subscribed_power,
|
||||
)
|
||||
|
||||
except KeyError as error:
|
||||
_LOGGER.error("Missing last value in values: %s: %s", values, error)
|
||||
|
||||
@property
|
||||
def day_usage(self):
|
||||
"""Return latest daily usage value."""
|
||||
return self._day_usage
|
||||
|
||||
@property
|
||||
def day_price(self):
|
||||
"""Return latest daily usage value."""
|
||||
return self._day_price
|
||||
|
||||
@Throttle(DAILY_SCAN_INTERVAL)
|
||||
def update_day_usage(self):
|
||||
"""Return current daily power usage."""
|
||||
try:
|
||||
values = self.atome_client.get_consumption(DAILY_TYPE)
|
||||
self._day_usage = values["total"] / 1000
|
||||
self._day_price = values["price"]
|
||||
_LOGGER.debug("Updating Atome daily data. Got: %d.", self._day_usage)
|
||||
|
||||
except KeyError as error:
|
||||
_LOGGER.error("Missing last value in values: %s: %s", values, error)
|
||||
|
||||
@property
|
||||
def week_usage(self):
|
||||
"""Return latest weekly usage value."""
|
||||
return self._week_usage
|
||||
|
||||
@property
|
||||
def week_price(self):
|
||||
"""Return latest weekly usage value."""
|
||||
return self._week_price
|
||||
|
||||
@Throttle(WEEKLY_SCAN_INTERVAL)
|
||||
def update_week_usage(self):
|
||||
"""Return current weekly power usage."""
|
||||
try:
|
||||
values = self.atome_client.get_consumption(WEEKLY_TYPE)
|
||||
self._week_usage = values["total"] / 1000
|
||||
self._week_price = values["price"]
|
||||
_LOGGER.debug("Updating Atome weekly data. Got: %d.", self._week_usage)
|
||||
|
||||
except KeyError as error:
|
||||
_LOGGER.error("Missing last value in values: %s: %s", values, error)
|
||||
|
||||
@property
|
||||
def month_usage(self):
|
||||
"""Return latest monthly usage value."""
|
||||
return self._month_usage
|
||||
|
||||
@property
|
||||
def month_price(self):
|
||||
"""Return latest monthly usage value."""
|
||||
return self._month_price
|
||||
|
||||
@Throttle(MONTHLY_SCAN_INTERVAL)
|
||||
def update_month_usage(self):
|
||||
"""Return current monthly power usage."""
|
||||
try:
|
||||
values = self.atome_client.get_consumption(MONTHLY_TYPE)
|
||||
self._month_usage = values["total"] / 1000
|
||||
self._month_price = values["price"]
|
||||
_LOGGER.debug("Updating Atome monthly data. Got: %d.", self._month_usage)
|
||||
|
||||
except KeyError as error:
|
||||
_LOGGER.error("Missing last value in values: %s: %s", values, error)
|
||||
|
||||
@property
|
||||
def year_usage(self):
|
||||
"""Return latest yearly usage value."""
|
||||
return self._year_usage
|
||||
|
||||
@property
|
||||
def year_price(self):
|
||||
"""Return latest yearly usage value."""
|
||||
return self._year_price
|
||||
|
||||
@Throttle(YEARLY_SCAN_INTERVAL)
|
||||
def update_year_usage(self):
|
||||
"""Return current yearly power usage."""
|
||||
try:
|
||||
values = self.atome_client.get_consumption(YEARLY_TYPE)
|
||||
self._year_usage = values["total"] / 1000
|
||||
self._year_price = values["price"]
|
||||
_LOGGER.debug("Updating Atome yearly data. Got: %d.", self._year_usage)
|
||||
|
||||
except KeyError as error:
|
||||
_LOGGER.error("Missing last value in values: %s: %s", values, error)
|
||||
|
||||
|
||||
class AtomeSensor(Entity):
|
||||
"""Representation of a sensor entity for Atome."""
|
||||
|
||||
def __init__(self, data, name, sensor_type):
|
||||
"""Initialize the sensor."""
|
||||
self._name = name
|
||||
self._data = data
|
||||
self._state = None
|
||||
self._attributes = {}
|
||||
|
||||
self._sensor_type = sensor_type
|
||||
|
||||
if sensor_type == LIVE_TYPE:
|
||||
self._unit_of_measurement = POWER_WATT
|
||||
else:
|
||||
self._unit_of_measurement = ENERGY_KILO_WATT_HOUR
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the sensor."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the state of the sensor."""
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the state attributes."""
|
||||
return self._attributes
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
"""Return the unit of measurement."""
|
||||
return self._unit_of_measurement
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Icon to use in the frontend, if any."""
|
||||
return ICON
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the device class."""
|
||||
return DEVICE_CLASS_POWER
|
||||
|
||||
def update(self):
|
||||
"""Update device state."""
|
||||
update_function = getattr(self._data, f"update_{self._sensor_type}_usage")
|
||||
update_function()
|
||||
|
||||
if self._sensor_type == LIVE_TYPE:
|
||||
self._state = self._data.live_power
|
||||
self._attributes["subscribed_power"] = self._data.subscribed_power
|
||||
self._attributes["is_connected"] = self._data.is_connected
|
||||
else:
|
||||
self._state = getattr(self._data, f"{self._sensor_type}_usage")
|
||||
self._attributes["price"] = getattr(
|
||||
self._data, f"{self._sensor_type}_price"
|
||||
)
|
||||
@@ -73,4 +73,4 @@ class AugustCamera(Camera):
|
||||
@property
|
||||
def unique_id(self) -> str:
|
||||
"""Get the unique id of the camera."""
|
||||
return "{:s}_camera".format(self._doorbell.device_id)
|
||||
return f"{self._doorbell.device_id:s}_camera"
|
||||
|
||||
@@ -93,4 +93,4 @@ class AugustLock(LockDevice):
|
||||
@property
|
||||
def unique_id(self) -> str:
|
||||
"""Get the unique id of the lock."""
|
||||
return "{:s}_lock".format(self._lock.device_id)
|
||||
return f"{self._lock.device_id:s}_lock"
|
||||
|
||||
@@ -64,7 +64,7 @@ class AuroraSensor(BinarySensorDevice):
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the sensor."""
|
||||
return "{}".format(self._name)
|
||||
return f"{self._name}"
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
|
||||
@@ -49,7 +49,7 @@ class AuroraABBSolarPVMonitorSensor(Entity):
|
||||
|
||||
def __init__(self, client, name, typename):
|
||||
"""Initialize the sensor."""
|
||||
self._name = "{} {}".format(name, typename)
|
||||
self._name = f"{name} {typename}"
|
||||
self.client = client
|
||||
self._state = None
|
||||
|
||||
|
||||
@@ -16,9 +16,13 @@
|
||||
"description": "Se ha enviado una contrase\u00f1a \u00fanica a trav\u00e9s de **notify.{notify_service}**. Por favor ingr\u00e9selo a continuaci\u00f3n:",
|
||||
"title": "Verificar la configuracion"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "Notificar contrase\u00f1a de un solo uso"
|
||||
},
|
||||
"totp": {
|
||||
"error": {
|
||||
"invalid_code": "C\u00f3digo no v\u00e1lido, por favor vuelva a intentarlo. Si recibe este error constantemente, aseg\u00farese de que el reloj de su sistema Home Assistant sea exacto."
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"description": "Para activar la autenticaci\u00f3n de dos factores utilizando contrase\u00f1as de un solo uso basadas en el tiempo, escanee el c\u00f3digo QR con su aplicaci\u00f3n de autenticaci\u00f3n. Si no tiene uno, le recomendamos [Autenticador de Google] (https://support.google.com/accounts/answer/1066447) o [Authy] (https://authy.com/). \n\n {qr_code} \n \n Despu\u00e9s de escanear el c\u00f3digo, ingrese el c\u00f3digo de seis d\u00edgitos de su aplicaci\u00f3n para verificar la configuraci\u00f3n. Si tiene problemas para escanear el c\u00f3digo QR, realice una configuraci\u00f3n manual con el c\u00f3digo ** ` {code} ` **.",
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"step": {
|
||||
"init": {
|
||||
"description": "Selezionare uno dei servizi di notifica:",
|
||||
"title": "Imposta la password one-time fornita dal componente di notifica"
|
||||
"title": "Imposta la password monouso fornita dal componente di notifica"
|
||||
},
|
||||
"setup": {
|
||||
"description": "\u00c8 stata inviata una password monouso tramite **notify.{notify_service}**. Per favore, inseriscila qui sotto:",
|
||||
@@ -25,7 +25,7 @@
|
||||
},
|
||||
"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} ` **.",
|
||||
"description": "Per attivare l'autenticazione a due fattori utilizzando le 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 \nDopo la scansione, 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 il codice ** ` {code} ` **.",
|
||||
"title": "Imposta l'autenticazione a due fattori usando TOTP"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
},
|
||||
"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 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.",
|
||||
"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 [\uad6c\uae00 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 2\ub2e8\uacc4 \uc778\uc99d \uad6c\uc131"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"title": "Skonfiguruj has\u0142o jednorazowe dostarczone przez komponent powiadomie\u0144"
|
||||
},
|
||||
"setup": {
|
||||
"description": "Has\u0142o jednorazowe zosta\u0142o wys\u0142ane przez **notify.{notify_service}**. Wpisz je poni\u017cej:",
|
||||
"description": "Has\u0142o jednorazowe zosta\u0142o wys\u0142ane przez **notify.{notify_service}**. Wprowad\u017a je poni\u017cej:",
|
||||
"title": "Sprawd\u017a konfiguracj\u0119"
|
||||
}
|
||||
},
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user