Merge pull request #26710 from home-assistant/rc

0.99.0
This commit is contained in:
Paulus Schoutsen 2019-09-18 15:29:01 -07:00 committed by GitHub
commit 884591a105
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1177 changed files with 24522 additions and 6471 deletions

View File

@ -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

View File

@ -1,35 +1,33 @@
// See https://aka.ms/vscode-remote/devcontainer.json for format details.
{
"name": "Home Assistant Dev",
"context": "..",
"dockerFile": "../Dockerfile.dev",
"postCreateCommand": "pip3 install -e .",
"appPort": 8123,
"runArgs": [
"-e",
"GIT_EDITOR=\"code --wait\""
],
"extensions": [
"ms-python.python",
"ms-azure-devops.azure-pipelines",
"redhat.vscode-yaml"
],
"settings": {
"python.pythonPath": "/usr/local/bin/python",
"python.linting.pylintEnabled": true,
"python.linting.enabled": true,
"python.formatting.provider": "black",
"editor.formatOnPaste": false,
"editor.formatOnSave": true,
"editor.formatOnType": true,
"files.trimTrailingWhitespace": true,
"terminal.integrated.shell.linux": "/bin/bash",
"yaml.customTags": [
"!secret scalar",
"!include_dir_named scalar",
"!include_dir_list scalar",
"!include_dir_merge_list scalar",
"!include_dir_merge_named scalar"
]
}
}
"name": "Home Assistant Dev",
"context": "..",
"dockerFile": "../Dockerfile.dev",
"postCreateCommand": "mkdir -p config && pip3 install -e .",
"appPort": 8123,
"runArgs": ["-e", "GIT_EDITOR=\"code --wait\""],
"extensions": [
"ms-python.python",
"ms-azure-devops.azure-pipelines",
"redhat.vscode-yaml",
"esbenp.prettier-vscode"
],
"settings": {
"python.pythonPath": "/usr/local/bin/python",
"python.linting.pylintEnabled": true,
"python.linting.enabled": true,
"python.formatting.provider": "black",
"editor.formatOnPaste": false,
"editor.formatOnSave": true,
"editor.formatOnType": true,
"files.trimTrailingWhitespace": true,
"terminal.integrated.shell.linux": "/bin/bash",
"yaml.customTags": [
"!secret scalar",
"!include_dir_named scalar",
"!include_dir_list scalar",
"!include_dir_merge_list scalar",
"!include_dir_merge_named scalar"
]
}
}

1
.gitignore vendored
View File

@ -64,6 +64,7 @@ nosetests.xml
htmlcov/
test-reports/
test-results.xml
test-output.xml
# Translations
*.mo

View File

@ -16,18 +16,14 @@ addons:
matrix:
fast_finish: true
include:
- python: "3.6.0"
- python: "3.6.1"
env: TOXENV=lint
dist: trusty
- python: "3.6.0"
- python: "3.6.1"
env: TOXENV=pylint
dist: trusty
- python: "3.6.0"
- python: "3.6.1"
env: TOXENV=typing
dist: trusty
- python: "3.6.0"
- python: "3.6.1"
env: TOXENV=py36
dist: trusty
- python: "3.7"
env: TOXENV=py37

193
.vscode/tasks.json vendored
View File

