Merge pull request #45911 from home-assistant/rc

This commit is contained in:
Franck Nijhof 2021-02-03 17:51:34 +01:00 committed by GitHub
commit 1eff2ab22b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2873 changed files with 73532 additions and 14615 deletions

View File

@ -29,6 +29,8 @@ omit =
homeassistant/components/agent_dvr/camera.py
homeassistant/components/agent_dvr/const.py
homeassistant/components/agent_dvr/helpers.py
homeassistant/components/airnow/__init__.py
homeassistant/components/airnow/sensor.py
homeassistant/components/airvisual/__init__.py
homeassistant/components/airvisual/air_quality.py
homeassistant/components/airvisual/sensor.py
@ -142,7 +144,7 @@ omit =
homeassistant/components/co2signal/*
homeassistant/components/coinbase/*
homeassistant/components/comed_hourly_pricing/sensor.py
homeassistant/components/comfoconnect/*
homeassistant/components/comfoconnect/fan.py
homeassistant/components/concord232/alarm_control_panel.py
homeassistant/components/concord232/binary_sensor.py
homeassistant/components/control4/__init__.py
@ -213,7 +215,11 @@ omit =
homeassistant/components/ecobee/notify.py
homeassistant/components/ecobee/sensor.py
homeassistant/components/ecobee/weather.py
homeassistant/components/econet/*
homeassistant/components/econet/__init__.py
homeassistant/components/econet/binary_sensor.py
homeassistant/components/econet/const.py
homeassistant/components/econet/sensor.py
homeassistant/components/econet/water_heater.py
homeassistant/components/ecovacs/*
homeassistant/components/edl21/*
homeassistant/components/eddystone_temperature/sensor.py
@ -298,8 +304,8 @@ omit =
homeassistant/components/folder_watcher/*
homeassistant/components/foobot/sensor.py
homeassistant/components/fortios/device_tracker.py
homeassistant/components/foscam/__init__.py
homeassistant/components/foscam/camera.py
homeassistant/components/foscam/const.py
homeassistant/components/foursquare/*
homeassistant/components/free_mobile/notify.py
homeassistant/components/freebox/__init__.py
@ -308,6 +314,9 @@ omit =
homeassistant/components/freebox/sensor.py
homeassistant/components/freebox/switch.py
homeassistant/components/fritz/device_tracker.py
homeassistant/components/fritzbox_callmonitor/__init__.py
homeassistant/components/fritzbox_callmonitor/const.py
homeassistant/components/fritzbox_callmonitor/base.py
homeassistant/components/fritzbox_callmonitor/sensor.py
homeassistant/components/fritzbox_netmonitor/sensor.py
homeassistant/components/fronius/sensor.py
@ -356,7 +365,10 @@ omit =
homeassistant/components/hangouts/hangouts_bot.py
homeassistant/components/hangouts/hangups_utils.py
homeassistant/components/harman_kardon_avr/media_player.py
homeassistant/components/harmony/*
homeassistant/components/harmony/const.py
homeassistant/components/harmony/data.py
homeassistant/components/harmony/remote.py
homeassistant/components/harmony/util.py
homeassistant/components/haveibeenpwned/sensor.py
homeassistant/components/hdmi_cec/*
homeassistant/components/heatmiser/climate.py
@ -578,13 +590,7 @@ omit =
homeassistant/components/neato/vacuum.py
homeassistant/components/nederlandse_spoorwegen/sensor.py
homeassistant/components/nello/lock.py
homeassistant/components/nest/__init__.py
homeassistant/components/nest/api.py
homeassistant/components/nest/binary_sensor.py
homeassistant/components/nest/camera.py
homeassistant/components/nest/climate.py
homeassistant/components/nest/legacy/*
homeassistant/components/nest/sensor.py
homeassistant/components/netatmo/__init__.py
homeassistant/components/netatmo/api.py
homeassistant/components/netatmo/camera.py
@ -629,6 +635,11 @@ omit =
homeassistant/components/omnilogic/__init__.py
homeassistant/components/omnilogic/common.py
homeassistant/components/omnilogic/sensor.py
homeassistant/components/ondilo_ico/__init__.py
homeassistant/components/ondilo_ico/api.py
homeassistant/components/ondilo_ico/const.py
homeassistant/components/ondilo_ico/oauth_impl.py
homeassistant/components/ondilo_ico/sensor.py
homeassistant/components/onkyo/media_player.py
homeassistant/components/onvif/__init__.py
homeassistant/components/onvif/base.py
@ -690,8 +701,6 @@ omit =
homeassistant/components/pjlink/media_player.py
homeassistant/components/plaato/*
homeassistant/components/plex/media_player.py
homeassistant/components/plex/models.py
homeassistant/components/plex/sensor.py
homeassistant/components/plum_lightpad/light.py
homeassistant/components/pocketcasts/sensor.py
homeassistant/components/point/*
@ -706,7 +715,6 @@ omit =
homeassistant/components/prowl/notify.py
homeassistant/components/proxmoxve/*
homeassistant/components/proxy/camera.py
homeassistant/components/ptvsd/*
homeassistant/components/pulseaudio_loopback/switch.py
homeassistant/components/pushbullet/notify.py
homeassistant/components/pushbullet/sensor.py
@ -790,11 +798,9 @@ omit =
homeassistant/components/shodan/sensor.py
homeassistant/components/shelly/__init__.py
homeassistant/components/shelly/binary_sensor.py
homeassistant/components/shelly/cover.py
homeassistant/components/shelly/entity.py
homeassistant/components/shelly/light.py
homeassistant/components/shelly/sensor.py
homeassistant/components/shelly/switch.py
homeassistant/components/shelly/utils.py
homeassistant/components/sht31/sensor.py
homeassistant/components/sigfox/sensor.py
@ -837,7 +843,8 @@ omit =
homeassistant/components/soma/cover.py
homeassistant/components/soma/sensor.py
homeassistant/components/somfy/*
homeassistant/components/somfy_mylink/*
homeassistant/components/somfy_mylink/__init__.py
homeassistant/components/somfy_mylink/cover.py
homeassistant/components/sonos/*
homeassistant/components/sony_projector/switch.py
homeassistant/components/spc/*
@ -1090,6 +1097,9 @@ omit =
homeassistant/components/zoneminder/*
homeassistant/components/supla/*
homeassistant/components/zwave/util.py
homeassistant/components/zwave_js/discovery.py
homeassistant/components/zwave_js/light.py
homeassistant/components/zwave_js/sensor.py
[report]
# Regexes for lines to exclude from consideration

View File

@ -11,7 +11,7 @@ on:
env:
CACHE_VERSION: 1
DEFAULT_PYTHON: 3.7
DEFAULT_PYTHON: 3.8
PRE_COMMIT_HOME: ~/.cache/pre-commit
jobs:
@ -521,13 +521,12 @@ jobs:
needs: prepare-tests
strategy:
matrix:
python-version: [3.7]
python-version: [3.8]
container: homeassistant/ci-azure:${{ matrix.python-version }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@v2
- name:
Restore full Python ${{ matrix.python-version }} virtual environment
- name: Restore full Python ${{ matrix.python-version }} virtual environment
id: cache-venv
uses: actions/cache@v2
with:
@ -585,13 +584,12 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.7, 3.8]
python-version: [3.8, 3.9]
container: homeassistant/ci-azure:${{ matrix.python-version }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@v2
- name:
Restore full Python ${{ matrix.python-version }} virtual environment
- name: Restore full Python ${{ matrix.python-version }} virtual environment
id: cache-venv
uses: actions/cache@v2
with:
@ -605,10 +603,13 @@ jobs:
${{ env.CACHE_VERSION}}-${{ runner.os }}-venv-${{ matrix.python-version }}-${{ hashFiles('requirements_test.txt') }}-${{ hashFiles('requirements_all.txt') }}
${{ env.CACHE_VERSION}}-${{ runner.os }}-venv-${{ matrix.python-version }}-${{ hashFiles('requirements_test.txt') }}
${{ env.CACHE_VERSION}}-${{ runner.os }}-venv-${{ matrix.python-version }}-
- name:
Create full Python ${{ matrix.python-version }} virtual environment
- name: Create full Python ${{ matrix.python-version }} virtual environment
if: steps.cache-venv.outputs.cache-hit != 'true'
run: |
# Temporary addition of cmake, needed to build some Python 3.9 packages
apt-get update
apt-get -y install cmake
python -m venv venv
. venv/bin/activate
pip install -U "pip<20.3" setuptools wheel
@ -622,13 +623,12 @@ jobs:
needs: prepare-tests
strategy:
matrix:
python-version: [3.7]
python-version: [3.8]
container: homeassistant/ci-azure:${{ matrix.python-version }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@v2
- name:
Restore full Python ${{ matrix.python-version }} virtual environment
- name: Restore full Python ${{ matrix.python-version }} virtual environment
id: cache-venv
uses: actions/cache@v2
with:
@ -657,13 +657,12 @@ jobs:
needs: prepare-tests
strategy:
matrix:
python-version: [3.7]
python-version: [3.8]
container: homeassistant/ci-azure:${{ matrix.python-version }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@v2
- name:
Restore full Python ${{ matrix.python-version }} virtual environment
- name: Restore full Python ${{ matrix.python-version }} virtual environment
id: cache-venv
uses: actions/cache@v2
with:
@ -692,15 +691,14 @@ jobs:
strategy:
matrix:
group: [1, 2, 3, 4]
python-version: [3.7, 3.8]
python-version: [3.8, 3.9]
name: >-
Run tests Python ${{ matrix.python-version }} (group ${{ matrix.group }})
container: homeassistant/ci-azure:${{ matrix.python-version }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@v2
- name:
Restore full Python ${{ matrix.python-version }} virtual environment
- name: Restore full Python ${{ matrix.python-version }} virtual environment
id: cache-venv
uses: actions/cache@v2
with:
@ -741,7 +739,7 @@ jobs:
-p no:sugar \
tests
- name: Upload coverage artifact
uses: actions/upload-artifact@v2.2.1
uses: actions/upload-artifact@v2.2.2
with:
name: coverage-${{ matrix.python-version }}-group${{ matrix.group }}
path: .coverage
@ -755,13 +753,12 @@ jobs:
needs: pytest
strategy:
matrix:
python-version: [3.7]
python-version: [3.8]
container: homeassistant/ci-azure:${{ matrix.python-version }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@v2
- name:
Restore full Python ${{ matrix.python-version }} virtual environment
- name: Restore full Python ${{ matrix.python-version }} virtual environment
id: cache-venv
uses: actions/cache@v2
with:
@ -785,4 +782,4 @@ jobs:
coverage report --fail-under=94
coverage xml
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v1.1.1
uses: codecov/codecov-action@v1.2.1

View File

@ -9,7 +9,7 @@ jobs:
lock:
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@v2.0.1
- uses: dessant/lock-threads@v2.0.3
with:
github-token: ${{ github.token }}
issue-lock-inactive-days: "30"

View File

@ -4,23 +4,27 @@ name: Stale
on:
schedule:
- cron: "0 * * * *"
workflow_dispatch:
jobs:
stale:
runs-on: ubuntu-latest
steps:
# The 90 day stale policy
# Used for: Everything (unless 30 day policy below beats it)
- name: 90 days stale policy
uses: actions/stale@v3.0.14
# Used for:
# - Issues & PRs
# - No PRs marked as no-stale
# - No issues marked as no-stale or help-wanted
- name: 90 days stale issues & PRs policy
uses: actions/stale@v3.0.15
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 90
days-before-close: 7
operations-per-run: 25
operations-per-run: 150
remove-stale-when-updated: true
stale-issue-label: "stale"
exempt-issue-labels: "no-stale,Help%20wanted,help-wanted"
exempt-issue-labels: "no-stale,help-wanted"
stale-issue-message: >
There hasn't been any activity on this issue recently. Due to the
high number of incoming GitHub notifications, we have to clean some
@ -43,22 +47,48 @@ jobs:
Thank you for your contributions.
# The 30 day stale policy
# The 30 day stale policy for PRS
# Used for:
# - Issues that are pending more information (incomplete issues)
# - PRs that are not marked as new-integration
- name: 30 days stale policy
uses: actions/stale@v3.0.14
# - PRs
# - No PRs marked as no-stale or new-integrations
# - No issues (-1)
- name: 30 days stale PRs policy
uses: actions/stale@v3.0.15
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
# PRs have a CLA signed label, we can misuse it to apply this policy
only-labels: "cla-signed,needs-more-information"
days-before-stale: 30
days-before-close: 7
operations-per-run: 5
days-before-issue-close: -1
operations-per-run: 50
remove-stale-when-updated: true
stale-pr-label: "stale"
# Exempt new integrations, these often take more time.
# They will automatically be handled by the 90 day version above.
exempt-pr-labels: "no-stale,new-integration"
stale-pr-message: >
There hasn't been any activity on this pull request recently. This
pull request has been automatically marked as stale because of that
and will be closed if no further activity occurs within 7 days.
Thank you for your contributions.
# The 30 day stale policy for issues
# Used for:
# - Issues that are pending more information (incomplete issues)
# - No Issues marked as no-stale or help-wanted
# - No PRs (-1)
- name: Needs more information stale issues policy
uses: actions/stale@v3.0.15
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
only-labels: "needs-more-information"
days-before-stale: 14
days-before-close: 7
days-before-pr-close: -1
operations-per-run: 50
remove-stale-when-updated: true
stale-issue-label: "stale"
exempt-issue-labels: "no-stale,Help%20wanted,help-wanted"
exempt-issue-labels: "no-stale,help-wanted"
stale-issue-message: >
There hasn't been any activity on this issue recently. Due to the
high number of incoming GitHub notifications, we have to clean some
@ -71,14 +101,3 @@ jobs:
This issue has now been marked as stale and will be closed if no
further activity occurs. Thank you for your contributions.
stale-pr-label: "stale"
# Exempt new integrations, these often take more time.
# They will automatically be handled by the 90 day version above.
exempt-pr-labels: "no-stale,new-integration"
stale-pr-message: >
There hasn't been any activity on this pull request recently. This
pull request has been automatically marked as stale because of that
and will be closed if no further activity occurs within 7 days.
Thank you for your contributions.

View File

@ -3,7 +3,7 @@ repos:
rev: v2.7.2
hooks:
- id: pyupgrade
args: [--py37-plus]
args: [--py38-plus]
- repo: https://github.com/psf/black
rev: 20.8b1
hooks:
@ -13,14 +13,15 @@ repos:
- --quiet
files: ^((homeassistant|script|tests)/.+)?[^/]+\.py$
- repo: https://github.com/codespell-project/codespell
rev: v1.17.1
rev: v2.0.0
hooks:
- id: codespell
args:
- --ignore-words-list=hass,alot,datas,dof,dur,farenheit,hist,iff,ines,ist,lightsensor,mut,nd,pres,referer,ser,serie,te,technik,ue,uint,visability,wan,wanna,withing,iam,incomfort
- --ignore-words-list=hass,alot,datas,dof,dur,ether,farenheit,hist,iff,ines,ist,lightsensor,mut,nd,pres,referer,ser,serie,te,technik,ue,uint,visability,wan,wanna,withing,iam,incomfort
- --skip="./.*,*.csv,*.json"
- --quiet-level=2
exclude_types: [csv, json]
exclude: ^tests/fixtures/
- repo: https://gitlab.com/pycqa/flake8
rev: 3.8.4
hooks:

View File

@ -4,7 +4,7 @@ build:
image: latest
python:
version: 3.7
version: 3.8
setup_py_install: true
requirements_file: requirements_docs.txt

View File

@ -5,5 +5,5 @@
// https://github.com/microsoft/vscode-python/issues/14067
"python.testing.pytestArgs": ["--no-cov"],
// https://code.visualstudio.com/docs/python/testing#_pytest-configuration-settings
"python.testing.pytestEnabled": true
"python.testing.pytestEnabled": false
}

View File

@ -26,6 +26,7 @@ homeassistant/components/adguard/* @frenck
homeassistant/components/advantage_air/* @Bre77
homeassistant/components/agent_dvr/* @ispysoftware
homeassistant/components/airly/* @bieniu
homeassistant/components/airnow/* @asymworks
homeassistant/components/airvisual/* @bachya
homeassistant/components/alarmdecoder/* @ajschmidt8
homeassistant/components/alexa/* @home-assistant/cloud @ochlocracy
@ -55,7 +56,6 @@ homeassistant/components/auth/* @home-assistant/core
homeassistant/components/automation/* @home-assistant/core
homeassistant/components/avea/* @pattyland
homeassistant/components/awair/* @ahayworth @danielsjf
homeassistant/components/aws/* @awarecan
homeassistant/components/axis/* @Kane610
homeassistant/components/azure_devops/* @timmo001
homeassistant/components/azure_event_hub/* @eavanvalkenburg
@ -107,6 +107,7 @@ homeassistant/components/derivative/* @afaucogney
homeassistant/components/device_automation/* @home-assistant/core
homeassistant/components/devolo_home_control/* @2Fake @Shutgun
homeassistant/components/dexcom/* @gagebenne
homeassistant/components/dhcp/* @bdraco
homeassistant/components/digital_ocean/* @fabaff
homeassistant/components/directv/* @ctalkington
homeassistant/components/discogs/* @thibmaek
@ -119,6 +120,7 @@ homeassistant/components/dweet/* @fabaff
homeassistant/components/dynalite/* @ziv1234
homeassistant/components/eafm/* @Jc2k
homeassistant/components/ecobee/* @marthoc
homeassistant/components/econet/* @vangorra @w1ll1am23
homeassistant/components/ecovacs/* @OverloadUT
homeassistant/components/edl21/* @mtdcr
homeassistant/components/egardia/* @jeroenterheerdt
@ -179,7 +181,7 @@ homeassistant/components/griddy/* @bdraco
homeassistant/components/group/* @home-assistant/core
homeassistant/components/growatt_server/* @indykoning
homeassistant/components/guardian/* @bachya
homeassistant/components/harmony/* @ehendrix23 @bramkragten @bdraco
homeassistant/components/harmony/* @ehendrix23 @bramkragten @bdraco @mkeesey
homeassistant/components/hassio/* @home-assistant/supervisor
homeassistant/components/hdmi_cec/* @newAM
homeassistant/components/heatmiser/* @andylockran
@ -201,6 +203,7 @@ homeassistant/components/http/* @home-assistant/core
homeassistant/components/huawei_lte/* @scop @fphammerle
homeassistant/components/huawei_router/* @abmantis
homeassistant/components/hue/* @balloob @frenck
homeassistant/components/huisbaasje/* @denniss17
homeassistant/components/humidifier/* @home-assistant/core @Shulyaka
homeassistant/components/hunterdouglas_powerview/* @bdraco
homeassistant/components/hvv_departures/* @vigonotion
@ -256,7 +259,7 @@ homeassistant/components/luci/* @mzdrale
homeassistant/components/luftdaten/* @fabaff
homeassistant/components/lupusec/* @majuss
homeassistant/components/lutron/* @JonGilmore
homeassistant/components/lutron_caseta/* @swails
homeassistant/components/lutron_caseta/* @swails @bdraco
homeassistant/components/mastodon/* @fabaff
homeassistant/components/matrix/* @tinloaf
homeassistant/components/mcp23017/* @jardiamj
@ -289,10 +292,10 @@ homeassistant/components/neato/* @dshokouhi @Santobert
homeassistant/components/nederlandse_spoorwegen/* @YarmoM
homeassistant/components/nello/* @pschmitt
homeassistant/components/ness_alarm/* @nickw444
homeassistant/components/nest/* @awarecan @allenporter
homeassistant/components/nest/* @allenporter
homeassistant/components/netatmo/* @cgtobi
homeassistant/components/netdata/* @fabaff
homeassistant/components/nexia/* @ryannazaretian @bdraco
homeassistant/components/nexia/* @bdraco
homeassistant/components/nextbus/* @vividboarder
homeassistant/components/nextcloud/* @meichthys
homeassistant/components/nightscout/* @marciogranzotto
@ -318,6 +321,7 @@ homeassistant/components/ohmconnect/* @robbiet480
homeassistant/components/ombi/* @larssont
homeassistant/components/omnilogic/* @oliver84 @djtimca @gentoosu
homeassistant/components/onboarding/* @home-assistant/core
homeassistant/components/ondilo_ico/* @JeromeHXP
homeassistant/components/onewire/* @garbled1 @epenet
homeassistant/components/onvif/* @hunterjm
homeassistant/components/openerz/* @misialq
@ -351,7 +355,6 @@ homeassistant/components/progettihwsw/* @ardaseremet
homeassistant/components/prometheus/* @knyar
homeassistant/components/proxmoxve/* @k4ds3 @jhollowe
homeassistant/components/ps4/* @ktnrg45
homeassistant/components/ptvsd/* @swamp-ig
homeassistant/components/push/* @dgomes
homeassistant/components/pvoutput/* @fabaff
homeassistant/components/pvpc_hourly_pricing/* @azogue
@ -362,6 +365,7 @@ homeassistant/components/quantum_gateway/* @cisasteelersfan
homeassistant/components/qvr_pro/* @oblogic7
homeassistant/components/qwikswitch/* @kellerza
homeassistant/components/rachio/* @bdraco
homeassistant/components/radiotherm/* @vinnyfuria
homeassistant/components/rainbird/* @konikvranik
homeassistant/components/raincloud/* @vanstinator
homeassistant/components/rainforest_eagle/* @gtdiehl @jcalbert
@ -396,7 +400,7 @@ homeassistant/components/seven_segments/* @fabaff
homeassistant/components/seventeentrack/* @bachya
homeassistant/components/sharkiq/* @ajmarks
homeassistant/components/shell_command/* @home-assistant/core
homeassistant/components/shelly/* @balloob @bieniu @thecode
homeassistant/components/shelly/* @balloob @bieniu @thecode @chemelli74
homeassistant/components/shiftr/* @fabaff
homeassistant/components/shodan/* @fabaff
homeassistant/components/sighthound/* @robmarkcole
@ -420,6 +424,7 @@ homeassistant/components/solarlog/* @Ernst79
homeassistant/components/solax/* @squishykid
homeassistant/components/soma/* @ratsept
homeassistant/components/somfy/* @tetienne
homeassistant/components/somfy_mylink/* @bdraco
homeassistant/components/sonarr/* @ctalkington
homeassistant/components/songpal/* @rytilahti @shenxn
homeassistant/components/sonos/* @cgtobi
@ -535,6 +540,7 @@ homeassistant/components/zodiac/* @JulienTant
homeassistant/components/zone/* @home-assistant/core
homeassistant/components/zoneminder/* @rohankapoorcom
homeassistant/components/zwave/* @home-assistant/z-wave
homeassistant/components/zwave_js/* @home-assistant/z-wave
# Individual files
homeassistant/components/demo/weather @fabaff

View File

@ -1,8 +1,9 @@
ARG BUILD_FROM
FROM ${BUILD_FROM}
# Synchronize with homeassistant/core.py:async_stop
ENV \
S6_SERVICES_GRACETIME=60000
S6_SERVICES_GRACETIME=220000
WORKDIR /usr/src

View File

@ -14,8 +14,6 @@ pr:
resources:
containers:
- container: 37
image: homeassistant/ci-azure:3.7
- container: 38
image: homeassistant/ci-azure:3.8
repositories:
@ -25,7 +23,7 @@ resources:
endpoint: "home-assistant"
variables:
- name: PythonMain
value: "37"
value: "38"
- name: versionHadolint
value: "v1.17.6"
@ -150,8 +148,6 @@ stages:
strategy:
maxParallel: 3
matrix:
Python37:
python.container: "37"
Python38:
python.container: "38"
container: $[ variables['python.container'] ]

View File

@ -60,9 +60,9 @@ stages:
vmImage: 'ubuntu-latest'
steps:
- task: UsePythonVersion@0
displayName: 'Use Python 3.7'
displayName: 'Use Python 3.8'
inputs:
versionSpec: '3.7'
versionSpec: '3.8'
- script: pip install twine wheel
displayName: 'Install tools'
- script: python setup.py sdist bdist_wheel

View File

@ -30,9 +30,9 @@ jobs:
vmImage: 'ubuntu-latest'
steps:
- task: UsePythonVersion@0
displayName: 'Use Python 3.7'
displayName: 'Use Python 3.8'
inputs:
versionSpec: '3.7'
versionSpec: '3.8'
- script: |
export LOKALISE_TOKEN="$(lokaliseToken)"
export AZURE_BRANCH="$(Build.SourceBranchName)"

View File

@ -1,11 +1,11 @@
{
"image": "homeassistant/{arch}-homeassistant",
"build_from": {
"aarch64": "homeassistant/aarch64-homeassistant-base:2021.01.0",
"armhf": "homeassistant/armhf-homeassistant-base:2021.01.0",
"armv7": "homeassistant/armv7-homeassistant-base:2021.01.0",
"amd64": "homeassistant/amd64-homeassistant-base:2021.01.0",
"i386": "homeassistant/i386-homeassistant-base:2021.01.0"
"aarch64": "homeassistant/aarch64-homeassistant-base:2021.02.0",
"armhf": "homeassistant/armhf-homeassistant-base:2021.02.0",
"armv7": "homeassistant/armv7-homeassistant-base:2021.02.0",
"amd64": "homeassistant/amd64-homeassistant-base:2021.02.0",
"i386": "homeassistant/i386-homeassistant-base:2021.02.0"
},
"labels": {
"io.hass.type": "core"

View File

@ -48,7 +48,7 @@ COOLDOWN_TIME = 60
MAX_LOAD_CONCURRENTLY = 6
DEBUGGER_INTEGRATIONS = {"debugpy", "ptvsd"}
DEBUGGER_INTEGRATIONS = {"debugpy"}
CORE_INTEGRATIONS = ("homeassistant", "persistent_notification")
LOGGING_INTEGRATIONS = {
# Set log levels
@ -307,12 +307,10 @@ def async_enable_logging(
sys.excepthook = lambda *args: logging.getLogger(None).exception(
"Uncaught exception", exc_info=args # type: ignore
)
if sys.version_info[:2] >= (3, 8):
threading.excepthook = lambda args: logging.getLogger(None).exception(
"Uncaught thread exception",
exc_info=(args.exc_type, args.exc_value, args.exc_traceback),
)
threading.excepthook = lambda args: logging.getLogger(None).exception(
"Uncaught thread exception",
exc_info=(args.exc_type, args.exc_value, args.exc_traceback), # type: ignore[arg-type]
)
# Log errors to a file if we have write access to file or config dir
if log_file is None:
@ -383,7 +381,7 @@ def _get_domains(hass: core.HomeAssistant, config: Dict[str, Any]) -> Set[str]:
async def _async_log_pending_setups(
domains: Set[str], setup_started: Dict[str, datetime]
hass: core.HomeAssistant, domains: Set[str], setup_started: Dict[str, datetime]
) -> None:
"""Periodic log of setups that are pending for longer than LOG_SLOW_STARTUP_INTERVAL."""
while True:
@ -395,6 +393,7 @@ async def _async_log_pending_setups(
"Waiting on integrations to complete setup: %s",
", ".join(remaining),
)
_LOGGER.debug("Running timeout Zones: %s", hass.timeout.zones)
async def async_setup_multi_components(
@ -408,7 +407,9 @@ async def async_setup_multi_components(
domain: hass.async_create_task(async_setup_component(hass, domain, config))
for domain in domains
}
log_task = asyncio.create_task(_async_log_pending_setups(domains, setup_started))
log_task = asyncio.create_task(
_async_log_pending_setups(hass, domains, setup_started)
)
await asyncio.wait(futures.values())
log_task.cancel()
errors = [domain for domain in domains if futures[domain].exception()]

View File

@ -2,7 +2,7 @@
"config": {
"abort": {
"reauth_successful": "Die erneute Authentifizierung war erfolgreich",
"single_instance_allowed": "Es ist nur eine einzige Konfiguration von Abode erlaubt."
"single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich."
},
"error": {
"cannot_connect": "Verbindung fehlgeschlagen",

View File

@ -1,7 +1,7 @@
{
"config": {
"abort": {
"reauth_successful": "La reautenticaci\u00f3n fue exitosa",
"reauth_successful": "La reautenticaci\u00f3n se realiz\u00f3 correctamente",
"single_instance_allowed": "Ya est\u00e1 configurado. Solo es posible una \u00fanica configuraci\u00f3n."
},
"error": {
@ -19,7 +19,7 @@
"reauth_confirm": {
"data": {
"password": "Contrase\u00f1a",
"username": "Correo electronico"
"username": "Correo electr\u00f3nico"
},
"title": "Rellene su informaci\u00f3n de inicio de sesi\u00f3n de Abode"
},

View File

@ -1,17 +1,32 @@
{
"config": {
"abort": {
"single_instance_allowed": "Une seule configuration d'Abode est autoris\u00e9e."
"reauth_successful": "La r\u00e9-authentification a r\u00e9ussi",
"single_instance_allowed": "D\u00e9ja configur\u00e9. Une seule configuration possible."
},
"error": {
"cannot_connect": "\u00c9chec de connexion",
"invalid_auth": "Authentification invalide"
"invalid_auth": "Authentification invalide",
"invalid_mfa_code": "Code MFA non valide"
},
"step": {
"mfa": {
"data": {
"mfa_code": "Code MFA (6 chiffres)"
},
"title": "Entrez votre code MFA pour Abode"
},
"reauth_confirm": {
"data": {
"password": "Mot de passe",
"username": "Email"
},
"title": "Remplissez vos informations de connexion Abode"
},
"user": {
"data": {
"password": "Mot de passe",
"username": "Adresse e-mail"
"username": "Email"
},
"title": "Remplissez vos informations de connexion Abode"
}

View File

@ -0,0 +1,34 @@
{
"config": {
"abort": {
"reauth_successful": "Yeniden kimlik do\u011frulama ba\u015far\u0131l\u0131 oldu",
"single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr."
},
"error": {
"cannot_connect": "Ba\u011flanma hatas\u0131",
"invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama",
"invalid_mfa_code": "Ge\u00e7ersiz MFA kodu"
},
"step": {
"mfa": {
"data": {
"mfa_code": "MFA kodu (6 basamakl\u0131)"
},
"title": "Abode i\u00e7in MFA kodunuzu girin"
},
"reauth_confirm": {
"data": {
"password": "Parola",
"username": "E-posta"
},
"title": "Abode giri\u015f bilgilerinizi doldurun"
},
"user": {
"data": {
"password": "Parola",
"username": "E-posta"
}
}
}
}
}

View File

@ -0,0 +1,35 @@
{
"config": {
"abort": {
"reauth_successful": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u0430 \u0430\u0432\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f \u043f\u0440\u043e\u0439\u0448\u043b\u0430 \u0443\u0441\u043f\u0456\u0448\u043d\u043e",
"single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e."
},
"error": {
"cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f",
"invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f.",
"invalid_mfa_code": "\u041d\u0435\u0434\u0456\u0439\u0441\u043d\u0438\u0439 \u043a\u043e\u0434 MFA."
},
"step": {
"mfa": {
"data": {
"mfa_code": "\u041a\u043e\u0434 MFA (6 \u0446\u0438\u0444\u0440)"
},
"title": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u043a\u043e\u0434 MFA \u0434\u043b\u044f Abode"
},
"reauth_confirm": {
"data": {
"password": "\u041f\u0430\u0440\u043e\u043b\u044c",
"username": "\u0410\u0434\u0440\u0435\u0441\u0430 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0457 \u043f\u043e\u0448\u0442\u0438"
},
"title": "Abode"
},
"user": {
"data": {
"password": "\u041f\u0430\u0440\u043e\u043b\u044c",
"username": "\u0410\u0434\u0440\u0435\u0441\u0430 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0457 \u043f\u043e\u0448\u0442\u0438"
},
"title": "Abode"
}
}
}
}

View File

@ -100,13 +100,13 @@ class AccuWeatherDataUpdateCoordinator(DataUpdateCoordinator):
self.accuweather = AccuWeather(api_key, session, location_key=self.location_key)
# Enabling the forecast download increases the number of requests per data
# update, we use 32 minutes for current condition only and 64 minutes for
# update, we use 40 minutes for current condition only and 80 minutes for
# current condition and forecast as update interval to not exceed allowed number
# of requests. We have 50 requests allowed per day, so we use 45 and leave 5 as
# of requests. We have 50 requests allowed per day, so we use 36 and leave 14 as
# a reserve for restarting HA.
update_interval = (
timedelta(minutes=64) if self.forecast else timedelta(minutes=32)
)
update_interval = timedelta(minutes=40)
if self.forecast:
update_interval *= 2
_LOGGER.debug("Data will be update every %s", update_interval)
super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=update_interval)

View File

@ -52,12 +52,12 @@ CONDITION_CLASSES = {
ATTR_CONDITION_HAIL: [25],
ATTR_CONDITION_LIGHTNING: [15],
ATTR_CONDITION_LIGHTNING_RAINY: [16, 17, 41, 42],
ATTR_CONDITION_PARTLYCLOUDY: [4, 6, 35, 36],
ATTR_CONDITION_PARTLYCLOUDY: [3, 4, 6, 35, 36],
ATTR_CONDITION_POURING: [18],
ATTR_CONDITION_RAINY: [12, 13, 14, 26, 39, 40],
ATTR_CONDITION_SNOWY: [19, 20, 21, 22, 23, 43, 44],
ATTR_CONDITION_SNOWY_RAINY: [29],
ATTR_CONDITION_SUNNY: [1, 2, 3, 5],
ATTR_CONDITION_SUNNY: [1, 2, 5],
ATTR_CONDITION_WINDY: [32],
}

View File

@ -25,7 +25,7 @@
"step": {
"user": {
"title": "AccuWeather Options",
"description": "Due to the limitations of the free version of the AccuWeather API key, when you enable weather forecast, data updates will be performed every 64 minutes instead of every 32 minutes.",
"description": "Due to the limitations of the free version of the AccuWeather API key, when you enable weather forecast, data updates will be performed every 80 minutes instead of every 40 minutes.",
"data": {
"forecast": "Weather forecast"
}

View File

@ -27,7 +27,7 @@
"data": {
"forecast": "Previsi\u00f3 meteorol\u00f2gica"
},
"description": "Per culpa de les limitacions de la versi\u00f3 gratu\u00efta l'API d'AccuWeather, quan habilitis la previsi\u00f3 meteorol\u00f2gica, les actualitzacions es realitzaran cada 64 minuts en comptes de 32.",
"description": "Per culpa de les limitacions de la versi\u00f3 gratu\u00efta l'API d'AccuWeather, quan habilitis la previsi\u00f3 meteorol\u00f2gica, les actualitzacions de dades es faran cada 80 minuts en comptes de cada 40.",
"title": "Opcions d'AccuWeather"
}
}

View File

@ -27,7 +27,7 @@
"data": {
"forecast": "P\u0159edpov\u011b\u010f po\u010das\u00ed"
},
"description": "Kdy\u017e povol\u00edte p\u0159edpov\u011b\u010f po\u010das\u00ed, budou aktualizace dat prov\u00e1d\u011bny ka\u017ed\u00fdch 64 minut nam\u00edsto 32 minut z d\u016fvodu omezen\u00ed bezplatn\u00e9 verze AccuWeather.",
"description": "Kdy\u017e povol\u00edte p\u0159edpov\u011b\u010f po\u010das\u00ed, budou aktualizace dat prov\u00e1d\u011bny ka\u017ed\u00fdch 80 minut nam\u00edsto 40 minut z d\u016fvodu omezen\u00ed bezplatn\u00e9 verze AccuWeather.",
"title": "Mo\u017enosti AccuWeather"
}
}

View File

@ -1,11 +1,16 @@
{
"config": {
"abort": {
"single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich."
},
"error": {
"cannot_connect": "Verbindungsfehler"
"cannot_connect": "Verbindung fehlgeschlagen",
"invalid_api_key": "Ung\u00fcltiger API-Schl\u00fcssel"
},
"step": {
"user": {
"data": {
"api_key": "API-Schl\u00fcssel",
"latitude": "Breitengrad",
"longitude": "L\u00e4ngengrad",
"name": "Name"
@ -25,7 +30,7 @@
},
"system_health": {
"info": {
"can_reach_server": "AccuWeather Server erreichen",
"can_reach_server": "AccuWeather-Server erreichen",
"remaining_requests": "Verbleibende erlaubte Anfragen"
}
}

View File

@ -27,7 +27,7 @@
"data": {
"forecast": "Weather forecast"
},
"description": "Due to the limitations of the free version of the AccuWeather API key, when you enable weather forecast, data updates will be performed every 64 minutes instead of every 32 minutes.",
"description": "Due to the limitations of the free version of the AccuWeather API key, when you enable weather forecast, data updates will be performed every 80 minutes instead of every 40 minutes.",
"title": "AccuWeather Options"
}
}

View File

@ -27,7 +27,7 @@
"data": {
"forecast": "Ilmateade"
},
"description": "AccuWeather API tasuta versioonis toimub ilmaennustuse lubamisel andmete v\u00e4rskendamine iga 32 minuti asemel iga 64 minuti j\u00e4rel.",
"description": "AccuWeather API tasuta versioonis toimub ilmaennustuse lubamisel andmete v\u00e4rskendamine iga 80 minuti j\u00e4rel (muidu 40 minutit).",
"title": "AccuWeatheri valikud"
}
}

View File

@ -34,7 +34,8 @@
},
"system_health": {
"info": {
"can_reach_server": "Acc\u00e8s au serveur AccuWeather"
"can_reach_server": "Acc\u00e8s au serveur AccuWeather",
"remaining_requests": "Demandes restantes autoris\u00e9es"
}
}
}

View File

@ -27,7 +27,7 @@
"data": {
"forecast": "Previsioni meteo"
},
"description": "A causa delle limitazioni della versione gratuita della chiave API AccuWeather, quando si abilitano le previsioni del tempo, gli aggiornamenti dei dati verranno eseguiti ogni 64 minuti invece che ogni 32 minuti.",
"description": "A causa delle limitazioni della versione gratuita della chiave API AccuWeather, quando si abilitano le previsioni del tempo, gli aggiornamenti dei dati verranno eseguiti ogni 80 minuti invece che ogni 40.",
"title": "Opzioni AccuWeather"
}
}

View File

@ -27,7 +27,7 @@
"data": {
"forecast": "V\u00e6rmelding"
},
"description": "P\u00e5 grunn av begrensningene i gratisversjonen av AccuWeather API-n\u00f8kkelen, n\u00e5r du aktiverer v\u00e6rmelding, vil dataoppdateringer bli utf\u00f8rt hvert 64. minutt i stedet for hvert 32. minutt.",
"description": "P\u00e5 grunn av begrensningene i den gratis versjonen av AccuWeather API-n\u00f8kkelen, vil dataoppdateringer utf\u00f8res hvert 80. minutt i stedet for hvert 40. minutt n\u00e5r du aktiverer v\u00e6rmelding.",
"title": "AccuWeather-alternativer"
}
}

View File

@ -27,7 +27,7 @@
"data": {
"forecast": "Prognoza pogody"
},
"description": "Ze wzgl\u0119du na ograniczenia darmowej wersji klucza API AccuWeather po w\u0142\u0105czeniu prognozy pogody aktualizacje danych b\u0119d\u0105 wykonywane co 64 minuty zamiast co 32 minuty.",
"description": "Ze wzgl\u0119du na ograniczenia darmowej wersji klucza API AccuWeather po w\u0142\u0105czeniu prognozy pogody aktualizacje danych b\u0119d\u0105 wykonywane co 80 minut zamiast co 40 minut.",
"title": "Opcje AccuWeather"
}
}

View File

@ -27,7 +27,7 @@
"data": {
"forecast": "\u041f\u0440\u043e\u0433\u043d\u043e\u0437 \u043f\u043e\u0433\u043e\u0434\u044b"
},
"description": "\u0412 \u0441\u0432\u044f\u0437\u0438 \u0441 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u044f\u043c\u0438 \u0431\u0435\u0441\u043f\u043b\u0430\u0442\u043d\u043e\u0439 \u0432\u0435\u0440\u0441\u0438\u0438 \u043a\u043b\u044e\u0447\u0430 API AccuWeather, \u043f\u0440\u0438 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438 \u043f\u0440\u043e\u0433\u043d\u043e\u0437\u0430 \u043f\u043e\u0433\u043e\u0434\u044b \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442\u044c \u043a\u0430\u0436\u0434\u044b\u0435 64 \u043c\u0438\u043d\u0443\u0442\u044b, \u0430 \u043d\u0435 \u043a\u0430\u0436\u0434\u044b\u0435 32 \u043c\u0438\u043d\u0443\u0442\u044b.",
"description": "\u0412 \u0441\u0432\u044f\u0437\u0438 \u0441 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u044f\u043c\u0438 \u0431\u0435\u0441\u043f\u043b\u0430\u0442\u043d\u043e\u0439 \u0432\u0435\u0440\u0441\u0438\u0438 \u043a\u043b\u044e\u0447\u0430 API AccuWeather, \u043f\u0440\u0438 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438 \u043f\u0440\u043e\u0433\u043d\u043e\u0437\u0430 \u043f\u043e\u0433\u043e\u0434\u044b \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442\u044c \u043a\u0430\u0436\u0434\u044b\u0435 80 \u043c\u0438\u043d\u0443\u0442, \u0430 \u043d\u0435 \u043a\u0430\u0436\u0434\u044b\u0435 40 \u043c\u0438\u043d\u0443\u0442.",
"title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 AccuWeather"
}
}

View File

@ -0,0 +1,9 @@
{
"state": {
"accuweather__pressure_tendency": {
"falling": "\u0417\u043d\u0438\u0436\u0435\u043d\u043d\u044f",
"rising": "\u0417\u0440\u043e\u0441\u0442\u0430\u043d\u043d\u044f",
"steady": "\u0421\u0442\u0430\u0431\u0456\u043b\u044c\u043d\u0438\u0439"
}
}
}

View File

@ -0,0 +1,38 @@
{
"config": {
"abort": {
"single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr."
},
"error": {
"cannot_connect": "Ba\u011flanma hatas\u0131",
"invalid_api_key": "Ge\u00e7ersiz API anahtar\u0131"
},
"step": {
"user": {
"data": {
"api_key": "API Anahtar\u0131",
"latitude": "Enlem",
"longitude": "Boylam",
"name": "Ad"
},
"title": "AccuWeather"
}
}
},
"options": {
"step": {
"user": {
"data": {
"forecast": "Hava Durumu tahmini"
},
"title": "AccuWeather Se\u00e7enekleri"
}
}
},
"system_health": {
"info": {
"can_reach_server": "AccuWeather sunucusuna ula\u015f\u0131n",
"remaining_requests": "Kalan izin verilen istekler"
}
}
}

View File

@ -1,15 +1,22 @@
{
"config": {
"abort": {
"single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e."
},
"error": {
"invalid_api_key": "\u0425\u0438\u0431\u043d\u0438\u0439 \u043a\u043b\u044e\u0447 API"
"cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f",
"invalid_api_key": "\u0425\u0438\u0431\u043d\u0438\u0439 \u043a\u043b\u044e\u0447 API",
"requests_exceeded": "\u041f\u0435\u0440\u0435\u0432\u0438\u0449\u0435\u043d\u043e \u0434\u043e\u043f\u0443\u0441\u0442\u0438\u043c\u0443 \u043a\u0456\u043b\u044c\u043a\u0456\u0441\u0442\u044c \u0437\u0430\u043f\u0438\u0442\u0456\u0432 \u0434\u043e API Accuweather. \u041d\u0435\u043e\u0431\u0445\u0456\u0434\u043d\u043e \u043f\u043e\u0447\u0435\u043a\u0430\u0442\u0438 \u0430\u0431\u043e \u0437\u043c\u0456\u043d\u0438\u0442\u0438 \u043a\u043b\u044e\u0447 API."
},
"step": {
"user": {
"data": {
"api_key": "\u041a\u043b\u044e\u0447 API",
"latitude": "\u0428\u0438\u0440\u043e\u0442\u0430",
"longitude": "\u0414\u043e\u0432\u0433\u043e\u0442\u0430",
"name": "\u041d\u0430\u0437\u0432\u0430 \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457"
"name": "\u041d\u0430\u0437\u0432\u0430"
},
"description": "\u041e\u0437\u043d\u0430\u0439\u043e\u043c\u0442\u0435\u0441\u044f \u0437 \u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u044f\u043c\u0438, \u044f\u043a\u0449\u043e \u0412\u0430\u043c \u043f\u043e\u0442\u0440\u0456\u0431\u043d\u0430 \u0434\u043e\u043f\u043e\u043c\u043e\u0433\u0430 \u0437 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f\u043c:\n https://www.home-assistant.io/integrations/accuweather/ \n\n\u0417\u0430 \u0437\u0430\u043c\u043e\u0432\u0447\u0443\u0432\u0430\u043d\u043d\u044f\u043c \u0434\u0435\u044f\u043a\u0456 \u0441\u0435\u043d\u0441\u043e\u0440\u0438 \u043f\u0440\u0438\u0445\u043e\u0432\u0430\u043d\u0456 \u0456 \u0432\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439 \u043f\u0440\u043e\u0433\u043d\u043e\u0437 \u043f\u043e\u0433\u043e\u0434\u0438. \u0412\u0438 \u043c\u043e\u0436\u0435\u0442\u0435 \u0430\u043a\u0442\u0438\u0432\u0443\u0432\u0430\u0442\u0438 \u0432\u0456\u0434\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u043d\u044f \u043f\u043e\u0442\u0440\u0456\u0431\u043d\u0438\u0445 \u0441\u0435\u043d\u0441\u043e\u0440\u0456\u0432 \u0432 \u0440\u0435\u0454\u0441\u0442\u0440\u0456 \u043e\u0431'\u0454\u043a\u0442\u0456\u0432 \u0456 \u0432\u043a\u043b\u044e\u0447\u0438\u0442\u0438 \u043f\u0440\u043e\u0433\u043d\u043e\u0437 \u043f\u043e\u0433\u043e\u0434\u0438 \u0432 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f\u0445 \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457.",
"title": "AccuWeather"
}
}
@ -19,8 +26,16 @@
"user": {
"data": {
"forecast": "\u041f\u0440\u043e\u0433\u043d\u043e\u0437 \u043f\u043e\u0433\u043e\u0434\u0438"
}
},
"description": "\u0423 \u0437\u0432'\u044f\u0437\u043a\u0443 \u0437 \u043e\u0431\u043c\u0435\u0436\u0435\u043d\u043d\u044f\u043c\u0438 \u0431\u0435\u0437\u043a\u043e\u0448\u0442\u043e\u0432\u043d\u043e\u0457 \u0432\u0435\u0440\u0441\u0456\u0457 \u043a\u043b\u044e\u0447\u0430 API AccuWeather, \u043f\u0440\u0438 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u0456 \u043f\u0440\u043e\u0433\u043d\u043e\u0437\u0443 \u043f\u043e\u0433\u043e\u0434\u0438 \u043e\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044f \u0434\u0430\u043d\u0438\u0445 \u0431\u0443\u0434\u0435 \u0432\u0456\u0434\u0431\u0443\u0432\u0430\u0442\u0438\u0441\u044f \u043a\u043e\u0436\u043d\u0456 64 \u0445\u0432\u0438\u043b\u0438\u043d\u0438, \u0430 \u043d\u0435 \u043a\u043e\u0436\u043d\u0456 32 \u0445\u0432\u0438\u043b\u0438\u043d\u0438.",
"title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f AccuWeather"
}
}
},
"system_health": {
"info": {
"can_reach_server": "\u0414\u043e\u0441\u0442\u0443\u043f \u0434\u043e \u0441\u0435\u0440\u0432\u0435\u0440\u0430 AccuWeather",
"remaining_requests": "\u0417\u0430\u043f\u0438\u0442\u0456\u0432 \u0437\u0430\u043b\u0438\u0448\u0438\u043b\u043e\u0441\u044c"
}
}
}

View File

@ -27,7 +27,7 @@
"data": {
"forecast": "\u5929\u6c23\u9810\u5831"
},
"description": "\u7531\u65bc AccuWeather API \u5bc6\u9470\u514d\u8cbb\u7248\u672c\u9650\u5236\uff0c\u7576\u958b\u555f\u5929\u6c23\u9810\u5831\u6642\u3001\u6578\u64da\u6703\u6bcf 64 \u5206\u9418\u66f4\u65b0\u4e00\u6b21\uff0c\u800c\u975e 32 \u5206\u9418\u3002",
"description": "\u7531\u65bc AccuWeather API \u5bc6\u9470\u514d\u8cbb\u7248\u672c\u9650\u5236\uff0c\u7576\u958b\u555f\u5929\u6c23\u9810\u5831\u6642\u3001\u6578\u64da\u6703\u6bcf 80 \u5206\u9418\u66f4\u65b0\u4e00\u6b21\uff0c\u800c\u975e 40 \u5206\u9418\u3002",
"title": "AccuWeather \u9078\u9805"
}
}

View File

@ -26,9 +26,7 @@ class AcmedaBase(entity.Entity):
ent_registry.async_remove(self.entity_id)
dev_registry = await get_dev_reg(self.hass)
device = dev_registry.async_get_device(
identifiers={(DOMAIN, self.unique_id)}, connections=set()
)
device = dev_registry.async_get_device(identifiers={(DOMAIN, self.unique_id)})
if device is not None:
dev_registry.async_update_device(
device.id, remove_config_entry_id=self.registry_entry.config_entry_id

View File

@ -32,9 +32,7 @@ async def update_devices(hass, config_entry, api):
for api_item in api.values():
# Update Device name
device = dev_registry.async_get_device(
identifiers={(DOMAIN, api_item.id)}, connections=set()
)
device = dev_registry.async_get_device(identifiers={(DOMAIN, api_item.id)})
if device is not None:
dev_registry.async_update_device(
device.id,

View File

@ -1,11 +1,14 @@
{
"config": {
"abort": {
"no_devices_found": "Keine Ger\u00e4te im Netzwerk gefunden"
},
"step": {
"user": {
"data": {
"id": "Host-ID"
},
"title": "W\u00e4hlen Sie einen Hub zum Hinzuf\u00fcgen aus"
"title": "W\u00e4hle einen Hub zum Hinzuf\u00fcgen aus"
}
}
}

View File

@ -0,0 +1,11 @@
{
"config": {
"step": {
"user": {
"data": {
"id": "Ana bilgisayar kimli\u011fi"
}
}
}
}
}

View File

@ -0,0 +1,15 @@
{
"config": {
"abort": {
"no_devices_found": "\u041f\u0440\u0438\u0441\u0442\u0440\u043e\u0457 \u043d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u0456 \u0432 \u043c\u0435\u0440\u0435\u0436\u0456."
},
"step": {
"user": {
"data": {
"id": "\u0406\u0434\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u043e\u0440 \u0445\u043e\u0441\u0442\u0430"
},
"title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0445\u0430\u0431, \u044f\u043a\u0438\u0439 \u043f\u043e\u0442\u0440\u0456\u0431\u043d\u043e \u0434\u043e\u0434\u0430\u0442\u0438"
}
}
}
}

View File

@ -2,10 +2,10 @@
"config": {
"abort": {
"existing_instance_updated": "Bestehende Konfiguration wurde aktualisiert.",
"single_instance_allowed": "Es ist nur eine einzige Konfiguration von AdGuard Home zul\u00e4ssig."
"single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich."
},
"error": {
"cannot_connect": "Verbindungsfehler"
"cannot_connect": "Verbindung fehlgeschlagen"
},
"step": {
"hassio_confirm": {
@ -19,7 +19,7 @@
"port": "Port",
"ssl": "AdGuard Home verwendet ein SSL-Zertifikat",
"username": "Benutzername",
"verify_ssl": "AdGuard Home verwendet ein richtiges Zertifikat"
"verify_ssl": "SSL-Zertifikat \u00fcberpr\u00fcfen"
},
"description": "Richte deine AdGuard Home-Instanz ein um sie zu \u00dcberwachen und zu Steuern."
}

View File

@ -9,8 +9,8 @@
},
"step": {
"hassio_confirm": {
"description": "Vil du konfigurere Home Assistant til \u00e5 koble til AdGuard Hjem gitt av hass.io tillegget {addon}?",
"title": "AdGuard Hjem via Hass.io tillegg"
"description": "Vil du konfigurere Home Assistant til \u00e5 koble til AdGuard Home gitt av Hass.io-tillegg {addon}?",
"title": "AdGuard Home via Hass.io-tillegg"
},
"user": {
"data": {

View File

@ -9,8 +9,8 @@
},
"step": {
"hassio_confirm": {
"description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a AdGuard Home (\u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0434\u043b\u044f Hass.io \"{addon}\")?",
"title": "AdGuard Home (\u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0434\u043b\u044f Hass.io)"
"description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a AdGuard Home (\u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0434\u043b\u044f Home Assistant \"{addon}\")?",
"title": "AdGuard Home (\u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0434\u043b\u044f Home Assistant)"
},
"user": {
"data": {

View File

@ -0,0 +1,20 @@
{
"config": {
"abort": {
"single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr."
},
"error": {
"cannot_connect": "Ba\u011flanma hatas\u0131"
},
"step": {
"user": {
"data": {
"host": "Ana Bilgisayar",
"password": "Parola",
"port": "Port",
"username": "Kullan\u0131c\u0131 Ad\u0131"
}
}
}
}
}

View File

@ -0,0 +1,28 @@
{
"config": {
"abort": {
"existing_instance_updated": "\u041a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f \u043e\u043d\u043e\u0432\u043b\u0435\u043d\u0430.",
"single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e."
},
"error": {
"cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f"
},
"step": {
"hassio_confirm": {
"description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e AdGuard Home (\u0434\u043e\u0434\u0430\u0442\u043e\u043a \u0434\u043b\u044f Hass.io \"{addon}\")?",
"title": "AdGuard Home (\u0434\u043e\u0434\u0430\u0442\u043e\u043a \u0434\u043b\u044f Hass.io)"
},
"user": {
"data": {
"host": "\u0425\u043e\u0441\u0442",
"password": "\u041f\u0430\u0440\u043e\u043b\u044c",
"port": "\u041f\u043e\u0440\u0442",
"ssl": "\u0412\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442 SSL",
"username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430",
"verify_ssl": "\u041f\u0435\u0440\u0435\u0432\u0456\u0440\u043a\u0430 \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u0430 SSL"
},
"description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u043c\u043e\u043d\u0456\u0442\u043e\u0440\u0438\u043d\u0433\u0443 \u0456 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044e AdGuard Home."
}
}
}
}

View File

@ -1,7 +1,10 @@
{
"config": {
"abort": {
"already_configured": "Ger\u00e4t ist bereits konfiguriert"
},
"error": {
"cannot_connect": "Verbindungsfehler"
"cannot_connect": "Verbindung fehlgeschlagen"
},
"step": {
"user": {

View File

@ -0,0 +1,19 @@
{
"config": {
"abort": {
"already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f"
},
"error": {
"cannot_connect": "Ba\u011flanma hatas\u0131"
},
"step": {
"user": {
"data": {
"ip_address": "\u0130p Adresi",
"port": "Port"
},
"title": "Ba\u011flan"
}
}
}
}

View File

@ -0,0 +1,20 @@
{
"config": {
"abort": {
"already_configured": "\u0426\u0435\u0439 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant."
},
"error": {
"cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f"
},
"step": {
"user": {
"data": {
"ip_address": "IP-\u0430\u0434\u0440\u0435\u0441\u0430",
"port": "\u041f\u043e\u0440\u0442"
},
"description": "\u041f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e API \u0412\u0430\u0448\u043e\u0433\u043e \u043d\u0430\u0441\u0442\u0456\u043d\u043d\u043e\u0433\u043e \u043f\u043b\u0430\u043d\u0448\u0435\u0442\u0430 Advantage Air.",
"title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f"
}
}
}
}

View File

@ -4,8 +4,8 @@
"already_configured": "Ger\u00e4t ist bereits konfiguriert"
},
"error": {
"already_in_progress": "Der Konfigurationsfluss f\u00fcr das Ger\u00e4t wird bereits ausgef\u00fchrt.",
"cannot_connect": "Verbindungsfehler"
"already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt",
"cannot_connect": "Verbindung fehlgeschlagen"
},
"step": {
"user": {

View File

@ -0,0 +1,20 @@
{
"config": {
"abort": {
"already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f"
},
"error": {
"already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor",
"cannot_connect": "Ba\u011flanma hatas\u0131"
},
"step": {
"user": {
"data": {
"host": "Ana Bilgisayar",
"port": "Port"
},
"title": "Agent DVR'\u0131 kurun"
}
}
}
}

View File

@ -0,0 +1,20 @@
{
"config": {
"abort": {
"already_configured": "\u0426\u0435\u0439 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant."
},
"error": {
"already_in_progress": "\u041f\u0440\u043e\u0446\u0435\u0441 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0442\u0440\u0438\u0432\u0430\u0454.",
"cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f"
},
"step": {
"user": {
"data": {
"host": "\u0425\u043e\u0441\u0442",
"port": "\u041f\u043e\u0440\u0442"
},
"title": "Agent DVR"
}
}
}
}

View File

@ -20,6 +20,7 @@ from .const import (
ATTR_API_CAQI,
ATTR_API_CAQI_DESCRIPTION,
ATTR_API_CAQI_LEVEL,
CONF_USE_NEAREST,
DOMAIN,
MAX_REQUESTS_PER_DAY,
NO_AIRLY_SENSORS,
@ -53,6 +54,7 @@ async def async_setup_entry(hass, config_entry):
api_key = config_entry.data[CONF_API_KEY]
latitude = config_entry.data[CONF_LATITUDE]
longitude = config_entry.data[CONF_LONGITUDE]
use_nearest = config_entry.data.get(CONF_USE_NEAREST, False)
# For backwards compat, set unique ID
if config_entry.unique_id is None:
@ -67,7 +69,7 @@ async def async_setup_entry(hass, config_entry):
)
coordinator = AirlyDataUpdateCoordinator(
hass, websession, api_key, latitude, longitude, update_interval
hass, websession, api_key, latitude, longitude, update_interval, use_nearest
)
await coordinator.async_refresh()
@ -107,21 +109,36 @@ async def async_unload_entry(hass, config_entry):
class AirlyDataUpdateCoordinator(DataUpdateCoordinator):
"""Define an object to hold Airly data."""
def __init__(self, hass, session, api_key, latitude, longitude, update_interval):
def __init__(
self,
hass,
session,
api_key,
latitude,
longitude,
update_interval,
use_nearest,
):
"""Initialize."""
self.latitude = latitude
self.longitude = longitude
self.airly = Airly(api_key, session)
self.use_nearest = use_nearest
super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=update_interval)
async def _async_update_data(self):
"""Update data via library."""
data = {}
with async_timeout.timeout(20):
if self.use_nearest:
measurements = self.airly.create_measurements_session_nearest(
self.latitude, self.longitude, max_distance_km=5
)
else:
measurements = self.airly.create_measurements_session_point(
self.latitude, self.longitude
)
with async_timeout.timeout(20):
try:
await measurements.update()
except (AirlyError, ClientConnectorError) as error:

View File

@ -87,13 +87,13 @@ class AirlyAirQuality(CoordinatorEntity, AirQualityEntity):
@round_state
def particulate_matter_2_5(self):
"""Return the particulate matter 2.5 level."""
return self.coordinator.data[ATTR_API_PM25]
return self.coordinator.data.get(ATTR_API_PM25)
@property
@round_state
def particulate_matter_10(self):
"""Return the particulate matter 10 level."""
return self.coordinator.data[ATTR_API_PM10]
return self.coordinator.data.get(ATTR_API_PM10)
@property
def attribution(self):
@ -120,12 +120,19 @@ class AirlyAirQuality(CoordinatorEntity, AirQualityEntity):
@property
def device_state_attributes(self):
"""Return the state attributes."""
return {
attrs = {
LABEL_AQI_DESCRIPTION: self.coordinator.data[ATTR_API_CAQI_DESCRIPTION],
LABEL_ADVICE: self.coordinator.data[ATTR_API_ADVICE],
LABEL_AQI_LEVEL: self.coordinator.data[ATTR_API_CAQI_LEVEL],
LABEL_PM_2_5_LIMIT: self.coordinator.data[ATTR_API_PM25_LIMIT],
LABEL_PM_2_5_PERCENT: round(self.coordinator.data[ATTR_API_PM25_PERCENT]),
LABEL_PM_10_LIMIT: self.coordinator.data[ATTR_API_PM10_LIMIT],
LABEL_PM_10_PERCENT: round(self.coordinator.data[ATTR_API_PM10_PERCENT]),
}
if ATTR_API_PM25 in self.coordinator.data:
attrs[LABEL_PM_2_5_LIMIT] = self.coordinator.data[ATTR_API_PM25_LIMIT]
attrs[LABEL_PM_2_5_PERCENT] = round(
self.coordinator.data[ATTR_API_PM25_PERCENT]
)
if ATTR_API_PM10 in self.coordinator.data:
attrs[LABEL_PM_10_LIMIT] = self.coordinator.data[ATTR_API_PM10_LIMIT]
attrs[LABEL_PM_10_PERCENT] = round(
self.coordinator.data[ATTR_API_PM10_PERCENT]
)
return attrs

View File

@ -10,12 +10,17 @@ from homeassistant.const import (
CONF_LATITUDE,
CONF_LONGITUDE,
CONF_NAME,
HTTP_NOT_FOUND,
HTTP_UNAUTHORIZED,
)
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
from .const import DOMAIN, NO_AIRLY_SENSORS # pylint:disable=unused-import
from .const import ( # pylint:disable=unused-import
CONF_USE_NEAREST,
DOMAIN,
NO_AIRLY_SENSORS,
)
class AirlyFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
@ -27,6 +32,7 @@ class AirlyFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
async def async_step_user(self, user_input=None):
"""Handle a flow initialized by the user."""
errors = {}
use_nearest = False
websession = async_get_clientsession(self.hass)
@ -36,23 +42,32 @@ class AirlyFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
)
self._abort_if_unique_id_configured()
try:
location_valid = await test_location(
location_point_valid = await test_location(
websession,
user_input["api_key"],
user_input["latitude"],
user_input["longitude"],
)
if not location_point_valid:
await test_location(
websession,
user_input["api_key"],
user_input["latitude"],
user_input["longitude"],
use_nearest=True,
)
except AirlyError as err:
if err.status_code == HTTP_UNAUTHORIZED:
errors["base"] = "invalid_api_key"
else:
if not location_valid:
if err.status_code == HTTP_NOT_FOUND:
errors["base"] = "wrong_location"
if not errors:
return self.async_create_entry(
title=user_input[CONF_NAME], data=user_input
)
else:
if not location_point_valid:
use_nearest = True
return self.async_create_entry(
title=user_input[CONF_NAME],
data={**user_input, CONF_USE_NEAREST: use_nearest},
)
return self.async_show_form(
step_id="user",
@ -74,13 +89,17 @@ class AirlyFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
)
async def test_location(client, api_key, latitude, longitude):
async def test_location(client, api_key, latitude, longitude, use_nearest=False):
"""Return true if location is valid."""
airly = Airly(api_key, client)
measurements = airly.create_measurements_session_point(
latitude=latitude, longitude=longitude
)
if use_nearest:
measurements = airly.create_measurements_session_nearest(
latitude=latitude, longitude=longitude, max_distance_km=5
)
else:
measurements = airly.create_measurements_session_point(
latitude=latitude, longitude=longitude
)
with async_timeout.timeout(10):
await measurements.update()

View File

@ -13,6 +13,7 @@ ATTR_API_PM25_LIMIT = "PM25_LIMIT"
ATTR_API_PM25_PERCENT = "PM25_PERCENT"
ATTR_API_PRESSURE = "PRESSURE"
ATTR_API_TEMPERATURE = "TEMPERATURE"
CONF_USE_NEAREST = "use_nearest"
DEFAULT_NAME = "Airly"
DOMAIN = "airly"
MANUFACTURER = "Airly sp. z o.o."

View File

@ -67,7 +67,9 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
sensors = []
for sensor in SENSOR_TYPES:
sensors.append(AirlySensor(coordinator, name, sensor))
# When we use the nearest method, we are not sure which sensors are available
if coordinator.data.get(sensor):
sensors.append(AirlySensor(coordinator, name, sensor))
async_add_entities(sensors, False)

View File

@ -1,9 +1,10 @@
{
"config": {
"abort": {
"already_configured": "Die Airly-Integration ist f\u00fcr diese Koordinaten bereits konfiguriert."
"already_configured": "Standort ist bereits konfiguriert"
},
"error": {
"invalid_api_key": "Ung\u00fcltiger API-Schl\u00fcssel",
"wrong_location": "Keine Airly Luftmessstation an diesem Ort"
},
"step": {
@ -12,7 +13,7 @@
"api_key": "API-Schl\u00fcssel",
"latitude": "Breitengrad",
"longitude": "L\u00e4ngengrad",
"name": "Name der Integration"
"name": "Name"
},
"description": "Einrichtung der Airly-Luftqualit\u00e4t Integration. Um einen API-Schl\u00fcssel zu generieren, registriere dich auf https://developer.airly.eu/register",
"title": "Airly"
@ -21,7 +22,7 @@
},
"system_health": {
"info": {
"can_reach_server": "Airly Server erreichen"
"can_reach_server": "Airly-Server erreichen"
}
}
}

View File

@ -1,4 +1,21 @@
{
"config": {
"abort": {
"already_configured": "Konum zaten yap\u0131land\u0131r\u0131lm\u0131\u015f"
},
"error": {
"invalid_api_key": "Ge\u00e7ersiz API anahtar\u0131"
},
"step": {
"user": {
"data": {
"api_key": "API Anahtar\u0131",
"latitude": "Enlem",
"longitude": "Boylam"
}
}
}
},
"system_health": {
"info": {
"can_reach_server": "Airly sunucusuna eri\u015fin"

View File

@ -0,0 +1,28 @@
{
"config": {
"abort": {
"already_configured": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0434\u043b\u044f \u0446\u044c\u043e\u0433\u043e \u043c\u0456\u0441\u0446\u0435\u0437\u043d\u0430\u0445\u043e\u0434\u0436\u0435\u043d\u043d\u044f \u0432\u0436\u0435 \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u0435."
},
"error": {
"invalid_api_key": "\u0425\u0438\u0431\u043d\u0438\u0439 \u043a\u043b\u044e\u0447 API",
"wrong_location": "\u0423 \u0446\u0456\u0439 \u043e\u0431\u043b\u0430\u0441\u0442\u0456 \u043d\u0435\u043c\u0430\u0454 \u0432\u0438\u043c\u0456\u0440\u044e\u0432\u0430\u043b\u044c\u043d\u0438\u0445 \u0441\u0442\u0430\u043d\u0446\u0456\u0439 Airly."
},
"step": {
"user": {
"data": {
"api_key": "\u041a\u043b\u044e\u0447 API",
"latitude": "\u0428\u0438\u0440\u043e\u0442\u0430",
"longitude": "\u0414\u043e\u0432\u0433\u043e\u0442\u0430",
"name": "\u041d\u0430\u0437\u0432\u0430"
},
"description": "\u0406\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u044f \u0441\u0435\u0440\u0432\u0456\u0441\u0443 \u0437 \u0430\u043d\u0430\u043b\u0456\u0437\u0443 \u044f\u043a\u043e\u0441\u0442\u0456 \u043f\u043e\u0432\u0456\u0442\u0440\u044f Airly. \u0429\u043e\u0431 \u0441\u0442\u0432\u043e\u0440\u0438\u0442\u0438 \u043a\u043b\u044e\u0447 API, \u043f\u0435\u0440\u0435\u0439\u0434\u0456\u0442\u044c \u0437\u0430 \u043f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f\u043c https://developer.airly.eu/register.",
"title": "Airly"
}
}
},
"system_health": {
"info": {
"can_reach_server": "\u0414\u043e\u0441\u0442\u0443\u043f \u0434\u043e \u0441\u0435\u0440\u0432\u0435\u0440\u0430 Airly"
}
}
}

View File

@ -0,0 +1,161 @@
"""The AirNow integration."""
import asyncio
import datetime
import logging
from aiohttp.client_exceptions import ClientConnectorError
from pyairnow import WebServiceAPI
from pyairnow.conv import aqi_to_concentration
from pyairnow.errors import AirNowError
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_RADIUS
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import (
ATTR_API_AQI,
ATTR_API_AQI_DESCRIPTION,
ATTR_API_AQI_LEVEL,
ATTR_API_AQI_PARAM,
ATTR_API_CAT_DESCRIPTION,
ATTR_API_CAT_LEVEL,
ATTR_API_CATEGORY,
ATTR_API_PM25,
ATTR_API_POLLUTANT,
ATTR_API_REPORT_DATE,
ATTR_API_REPORT_HOUR,
ATTR_API_STATE,
ATTR_API_STATION,
ATTR_API_STATION_LATITUDE,
ATTR_API_STATION_LONGITUDE,
DOMAIN,
)
_LOGGER = logging.getLogger(__name__)
PLATFORMS = ["sensor"]
async def async_setup(hass: HomeAssistant, config: dict):
"""Set up the AirNow component."""
return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
"""Set up AirNow from a config entry."""
api_key = entry.data[CONF_API_KEY]
latitude = entry.data[CONF_LATITUDE]
longitude = entry.data[CONF_LONGITUDE]
distance = entry.data[CONF_RADIUS]
# Reports are published hourly but update twice per hour
update_interval = datetime.timedelta(minutes=30)
# Setup the Coordinator
session = async_get_clientsession(hass)
coordinator = AirNowDataUpdateCoordinator(
hass, session, api_key, latitude, longitude, distance, update_interval
)
# Sync with Coordinator
await coordinator.async_refresh()
if not coordinator.last_update_success:
raise ConfigEntryNotReady
# Store Entity and Initialize Platforms
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.entry_id] = coordinator
for component in PLATFORMS:
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, component)
)
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
"""Unload a config entry."""
unload_ok = all(
await asyncio.gather(
*[
hass.config_entries.async_forward_entry_unload(entry, component)
for component in PLATFORMS
]
)
)
if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok
class AirNowDataUpdateCoordinator(DataUpdateCoordinator):
"""Define an object to hold Airly data."""
def __init__(
self, hass, session, api_key, latitude, longitude, distance, update_interval
):
"""Initialize."""
self.latitude = latitude
self.longitude = longitude
self.distance = distance
self.airnow = WebServiceAPI(api_key, session=session)
super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=update_interval)
async def _async_update_data(self):
"""Update data via library."""
data = {}
try:
obs = await self.airnow.observations.latLong(
self.latitude,
self.longitude,
distance=self.distance,
)
except (AirNowError, ClientConnectorError) as error:
raise UpdateFailed(error) from error
if not obs:
raise UpdateFailed("No data was returned from AirNow")
max_aqi = 0
max_aqi_level = 0
max_aqi_desc = ""
max_aqi_poll = ""
for obv in obs:
# Convert AQIs to Concentration
pollutant = obv[ATTR_API_AQI_PARAM]
concentration = aqi_to_concentration(obv[ATTR_API_AQI], pollutant)
data[obv[ATTR_API_AQI_PARAM]] = concentration
# Overall AQI is the max of all pollutant AQIs
if obv[ATTR_API_AQI] > max_aqi:
max_aqi = obv[ATTR_API_AQI]
max_aqi_level = obv[ATTR_API_CATEGORY][ATTR_API_CAT_LEVEL]
max_aqi_desc = obv[ATTR_API_CATEGORY][ATTR_API_CAT_DESCRIPTION]
max_aqi_poll = pollutant
# Copy other data from PM2.5 Value
if obv[ATTR_API_AQI_PARAM] == ATTR_API_PM25:
# Copy Report Details
data[ATTR_API_REPORT_DATE] = obv[ATTR_API_REPORT_DATE]
data[ATTR_API_REPORT_HOUR] = obv[ATTR_API_REPORT_HOUR]
# Copy Station Details
data[ATTR_API_STATE] = obv[ATTR_API_STATE]
data[ATTR_API_STATION] = obv[ATTR_API_STATION]
data[ATTR_API_STATION_LATITUDE] = obv[ATTR_API_STATION_LATITUDE]
data[ATTR_API_STATION_LONGITUDE] = obv[ATTR_API_STATION_LONGITUDE]
# Store Overall AQI
data[ATTR_API_AQI] = max_aqi
data[ATTR_API_AQI_LEVEL] = max_aqi_level
data[ATTR_API_AQI_DESCRIPTION] = max_aqi_desc
data[ATTR_API_POLLUTANT] = max_aqi_poll
return data

View File

@ -0,0 +1,110 @@
"""Config flow for AirNow integration."""
import logging
from pyairnow import WebServiceAPI
from pyairnow.errors import AirNowError, InvalidKeyError
import voluptuous as vol
from homeassistant import config_entries, core, exceptions
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_RADIUS
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
from .const import DOMAIN # pylint:disable=unused-import
_LOGGER = logging.getLogger(__name__)
async def validate_input(hass: core.HomeAssistant, data):
"""
Validate the user input allows us to connect.
Data has the keys from DATA_SCHEMA with values provided by the user.
"""
session = async_get_clientsession(hass)
client = WebServiceAPI(data[CONF_API_KEY], session=session)
lat = data[CONF_LATITUDE]
lng = data[CONF_LONGITUDE]
distance = data[CONF_RADIUS]
# Check that the provided latitude/longitude provide a response
try:
test_data = await client.observations.latLong(lat, lng, distance=distance)
except InvalidKeyError as exc:
raise InvalidAuth from exc
except AirNowError as exc:
raise CannotConnect from exc
if not test_data:
raise InvalidLocation
# Validation Succeeded
return True
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for AirNow."""
VERSION = 1
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL
async def async_step_user(self, user_input=None):
"""Handle the initial step."""
errors = {}
if user_input is not None:
# Set a unique id based on latitude/longitude
await self.async_set_unique_id(
f"{user_input[CONF_LATITUDE]}-{user_input[CONF_LONGITUDE]}"
)
self._abort_if_unique_id_configured()
try:
# Validate inputs
await validate_input(self.hass, user_input)
except CannotConnect:
errors["base"] = "cannot_connect"
except InvalidAuth:
errors["base"] = "invalid_auth"
except InvalidLocation:
errors["base"] = "invalid_location"
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
else:
# Create Entry
return self.async_create_entry(
title=f"AirNow Sensor at {user_input[CONF_LATITUDE]}, {user_input[CONF_LONGITUDE]}",
data=user_input,
)
return self.async_show_form(
step_id="user",
data_schema=vol.Schema(
{
vol.Required(CONF_API_KEY): str,
vol.Optional(
CONF_LATITUDE, default=self.hass.config.latitude
): cv.latitude,
vol.Optional(
CONF_LONGITUDE, default=self.hass.config.longitude
): cv.longitude,
vol.Optional(CONF_RADIUS, default=150): int,
}
),
errors=errors,
)
class CannotConnect(exceptions.HomeAssistantError):
"""Error to indicate we cannot connect."""
class InvalidAuth(exceptions.HomeAssistantError):
"""Error to indicate there is invalid auth."""
class InvalidLocation(exceptions.HomeAssistantError):
"""Error to indicate the location is invalid."""

View File

@ -0,0 +1,21 @@
"""Constants for the AirNow integration."""
ATTR_API_AQI = "AQI"
ATTR_API_AQI_LEVEL = "Category.Number"
ATTR_API_AQI_DESCRIPTION = "Category.Name"
ATTR_API_AQI_PARAM = "ParameterName"
ATTR_API_CATEGORY = "Category"
ATTR_API_CAT_LEVEL = "Number"
ATTR_API_CAT_DESCRIPTION = "Name"
ATTR_API_O3 = "O3"
ATTR_API_PM25 = "PM2.5"
ATTR_API_POLLUTANT = "Pollutant"
ATTR_API_REPORT_DATE = "HourObserved"
ATTR_API_REPORT_HOUR = "DateObserved"
ATTR_API_STATE = "StateCode"
ATTR_API_STATION = "ReportingArea"
ATTR_API_STATION_LATITUDE = "Latitude"
ATTR_API_STATION_LONGITUDE = "Longitude"
DEFAULT_NAME = "AirNow"
DOMAIN = "airnow"
SENSOR_AQI_ATTR_DESCR = "description"
SENSOR_AQI_ATTR_LEVEL = "level"

View File

@ -0,0 +1,12 @@
{
"domain": "airnow",
"name": "AirNow",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/airnow",
"requirements": [
"pyairnow==1.1.0"
],
"codeowners": [
"@asymworks"
]
}

View File

@ -0,0 +1,118 @@
"""Support for the AirNow sensor service."""
from homeassistant.const import (
ATTR_ATTRIBUTION,
ATTR_DEVICE_CLASS,
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_MILLION,
)
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import (
ATTR_API_AQI,
ATTR_API_AQI_DESCRIPTION,
ATTR_API_AQI_LEVEL,
ATTR_API_O3,
ATTR_API_PM25,
DOMAIN,
SENSOR_AQI_ATTR_DESCR,
SENSOR_AQI_ATTR_LEVEL,
)
ATTRIBUTION = "Data provided by AirNow"
ATTR_ICON = "icon"
ATTR_LABEL = "label"
ATTR_UNIT = "unit"
PARALLEL_UPDATES = 1
SENSOR_TYPES = {
ATTR_API_AQI: {
ATTR_DEVICE_CLASS: None,
ATTR_ICON: "mdi:blur",
ATTR_LABEL: ATTR_API_AQI,
ATTR_UNIT: "aqi",
},
ATTR_API_PM25: {
ATTR_DEVICE_CLASS: None,
ATTR_ICON: "mdi:blur",
ATTR_LABEL: ATTR_API_PM25,
ATTR_UNIT: CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
},
ATTR_API_O3: {
ATTR_DEVICE_CLASS: None,
ATTR_ICON: "mdi:blur",
ATTR_LABEL: ATTR_API_O3,
ATTR_UNIT: CONCENTRATION_PARTS_PER_MILLION,
},
}
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up AirNow sensor entities based on a config entry."""
coordinator = hass.data[DOMAIN][config_entry.entry_id]
sensors = []
for sensor in SENSOR_TYPES:
sensors.append(AirNowSensor(coordinator, sensor))
async_add_entities(sensors, False)
class AirNowSensor(CoordinatorEntity):
"""Define an AirNow sensor."""
def __init__(self, coordinator, kind):
"""Initialize."""
super().__init__(coordinator)
self.kind = kind
self._device_class = None
self._state = None
self._icon = None
self._unit_of_measurement = None
self._attrs = {ATTR_ATTRIBUTION: ATTRIBUTION}
@property
def name(self):
"""Return the name."""
return f"AirNow {SENSOR_TYPES[self.kind][ATTR_LABEL]}"
@property
def state(self):
"""Return the state."""
self._state = self.coordinator.data[self.kind]
return self._state
@property
def device_state_attributes(self):
"""Return the state attributes."""
if self.kind == ATTR_API_AQI:
self._attrs[SENSOR_AQI_ATTR_DESCR] = self.coordinator.data[
ATTR_API_AQI_DESCRIPTION
]
self._attrs[SENSOR_AQI_ATTR_LEVEL] = self.coordinator.data[
ATTR_API_AQI_LEVEL
]
return self._attrs
@property
def icon(self):
"""Return the icon."""
self._icon = SENSOR_TYPES[self.kind][ATTR_ICON]
return self._icon
@property
def device_class(self):
"""Return the device_class."""
return SENSOR_TYPES[self.kind][ATTR_DEVICE_CLASS]
@property
def unique_id(self):
"""Return a unique_id for this entity."""
return f"{self.coordinator.latitude}-{self.coordinator.longitude}-{self.kind.lower()}"
@property
def unit_of_measurement(self):
"""Return the unit the value is expressed in."""
return SENSOR_TYPES[self.kind][ATTR_UNIT]

View File

@ -0,0 +1,26 @@
{
"title": "AirNow",
"config": {
"step": {
"user": {
"title": "AirNow",
"description": "Set up AirNow air quality integration. To generate API key go to https://docs.airnowapi.org/account/request/",
"data": {
"api_key": "[%key:common::config_flow::data::api_key%]",
"latitude": "[%key:common::config_flow::data::latitude%]",
"longitude": "[%key:common::config_flow::data::longitude%]",
"radius": "Station Radius (miles; optional)"
}
}
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"invalid_location": "No results found for that location",
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
}
}
}

View File

@ -0,0 +1,26 @@
{
"config": {
"abort": {
"already_configured": "El dispositiu ja est\u00e0 configurat"
},
"error": {
"cannot_connect": "Ha fallat la connexi\u00f3",
"invalid_auth": "Autenticaci\u00f3 inv\u00e0lida",
"invalid_location": "No s'ha trobat cap resultat per a aquesta ubicaci\u00f3",
"unknown": "Error inesperat"
},
"step": {
"user": {
"data": {
"api_key": "Clau API",
"latitude": "Latitud",
"longitude": "Longitud",
"radius": "Radi de l'estaci\u00f3 (milles; opcional)"
},
"description": "Configura la integraci\u00f3 de qualitat d'aire AirNow. Per generar la clau API, v\u00e9s a https://docs.airnowapi.org/account/request/",
"title": "AirNow"
}
}
},
"title": "AirNow"
}

View File

@ -0,0 +1,23 @@
{
"config": {
"abort": {
"already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno"
},
"error": {
"cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit",
"invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed",
"unknown": "Neo\u010dek\u00e1van\u00e1 chyba"
},
"step": {
"user": {
"data": {
"api_key": "Kl\u00ed\u010d API",
"latitude": "Zem\u011bpisn\u00e1 \u0161\u00ed\u0159ka",
"longitude": "Zem\u011bpisn\u00e1 d\u00e9lka"
},
"title": "AirNow"
}
}
},
"title": "AirNow"
}

View File

@ -0,0 +1,24 @@
{
"config": {
"abort": {
"already_configured": "Ger\u00e4t ist bereits konfiguriert"
},
"error": {
"cannot_connect": "Verbindung fehlgeschlagen",
"invalid_auth": "Ung\u00fcltige Authentifizierung",
"invalid_location": "F\u00fcr diesen Standort wurden keine Ergebnisse gefunden",
"unknown": "Unerwarteter Fehler"
},
"step": {
"user": {
"data": {
"api_key": "API-Schl\u00fcssel",
"latitude": "Breitengrad",
"longitude": "L\u00e4ngengrad"
},
"title": "AirNow"
}
}
},
"title": "AirNow"
}

View File

@ -0,0 +1,26 @@
{
"config": {
"abort": {
"already_configured": "Device is already configured"
},
"error": {
"cannot_connect": "Failed to connect",
"invalid_auth": "Invalid authentication",
"invalid_location": "No results found for that location",
"unknown": "Unexpected error"
},
"step": {
"user": {
"data": {
"api_key": "API Key",
"latitude": "Latitude",
"longitude": "Longitude",
"radius": "Station Radius (miles; optional)"
},
"description": "Set up AirNow air quality integration. To generate API key go to https://docs.airnowapi.org/account/request/",
"title": "AirNow"
}
}
},
"title": "AirNow"
}

View File

@ -0,0 +1,26 @@
{
"config": {
"abort": {
"already_configured": "El dispositivo ya est\u00e1 configurado"
},
"error": {
"cannot_connect": "No se pudo conectar",
"invalid_auth": "Autenticaci\u00f3n no v\u00e1lida",
"invalid_location": "No se han encontrado resultados para esa ubicaci\u00f3n",
"unknown": "Error inesperado"
},
"step": {
"user": {
"data": {
"api_key": "Clave API",
"latitude": "Latitud",
"longitude": "Longitud",
"radius": "Radio de la estaci\u00f3n (millas; opcional)"
},
"description": "Configurar la integraci\u00f3n de calidad del aire de AirNow. Para generar una clave API, ve a https://docs.airnowapi.org/account/request/",
"title": "AirNow"
}
}
},
"title": "AirNow"
}

View File

@ -0,0 +1,26 @@
{
"config": {
"abort": {
"already_configured": "Seade on juba h\u00e4\u00e4lestatud"
},
"error": {
"cannot_connect": "\u00dchendamine nurjus",
"invalid_auth": "Vigane autentimine",
"invalid_location": "Selle asukoha jaoks ei leitud andmeid",
"unknown": "Ootamatu t\u00f5rge"
},
"step": {
"user": {
"data": {
"api_key": "API v\u00f5ti",
"latitude": "Laiuskraad",
"longitude": "Pikkuskraad",
"radius": "Jaama raadius (miilid; valikuline)"
},
"description": "Seadista AirNow \u00f5hukvaliteedi sidumine. API-v\u00f5tme loomiseks mine aadressile https://docs.airnowapi.org/account/request/",
"title": ""
}
}
},
"title": ""
}

View File

@ -0,0 +1,26 @@
{
"config": {
"abort": {
"already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9"
},
"error": {
"cannot_connect": "\u00c9chec \u00e0 la connexion",
"invalid_auth": "Authentification invalide",
"invalid_location": "Aucun r\u00e9sultat trouv\u00e9 pour cet emplacement",
"unknown": "Erreur inattendue"
},
"step": {
"user": {
"data": {
"api_key": "Cl\u00e9 API",
"latitude": "Latitude",
"longitude": "Longitude",
"radius": "Rayon d'action de la station (en miles, facultatif)"
},
"description": "Configurez l'int\u00e9gration de la qualit\u00e9 de l'air AirNow. Pour g\u00e9n\u00e9rer la cl\u00e9 API, acc\u00e9dez \u00e0 https://docs.airnowapi.org/account/request/",
"title": "AirNow"
}
}
},
"title": "AirNow"
}

View File

@ -0,0 +1,26 @@
{
"config": {
"abort": {
"already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato"
},
"error": {
"cannot_connect": "Impossibile connettersi",
"invalid_auth": "Autenticazione non valida",
"invalid_location": "Nessun risultato trovato per quella localit\u00e0",
"unknown": "Errore imprevisto"
},
"step": {
"user": {
"data": {
"api_key": "Chiave API",
"latitude": "Latitudine",
"longitude": "Logitudine",
"radius": "Raggio stazione (miglia; opzionale)"
},
"description": "Configura l'integrazione per la qualit\u00e0 dell'aria AirNow. Per generare la chiave API, vai su https://docs.airnowapi.org/account/request/",
"title": "AirNow"
}
}
},
"title": "AirNow"
}

View File

@ -0,0 +1,24 @@
{
"config": {
"abort": {
"already_configured": "Apparat ass scho konfigur\u00e9iert"
},
"error": {
"cannot_connect": "Feeler beim verbannen",
"invalid_auth": "Ong\u00eblteg Authentifikatioun",
"invalid_location": "Keng Resultater fonnt fir d\u00ebse Standuert",
"unknown": "Onerwaarte Feeler"
},
"step": {
"user": {
"data": {
"api_key": "API Schl\u00ebssel",
"latitude": "L\u00e4ngegrad",
"longitude": "Breedegrag"
},
"title": "AirNow"
}
}
},
"title": "AirNow"
}

View File

@ -0,0 +1,26 @@
{
"config": {
"abort": {
"already_configured": "Enheten er allerede konfigurert"
},
"error": {
"cannot_connect": "Tilkobling mislyktes",
"invalid_auth": "Ugyldig godkjenning",
"invalid_location": "Ingen resultater funnet for den plasseringen",
"unknown": "Uventet feil"
},
"step": {
"user": {
"data": {
"api_key": "API-n\u00f8kkel",
"latitude": "Breddegrad",
"longitude": "Lengdegrad",
"radius": "Stasjonsradius (miles; valgfritt)"
},
"description": "Konfigurer integrering av luftkvalitet i AirNow. For \u00e5 generere en API-n\u00f8kkel, g\u00e5r du til https://docs.airnowapi.org/account/request/",
"title": ""
}
}
},
"title": ""
}

View File

@ -0,0 +1,26 @@
{
"config": {
"abort": {
"already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane"
},
"error": {
"cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia",
"invalid_auth": "Niepoprawne uwierzytelnienie",
"invalid_location": "Brak wynik\u00f3w dla tej lokalizacji",
"unknown": "Nieoczekiwany b\u0142\u0105d"
},
"step": {
"user": {
"data": {
"api_key": "Klucz API",
"latitude": "Szeroko\u015b\u0107 geograficzna",
"longitude": "D\u0142ugo\u015b\u0107 geograficzna",
"radius": "Promie\u0144 od stacji (w milach; opcjonalnie)"
},
"description": "Konfiguracja integracji jako\u015bci powietrza AirNow. Aby wygenerowa\u0107 klucz API, przejd\u017a do https://docs.airnowapi.org/account/request/",
"title": "AirNow"
}
}
},
"title": "AirNow"
}

View File

@ -0,0 +1,23 @@
{
"config": {
"abort": {
"already_configured": "O dispositivo j\u00e1 est\u00e1 configurado"
},
"error": {
"cannot_connect": "Falha na liga\u00e7\u00e3o",
"invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida",
"unknown": "Erro inesperado"
},
"step": {
"user": {
"data": {
"api_key": "API Key",
"latitude": "Latitude",
"longitude": "Longitude"
},
"title": ""
}
}
},
"title": ""
}

View File

@ -0,0 +1,26 @@
{
"config": {
"abort": {
"already_configured": "\u042d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e \u0432 Home Assistant."
},
"error": {
"cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.",
"invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.",
"invalid_location": "\u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e.",
"unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430."
},
"step": {
"user": {
"data": {
"api_key": "\u041a\u043b\u044e\u0447 API",
"latitude": "\u0428\u0438\u0440\u043e\u0442\u0430",
"longitude": "\u0414\u043e\u043b\u0433\u043e\u0442\u0430",
"radius": "\u0420\u0430\u0434\u0438\u0443\u0441 \u0441\u0442\u0430\u043d\u0446\u0438\u0438 (\u0432 \u043c\u0438\u043b\u044f\u0445; \u043d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e)"
},
"description": "\u0418\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u043f\u043e \u0430\u043d\u0430\u043b\u0438\u0437\u0443 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0430 \u0432\u043e\u0437\u0434\u0443\u0445\u0430 AirNow. \u0427\u0442\u043e\u0431\u044b \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043a\u043b\u044e\u0447 API, \u043f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u043f\u043e \u0441\u0441\u044b\u043b\u043a\u0435 https://docs.airnowapi.org/account/request/.",
"title": "AirNow"
}
}
},
"title": "AirNow"
}

View File

@ -0,0 +1,25 @@
{
"config": {
"abort": {
"already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f"
},
"error": {
"cannot_connect": "Ba\u011flanma hatas\u0131",
"invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama",
"invalid_location": "Bu konum i\u00e7in hi\u00e7bir sonu\u00e7 bulunamad\u0131",
"unknown": "Beklenmeyen hata"
},
"step": {
"user": {
"data": {
"api_key": "API Anahtar\u0131",
"latitude": "Enlem",
"longitude": "Boylam",
"radius": "\u0130stasyon Yar\u0131\u00e7ap\u0131 (mil; iste\u011fe ba\u011fl\u0131)"
},
"title": "AirNow"
}
}
},
"title": "AirNow"
}

View File

@ -0,0 +1,26 @@
{
"config": {
"abort": {
"already_configured": "\u0426\u0435\u0439 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant."
},
"error": {
"cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f",
"invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f.",
"invalid_location": "\u041d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u043e \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0456\u0432 \u0434\u043b\u044f \u0446\u044c\u043e\u0433\u043e \u043c\u0456\u0441\u0446\u0435\u0437\u043d\u0430\u0445\u043e\u0434\u0436\u0435\u043d\u043d\u044f",
"unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430"
},
"step": {
"user": {
"data": {
"api_key": "\u041a\u043b\u044e\u0447 API",
"latitude": "\u0428\u0438\u0440\u043e\u0442\u0430",
"longitude": "\u0414\u043e\u0432\u0433\u043e\u0442\u0430",
"radius": "\u0420\u0430\u0434\u0456\u0443\u0441 \u0441\u0442\u0430\u043d\u0446\u0456\u0457 (\u043c\u0438\u043b\u0456; \u043d\u0435\u043e\u0431\u043e\u0432\u2019\u044f\u0437\u043a\u043e\u0432\u043e)"
},
"description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u044e \u044f\u043a\u043e\u0441\u0442\u0456 \u043f\u043e\u0432\u0456\u0442\u0440\u044f AirNow. \u0429\u043e\u0431 \u0437\u0433\u0435\u043d\u0435\u0440\u0443\u0432\u0430\u0442\u0438 \u043a\u043b\u044e\u0447 API, \u043f\u0435\u0440\u0435\u0439\u0434\u0456\u0442\u044c \u043d\u0430 \u0441\u0442\u043e\u0440\u0456\u043d\u043a\u0443 https://docs.airnowapi.org/account/request/",
"title": "AirNow"
}
}
},
"title": "AirNow"
}

View File

@ -0,0 +1,26 @@
{
"config": {
"abort": {
"already_configured": "\u88dd\u7f6e\u7d93\u8a2d\u5b9a\u5b8c\u6210"
},
"error": {
"cannot_connect": "\u9023\u7dda\u5931\u6557",
"invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548",
"invalid_location": "\u627e\u4e0d\u5230\u8a72\u4f4d\u7f6e\u7684\u7d50\u679c",
"unknown": "\u672a\u9810\u671f\u932f\u8aa4"
},
"step": {
"user": {
"data": {
"api_key": "API \u5bc6\u9470",
"latitude": "\u7def\u5ea6",
"longitude": "\u7d93\u5ea6",
"radius": "\u89c0\u6e2c\u7ad9\u534a\u5f91\uff08\u82f1\u91cc\uff1b\u9078\u9805\uff09"
},
"description": "\u6b32\u8a2d\u5b9a AirNow \u7a7a\u6c23\u54c1\u8cea\u6574\u5408\u3002\u8acb\u81f3 https://docs.airnowapi.org/account/request/ \u7522\u751f API \u5bc6\u9470",
"title": "AirNow"
}
}
},
"title": "AirNow"
}

View File

@ -58,25 +58,6 @@ NODE_PRO_SENSORS = [
(SENSOR_KIND_TEMPERATURE, "Temperature", DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS),
]
POLLUTANT_LEVEL_MAPPING = [
{"label": "Good", "icon": "mdi:emoticon-excited", "minimum": 0, "maximum": 50},
{"label": "Moderate", "icon": "mdi:emoticon-happy", "minimum": 51, "maximum": 100},
{
"label": "Unhealthy for sensitive groups",
"icon": "mdi:emoticon-neutral",
"minimum": 101,
"maximum": 150,
},
{"label": "Unhealthy", "icon": "mdi:emoticon-sad", "minimum": 151, "maximum": 200},
{
"label": "Very Unhealthy",
"icon": "mdi:emoticon-dead",
"minimum": 201,
"maximum": 300,
},
{"label": "Hazardous", "icon": "mdi:biohazard", "minimum": 301, "maximum": 10000},
]
POLLUTANT_MAPPING = {
"co": {"label": "Carbon Monoxide", "unit": CONCENTRATION_PARTS_PER_MILLION},
"n2": {"label": "Nitrogen Dioxide", "unit": CONCENTRATION_PARTS_PER_BILLION},
@ -87,6 +68,22 @@ POLLUTANT_MAPPING = {
}
@callback
def async_get_pollutant_level_info(value):
"""Return a verbal pollutant level (and associated icon) for a numeric value."""
if 0 <= value <= 50:
return ("Good", "mdi:emoticon-excited")
if 51 <= value <= 100:
return ("Moderate", "mdi:emoticon-happy")
if 101 <= value <= 150:
return ("Unhealthy for sensitive groups", "mdi:emoticon-neutral")
if 151 <= value <= 200:
return ("Unhealthy", "mdi:emoticon-sad")
if 201 <= value <= 300:
return ("Very Unhealthy", "mdi:emoticon-dead")
return ("Hazardous", "mdi:biohazard")
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up AirVisual sensors based on a config entry."""
coordinator = hass.data[DOMAIN][DATA_COORDINATOR][config_entry.entry_id]
@ -171,13 +168,7 @@ class AirVisualGeographySensor(AirVisualEntity):
if self._kind == SENSOR_KIND_LEVEL:
aqi = data[f"aqi{self._locale}"]
[level] = [
i
for i in POLLUTANT_LEVEL_MAPPING
if i["minimum"] <= aqi <= i["maximum"]
]
self._state = level["label"]
self._icon = level["icon"]
self._state, self._icon = async_get_pollutant_level_info(aqi)
elif self._kind == SENSOR_KIND_AQI:
self._state = data[f"aqi{self._locale}"]
elif self._kind == SENSOR_KIND_POLLUTANT:

View File

@ -0,0 +1,11 @@
{
"config": {
"step": {
"geography_by_name": {
"data": {
"country": "\u0627\u0644\u062f\u0648\u0644\u0629"
}
}
}
}
}

View File

@ -7,7 +7,8 @@
"error": {
"cannot_connect": "Ha fallat la connexi\u00f3",
"general_error": "Error inesperat",
"invalid_api_key": "Clau API inv\u00e0lida"
"invalid_api_key": "Clau API inv\u00e0lida",
"location_not_found": "No s'ha trobat la ubicaci\u00f3"
},
"step": {
"geography": {
@ -17,7 +18,26 @@
"longitude": "Longitud"
},
"description": "Utilitza l'API d'AirVisual per monitoritzar una ubicaci\u00f3 geogr\u00e0fica.",
"title": "Configuraci\u00f3 localitzaci\u00f3 geogr\u00e0fica"
"title": "Configura una ubicaci\u00f3 geogr\u00e0fica"
},
"geography_by_coords": {
"data": {
"api_key": "Clau API",
"latitude": "Latitud",
"longitude": "Longitud"
},
"description": "Utilitza l'API d'AirVisual per monitoritzar una latitud/longitud.",
"title": "Configura una ubicaci\u00f3 geogr\u00e0fica"
},
"geography_by_name": {
"data": {
"api_key": "Clau API",
"city": "Ciutat",
"country": "Pa\u00eds",
"state": "Estat"
},
"description": "Utilitza l'API d'AirVisual per monitoritzar un/a ciutat/estat/pa\u00eds",
"title": "Configura una ubicaci\u00f3 geogr\u00e0fica"
},
"node_pro": {
"data": {

View File

@ -1,12 +1,13 @@
{
"config": {
"abort": {
"already_configured": "Diese Koordinaten oder Node/Pro ID sind bereits registriert."
"already_configured": "Diese Koordinaten oder Node/Pro ID sind bereits registriert.",
"reauth_successful": "Die erneute Authentifizierung war erfolgreich"
},
"error": {
"cannot_connect": "Verbindungsfehler",
"general_error": "Es gab einen unbekannten Fehler.",
"invalid_api_key": "Ung\u00fcltiger API-Schl\u00fcssel bereitgestellt."
"cannot_connect": "Verbindung fehlgeschlagen",
"general_error": "Unerwarteter Fehler",
"invalid_api_key": "Ung\u00fcltiger API-Schl\u00fcssel"
},
"step": {
"geography": {
@ -19,7 +20,7 @@
},
"node_pro": {
"data": {
"ip_address": "IP-Adresse/Hostname des Ger\u00e4ts",
"ip_address": "Host",
"password": "Passwort"
},
"description": "\u00dcberwachen Sie eine pers\u00f6nliche AirVisual-Einheit. Das Passwort kann von der Benutzeroberfl\u00e4che des Ger\u00e4ts abgerufen werden.",

View File

@ -19,6 +19,15 @@
"description": "Use the AirVisual cloud API to monitor a geographical location.",
"title": "Configure a Geography"
},
"geography_by_coords": {
"data": {
"api_key": "API Key",
"latitude": "Latitude",
"longitude": "Longitude"
},
"description": "Use the AirVisual cloud API to monitor a geographical location.",
"title": "Configure a Geography"
},
"node_pro": {
"data": {
"ip_address": "Host",
@ -54,4 +63,4 @@
}
}
}
}
}

View File

@ -7,7 +7,8 @@
"error": {
"cannot_connect": "\u00dchendamine nurjus",
"general_error": "Tundmatu viga",
"invalid_api_key": "Vale API v\u00f5ti"
"invalid_api_key": "Vale API v\u00f5ti",
"location_not_found": "Asukohta ei leitud"
},
"step": {
"geography": {
@ -19,6 +20,25 @@
"description": "Kasutage AirVisual pilve API-t geograafilise asukoha j\u00e4lgimiseks.",
"title": "Seadista Geography"
},
"geography_by_coords": {
"data": {
"api_key": "API v\u00f5ti",
"latitude": "Laiuskraad",
"longitude": "Pikkuskraad"
},
"description": "Kasuta AirVisual pilve API-t pikkus/laiuskraadi j\u00e4lgimiseks.",
"title": "Seadista Geography sidumine"
},
"geography_by_name": {
"data": {
"api_key": "API v\u00f5ti",
"city": "Linn",
"country": "Riik",
"state": "olek"
},
"description": "Kasuta AirVisual pilve API-t linna/osariigi/riigi j\u00e4lgimiseks.",
"title": "Seadista Geography sidumine"
},
"node_pro": {
"data": {
"ip_address": "\u00dcksuse IP-aadress / hostinimi",

View File

@ -6,8 +6,8 @@
},
"error": {
"cannot_connect": "\u00c9chec de connexion",
"general_error": "Une erreur inconnue est survenue.",
"invalid_api_key": "La cl\u00e9 API fournie n'est pas valide."
"general_error": "Erreur inattendue",
"invalid_api_key": "Cl\u00e9 API invalide"
},
"step": {
"geography": {
@ -21,11 +21,11 @@
},
"node_pro": {
"data": {
"ip_address": "Adresse IP / nom d'h\u00f4te de l'unit\u00e9",
"ip_address": "H\u00f4te",
"password": "Mot de passe"
},
"description": "Surveillez une unit\u00e9 AirVisual personnelle. Le mot de passe peut \u00eatre r\u00e9cup\u00e9r\u00e9 dans l'interface utilisateur de l'unit\u00e9.",
"title": "Configurer un AirVisual Node/Pro"
"description": "Surveillez une unit\u00e9 personnelle AirVisual. Le mot de passe peut \u00eatre r\u00e9cup\u00e9r\u00e9 dans l'interface utilisateur de l'unit\u00e9.",
"title": "Configurer un noeud AirVisual Pro"
},
"reauth_confirm": {
"data": {

View File

@ -7,7 +7,8 @@
"error": {
"cannot_connect": "Tilkobling mislyktes",
"general_error": "Uventet feil",
"invalid_api_key": "Ugyldig API-n\u00f8kkel"
"invalid_api_key": "Ugyldig API-n\u00f8kkel",
"location_not_found": "Stedet ble ikke funnet"
},
"step": {
"geography": {
@ -19,6 +20,25 @@
"description": "Bruk AirVisual cloud API til \u00e5 overv\u00e5ke en geografisk plassering.",
"title": "Konfigurer en Geography"
},
"geography_by_coords": {
"data": {
"api_key": "API-n\u00f8kkel",
"latitude": "Breddegrad",
"longitude": "Lengdegrad"
},
"description": "Bruk AirVisual cloud API til \u00e5 overv\u00e5ke en breddegrad/lengdegrad.",
"title": "Konfigurer en Geography"
},
"geography_by_name": {
"data": {
"api_key": "API-n\u00f8kkel",
"city": "By",
"country": "Land",
"state": "stat"
},
"description": "Bruk AirVisual cloud API til \u00e5 overv\u00e5ke en by/stat/land.",
"title": "Konfigurer en Geography"
},
"node_pro": {
"data": {
"ip_address": "Vert",

View File

@ -7,7 +7,8 @@
"error": {
"cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia",
"general_error": "Nieoczekiwany b\u0142\u0105d",
"invalid_api_key": "Nieprawid\u0142owy klucz API"
"invalid_api_key": "Nieprawid\u0142owy klucz API",
"location_not_found": "Nie znaleziono lokalizacji"
},
"step": {
"geography": {
@ -19,6 +20,25 @@
"description": "U\u017cyj interfejsu API chmury AirVisual do monitorowania lokalizacji geograficznej.",
"title": "Konfiguracja Geography"
},
"geography_by_coords": {
"data": {
"api_key": "Klucz API",
"latitude": "Szeroko\u015b\u0107 geograficzna",
"longitude": "D\u0142ugo\u015b\u0107 geograficzna"
},
"description": "U\u017cyj API chmury AirVisual do monitorowania szeroko\u015bci/d\u0142ugo\u015bci geograficznej.",
"title": "Konfiguracja Geography"
},
"geography_by_name": {
"data": {
"api_key": "Klucz API",
"city": "Miasto",
"country": "Kraj",
"state": "Stan"
},
"description": "U\u017cyj API chmury AirVisual do monitorowania miasta/stanu/kraju.",
"title": "Konfiguracja Geography"
},
"node_pro": {
"data": {
"ip_address": "Nazwa hosta lub adres IP",

View File

@ -1,7 +1,8 @@
{
"config": {
"error": {
"general_error": "Ett ok\u00e4nt fel intr\u00e4ffade."
"general_error": "Ett ok\u00e4nt fel intr\u00e4ffade.",
"invalid_api_key": "Ogiltig API-nyckel"
},
"step": {
"geography": {

View File

@ -0,0 +1,59 @@
{
"config": {
"abort": {
"reauth_successful": "Yeniden kimlik do\u011frulama ba\u015far\u0131l\u0131 oldu"
},
"error": {
"cannot_connect": "Ba\u011flanma hatas\u0131",
"general_error": "Beklenmeyen hata",
"invalid_api_key": "Ge\u00e7ersiz API anahtar\u0131",
"location_not_found": "Konum bulunamad\u0131"
},
"step": {
"geography": {
"data": {
"api_key": "API Anahtar\u0131",
"latitude": "Enlem",
"longitude": "Boylam"
}
},
"geography_by_coords": {
"data": {
"api_key": "API Anahtar\u0131",
"latitude": "Enlem",
"longitude": "Boylam"
},
"description": "Bir enlem / boylam\u0131 izlemek i\u00e7in AirVisual bulut API'sini kullan\u0131n.",
"title": "Bir Co\u011frafyay\u0131 Yap\u0131land\u0131rma"
},
"geography_by_name": {
"data": {
"api_key": "API Anahtar\u0131",
"city": "\u015eehir",
"country": "\u00dclke",
"state": "durum"
},
"title": "Bir Co\u011frafyay\u0131 Yap\u0131land\u0131rma"
},
"node_pro": {
"data": {
"ip_address": "Ana Bilgisayar",
"password": "Parola"
},
"description": "Ki\u015fisel bir AirVisual \u00fcnitesini izleyin. Parola, \u00fcnitenin kullan\u0131c\u0131 aray\u00fcz\u00fcnden al\u0131nabilir."
},
"reauth_confirm": {
"data": {
"api_key": "API Anahtar\u0131"
}
}
}
},
"options": {
"step": {
"init": {
"title": "AirVisual'\u0131 yap\u0131land\u0131r\u0131n"
}
}
}
}

View File

@ -0,0 +1,57 @@
{
"config": {
"abort": {
"already_configured": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0434\u043b\u044f \u0446\u044c\u043e\u0433\u043e \u043c\u0456\u0441\u0446\u0435\u0437\u043d\u0430\u0445\u043e\u0434\u0436\u0435\u043d\u043d\u044f \u0432\u0436\u0435 \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u0435. \u0410\u0431\u043e \u0446\u0435\u0439 Node / Pro ID \u0432\u0436\u0435 \u0437\u0430\u0440\u0435\u0454\u0441\u0442\u0440\u043e\u0432\u0430\u043d\u0438\u0439.",
"reauth_successful": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u0430 \u0430\u0432\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f \u043f\u0440\u043e\u0439\u0448\u043b\u0430 \u0443\u0441\u043f\u0456\u0448\u043d\u043e"
},
"error": {
"cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f",
"general_error": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430",
"invalid_api_key": "\u0425\u0438\u0431\u043d\u0438\u0439 \u043a\u043b\u044e\u0447 API"
},
"step": {
"geography": {
"data": {
"api_key": "\u041a\u043b\u044e\u0447 API",
"latitude": "\u0428\u0438\u0440\u043e\u0442\u0430",
"longitude": "\u0414\u043e\u0432\u0433\u043e\u0442\u0430"
},
"description": "\u041c\u043e\u043d\u0456\u0442\u043e\u0440\u0438\u043d\u0433 \u043c\u0456\u0441\u0446\u0435\u0437\u043d\u0430\u0445\u043e\u0434\u0436\u0435\u043d\u043d\u044f \u0437\u0430 \u0434\u043e\u043f\u043e\u043c\u043e\u0433\u043e\u044e \u0445\u043c\u0430\u0440\u043d\u043e\u0433\u043e API AirVisual.",
"title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043c\u0456\u0441\u0446\u0435\u0437\u043d\u0430\u0445\u043e\u0434\u0436\u0435\u043d\u043d\u044f"
},
"node_pro": {
"data": {
"ip_address": "\u0425\u043e\u0441\u0442",
"password": "\u041f\u0430\u0440\u043e\u043b\u044c"
},
"description": "\u041c\u043e\u043d\u0456\u0442\u043e\u0440\u0438\u043d\u0433 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e AirVisual. \u041f\u0430\u0440\u043e\u043b\u044c \u043c\u043e\u0436\u043d\u0430 \u043e\u0442\u0440\u0438\u043c\u0430\u0442\u0438 \u0432 \u0456\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0456 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432.",
"title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f AirVisual Node / Pro"
},
"reauth_confirm": {
"data": {
"api_key": "\u041a\u043b\u044e\u0447 API"
},
"title": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f \u043f\u0440\u043e\u0444\u0456\u043b\u044e"
},
"user": {
"data": {
"cloud_api": "\u041c\u0456\u0441\u0446\u0435\u0437\u043d\u0430\u0445\u043e\u0434\u0436\u0435\u043d\u043d\u044f",
"node_pro": "AirVisual Node Pro",
"type": "\u0422\u0438\u043f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457"
},
"description": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0442\u0438\u043f \u0434\u0430\u043d\u0438\u0445 AirVisual, \u044f\u043a\u0438\u0439 \u0412\u0438 \u0445\u043e\u0447\u0435\u0442\u0435 \u0432\u0456\u0434\u0441\u0442\u0435\u0436\u0443\u0432\u0430\u0442\u0438.",
"title": "AirVisual"
}
}
},
"options": {
"step": {
"init": {
"data": {
"show_on_map": "\u041f\u043e\u043a\u0430\u0437\u0443\u0432\u0430\u0442\u0438 \u0432\u0456\u0434\u0441\u0442\u0435\u0436\u0443\u0432\u0430\u043d\u0443 \u043e\u0431\u043b\u0430\u0441\u0442\u044c \u043d\u0430 \u043a\u0430\u0440\u0442\u0456"
},
"title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f AirVisual"
}
}
}
}

View File

@ -7,7 +7,8 @@
"error": {
"cannot_connect": "\u9023\u7dda\u5931\u6557",
"general_error": "\u672a\u9810\u671f\u932f\u8aa4",
"invalid_api_key": "API \u5bc6\u9470\u7121\u6548"
"invalid_api_key": "API \u5bc6\u9470\u7121\u6548",
"location_not_found": "\u627e\u4e0d\u5230\u5730\u9ede"
},
"step": {
"geography": {
@ -19,6 +20,25 @@
"description": "\u4f7f\u7528 AirVisual \u96f2\u7aef API \u4ee5\u76e3\u63a7\u5730\u7406\u5ea7\u6a19\u3002",
"title": "\u8a2d\u5b9a\u5730\u7406\u5ea7\u6a19"
},
"geography_by_coords": {
"data": {
"api_key": "API \u5bc6\u9470",
"latitude": "\u7def\u5ea6",
"longitude": "\u7d93\u5ea6"
},
"description": "\u4f7f\u7528 AirVisual \u96f2\u7aef API \u4ee5\u76e3\u63a7\u7d93\u5ea6/\u7def\u5ea6\u3002",
"title": "\u8a2d\u5b9a\u5730\u7406\u5ea7\u6a19"
},
"geography_by_name": {
"data": {
"api_key": "API \u5bc6\u9470",
"city": "\u57ce\u5e02",
"country": "\u570b\u5bb6",
"state": "\u5dde"
},
"description": "\u4f7f\u7528 AirVisual \u96f2\u7aef API \u4ee5\u76e3\u63a7\u57ce\u5e02/\u5dde/\u570b\u5bb6\u3002",
"title": "\u8a2d\u5b9a\u5730\u7406\u5ea7\u6a19"
},
"node_pro": {
"data": {
"ip_address": "\u4e3b\u6a5f\u7aef",

View File

@ -23,7 +23,6 @@ from homeassistant.const import (
STATE_ALARM_ARMED_NIGHT,
STATE_ALARM_ARMING,
STATE_ALARM_DISARMED,
STATE_ALARM_PENDING,
STATE_ALARM_TRIGGERED,
)
from homeassistant.core import CALLBACK_TYPE, HomeAssistant
@ -143,13 +142,10 @@ async def async_attach_trigger(
from_state = STATE_ALARM_DISARMED
to_state = STATE_ALARM_ARMING
elif config[CONF_TYPE] == "armed_home":
from_state = STATE_ALARM_PENDING or STATE_ALARM_ARMING
to_state = STATE_ALARM_ARMED_HOME
elif config[CONF_TYPE] == "armed_away":
from_state = STATE_ALARM_PENDING or STATE_ALARM_ARMING
to_state = STATE_ALARM_ARMED_AWAY
elif config[CONF_TYPE] == "armed_night":
from_state = STATE_ALARM_PENDING or STATE_ALARM_ARMING
to_state = STATE_ALARM_ARMED_NIGHT
state_config = {

View File

@ -1,4 +1,10 @@
{
"device_automation": {
"trigger_type": {
"disarmed": "{entity_name} b\u0131rak\u0131ld\u0131",
"triggered": "{entity_name} tetiklendi"
}
},
"state": {
"_": {
"armed": "Etkin",

View File

@ -1,13 +1,36 @@
{
"device_automation": {
"action_type": {
"arm_away": "\u0423\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u0438 \u0440\u0435\u0436\u0438\u043c \u043e\u0445\u043e\u0440\u043e\u043d\u0438 \"\u041d\u0435 \u0432\u0434\u043e\u043c\u0430\" \u043d\u0430 \u043f\u0430\u043d\u0435\u043b\u0456 {entity_name}",
"arm_home": "\u0423\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u0438 \u0440\u0435\u0436\u0438\u043c \u043e\u0445\u043e\u0440\u043e\u043d\u0438 \"\u0412\u0434\u043e\u043c\u0430\" \u043d\u0430 \u043f\u0430\u043d\u0435\u043b\u0456 {entity_name}",
"arm_night": "\u0423\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u0438 \u0440\u0435\u0436\u0438\u043c \u043e\u0445\u043e\u0440\u043e\u043d\u0438 \"\u041d\u0456\u0447\" \u043d\u0430 \u043f\u0430\u043d\u0435\u043b\u0456 {entity_name}",
"disarm": "\u0412\u0456\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u0438 \u043e\u0445\u043e\u0440\u043e\u043d\u0443 \u043d\u0430 \u043f\u0430\u043d\u0435\u043b\u0456 {entity_name}",
"trigger": "{entity_name} \u0441\u043f\u0440\u0430\u0446\u044c\u043e\u0432\u0443\u0454"
},
"condition_type": {
"is_armed_away": "\u0423\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c \u043e\u0445\u043e\u0440\u043e\u043d\u0438 \"\u041d\u0435 \u0432\u0434\u043e\u043c\u0430\" \u043d\u0430 \u043f\u0430\u043d\u0435\u043b\u0456 {entity_name}",
"is_armed_home": "\u0423\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c \u043e\u0445\u043e\u0440\u043e\u043d\u0438 \"\u0412\u0434\u043e\u043c\u0430\" \u043d\u0430 \u043f\u0430\u043d\u0435\u043b\u0456 {entity_name}",
"is_armed_night": "\u0423\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c \u043e\u0445\u043e\u0440\u043e\u043d\u0438 \"\u041d\u0456\u0447\" \u043d\u0430 \u043f\u0430\u043d\u0435\u043b\u0456 {entity_name}",
"is_disarmed": "\u0412\u0438\u043c\u043a\u043d\u0435\u043d\u0430 \u043e\u0445\u043e\u0440\u043e\u043d\u0430 \u043d\u0430 \u043f\u0430\u043d\u0435\u043b\u0456 {entity_name}",
"is_triggered": "{entity_name} \u0441\u043f\u0440\u0430\u0446\u044c\u043e\u0432\u0443\u0454"
},
"trigger_type": {
"armed_away": "\u0423\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c \u043e\u0445\u043e\u0440\u043e\u043d\u0438 \"\u041d\u0435 \u0432\u0434\u043e\u043c\u0430\" \u043d\u0430 \u043f\u0430\u043d\u0435\u043b\u0456 {entity_name}",
"armed_home": "\u0423\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c \u043e\u0445\u043e\u0440\u043e\u043d\u0438 \"\u0412\u0434\u043e\u043c\u0430\" \u043d\u0430 \u043f\u0430\u043d\u0435\u043b\u0456 {entity_name}",
"armed_night": "\u0423\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c \u043e\u0445\u043e\u0440\u043e\u043d\u0438 \"\u041d\u0456\u0447\" \u043d\u0430 \u043f\u0430\u043d\u0435\u043b\u0456 {entity_name}",
"disarmed": "\u0412\u0438\u043c\u043a\u043d\u0435\u043d\u0430 \u043e\u0445\u043e\u0440\u043e\u043d\u0430 \u043d\u0430 \u043f\u0430\u043d\u0435\u043b\u0456 {entity_name}",
"triggered": "{entity_name} \u0441\u043f\u0440\u0430\u0446\u044c\u043e\u0432\u0443\u0454"
}
},
"state": {
"_": {
"armed": "\u041e\u0445\u043e\u0440\u043e\u043d\u0430",
"armed_away": "\u041e\u0445\u043e\u0440\u043e\u043d\u0430 (\u043d\u0435 \u0432\u0434\u043e\u043c\u0430)",
"armed_away": "\u041e\u0445\u043e\u0440\u043e\u043d\u0430 (\u041d\u0435 \u0432\u0434\u043e\u043c\u0430)",
"armed_custom_bypass": "\u041e\u0445\u043e\u0440\u043e\u043d\u0430 \u0437 \u0432\u0438\u043d\u044f\u0442\u043a\u0430\u043c\u0438",
"armed_home": "\u0411\u0443\u0434\u0438\u043d\u043a\u043e\u0432\u0430 \u043e\u0445\u043e\u0440\u043e\u043d\u0430",
"armed_home": "\u041e\u0445\u043e\u0440\u043e\u043d\u0430 (\u0412\u0434\u043e\u043c\u0430)",
"armed_night": "\u041d\u0456\u0447\u043d\u0430 \u043e\u0445\u043e\u0440\u043e\u043d\u0430",
"arming": "\u0421\u0442\u0430\u0432\u043b\u044e \u043d\u0430 \u043e\u0445\u043e\u0440\u043e\u043d\u0443",
"disarmed": "\u0417\u043d\u044f\u0442\u043e",
"disarmed": "\u0417\u043d\u044f\u0442\u043e \u0437 \u043e\u0445\u043e\u0440\u043e\u043d\u0438",
"disarming": "\u0417\u043d\u044f\u0442\u0442\u044f",
"pending": "\u041e\u0447\u0456\u043a\u0443\u044e",
"triggered": "\u0422\u0440\u0438\u0432\u043e\u0433\u0430"

View File

@ -1,7 +1,10 @@
{
"config": {
"abort": {
"already_configured": "Ger\u00e4t ist bereits konfiguriert"
},
"error": {
"cannot_connect": "Verbindungsfehler"
"cannot_connect": "Verbindung fehlgeschlagen"
},
"step": {
"protocol": {
@ -42,7 +45,7 @@
"data": {
"zone_number": "Zonennummer"
},
"description": "Geben Sie die Zonennummer ein, die Sie hinzuf\u00fcgen, bearbeiten oder entfernen m\u00f6chten."
"description": "Gib die die Zonennummer ein, die du hinzuf\u00fcgen, bearbeiten oder entfernen m\u00f6chtest."
}
}
}

View File

@ -0,0 +1,48 @@
{
"config": {
"abort": {
"already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f"
},
"error": {
"cannot_connect": "Ba\u011flanma hatas\u0131"
},
"step": {
"protocol": {
"data": {
"host": "Ana Bilgisayar",
"port": "Port"
}
}
}
},
"options": {
"error": {
"relay_inclusive": "R\u00f6le Adresi ve R\u00f6le Kanal\u0131 birbirine ba\u011fl\u0131d\u0131r ve birlikte eklenmelidir."
},
"step": {
"arm_settings": {
"data": {
"alt_night_mode": "Alternatif Gece Modu"
}
},
"init": {
"data": {
"edit_select": "D\u00fczenle"
}
},
"zone_details": {
"data": {
"zone_name": "B\u00f6lge Ad\u0131",
"zone_relayaddr": "R\u00f6le Adresi",
"zone_relaychan": "R\u00f6le Kanal\u0131"
}
},
"zone_select": {
"data": {
"zone_number": "B\u00f6lge Numaras\u0131"
},
"title": "AlarmDecoder'\u0131 yap\u0131land\u0131r\u0131n"
}
}
}
}

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