mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 21:27:38 +00:00
commit
884591a105
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
|
||||
|
@ -3,16 +3,14 @@
|
||||
"name": "Home Assistant Dev",
|
||||
"context": "..",
|
||||
"dockerFile": "../Dockerfile.dev",
|
||||
"postCreateCommand": "pip3 install -e .",
|
||||
"postCreateCommand": "mkdir -p config && pip3 install -e .",
|
||||
"appPort": 8123,
|
||||
"runArgs": [
|
||||
"-e",
|
||||
"GIT_EDITOR=\"code --wait\""
|
||||
],
|
||||
"runArgs": ["-e", "GIT_EDITOR=\"code --wait\""],
|
||||
"extensions": [
|
||||
"ms-python.python",
|
||||
"ms-azure-devops.azure-pipelines",
|
||||
"redhat.vscode-yaml"
|
||||
"redhat.vscode-yaml",
|
||||
"esbenp.prettier-vscode"
|
||||
],
|
||||
"settings": {
|
||||
"python.pythonPath": "/usr/local/bin/python",
|
||||
|
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
|
||||
|
||||
|
27
.vscode/tasks.json
vendored
27
.vscode/tasks.json
vendored
@ -7,7 +7,7 @@
|
||||
"command": "hass -c ./config",
|
||||
"group": {
|
||||
"kind": "test",
|
||||
"isDefault": true,
|
||||
"isDefault": true
|
||||
},
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
@ -19,9 +19,10 @@
|
||||
"label": "Pytest",
|
||||
"type": "shell",
|
||||
"command": "pytest --timeout=10 tests",
|
||||
"dependsOn": ["Install all Test Requirements"],
|
||||
"group": {
|
||||
"kind": "test",
|
||||
"isDefault": true,
|
||||
"isDefault": true
|
||||
},
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
@ -35,7 +36,7 @@
|
||||
"command": "flake8 homeassistant tests",
|
||||
"group": {
|
||||
"kind": "test",
|
||||
"isDefault": true,
|
||||
"isDefault": true
|
||||
},
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
@ -47,12 +48,10 @@
|
||||
"label": "Pylint",
|
||||
"type": "shell",
|
||||
"command": "pylint homeassistant",
|
||||
"dependsOn": [
|
||||
"Install all Requirements"
|
||||
],
|
||||
"dependsOn": ["Install all Requirements"],
|
||||
"group": {
|
||||
"kind": "test",
|
||||
"isDefault": true,
|
||||
"isDefault": true
|
||||
},
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
@ -87,6 +86,20 @@
|
||||
"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
|
||||
|
||||
|
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:
|
||||
@ -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),
|
||||
|
@ -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'
|
@ -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)
|
||||
|
||||
|
@ -60,7 +60,7 @@ async def async_send_changereport_message(
|
||||
"""
|
||||
token = await config.async_get_access_token()
|
||||
|
||||
headers = {"Authorization": "Bearer {}".format(token)}
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
|
||||
endpoint = alexa_entity.alexa_id()
|
||||
|
||||
@ -125,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 = []
|
||||
|
||||
@ -155,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
|
||||
|
@ -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"
|
||||
}
|
||||
},
|
||||
|
@ -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,7 +431,9 @@ 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.
|
||||
# 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.
|
||||
@ -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,7 +510,9 @@ 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.
|
||||
# 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.
|
||||
@ -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/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/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
|
||||
|
||||
|
@ -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"
|
||||
}
|
||||
},
|
||||
|
@ -34,7 +34,7 @@ async def async_setup(hass):
|
||||
"""Create a setup flow. handler is a mfa module."""
|
||||
mfa_module = hass.auth.get_auth_mfa_module(handler)
|
||||
if mfa_module is None:
|
||||
raise ValueError("Mfa module {} is not found".format(handler))
|
||||
raise ValueError(f"Mfa module {handler} is not found")
|
||||
|
||||
user_id = data.pop("user_id")
|
||||
return await mfa_module.async_setup_flow(user_id)
|
||||
@ -80,9 +80,7 @@ def websocket_setup_mfa(
|
||||
if mfa_module is None:
|
||||
connection.send_message(
|
||||
websocket_api.error_message(
|
||||
msg["id"],
|
||||
"no_module",
|
||||
"MFA module {} is not found".format(mfa_module_id),
|
||||
msg["id"], "no_module", f"MFA module {mfa_module_id} is not found"
|
||||
)
|
||||
)
|
||||
return
|
||||
@ -117,7 +115,7 @@ def websocket_depose_mfa(
|
||||
websocket_api.error_message(
|
||||
msg["id"],
|
||||
"disable_failed",
|
||||
"Cannot disable MFA Module {}: {}".format(mfa_module_id, err),
|
||||
f"Cannot disable MFA Module {mfa_module_id}: {err}",
|
||||
)
|
||||
)
|
||||
return
|
||||
|
@ -6,6 +6,9 @@ import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.device_automation.exceptions import (
|
||||
InvalidDeviceAutomationConfig,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
ATTR_NAME,
|
||||
@ -143,7 +146,7 @@ async def async_setup(hass, config):
|
||||
async def turn_onoff_service_handler(service_call):
|
||||
"""Handle automation turn on/off service calls."""
|
||||
tasks = []
|
||||
method = "async_{}".format(service_call.service)
|
||||
method = f"async_{service_call.service}"
|
||||
for entity in await component.async_extract_from_service(service_call):
|
||||
tasks.append(getattr(entity, method)())
|
||||
|
||||
@ -378,7 +381,7 @@ async def _async_process_config(hass, config, component):
|
||||
|
||||
for list_no, config_block in enumerate(conf):
|
||||
automation_id = config_block.get(CONF_ID)
|
||||
name = config_block.get(CONF_ALIAS) or "{} {}".format(config_key, list_no)
|
||||
name = config_block.get(CONF_ALIAS) or f"{config_key} {list_no}"
|
||||
|
||||
hidden = config_block[CONF_HIDE_ENTITY]
|
||||
initial_state = config_block.get(CONF_INITIAL_STATE)
|
||||
@ -386,7 +389,7 @@ async def _async_process_config(hass, config, component):
|
||||
action = _async_get_action(hass, config_block.get(CONF_ACTION, {}), name)
|
||||
|
||||
if CONF_CONDITION in config_block:
|
||||
cond_func = _async_process_if(hass, config, config_block)
|
||||
cond_func = await _async_process_if(hass, config, config_block)
|
||||
|
||||
if cond_func is None:
|
||||
continue
|
||||
@ -431,20 +434,20 @@ def _async_get_action(hass, config, name):
|
||||
await script_obj.async_run(variables, context)
|
||||
except Exception as err: # pylint: disable=broad-except
|
||||
script_obj.async_log_exception(
|
||||
_LOGGER, "Error while executing automation {}".format(entity_id), err
|
||||
_LOGGER, f"Error while executing automation {entity_id}", err
|
||||
)
|
||||
|
||||
return action
|
||||
|
||||
|
||||
def _async_process_if(hass, config, p_config):
|
||||
async def _async_process_if(hass, config, p_config):
|
||||
"""Process if checks."""
|
||||
if_configs = p_config.get(CONF_CONDITION)
|
||||
|
||||
checks = []
|
||||
for if_config in if_configs:
|
||||
try:
|
||||
checks.append(condition.async_from_config(if_config, False))
|
||||
checks.append(await condition.async_from_config(hass, if_config, False))
|
||||
except HomeAssistantError as ex:
|
||||
_LOGGER.warning("Invalid condition: %s", ex)
|
||||
return None
|
||||
@ -467,7 +470,10 @@ async def _async_process_trigger(hass, config, trigger_configs, name, action):
|
||||
for conf in trigger_configs:
|
||||
platform = importlib.import_module(".{}".format(conf[CONF_PLATFORM]), __name__)
|
||||
|
||||
try:
|
||||
remove = await platform.async_trigger(hass, conf, action, info)
|
||||
except InvalidDeviceAutomationConfig:
|
||||
remove = False
|
||||
|
||||
if not remove:
|
||||
_LOGGER.error("Error setting up trigger %s", name)
|
||||
|
@ -4,6 +4,7 @@
|
||||
"documentation": "https://www.home-assistant.io/components/automation",
|
||||
"requirements": [],
|
||||
"dependencies": [
|
||||
"device_automation",
|
||||
"group",
|
||||
"webhook"
|
||||
],
|
||||
|
@ -150,7 +150,7 @@ class AwairSensor(Entity):
|
||||
"""Initialize the sensor."""
|
||||
self._uuid = device[CONF_UUID]
|
||||
self._device_class = SENSOR_TYPES[sensor_type]["device_class"]
|
||||
self._name = "Awair {}".format(self._device_class)
|
||||
self._name = f"Awair {self._device_class}"
|
||||
unit = SENSOR_TYPES[sensor_type]["unit_of_measurement"]
|
||||
self._unit_of_measurement = unit
|
||||
self._data = data
|
||||
@ -202,7 +202,7 @@ class AwairSensor(Entity):
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return the unique id of this entity."""
|
||||
return "{}_{}".format(self._uuid, self._type)
|
||||
return f"{self._uuid}_{self._type}"
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
|
@ -3,6 +3,7 @@
|
||||
"abort": {
|
||||
"already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato",
|
||||
"bad_config_file": "Dati errati dal file di configurazione",
|
||||
"link_local_address": "Gli indirizzi locali di collegamento non sono supportati",
|
||||
"not_axis_device": "Il dispositivo rilevato non \u00e8 un dispositivo Axis"
|
||||
},
|
||||
"error": {
|
||||
|
@ -72,7 +72,7 @@ class AxisEventBase(AxisEntityBase):
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the event."""
|
||||
return "{} {} {}".format(self.device.name, self.event.TYPE, self.event.id)
|
||||
return f"{self.device.name} {self.event.TYPE} {self.event.id}"
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
@ -82,4 +82,4 @@ class AxisEventBase(AxisEntityBase):
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique identifier for this device."""
|
||||
return "{}-{}-{}".format(self.device.serial, self.event.topic, self.event.id)
|
||||
return f"{self.device.serial}-{self.event.topic}-{self.event.id}"
|
||||
|
@ -92,4 +92,4 @@ class AxisCamera(AxisEntityBase, MjpegCamera):
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique identifier for this device."""
|
||||
return "{}-camera".format(self.device.serial)
|
||||
return f"{self.device.serial}-camera"
|
||||
|
@ -56,8 +56,7 @@ def configured_devices(hass):
|
||||
}
|
||||
|
||||
|
||||
@config_entries.HANDLERS.register(DOMAIN)
|
||||
class AxisFlowHandler(config_entries.ConfigFlow):
|
||||
class AxisFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a Axis config flow."""
|
||||
|
||||
VERSION = 1
|
||||
@ -138,9 +137,9 @@ class AxisFlowHandler(config_entries.ConfigFlow):
|
||||
if entry.data[CONF_MODEL] == self.model
|
||||
]
|
||||
|
||||
self.name = "{}".format(self.model)
|
||||
self.name = f"{self.model}"
|
||||
for idx in range(len(same_model) + 1):
|
||||
self.name = "{} {}".format(self.model, idx)
|
||||
self.name = f"{self.model} {idx}"
|
||||
if self.name not in same_model:
|
||||
break
|
||||
|
||||
@ -151,7 +150,7 @@ class AxisFlowHandler(config_entries.ConfigFlow):
|
||||
CONF_MODEL: self.model,
|
||||
}
|
||||
|
||||
title = "{} - {}".format(self.model, self.serial_number)
|
||||
title = f"{self.model} - {self.serial_number}"
|
||||
return self.async_create_entry(title=title, data=data)
|
||||
|
||||
async def _update_entry(self, entry, host):
|
||||
|
@ -65,7 +65,7 @@ class AxisNetworkDevice:
|
||||
connections={(CONNECTION_NETWORK_MAC, self.serial)},
|
||||
identifiers={(DOMAIN, self.serial)},
|
||||
manufacturer="Axis Communications AB",
|
||||
model="{} {}".format(self.model, self.product_type),
|
||||
model=f"{self.model} {self.product_type}",
|
||||
name=self.name,
|
||||
sw_version=self.fw_version,
|
||||
)
|
||||
@ -115,7 +115,7 @@ class AxisNetworkDevice:
|
||||
@property
|
||||
def event_new_address(self):
|
||||
"""Device specific event to signal new device address."""
|
||||
return "axis_new_address_{}".format(self.serial)
|
||||
return f"axis_new_address_{self.serial}"
|
||||
|
||||
@staticmethod
|
||||
async def async_new_address_callback(hass, entry):
|
||||
@ -131,7 +131,7 @@ class AxisNetworkDevice:
|
||||
@property
|
||||
def event_reachable(self):
|
||||
"""Device specific event to signal a change in connection status."""
|
||||
return "axis_reachable_{}".format(self.serial)
|
||||
return f"axis_reachable_{self.serial}"
|
||||
|
||||
@callback
|
||||
def async_connection_status_callback(self, status):
|
||||
@ -149,7 +149,7 @@ class AxisNetworkDevice:
|
||||
@property
|
||||
def event_new_sensor(self):
|
||||
"""Device specific event to signal new sensor available."""
|
||||
return "axis_add_sensor_{}".format(self.serial)
|
||||
return f"axis_add_sensor_{self.serial}"
|
||||
|
||||
@callback
|
||||
def async_event_callback(self, action, event_id):
|
||||
|
@ -2,6 +2,7 @@
|
||||
from collections import namedtuple
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
from typing import List
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
@ -41,12 +42,11 @@ class BboxDeviceScanner(DeviceScanner):
|
||||
|
||||
def __init__(self, config):
|
||||
"""Get host from config."""
|
||||
from typing import List # noqa: pylint: disable=unused-import
|
||||
|
||||
self.host = config[CONF_HOST]
|
||||
|
||||
"""Initialize the scanner."""
|
||||
self.last_results = [] # type: List[Device]
|
||||
self.last_results: List[Device] = []
|
||||
|
||||
self.success_init = self._update_info()
|
||||
_LOGGER.info("Scanner initialized")
|
||||
|
@ -13,7 +13,7 @@ from homeassistant.util import Throttle
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
BANDWIDTH_MEGABITS_SECONDS = "Mb/s" # type: str
|
||||
BANDWIDTH_MEGABITS_SECONDS = "Mb/s"
|
||||
|
||||
ATTRIBUTION = "Powered by Bouygues Telecom"
|
||||
|
||||
@ -91,7 +91,7 @@ class BboxSensor(Entity):
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the sensor."""
|
||||
return "{} {}".format(self.client_name, self._name)
|
||||
return f"{self.client_name} {self._name}"
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
|
1
homeassistant/components/beewi_smartclim/__init__.py
Normal file
1
homeassistant/components/beewi_smartclim/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
"""The beewi_smartclim component."""
|
12
homeassistant/components/beewi_smartclim/manifest.json
Normal file
12
homeassistant/components/beewi_smartclim/manifest.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"domain": "beewi_smartclim",
|
||||
"name": "BeeWi SmartClim BLE sensor",
|
||||
"documentation": "https://www.home-assistant.io/components/beewi_smartclim",
|
||||
"requirements": [
|
||||
"beewi_smartclim==0.0.7"
|
||||
],
|
||||
"dependencies": [],
|
||||
"codeowners": [
|
||||
"@alemuro"
|
||||
]
|
||||
}
|
108
homeassistant/components/beewi_smartclim/sensor.py
Normal file
108
homeassistant/components/beewi_smartclim/sensor.py
Normal file
@ -0,0 +1,108 @@
|
||||
"""Platform for beewi_smartclim integration."""
|
||||
import logging
|
||||
|
||||
from beewi_smartclim import BeewiSmartClimPoller
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.sensor import PLATFORM_SCHEMA
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.const import (
|
||||
CONF_NAME,
|
||||
CONF_MAC,
|
||||
TEMP_CELSIUS,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
DEVICE_CLASS_BATTERY,
|
||||
)
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# Default values
|
||||
DEFAULT_NAME = "BeeWi SmartClim"
|
||||
|
||||
# Sensor config
|
||||
SENSOR_TYPES = [
|
||||
[DEVICE_CLASS_TEMPERATURE, "Temperature", TEMP_CELSIUS],
|
||||
[DEVICE_CLASS_HUMIDITY, "Humidity", "%"],
|
||||
[DEVICE_CLASS_BATTERY, "Battery", "%"],
|
||||
]
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||
{
|
||||
vol.Required(CONF_MAC): cv.string,
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up the beewi_smartclim platform."""
|
||||
|
||||
mac = config[CONF_MAC]
|
||||
prefix = config[CONF_NAME]
|
||||
poller = BeewiSmartClimPoller(mac)
|
||||
|
||||
sensors = []
|
||||
|
||||
for sensor_type in SENSOR_TYPES:
|
||||
device = sensor_type[0]
|
||||
name = sensor_type[1]
|
||||
unit = sensor_type[2]
|
||||
# `prefix` is the name configured by the user for the sensor, we're appending
|
||||
# the device type at the end of the name (garden -> garden temperature)
|
||||
if prefix:
|
||||
name = f"{prefix} {name}"
|
||||
|
||||
sensors.append(BeewiSmartclimSensor(poller, name, mac, device, unit))
|
||||
|
||||
add_entities(sensors)
|
||||
|
||||
|
||||
class BeewiSmartclimSensor(Entity):
|
||||
"""Representation of a Sensor."""
|
||||
|
||||
def __init__(self, poller, name, mac, device, unit):
|
||||
"""Initialize the sensor."""
|
||||
self._poller = poller
|
||||
self._name = name
|
||||
self._mac = mac
|
||||
self._device = device
|
||||
self._unit = unit
|
||||
self._state = None
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the sensor."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the state of the sensor. State is returned in Celsius."""
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Device class of this entity."""
|
||||
return self._device
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique, HASS-friendly identifier for this entity."""
|
||||
return f"{self._mac}_{self._device}"
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
"""Return the unit of measurement."""
|
||||
return self._unit
|
||||
|
||||
def update(self):
|
||||
"""Fetch new state data from the poller."""
|
||||
self._poller.update_sensor()
|
||||
self._state = None
|
||||
if self._device == DEVICE_CLASS_TEMPERATURE:
|
||||
self._state = self._poller.get_temperature()
|
||||
if self._device == DEVICE_CLASS_HUMIDITY:
|
||||
self._state = self._poller.get_humidity()
|
||||
if self._device == DEVICE_CLASS_BATTERY:
|
||||
self._state = self._poller.get_battery()
|
0
homeassistant/components/bizkaibus/sensor.py
Executable file → Normal file
0
homeassistant/components/bizkaibus/sensor.py
Executable file → Normal file
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user