@ -1,92 +1,105 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "Preview",
"type": "shell",
"command": "hass -c ./config",
"group": {
"kind": "test",
"isDefault": true,
},
"presentation": {
"reveal": "always",
"panel": "new"
},
"problemMatcher": []
},
{
"label": "Pytest",
"type": "shell",
"command": "pytest --timeout=10 tests",
"group": {
"kind": "test",
"isDefault": true,
},
"presentation": {
"reveal": "always",
"panel": "new"
},
"problemMatcher": []
},
{
"label": "Flake8",
"type": "shell",
"command": "flake8 homeassistant tests",
"group": {
"kind": "test",
"isDefault": true,
},
"presentation": {
"reveal": "always",
"panel": "new"
},
"problemMatcher": []
},
{
"label": "Pylint",
"type": "shell",
"command": "pylint homeassistant",
"dependsOn": [
"Install all Requirements"
],
"group": {
"kind": "test",
"isDefault": true,
},
"presentation": {
"reveal": "always",
"panel": "new"
},
"problemMatcher": []
},
{
"label": "Generate Requirements",
"type": "shell",
"command": "./script/gen_requirements_all.py",
"group": {
"kind": "build",
"isDefault": true
},
"presentation": {
"reveal": "always",
"panel": "new"
},
"problemMatcher": []
},
{
"label": "Install all Requirements",
"type": "shell",
"command": "pip3 install -r requirements_all.txt -c homeassistant/package_constraints.txt",
"group": {
"kind": "build",
"isDefault": true
},
"presentation": {
"reveal": "always",
"panel": "new"
},
"problemMatcher": []
}
]
"version": "2.0.0",
"tasks": [
{
"label": "Preview",
"type": "shell",
"command": "hass -c ./config",
"group": {
"kind": "test",
"isDefault": true
},
"presentation": {
"reveal": "always",
"panel": "new"
},
"problemMatcher": []
},
{
"label": "Pytest",
"type": "shell",
"command": "pytest --timeout=10 tests",
"dependsOn": ["Install all Test Requirements"],
"group": {
"kind": "test",
"isDefault": true
},
"presentation": {
"reveal": "always",
"panel": "new"
},
"problemMatcher": []
},
{
"label": "Flake8",
"type": "shell",
"command": "flake8 homeassistant tests",
"group": {
"kind": "test",
"isDefault": true
},
"presentation": {
"reveal": "always",
"panel": "new"
},
"problemMatcher": []
},
{
"label": "Pylint",
"type": "shell",
"command": "pylint homeassistant",
"dependsOn": ["Install all Requirements"],
"group": {
"kind": "test",
"isDefault": true
},
"presentation": {
"reveal": "always",
"panel": "new"
},
"problemMatcher": []
},
{
"label": "Generate Requirements",
"type": "shell",
"command": "./script/gen_requirements_all.py",
"group": {
"kind": "build",
"isDefault": true
},
"presentation": {
"reveal": "always",
"panel": "new"
},
"problemMatcher": []
},
{
"label": "Install all Requirements",
"type": "shell",
"command": "pip3 install -r requirements_all.txt -c homeassistant/package_constraints.txt",
"group": {
"kind": "build",
"isDefault": true
},
"presentation": {
"reveal": "always",
"panel": "new"
},
"problemMatcher": []
},
{
"label": "Install all Test Requirements",
"type": "shell",
"command": "pip3 install -r requirements_test_all.txt -c homeassistant/package_constraints.txt",
"group": {
"kind": "build",
"isDefault": true
},
"presentation": {
"reveal": "always",
"panel": "new"
},
"problemMatcher": []
}
]
}

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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

View 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'

View File

@ -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'

View File

@ -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

View File

@ -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,

View File

@ -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()

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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",

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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),

View File

@ -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"
}
}

View File

@ -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"
}
},

View File

@ -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):

View File

@ -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(
{

View File

@ -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):

View File

@ -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)

View File

@ -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'

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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 = []

View File

@ -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

View File

@ -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"
}

View File

@ -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"
}
},

View File

@ -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()

View File

@ -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": [

View File

@ -13,6 +13,7 @@
},
"title": "Inserisci i tuoi dati"
}
}
},
"title": "PWS ambientale"
}
}

View File

@ -11,7 +11,7 @@
"api_key": "Klucz API",
"app_key": "Klucz aplikacji"
},
"title": "Wprowad\u017a swoje dane"
"title": "Wprowad\u017a dane"
}
},
"title": "Ambient PWS"

View File

@ -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."""

View File

@ -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

View File

@ -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

View File

@ -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):

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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"]

View File

@ -392,7 +392,7 @@ class ADBDevice(MediaPlayerDevice):
"""Send an ADB command to an Android TV / Fire TV device."""
key = self._keys.get(cmd)
if key:
self.aftv.adb_shell("input keyevent {}".format(key))
self.aftv.adb_shell(f"input keyevent {key}")
self._adb_response = None
self.schedule_update_ha_state()
return
@ -431,8 +431,10 @@ class AndroidTVDevice(ADBDevice):
# Try to connect
self._available = self.aftv.connect(always_log_errors=False)
# To be safe, wait until the next update to run ADB commands.
return
# To be safe, wait until the next update to run ADB commands if
# using the Python ADB implementation.
if not self.aftv.adb_server_ip:
return
# If the ADB connection is not intact, don't update.
if not self._available:
@ -443,7 +445,9 @@ class AndroidTVDevice(ADBDevice):
self.aftv.update()
)
self._state = ANDROIDTV_STATES[state]
self._state = ANDROIDTV_STATES.get(state)
if self._state is None:
self._available = False
@property
def is_volume_muted(self):
@ -506,8 +510,10 @@ class FireTVDevice(ADBDevice):
# Try to connect
self._available = self.aftv.connect(always_log_errors=False)
# To be safe, wait until the next update to run ADB commands.
return
# To be safe, wait until the next update to run ADB commands if
# using the Python ADB implementation.
if not self.aftv.adb_server_ip:
return
# If the ADB connection is not intact, don't update.
if not self._available:
@ -518,7 +524,9 @@ class FireTVDevice(ADBDevice):
self._get_sources
)
self._state = ANDROIDTV_STATES[state]
self._state = ANDROIDTV_STATES.get(state)
if self._state is None:
self._available = False
@property
def source(self):

View File

@ -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

View File

@ -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
)

View File

@ -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")

View File

@ -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": []

View File

@ -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):

View File

@ -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:

View File

@ -0,0 +1,5 @@
{
"config": {
"title": "Arcam FMJ"
}
}

View File

@ -0,0 +1,5 @@
{
"config": {
"title": "Arcam FMJ"
}
}

View File

@ -0,0 +1,5 @@
{
"config": {
"title": "Arcam FMJ"
}
}

View File

@ -0,0 +1,5 @@
{
"config": {
"title": "Arcam FMJ"
}
}

View File

@ -0,0 +1,5 @@
{
"config": {
"title": "Arcam FMJ"
}
}

View File

@ -0,0 +1,5 @@
{
"config": {
"title": "Arcam FMJ"
}
}

View File

@ -0,0 +1,5 @@
{
"config": {
"title": "Arcam FMJ"
}
}

View File

@ -0,0 +1,5 @@
{
"config": {
"title": "Arcam FMJ"
}
}

View File

@ -0,0 +1,5 @@
{
"config": {
"title": "Arcam FMJ"
}
}

View File

@ -0,0 +1,5 @@
{
"config": {
"title": "Arcam FMJ"
}
}

View File

@ -0,0 +1,5 @@
{
"config": {
"title": "Arcam FMJ"
}
}

View File

@ -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"

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -0,0 +1 @@
"""Support for Atome devices connected to a Linky Energy Meter."""

View 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"]
}

View 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"
)

View File

@ -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"

View File

@ -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"

View File

@ -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):

View File

@ -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

View File

@ -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"
}
},

View File

@ -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"
}
},

View File

@ -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"
}
},

View File

@ -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

View File

@ -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__)
remove = await platform.async_trigger(hass, conf, action, info)
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)

View File

@ -4,6 +4,7 @@
"documentation": "https://www.home-assistant.io/components/automation",
"requirements": [],
"dependencies": [
"device_automation",
"group",
"webhook"
],

View File

@ -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):

View File

@ -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": {

View File

@ -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}"

View File

@ -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"

View File

@ -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):

View File

@ -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):

View File

@ -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")

View File

@ -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):

View File

@ -0,0 +1 @@
"""The beewi_smartclim component."""

View 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"
]
}

View 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
View File

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