Merge pull request #74522 from home-assistant/rc

This commit is contained in:
Franck Nijhof 2022-07-06 20:32:56 +02:00 committed by GitHub
commit eb0f8f9542
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2289 changed files with 53625 additions and 26109 deletions

View File

@ -132,7 +132,7 @@ requirements: &requirements
- homeassistant/package_constraints.txt
- script/pip_check
- requirements*.txt
- setup.cfg
- pyproject.toml
any:
- *base_platforms

View File

@ -210,7 +210,6 @@ omit =
homeassistant/components/denonavr/media_player.py
homeassistant/components/denonavr/receiver.py
homeassistant/components/deutsche_bahn/sensor.py
homeassistant/components/devolo_home_control/cover.py
homeassistant/components/devolo_home_control/light.py
homeassistant/components/devolo_home_control/sensor.py
homeassistant/components/devolo_home_control/switch.py
@ -262,7 +261,9 @@ omit =
homeassistant/components/eddystone_temperature/sensor.py
homeassistant/components/edimax/switch.py
homeassistant/components/egardia/*
homeassistant/components/eight_sleep/*
homeassistant/components/eight_sleep/__init__.py
homeassistant/components/eight_sleep/binary_sensor.py
homeassistant/components/eight_sleep/sensor.py
homeassistant/components/eliqonline/sensor.py
homeassistant/components/elkm1/__init__.py
homeassistant/components/elkm1/alarm_control_panel.py
@ -407,6 +408,7 @@ omit =
homeassistant/components/fritzbox_callmonitor/const.py
homeassistant/components/fritzbox_callmonitor/base.py
homeassistant/components/fritzbox_callmonitor/sensor.py
homeassistant/components/frontier_silicon/const.py
homeassistant/components/frontier_silicon/media_player.py
homeassistant/components/futurenow/light.py
homeassistant/components/garadget/cover.py
@ -504,16 +506,21 @@ omit =
homeassistant/components/huawei_lte/switch.py
homeassistant/components/hue/light.py
homeassistant/components/hunterdouglas_powerview/__init__.py
homeassistant/components/hunterdouglas_powerview/button.py
homeassistant/components/hunterdouglas_powerview/coordinator.py
homeassistant/components/hunterdouglas_powerview/cover.py
homeassistant/components/hunterdouglas_powerview/diagnostics.py
homeassistant/components/hunterdouglas_powerview/entity.py
homeassistant/components/hunterdouglas_powerview/model.py
homeassistant/components/hunterdouglas_powerview/scene.py
homeassistant/components/hunterdouglas_powerview/sensor.py
homeassistant/components/hunterdouglas_powerview/cover.py
homeassistant/components/hunterdouglas_powerview/entity.py
homeassistant/components/hunterdouglas_powerview/shade_data.py
homeassistant/components/hunterdouglas_powerview/util.py
homeassistant/components/hvv_departures/binary_sensor.py
homeassistant/components/hvv_departures/sensor.py
homeassistant/components/hvv_departures/__init__.py
homeassistant/components/hydrawise/*
homeassistant/components/ialarm/alarm_control_panel.py
homeassistant/components/ialarm_xr/alarm_control_panel.py
homeassistant/components/iammeter/sensor.py
homeassistant/components/iaqualink/binary_sensor.py
homeassistant/components/iaqualink/climate.py
@ -575,6 +582,7 @@ omit =
homeassistant/components/isy994/sensor.py
homeassistant/components/isy994/services.py
homeassistant/components/isy994/switch.py
homeassistant/components/isy994/util.py
homeassistant/components/itach/remote.py
homeassistant/components/itunes/media_player.py
homeassistant/components/jellyfin/__init__.py
@ -624,18 +632,16 @@ omit =
homeassistant/components/launch_library/const.py
homeassistant/components/launch_library/diagnostics.py
homeassistant/components/launch_library/sensor.py
homeassistant/components/lcn/binary_sensor.py
homeassistant/components/lcn/climate.py
homeassistant/components/lcn/helpers.py
homeassistant/components/lcn/scene.py
homeassistant/components/lcn/sensor.py
homeassistant/components/lcn/services.py
homeassistant/components/lg_netcast/media_player.py
homeassistant/components/lg_soundbar/media_player.py
homeassistant/components/life360/__init__.py
homeassistant/components/life360/const.py
homeassistant/components/life360/coordinator.py
homeassistant/components/life360/device_tracker.py
homeassistant/components/life360/helpers.py
homeassistant/components/lifx/__init__.py
homeassistant/components/lifx/const.py
homeassistant/components/lifx/light.py
@ -671,6 +677,7 @@ omit =
homeassistant/components/lutron_caseta/light.py
homeassistant/components/lutron_caseta/scene.py
homeassistant/components/lutron_caseta/switch.py
homeassistant/components/lutron_caseta/util.py
homeassistant/components/lw12wifi/light.py
homeassistant/components/lyric/__init__.py
homeassistant/components/lyric/api.py
@ -785,6 +792,7 @@ omit =
homeassistant/components/netgear/router.py
homeassistant/components/netgear/sensor.py
homeassistant/components/netgear/switch.py
homeassistant/components/netgear/update.py
homeassistant/components/netgear_lte/*
homeassistant/components/netio/switch.py
homeassistant/components/neurio_energy/sensor.py
@ -957,7 +965,13 @@ omit =
homeassistant/components/radarr/sensor.py
homeassistant/components/radio_browser/__init__.py
homeassistant/components/radio_browser/media_source.py
homeassistant/components/radiotherm/__init__.py
homeassistant/components/radiotherm/entity.py
homeassistant/components/radiotherm/climate.py
homeassistant/components/radiotherm/coordinator.py
homeassistant/components/radiotherm/data.py
homeassistant/components/radiotherm/switch.py
homeassistant/components/radiotherm/util.py
homeassistant/components/rainbird/*
homeassistant/components/raincloud/*
homeassistant/components/rainmachine/__init__.py
@ -1049,6 +1063,7 @@ omit =
homeassistant/components/shelly/sensor.py
homeassistant/components/shelly/utils.py
homeassistant/components/sigfox/sensor.py
homeassistant/components/simplepush/__init__.py
homeassistant/components/simplepush/notify.py
homeassistant/components/simplisafe/__init__.py
homeassistant/components/simplisafe/alarm_control_panel.py
@ -1059,7 +1074,14 @@ omit =
homeassistant/components/sisyphus/*
homeassistant/components/sky_hub/*
homeassistant/components/skybeacon/sensor.py
homeassistant/components/skybell/*
homeassistant/components/skybell/__init__.py
homeassistant/components/skybell/binary_sensor.py
homeassistant/components/skybell/camera.py
homeassistant/components/skybell/coordinator.py
homeassistant/components/skybell/entity.py
homeassistant/components/skybell/light.py
homeassistant/components/skybell/sensor.py
homeassistant/components/skybell/switch.py
homeassistant/components/slack/__init__.py
homeassistant/components/slack/notify.py
homeassistant/components/sia/__init__.py
@ -1100,12 +1122,6 @@ omit =
homeassistant/components/soma/cover.py
homeassistant/components/soma/sensor.py
homeassistant/components/soma/utils.py
homeassistant/components/somfy/__init__.py
homeassistant/components/somfy/api.py
homeassistant/components/somfy/climate.py
homeassistant/components/somfy/cover.py
homeassistant/components/somfy/sensor.py
homeassistant/components/somfy/switch.py
homeassistant/components/somfy_mylink/__init__.py
homeassistant/components/somfy_mylink/cover.py
homeassistant/components/sonos/__init__.py
@ -1181,6 +1197,7 @@ omit =
homeassistant/components/synology_dsm/binary_sensor.py
homeassistant/components/synology_dsm/button.py
homeassistant/components/synology_dsm/camera.py
homeassistant/components/synology_dsm/coordinator.py
homeassistant/components/synology_dsm/diagnostics.py
homeassistant/components/synology_dsm/common.py
homeassistant/components/synology_dsm/entity.py
@ -1284,9 +1301,6 @@ omit =
homeassistant/components/tradfri/light.py
homeassistant/components/tradfri/sensor.py
homeassistant/components/tradfri/switch.py
homeassistant/components/trafikverket_ferry/__init__.py
homeassistant/components/trafikverket_ferry/coordinator.py
homeassistant/components/trafikverket_ferry/sensor.py
homeassistant/components/trafikverket_train/__init__.py
homeassistant/components/trafikverket_train/sensor.py
homeassistant/components/trafikverket_weatherstation/__init__.py
@ -1345,6 +1359,7 @@ omit =
homeassistant/components/vasttrafik/sensor.py
homeassistant/components/velbus/__init__.py
homeassistant/components/velbus/binary_sensor.py
homeassistant/components/velbus/button.py
homeassistant/components/velbus/climate.py
homeassistant/components/velbus/const.py
homeassistant/components/velbus/cover.py
@ -1476,9 +1491,11 @@ omit =
homeassistant/components/yolink/__init__.py
homeassistant/components/yolink/api.py
homeassistant/components/yolink/binary_sensor.py
homeassistant/components/yolink/climate.py
homeassistant/components/yolink/const.py
homeassistant/components/yolink/coordinator.py
homeassistant/components/yolink/entity.py
homeassistant/components/yolink/lock.py
homeassistant/components/yolink/sensor.py
homeassistant/components/yolink/siren.py
homeassistant/components/yolink/switch.py
@ -1500,7 +1517,6 @@ omit =
homeassistant/components/zha/core/gateway.py
homeassistant/components/zha/core/helpers.py
homeassistant/components/zha/core/registries.py
homeassistant/components/zha/core/typing.py
homeassistant/components/zha/entity.py
homeassistant/components/zha/light.py
homeassistant/components/zha/sensor.py

View File

@ -29,7 +29,7 @@ jobs:
fetch-depth: 0
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v3.1.2
uses: actions/setup-python@v4.0.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
@ -70,7 +70,7 @@ jobs:
uses: actions/checkout@v3.0.2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v3.1.2
uses: actions/setup-python@v4.0.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
@ -104,7 +104,7 @@ jobs:
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
if: needs.init.outputs.channel == 'dev'
uses: actions/setup-python@v3.1.2
uses: actions/setup-python@v4.0.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
@ -135,7 +135,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build base image
uses: home-assistant/builder@2022.03.1
uses: home-assistant/builder@2022.06.2
with:
args: |
$BUILD_ARGS \
@ -171,6 +171,7 @@ jobs:
- raspberrypi4
- raspberrypi4-64
- tinker
- yellow
steps:
- name: Checkout the repository
uses: actions/checkout@v3.0.2
@ -200,7 +201,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build base image
uses: home-assistant/builder@2022.03.1
uses: home-assistant/builder@2022.06.2
with:
args: |
$BUILD_ARGS \

View File

@ -20,14 +20,15 @@ on:
type: boolean
env:
CACHE_VERSION: 9
PIP_CACHE_VERSION: 3
HA_SHORT_VERSION: 2022.6
CACHE_VERSION: 10
PIP_CACHE_VERSION: 4
HA_SHORT_VERSION: 2022.7
DEFAULT_PYTHON: 3.9
PRE_COMMIT_CACHE: ~/.cache/pre-commit
PIP_CACHE: /tmp/pip-cache
SQLALCHEMY_WARN_20: 1
PYTHONASYNCIODEBUG: 1
HASS_CI: 1
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
@ -155,7 +156,7 @@ jobs:
uses: actions/checkout@v3.0.2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v3.1.2
uses: actions/setup-python@v4.0.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Generate partial Python venv restore key
@ -172,7 +173,7 @@ jobs:
env.HA_SHORT_VERSION }}-$(date -u '+%Y-%m-%dT%H:%M:%s')"
- name: Restore base Python virtual environment
id: cache-venv
uses: actions/cache@v3.0.2
uses: actions/cache@v3.0.4
with:
path: venv
key: >-
@ -189,7 +190,7 @@ jobs:
# ${{ runner.os }}-${{ steps.python.outputs.python-version }}-base-venv-${{ env.CACHE_VERSION }}-
- name: Restore pip wheel cache
if: steps.cache-venv.outputs.cache-hit != 'true'
uses: actions/cache@v3.0.2
uses: actions/cache@v3.0.4
with:
path: ${{ env.PIP_CACHE }}
key: >-
@ -212,7 +213,7 @@ jobs:
hashFiles('.pre-commit-config.yaml') }}"
- name: Restore pre-commit environment from cache
id: cache-precommit
uses: actions/cache@v3.0.2
uses: actions/cache@v3.0.4
with:
path: ${{ env.PRE_COMMIT_CACHE }}
key: >-
@ -235,13 +236,13 @@ jobs:
- name: Check out code from GitHub
uses: actions/checkout@v3.0.2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v3.1.2
uses: actions/setup-python@v4.0.0
id: python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore base Python virtual environment
id: cache-venv
uses: actions/cache@v3.0.2
uses: actions/cache@v3.0.4
with:
path: venv
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
@ -253,7 +254,7 @@ jobs:
exit 1
- name: Restore pre-commit environment from cache
id: cache-precommit
uses: actions/cache@v3.0.2
uses: actions/cache@v3.0.4
with:
path: ${{ env.PRE_COMMIT_CACHE }}
key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }}
@ -285,13 +286,13 @@ jobs:
- name: Check out code from GitHub
uses: actions/checkout@v3.0.2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v3.1.2
uses: actions/setup-python@v4.0.0
id: python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore base Python virtual environment
id: cache-venv
uses: actions/cache@v3.0.2
uses: actions/cache@v3.0.4
with:
path: venv
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
@ -303,7 +304,7 @@ jobs:
exit 1
- name: Restore pre-commit environment from cache
id: cache-precommit
uses: actions/cache@v3.0.2
uses: actions/cache@v3.0.4
with:
path: ${{ env.PRE_COMMIT_CACHE }}
key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }}
@ -336,13 +337,13 @@ jobs:
- name: Check out code from GitHub
uses: actions/checkout@v3.0.2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v3.1.2
uses: actions/setup-python@v4.0.0
id: python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore base Python virtual environment
id: cache-venv
uses: actions/cache@v3.0.2
uses: actions/cache@v3.0.4
with:
path: venv
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
@ -354,7 +355,7 @@ jobs:
exit 1
- name: Restore pre-commit environment from cache
id: cache-precommit
uses: actions/cache@v3.0.2
uses: actions/cache@v3.0.4
with:
path: ${{ env.PRE_COMMIT_CACHE }}
key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }}
@ -378,13 +379,13 @@ jobs:
- name: Check out code from GitHub
uses: actions/checkout@v3.0.2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v3.1.2
uses: actions/setup-python@v4.0.0
id: python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore base Python virtual environment
id: cache-venv
uses: actions/cache@v3.0.2
uses: actions/cache@v3.0.4
with:
path: venv
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
@ -396,7 +397,7 @@ jobs:
exit 1
- name: Restore pre-commit environment from cache
id: cache-precommit
uses: actions/cache@v3.0.2
uses: actions/cache@v3.0.4
with:
path: ${{ env.PRE_COMMIT_CACHE }}
key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }}
@ -502,7 +503,7 @@ jobs:
uses: actions/checkout@v3.0.2
- name: Restore full Python ${{ matrix.python-version }} virtual environment
id: cache-venv
uses: actions/cache@v3.0.2
uses: actions/cache@v3.0.4
with:
path: venv
key: ${{ runner.os }}-${{ matrix.python-version }}-${{
@ -525,13 +526,13 @@ jobs:
- name: Check out code from GitHub
uses: actions/checkout@v3.0.2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v3.1.2
uses: actions/setup-python@v4.0.0
id: python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore base Python virtual environment
id: cache-venv
uses: actions/cache@v3.0.2
uses: actions/cache@v3.0.4
with:
path: venv
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
@ -573,7 +574,7 @@ jobs:
env.HA_SHORT_VERSION }}-$(date -u '+%Y-%m-%dT%H:%M:%s')"
- name: Restore full Python ${{ matrix.python-version }} virtual environment
id: cache-venv
uses: actions/cache@v3.0.2
uses: actions/cache@v3.0.4
with:
path: venv
key: >-
@ -590,7 +591,7 @@ jobs:
# ${{ runner.os }}-${{ matrix.python-version }}-venv-${{ env.CACHE_VERSION }}-
- name: Restore pip wheel cache
if: steps.cache-venv.outputs.cache-hit != 'true'
uses: actions/cache@v3.0.2
uses: actions/cache@v3.0.4
with:
path: ${{ env.PIP_CACHE }}
key: >-
@ -629,7 +630,7 @@ jobs:
uses: actions/checkout@v3.0.2
- name: Restore full Python ${{ matrix.python-version }} virtual environment
id: cache-venv
uses: actions/cache@v3.0.2
uses: actions/cache@v3.0.4
with:
path: venv
key: ${{ runner.os }}-${{ matrix.python-version }}-${{
@ -671,7 +672,7 @@ jobs:
uses: actions/checkout@v3.0.2
- name: Restore full Python ${{ matrix.python-version }} virtual environment
id: cache-venv
uses: actions/cache@v3.0.2
uses: actions/cache@v3.0.4
with:
path: venv
key: ${{ runner.os }}-${{ matrix.python-version }}-${{
@ -689,7 +690,7 @@ jobs:
run: |
. venv/bin/activate
python --version
mypy homeassistant
mypy homeassistant pylint
- name: Run mypy (partially)
if: needs.changes.outputs.test_full_suite == 'false'
shell: bash
@ -715,7 +716,7 @@ jobs:
uses: actions/checkout@v3.0.2
- name: Restore full Python ${{ matrix.python-version }} virtual environment
id: cache-venv
uses: actions/cache@v3.0.2
uses: actions/cache@v3.0.4
with:
path: venv
key: ${{ runner.os }}-${{ matrix.python-version }}-${{
@ -758,7 +759,7 @@ jobs:
uses: actions/checkout@v3.0.2
- name: Restore full Python ${{ matrix.python-version }} virtual environment
id: cache-venv
uses: actions/cache@v3.0.2
uses: actions/cache@v3.0.4
with:
path: venv
key: ${{ runner.os }}-${{ matrix.python-version }}-${{
@ -795,14 +796,14 @@ jobs:
--dist=loadfile \
--test-group-count ${{ needs.changes.outputs.test_group_count }} \
--test-group=${{ matrix.group }} \
--cov homeassistant \
--cov="homeassistant" \
--cov-report=xml \
-o console_output_style=count \
-p no:sugar \
tests
- name: Run pytest (partially)
if: needs.changes.outputs.test_full_suite == 'false'
timeout-minutes: 10
timeout-minutes: 20
shell: bash
run: |
. venv/bin/activate
@ -818,7 +819,7 @@ jobs:
--timeout=9 \
--durations=10 \
-n auto \
--cov homeassistant.components.${{ matrix.group }} \
--cov="homeassistant.components.${{ matrix.group }}" \
--cov-report=xml \
--cov-report=term-missing \
-o console_output_style=count \

View File

@ -24,7 +24,7 @@ jobs:
uses: actions/checkout@v3.0.2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v3.1.2
uses: actions/setup-python@v4.0.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
@ -43,7 +43,7 @@ jobs:
uses: actions/checkout@v3.0.2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v3.1.2
uses: actions/setup-python@v4.0.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}

View File

@ -47,6 +47,13 @@ jobs:
# execinfo-dev when building wheels. The setuptools build setup does not have an option for
# adding a single LDFLAG so copy all relevant linux flags here (as of 1.43.0)
echo "GRPC_PYTHON_LDFLAGS=-lpthread -Wl,-wrap,memcpy -static-libgcc -lexecinfo"
# Fix out of memory issues with rust
echo "CARGO_NET_GIT_FETCH_WITH_CLI=true"
# OpenCV headless installation
echo "CI_BUILD=1"
echo "ENABLE_HEADLESS=1"
) > .env_file
- name: Upload env_file
@ -62,7 +69,7 @@ jobs:
path: ./requirements_diff.txt
core:
name: Build wheels with ${{ matrix.tag }} (${{ matrix.arch }}) for core
name: Build musllinux wheels with musllinux_1_2 / cp310 at ${{ matrix.arch }} for core
if: github.repository_owner == 'home-assistant'
needs: init
runs-on: ubuntu-latest
@ -70,8 +77,6 @@ jobs:
fail-fast: false
matrix:
arch: ${{ fromJson(needs.init.outputs.architectures) }}
tag:
- "3.9-alpine3.14"
steps:
- name: Checkout the repository
uses: actions/checkout@v3.0.2
@ -87,23 +92,21 @@ jobs:
name: requirements_diff
- name: Build wheels
uses: home-assistant/wheels@2022.01.2
uses: home-assistant/wheels@2022.06.7
with:
tag: ${{ matrix.tag }}
abi: cp310
tag: musllinux_1_2
arch: ${{ matrix.arch }}
wheels-host: wheels.hass.io
wheels-key: ${{ secrets.WHEELS_KEY }}
wheels-user: wheels
env-file: true
apk: "build-base;cmake;git;linux-headers;bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;cargo"
pip: "Cython;numpy==1.21.6"
apk: "libffi-dev;openssl-dev;yaml-dev"
skip-binary: aiohttp
constraints: "homeassistant/package_constraints.txt"
requirements-diff: "requirements_diff.txt"
requirements: "requirements.txt"
integrations:
name: Build wheels with ${{ matrix.tag }} (${{ matrix.arch }}) for integrations
name: Build musllinux wheels with musllinux_1_2 / cp310 at ${{ matrix.arch }} for integrations
if: github.repository_owner == 'home-assistant'
needs: init
runs-on: ubuntu-latest
@ -111,8 +114,6 @@ jobs:
fail-fast: false
matrix:
arch: ${{ fromJson(needs.init.outputs.architectures) }}
tag:
- "3.9-alpine3.14"
steps:
- name: Checkout the repository
uses: actions/checkout@v3.0.2
@ -132,35 +133,41 @@ jobs:
requirement_files="requirements_all.txt requirements_diff.txt"
for requirement_file in ${requirement_files}; do
sed -i "s|# pybluez|pybluez|g" ${requirement_file}
sed -i "s|# bluepy|bluepy|g" ${requirement_file}
sed -i "s|# beacontools|beacontools|g" ${requirement_file}
sed -i "s|# fritzconnection|fritzconnection|g" ${requirement_file}
sed -i "s|# pyuserinput|pyuserinput|g" ${requirement_file}
sed -i "s|# evdev|evdev|g" ${requirement_file}
sed -i "s|# python-eq3bt|python-eq3bt|g" ${requirement_file}
sed -i "s|# pycups|pycups|g" ${requirement_file}
sed -i "s|# homekit|homekit|g" ${requirement_file}
sed -i "s|# decora_wifi|decora_wifi|g" ${requirement_file}
sed -i "s|# decora|decora|g" ${requirement_file}
sed -i "s|# avion|avion|g" ${requirement_file}
sed -i "s|# PySwitchbot|PySwitchbot|g" ${requirement_file}
sed -i "s|# pySwitchmate|pySwitchmate|g" ${requirement_file}
sed -i "s|# face_recognition|face_recognition|g" ${requirement_file}
sed -i "s|# python-gammu|python-gammu|g" ${requirement_file}
sed -i "s|# opencv-python-headless|opencv-python-headless|g" ${requirement_file}
done
- name: Adjust build env
run: |
if [ "${{ matrix.arch }}" = "i386" ]; then
echo "NPY_DISABLE_SVML=1" >> .env_file
fi
(
# cmake > 3.22.2 have issue on arm
# Tested until 3.22.5
echo "cmake==3.22.2"
) >> homeassistant/package_constraints.txt
- name: Build wheels
uses: home-assistant/wheels@2022.01.2
uses: home-assistant/wheels@2022.06.7
with:
tag: ${{ matrix.tag }}
abi: cp310
tag: musllinux_1_2
arch: ${{ matrix.arch }}
wheels-host: wheels.hass.io
wheels-key: ${{ secrets.WHEELS_KEY }}
wheels-user: wheels
env-file: true
apk: "build-base;cmake;git;linux-headers;libexecinfo-dev;bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;autoconf;automake;cups-dev;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev;gammu-dev;cargo"
pip: "Cython;numpy;scikit-build"
skip-binary: aiohttp,grpcio
apk: "libexecinfo-dev;bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;cups-dev;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev;gammu-dev;yaml-dev;openblas-dev;fftw-dev;lapack-dev;gfortran;blas-dev;eigen-dev;freetype-dev;glew-dev;harfbuzz-dev;hdf5-dev;libdc1394-dev;libtbb-dev;mesa-dev;openexr-dev;openjpeg-dev"
skip-binary: aiohttp;grpcio
legacy: true
constraints: "homeassistant/package_constraints.txt"
requirements-diff: "requirements_diff.txt"
requirements: "requirements_all.txt"

View File

@ -1,6 +1,6 @@
repos:
- repo: https://github.com/asottile/pyupgrade
rev: v2.32.1
rev: v2.34.0
hooks:
- id: pyupgrade
args: [--py39-plus]
@ -93,7 +93,7 @@ repos:
language: script
types: [python]
require_serial: true
files: ^homeassistant/.+\.py$
files: ^(homeassistant|pylint)/.+\.py$
- id: pylint
name: pylint
entry: script/run-in-env.sh pylint -j 0
@ -106,7 +106,7 @@ repos:
pass_filenames: false
language: script
types: [text]
files: ^(homeassistant/.+/manifest\.json|setup\.cfg|\.pre-commit-config\.yaml|script/gen_requirements_all\.py)$
files: ^(homeassistant/.+/manifest\.json|pyproject\.toml|\.pre-commit-config\.yaml|script/gen_requirements_all\.py)$
- id: hassfest
name: hassfest
entry: script/run-in-env.sh python3 -m script.hassfest
@ -120,7 +120,7 @@ repos:
pass_filenames: false
language: script
types: [text]
files: ^(script/hassfest/metadata\.py|homeassistant/const\.py$|setup\.cfg)$
files: ^(script/hassfest/metadata\.py|homeassistant/const\.py$|pyproject\.toml)$
- id: hassfest-mypy-config
name: hassfest-mypy-config
entry: script/run-in-env.sh python3 -m script.hassfest -p mypy_config

View File

@ -83,9 +83,11 @@ homeassistant.components.dunehd.*
homeassistant.components.efergy.*
homeassistant.components.elgato.*
homeassistant.components.elkm1.*
homeassistant.components.emulated_hue.*
homeassistant.components.esphome.*
homeassistant.components.energy.*
homeassistant.components.evil_genius_labs.*
homeassistant.components.fan.*
homeassistant.components.fastdotcom.*
homeassistant.components.filesize.*
homeassistant.components.fitbit.*
@ -113,6 +115,7 @@ homeassistant.components.homekit.aidmanager
homeassistant.components.homekit.config_flow
homeassistant.components.homekit.diagnostics
homeassistant.components.homekit.logbook
homeassistant.components.homekit.type_locks
homeassistant.components.homekit.type_triggers
homeassistant.components.homekit.util
homeassistant.components.homekit_controller
@ -127,7 +130,6 @@ homeassistant.components.homewizard.*
homeassistant.components.http.*
homeassistant.components.huawei_lte.*
homeassistant.components.hyperion.*
homeassistant.components.ialarm_xr.*
homeassistant.components.image_processing.*
homeassistant.components.input_button.*
homeassistant.components.input_select.*
@ -196,6 +198,7 @@ homeassistant.components.rtsp_to_webrtc.*
homeassistant.components.samsungtv.*
homeassistant.components.scene.*
homeassistant.components.select.*
homeassistant.components.sensibo.*
homeassistant.components.sensor.*
homeassistant.components.senseme.*
homeassistant.components.senz.*
@ -224,6 +227,7 @@ homeassistant.components.tplink.*
homeassistant.components.tolo.*
homeassistant.components.tractive.*
homeassistant.components.tradfri.*
homeassistant.components.trafikverket_ferry.*
homeassistant.components.trafikverket_train.*
homeassistant.components.trafikverket_weatherstation.*
homeassistant.components.tts.*

View File

@ -6,6 +6,7 @@
# Home Assistant Core
setup.cfg @home-assistant/core
pyproject.toml @home-assistant/core
/homeassistant/*.py @home-assistant/core
/homeassistant/helpers/ @home-assistant/core
/homeassistant/util/ @home-assistant/core
@ -128,8 +129,8 @@ build.json @home-assistant/supervisor
/homeassistant/components/binary_sensor/ @home-assistant/core
/tests/components/binary_sensor/ @home-assistant/core
/homeassistant/components/bizkaibus/ @UgaitzEtxebarria
/homeassistant/components/blebox/ @bbx-a @bbx-jp
/tests/components/blebox/ @bbx-a @bbx-jp
/homeassistant/components/blebox/ @bbx-a @bbx-jp @riokuu
/tests/components/blebox/ @bbx-a @bbx-jp @riokuu
/homeassistant/components/blink/ @fronzbot
/tests/components/blink/ @fronzbot
/homeassistant/components/blueprint/ @home-assistant/core
@ -272,6 +273,7 @@ build.json @home-assistant/supervisor
/tests/components/efergy/ @tkdrob
/homeassistant/components/egardia/ @jeroenterheerdt
/homeassistant/components/eight_sleep/ @mezz64 @raman325
/tests/components/eight_sleep/ @mezz64 @raman325
/homeassistant/components/elgato/ @frenck
/tests/components/elgato/ @frenck
/homeassistant/components/elkm1/ @gwww @bdraco
@ -283,6 +285,8 @@ build.json @home-assistant/supervisor
/homeassistant/components/emoncms/ @borpin
/homeassistant/components/emonitor/ @bdraco
/tests/components/emonitor/ @bdraco
/homeassistant/components/emulated_hue/ @bdraco
/tests/components/emulated_hue/ @bdraco
/homeassistant/components/emulated_kasa/ @kbickar
/tests/components/emulated_kasa/ @kbickar
/homeassistant/components/energy/ @home-assistant/core
@ -327,7 +331,6 @@ build.json @home-assistant/supervisor
/tests/components/firmata/ @DaAwesomeP
/homeassistant/components/fivem/ @Sander0542
/tests/components/fivem/ @Sander0542
/homeassistant/components/fixer/ @fabaff
/homeassistant/components/fjaraskupan/ @elupus
/tests/components/fjaraskupan/ @elupus
/homeassistant/components/flick_electric/ @ZephireNZ
@ -363,6 +366,7 @@ build.json @home-assistant/supervisor
/tests/components/fronius/ @nielstron @farmio
/homeassistant/components/frontend/ @home-assistant/frontend
/tests/components/frontend/ @home-assistant/frontend
/homeassistant/components/frontier_silicon/ @wlcrs
/homeassistant/components/garages_amsterdam/ @klaasnicolaas
/tests/components/garages_amsterdam/ @klaasnicolaas
/homeassistant/components/gdacs/ @exxamalte
@ -445,6 +449,8 @@ build.json @home-assistant/supervisor
/tests/components/home_plus_control/ @chemaaa
/homeassistant/components/homeassistant/ @home-assistant/core
/tests/components/homeassistant/ @home-assistant/core
/homeassistant/components/homeassistant_yellow/ @home-assistant/core
/tests/components/homeassistant_yellow/ @home-assistant/core
/homeassistant/components/homekit/ @bdraco
/tests/components/homekit/ @bdraco
/homeassistant/components/homekit_controller/ @Jc2k @bdraco
@ -465,8 +471,8 @@ build.json @home-assistant/supervisor
/tests/components/huisbaasje/ @dennisschroer
/homeassistant/components/humidifier/ @home-assistant/core @Shulyaka
/tests/components/humidifier/ @home-assistant/core @Shulyaka
/homeassistant/components/hunterdouglas_powerview/ @bdraco @trullock
/tests/components/hunterdouglas_powerview/ @bdraco @trullock
/homeassistant/components/hunterdouglas_powerview/ @bdraco @kingy444 @trullock
/tests/components/hunterdouglas_powerview/ @bdraco @kingy444 @trullock
/homeassistant/components/hvv_departures/ @vigonotion
/tests/components/hvv_departures/ @vigonotion
/homeassistant/components/hydrawise/ @ptcryan
@ -474,8 +480,6 @@ build.json @home-assistant/supervisor
/tests/components/hyperion/ @dermotduffy
/homeassistant/components/ialarm/ @RyuzakiKK
/tests/components/ialarm/ @RyuzakiKK
/homeassistant/components/ialarm_xr/ @bigmoby
/tests/components/ialarm_xr/ @bigmoby
/homeassistant/components/iammeter/ @lewei50
/homeassistant/components/iaqualink/ @flz
/tests/components/iaqualink/ @flz
@ -568,6 +572,7 @@ build.json @home-assistant/supervisor
/tests/components/lcn/ @alengwenus
/homeassistant/components/lg_netcast/ @Drafteed
/homeassistant/components/life360/ @pnbruckner
/tests/components/life360/ @pnbruckner
/homeassistant/components/lifx/ @Djelibeybi
/homeassistant/components/light/ @home-assistant/core
/tests/components/light/ @home-assistant/core
@ -824,7 +829,8 @@ build.json @home-assistant/supervisor
/tests/components/rachio/ @bdraco
/homeassistant/components/radio_browser/ @frenck
/tests/components/radio_browser/ @frenck
/homeassistant/components/radiotherm/ @vinnyfuria
/homeassistant/components/radiotherm/ @bdraco @vinnyfuria
/tests/components/radiotherm/ @bdraco @vinnyfuria
/homeassistant/components/rainbird/ @konikvranik
/homeassistant/components/raincloud/ @vanstinator
/homeassistant/components/rainforest_eagle/ @gtdiehl @jcalbert @hastarin
@ -924,6 +930,8 @@ build.json @home-assistant/supervisor
/tests/components/sighthound/ @robmarkcole
/homeassistant/components/signal_messenger/ @bbernhard
/tests/components/signal_messenger/ @bbernhard
/homeassistant/components/simplepush/ @engrbm87
/tests/components/simplepush/ @engrbm87
/homeassistant/components/simplisafe/ @bachya
/tests/components/simplisafe/ @bachya
/homeassistant/components/sinch/ @bendikrb
@ -931,6 +939,8 @@ build.json @home-assistant/supervisor
/tests/components/siren/ @home-assistant/core @raman325
/homeassistant/components/sisyphus/ @jkeljo
/homeassistant/components/sky_hub/ @rogerselwyn
/homeassistant/components/skybell/ @tkdrob
/tests/components/skybell/ @tkdrob
/homeassistant/components/slack/ @bachya @tkdrob
/tests/components/slack/ @bachya @tkdrob
/homeassistant/components/sleepiq/ @mfugate1 @kbickar
@ -961,8 +971,6 @@ build.json @home-assistant/supervisor
/tests/components/solax/ @squishykid
/homeassistant/components/soma/ @ratsept @sebfortier2288
/tests/components/soma/ @ratsept @sebfortier2288
/homeassistant/components/somfy/ @tetienne
/tests/components/somfy/ @tetienne
/homeassistant/components/sonarr/ @ctalkington
/tests/components/sonarr/ @ctalkington
/homeassistant/components/songpal/ @rytilahti @shenxn
@ -1061,12 +1069,12 @@ build.json @home-assistant/supervisor
/tests/components/todoist/ @boralyl
/homeassistant/components/tolo/ @MatthiasLohr
/tests/components/tolo/ @MatthiasLohr
/homeassistant/components/tomorrowio/ @raman325
/tests/components/tomorrowio/ @raman325
/homeassistant/components/tomorrowio/ @raman325 @lymanepp
/tests/components/tomorrowio/ @raman325 @lymanepp
/homeassistant/components/totalconnect/ @austinmroczek
/tests/components/totalconnect/ @austinmroczek
/homeassistant/components/tplink/ @rytilahti @thegardenmonkey @bdraco
/tests/components/tplink/ @rytilahti @thegardenmonkey @bdraco
/homeassistant/components/tplink/ @rytilahti @thegardenmonkey
/tests/components/tplink/ @rytilahti @thegardenmonkey
/homeassistant/components/traccar/ @ludeeus
/tests/components/traccar/ @ludeeus
/homeassistant/components/trace/ @home-assistant/core
@ -1157,8 +1165,8 @@ build.json @home-assistant/supervisor
/tests/components/watttime/ @bachya
/homeassistant/components/waze_travel_time/ @eifinger
/tests/components/waze_travel_time/ @eifinger
/homeassistant/components/weather/ @fabaff
/tests/components/weather/ @fabaff
/homeassistant/components/weather/ @home-assistant/core
/tests/components/weather/ @home-assistant/core
/homeassistant/components/webhook/ @home-assistant/core
/tests/components/webhook/ @home-assistant/core
/homeassistant/components/webostv/ @bendavid @thecode

View File

@ -123,7 +123,7 @@ enforcement ladder][mozilla].
## Adoption
This Code of Conduct was first adopted January 21st, 2017 and announced in
This Code of Conduct was first adopted on January 21st, 2017, and announced in
[this][coc-blog] blog post and has been updated on May 25th, 2020 to version
2.0 of the [Contributor Covenant][homepage] as announced in [this][coc2-blog]
blog post.

View File

@ -25,21 +25,6 @@ RUN \
-e ./homeassistant --use-deprecated=legacy-resolver \
&& python3 -m compileall homeassistant/homeassistant
# Fix Bug with Alpine 3.14 and sqlite 3.35
# https://gitlab.alpinelinux.org/alpine/aports/-/issues/12524
ARG BUILD_ARCH
RUN \
if [ "${BUILD_ARCH}" = "amd64" ]; then \
export APK_ARCH=x86_64; \
elif [ "${BUILD_ARCH}" = "i386" ]; then \
export APK_ARCH=x86; \
else \
export APK_ARCH=${BUILD_ARCH}; \
fi \
&& curl -O http://dl-cdn.alpinelinux.org/alpine/v3.13/main/${APK_ARCH}/sqlite-libs-3.34.1-r0.apk \
&& apk add --no-cache sqlite-libs-3.34.1-r0.apk \
&& rm -f sqlite-libs-3.34.1-r0.apk
# Home Assistant S6-Overlay
COPY rootfs /

View File

@ -18,6 +18,7 @@ RUN \
libavfilter-dev \
libpcap-dev \
libturbojpeg0 \
libyaml-dev \
libxml2 \
git \
cmake \

View File

@ -1,11 +1,11 @@
image: homeassistant/{arch}-homeassistant
shadow_repository: ghcr.io/home-assistant
build_from:
aarch64: ghcr.io/home-assistant/aarch64-homeassistant-base:2022.05.0
armhf: ghcr.io/home-assistant/armhf-homeassistant-base:2022.05.0
armv7: ghcr.io/home-assistant/armv7-homeassistant-base:2022.05.0
amd64: ghcr.io/home-assistant/amd64-homeassistant-base:2022.05.0
i386: ghcr.io/home-assistant/i386-homeassistant-base:2022.05.0
aarch64: ghcr.io/home-assistant/aarch64-homeassistant-base:2022.06.2
armhf: ghcr.io/home-assistant/armhf-homeassistant-base:2022.06.2
armv7: ghcr.io/home-assistant/armv7-homeassistant-base:2022.06.2
amd64: ghcr.io/home-assistant/amd64-homeassistant-base:2022.06.2
i386: ghcr.io/home-assistant/i386-homeassistant-base:2022.06.2
codenotary:
signer: notary@home-assistant.io
base_image: notary@home-assistant.io

View File

@ -20,6 +20,7 @@ from .mfa_modules import MultiFactorAuthModule, auth_mfa_module_from_config
from .providers import AuthProvider, LoginFlow, auth_provider_from_config
EVENT_USER_ADDED = "user_added"
EVENT_USER_UPDATED = "user_updated"
EVENT_USER_REMOVED = "user_removed"
_MfaModuleDict = dict[str, MultiFactorAuthModule]
@ -103,7 +104,7 @@ class AuthManagerFlowManager(data_entry_flow.FlowManager):
"""Return a user as result of login flow."""
flow = cast(LoginFlow, flow)
if result["type"] != data_entry_flow.RESULT_TYPE_CREATE_ENTRY:
if result["type"] != data_entry_flow.FlowResultType.CREATE_ENTRY:
return result
# we got final result
@ -338,6 +339,8 @@ class AuthManager:
else:
await self.async_deactivate_user(user)
self.hass.bus.async_fire(EVENT_USER_UPDATED, {"user_id": user.id})
async def async_activate_user(self, user: models.User) -> None:
"""Activate a user."""
await self._store.async_activate_user(user)

View File

@ -272,7 +272,7 @@ class LoginFlow(data_entry_flow.FlowHandler):
if not errors:
return await self.async_finish(self.credential)
description_placeholders: dict[str, str | None] = {
description_placeholders: dict[str, str] = {
"mfa_module_name": auth_module.name,
"mfa_module_id": auth_module.id,
}

View File

@ -7,6 +7,7 @@ from datetime import datetime, timedelta
import logging
import logging.handlers
import os
import platform
import sys
import threading
from time import monotonic
@ -398,7 +399,7 @@ def _get_domains(hass: core.HomeAssistant, config: dict[str, Any]) -> set[str]:
domains.update(hass.config_entries.async_domains())
# Make sure the Hass.io component is loaded
if "HASSIO" in os.environ:
if "SUPERVISOR" in os.environ:
domains.add("hassio")
return domains
@ -540,11 +541,22 @@ async def _async_set_up_integrations(
stage_2_domains = domains_to_setup - logging_domains - debuggers - stage_1_domains
def _cache_uname_processor() -> None:
"""Cache the result of platform.uname().processor in the executor.
Multiple modules call this function at startup which
executes a blocking subprocess call. This is a problem for the
asyncio event loop. By primeing the cache of uname we can
avoid the blocking call in the event loop.
"""
platform.uname().processor # pylint: disable=expression-not-assigned
# Load the registries
await asyncio.gather(
device_registry.async_load(hass),
entity_registry.async_load(hass),
area_registry.async_load(hass),
hass.async_add_executor_job(_cache_uname_processor),
)
# Start setup

View File

@ -1,6 +1,7 @@
"""Config flow for the Abode Security System component."""
from __future__ import annotations
from collections.abc import Mapping
from http import HTTPStatus
from typing import Any, cast
@ -149,9 +150,9 @@ class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
return await self._async_abode_mfa_login()
async def async_step_reauth(self, config: dict[str, Any]) -> FlowResult:
async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult:
"""Handle reauthorization request from Abode."""
self._username = config[CONF_USERNAME]
self._username = entry_data[CONF_USERNAME]
return await self.async_step_reauth_confirm()

View File

@ -11,9 +11,7 @@ from homeassistant.components.light import (
ATTR_BRIGHTNESS,
ATTR_COLOR_TEMP,
ATTR_HS_COLOR,
SUPPORT_BRIGHTNESS,
SUPPORT_COLOR,
SUPPORT_COLOR_TEMP,
ColorMode,
LightEntity,
)
from homeassistant.config_entries import ConfigEntry
@ -101,11 +99,27 @@ class AbodeLight(AbodeDevice, LightEntity):
_hs = self._device.color
return _hs
@property
def color_mode(self) -> str | None:
"""Return the color mode of the light."""
if self._device.is_dimmable and self._device.is_color_capable:
if self.hs_color is not None:
return ColorMode.HS
return ColorMode.COLOR_TEMP
if self._device.is_dimmable:
return ColorMode.BRIGHTNESS
return ColorMode.ONOFF
@property
def supported_color_modes(self) -> set[str] | None:
"""Flag supported color modes."""
if self._device.is_dimmable and self._device.is_color_capable:
return {ColorMode.COLOR_TEMP, ColorMode.HS}
if self._device.is_dimmable:
return {ColorMode.BRIGHTNESS}
return {ColorMode.ONOFF}
@property
def supported_features(self) -> int:
"""Flag supported features."""
if self._device.is_dimmable and self._device.is_color_capable:
return SUPPORT_BRIGHTNESS | SUPPORT_COLOR | SUPPORT_COLOR_TEMP
if self._device.is_dimmable:
return SUPPORT_BRIGHTNESS
return 0

View File

@ -4,6 +4,11 @@
"single_instance_allowed": "Endast en enda konfiguration av Abode \u00e4r till\u00e5ten."
},
"step": {
"reauth_confirm": {
"data": {
"username": "E-postadress"
}
},
"user": {
"data": {
"password": "L\u00f6senord",

View File

@ -3,6 +3,9 @@
"abort": {
"single_instance_allowed": "Ya est\u00e1 configurado. Solo es posible una \u00fanica configuraci\u00f3n."
},
"create_entry": {
"default": "Algunos sensores no est\u00e1n habilitados de forma predeterminada. Puede habilitarlos en el registro de la entidad despu\u00e9s de la configuraci\u00f3n de la integraci\u00f3n.\n El pron\u00f3stico del tiempo no est\u00e1 habilitado de forma predeterminada. Puedes habilitarlo en las opciones de integraci\u00f3n."
},
"error": {
"cannot_connect": "No se pudo conectar",
"invalid_api_key": "Clave API no v\u00e1lida",

View File

@ -3,6 +3,9 @@
"abort": {
"single_instance_allowed": "\u05ea\u05e6\u05d5\u05e8\u05ea\u05d5 \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4. \u05e8\u05e7 \u05ea\u05e6\u05d5\u05e8\u05d4 \u05d0\u05d7\u05ea \u05d0\u05e4\u05e9\u05e8\u05d9\u05ea."
},
"create_entry": {
"default": "\u05d7\u05d9\u05d9\u05e9\u05e0\u05d9\u05dd \u05de\u05e1\u05d5\u05d9\u05de\u05d9\u05dd \u05d0\u05d9\u05e0\u05dd \u05de\u05d5\u05e4\u05e2\u05dc\u05d9\u05dd \u05db\u05d1\u05e8\u05d9\u05e8\u05ea \u05de\u05d7\u05d3\u05dc. \u05d1\u05d9\u05db\u05d5\u05dc\u05ea\u05da \u05dc\u05d4\u05e4\u05e2\u05d9\u05dc \u05d0\u05d5\u05ea\u05dd \u05d1\u05e8\u05d9\u05e9\u05d5\u05dd \u05d4\u05d9\u05e9\u05d5\u05d9\u05d5\u05ea \u05dc\u05d0\u05d7\u05e8 \u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05e9\u05d9\u05dc\u05d5\u05d1.\n \u05ea\u05d7\u05d6\u05d9\u05ea \u05de\u05d6\u05d2 \u05d4\u05d0\u05d5\u05d5\u05d9\u05e8 \u05d0\u05d9\u05e0\u05d4 \u05de\u05d5\u05e4\u05e2\u05dc\u05ea \u05db\u05d1\u05e8\u05d9\u05e8\u05ea \u05de\u05d7\u05d3\u05dc. \u05d1\u05d9\u05db\u05d5\u05dc\u05ea\u05da \u05dc\u05d4\u05e4\u05e2\u05d9\u05dc \u05d0\u05ea \u05d6\u05d4 \u05d1\u05d0\u05e4\u05e9\u05e8\u05d5\u05d9\u05d5\u05ea \u05d4\u05e9\u05d9\u05dc\u05d5\u05d1."
},
"error": {
"cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4",
"invalid_api_key": "\u05de\u05e4\u05ea\u05d7 API \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9",

View File

@ -0,0 +1,11 @@
{
"config": {
"step": {
"user": {
"data": {
"api_key": "API-nyckel"
}
}
}
}
}

View File

@ -6,19 +6,26 @@ from typing import Any, cast
from homeassistant.components.weather import (
ATTR_FORECAST_CONDITION,
ATTR_FORECAST_PRECIPITATION,
ATTR_FORECAST_NATIVE_PRECIPITATION,
ATTR_FORECAST_NATIVE_TEMP,
ATTR_FORECAST_NATIVE_TEMP_LOW,
ATTR_FORECAST_NATIVE_WIND_SPEED,
ATTR_FORECAST_PRECIPITATION_PROBABILITY,
ATTR_FORECAST_TEMP,
ATTR_FORECAST_TEMP_LOW,
ATTR_FORECAST_TIME,
ATTR_FORECAST_WIND_BEARING,
ATTR_FORECAST_WIND_SPEED,
Forecast,
WeatherEntity,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONF_NAME,
LENGTH_INCHES,
LENGTH_KILOMETERS,
LENGTH_MILES,
LENGTH_MILLIMETERS,
PRESSURE_HPA,
PRESSURE_INHG,
SPEED_KILOMETERS_PER_HOUR,
SPEED_MILES_PER_HOUR,
TEMP_CELSIUS,
TEMP_FAHRENHEIT,
@ -66,19 +73,25 @@ class AccuWeatherEntity(
) -> None:
"""Initialize."""
super().__init__(coordinator)
self._unit_system = API_METRIC if coordinator.is_metric else API_IMPERIAL
wind_speed_unit = self.coordinator.data["Wind"]["Speed"][self._unit_system][
"Unit"
]
if wind_speed_unit == "mi/h":
self._attr_wind_speed_unit = SPEED_MILES_PER_HOUR
# Coordinator data is used also for sensors which don't have units automatically
# converted, hence the weather entity's native units follow the configured unit
# system
if coordinator.is_metric:
self._attr_native_precipitation_unit = LENGTH_MILLIMETERS
self._attr_native_pressure_unit = PRESSURE_HPA
self._attr_native_temperature_unit = TEMP_CELSIUS
self._attr_native_visibility_unit = LENGTH_KILOMETERS
self._attr_native_wind_speed_unit = SPEED_KILOMETERS_PER_HOUR
self._unit_system = API_METRIC
else:
self._attr_wind_speed_unit = wind_speed_unit
self._unit_system = API_IMPERIAL
self._attr_native_precipitation_unit = LENGTH_INCHES
self._attr_native_pressure_unit = PRESSURE_INHG
self._attr_native_temperature_unit = TEMP_FAHRENHEIT
self._attr_native_visibility_unit = LENGTH_MILES
self._attr_native_wind_speed_unit = SPEED_MILES_PER_HOUR
self._attr_name = name
self._attr_unique_id = coordinator.location_key
self._attr_temperature_unit = (
TEMP_CELSIUS if coordinator.is_metric else TEMP_FAHRENHEIT
)
self._attr_attribution = ATTRIBUTION
self._attr_device_info = DeviceInfo(
entry_type=DeviceEntryType.SERVICE,
@ -106,14 +119,14 @@ class AccuWeatherEntity(
return None
@property
def temperature(self) -> float:
def native_temperature(self) -> float:
"""Return the temperature."""
return cast(
float, self.coordinator.data["Temperature"][self._unit_system]["Value"]
)
@property
def pressure(self) -> float:
def native_pressure(self) -> float:
"""Return the pressure."""
return cast(
float, self.coordinator.data["Pressure"][self._unit_system]["Value"]
@ -125,7 +138,7 @@ class AccuWeatherEntity(
return cast(int, self.coordinator.data["RelativeHumidity"])
@property
def wind_speed(self) -> float:
def native_wind_speed(self) -> float:
"""Return the wind speed."""
return cast(
float, self.coordinator.data["Wind"]["Speed"][self._unit_system]["Value"]
@ -137,7 +150,7 @@ class AccuWeatherEntity(
return cast(int, self.coordinator.data["Wind"]["Direction"]["Degrees"])
@property
def visibility(self) -> float:
def native_visibility(self) -> float:
"""Return the visibility."""
return cast(
float, self.coordinator.data["Visibility"][self._unit_system]["Value"]
@ -162,9 +175,9 @@ class AccuWeatherEntity(
return [
{
ATTR_FORECAST_TIME: utc_from_timestamp(item["EpochDate"]).isoformat(),
ATTR_FORECAST_TEMP: item["TemperatureMax"]["Value"],
ATTR_FORECAST_TEMP_LOW: item["TemperatureMin"]["Value"],
ATTR_FORECAST_PRECIPITATION: self._calc_precipitation(item),
ATTR_FORECAST_NATIVE_TEMP: item["TemperatureMax"]["Value"],
ATTR_FORECAST_NATIVE_TEMP_LOW: item["TemperatureMin"]["Value"],
ATTR_FORECAST_NATIVE_PRECIPITATION: self._calc_precipitation(item),
ATTR_FORECAST_PRECIPITATION_PROBABILITY: round(
mean(
[
@ -173,7 +186,7 @@ class AccuWeatherEntity(
]
)
),
ATTR_FORECAST_WIND_SPEED: item["WindDay"]["Speed"]["Value"],
ATTR_FORECAST_NATIVE_WIND_SPEED: item["WindDay"]["Speed"]["Value"],
ATTR_FORECAST_WIND_BEARING: item["WindDay"]["Direction"]["Degrees"],
ATTR_FORECAST_CONDITION: [
k for k, v in CONDITION_CLASSES.items() if item["IconDay"] in v

View File

@ -1,6 +1,8 @@
"""Support for Acmeda Roller Blinds."""
from __future__ import annotations
from typing import Any
from homeassistant.components.cover import (
ATTR_POSITION,
CoverEntity,
@ -45,7 +47,7 @@ class AcmedaCover(AcmedaBase, CoverEntity):
"""Representation of a Acmeda cover device."""
@property
def current_cover_position(self):
def current_cover_position(self) -> int | None:
"""Return the current position of the roller blind.
None is unknown, 0 is closed, 100 is fully open.
@ -56,7 +58,7 @@ class AcmedaCover(AcmedaBase, CoverEntity):
return position
@property
def current_cover_tilt_position(self):
def current_cover_tilt_position(self) -> int | None:
"""Return the current tilt of the roller blind.
None is unknown, 0 is closed, 100 is fully open.
@ -67,7 +69,7 @@ class AcmedaCover(AcmedaBase, CoverEntity):
return position
@property
def supported_features(self):
def supported_features(self) -> int:
"""Flag supported features."""
supported_features = 0
if self.current_cover_position is not None:
@ -88,35 +90,35 @@ class AcmedaCover(AcmedaBase, CoverEntity):
return supported_features
@property
def is_closed(self):
def is_closed(self) -> bool:
"""Return if the cover is closed."""
return self.roller.closed_percent == 100
async def async_close_cover(self, **kwargs):
async def async_close_cover(self, **kwargs: Any) -> None:
"""Close the roller."""
await self.roller.move_down()
async def async_open_cover(self, **kwargs):
async def async_open_cover(self, **kwargs: Any) -> None:
"""Open the roller."""
await self.roller.move_up()
async def async_stop_cover(self, **kwargs):
async def async_stop_cover(self, **kwargs: Any) -> None:
"""Stop the roller."""
await self.roller.move_stop()
async def async_set_cover_position(self, **kwargs):
async def async_set_cover_position(self, **kwargs: Any) -> None:
"""Move the roller shutter to a specific position."""
await self.roller.move_to(100 - kwargs[ATTR_POSITION])
async def async_close_cover_tilt(self, **kwargs):
async def async_close_cover_tilt(self, **kwargs: Any) -> None:
"""Close the roller."""
await self.roller.move_down()
async def async_open_cover_tilt(self, **kwargs):
async def async_open_cover_tilt(self, **kwargs: Any) -> None:
"""Open the roller."""
await self.roller.move_up()
async def async_stop_cover_tilt(self, **kwargs):
async def async_stop_cover_tilt(self, **kwargs: Any) -> None:
"""Stop the roller."""
await self.roller.move_stop()

View File

@ -115,14 +115,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload AdGuard Home config entry."""
hass.services.async_remove(DOMAIN, SERVICE_ADD_URL)
hass.services.async_remove(DOMAIN, SERVICE_REMOVE_URL)
hass.services.async_remove(DOMAIN, SERVICE_ENABLE_URL)
hass.services.async_remove(DOMAIN, SERVICE_DISABLE_URL)
hass.services.async_remove(DOMAIN, SERVICE_REFRESH)
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id)
if not hass.data[DOMAIN]:
hass.services.async_remove(DOMAIN, SERVICE_ADD_URL)
hass.services.async_remove(DOMAIN, SERVICE_REMOVE_URL)
hass.services.async_remove(DOMAIN, SERVICE_ENABLE_URL)
hass.services.async_remove(DOMAIN, SERVICE_DISABLE_URL)
hass.services.async_remove(DOMAIN, SERVICE_REFRESH)
del hass.data[DOMAIN]
return unload_ok

View File

@ -1,6 +1,8 @@
"""Support for ADS covers."""
from __future__ import annotations
from typing import Any
import pyads
import voluptuous as vol
@ -122,7 +124,7 @@ class AdsCover(AdsEntity, CoverEntity):
if ads_var_pos_set is not None:
self._attr_supported_features |= CoverEntityFeature.SET_POSITION
async def async_added_to_hass(self):
async def async_added_to_hass(self) -> None:
"""Register device notification."""
if self._ads_var is not None:
await self.async_initialize_device(self._ads_var, pyads.PLCTYPE_BOOL)
@ -133,7 +135,7 @@ class AdsCover(AdsEntity, CoverEntity):
)
@property
def is_closed(self):
def is_closed(self) -> bool | None:
"""Return if the cover is closed."""
if self._ads_var is not None:
return self._state_dict[STATE_KEY_STATE]
@ -142,16 +144,16 @@ class AdsCover(AdsEntity, CoverEntity):
return None
@property
def current_cover_position(self):
def current_cover_position(self) -> int:
"""Return current position of cover."""
return self._state_dict[STATE_KEY_POSITION]
def stop_cover(self, **kwargs):
def stop_cover(self, **kwargs: Any) -> None:
"""Fire the stop action."""
if self._ads_var_stop:
self._ads_hub.write_by_name(self._ads_var_stop, True, pyads.PLCTYPE_BOOL)
def set_cover_position(self, **kwargs):
def set_cover_position(self, **kwargs: Any) -> None:
"""Set cover position."""
position = kwargs[ATTR_POSITION]
if self._ads_var_pos_set is not None:
@ -159,14 +161,14 @@ class AdsCover(AdsEntity, CoverEntity):
self._ads_var_pos_set, position, pyads.PLCTYPE_BYTE
)
def open_cover(self, **kwargs):
def open_cover(self, **kwargs: Any) -> None:
"""Move the cover up."""
if self._ads_var_open is not None:
self._ads_hub.write_by_name(self._ads_var_open, True, pyads.PLCTYPE_BOOL)
elif self._ads_var_pos_set is not None:
self.set_cover_position(position=100)
def close_cover(self, **kwargs):
def close_cover(self, **kwargs: Any) -> None:
"""Move the cover down."""
if self._ads_var_close is not None:
self._ads_hub.write_by_name(self._ads_var_close, True, pyads.PLCTYPE_BOOL)

View File

@ -1,4 +1,6 @@
"""Cover platform for Advantage Air integration."""
from typing import Any
from homeassistant.components.cover import (
ATTR_POSITION,
CoverDeviceClass,
@ -56,18 +58,18 @@ class AdvantageAirZoneVent(AdvantageAirEntity, CoverEntity):
)
@property
def is_closed(self):
def is_closed(self) -> bool:
"""Return if vent is fully closed."""
return self._zone["state"] == ADVANTAGE_AIR_STATE_CLOSE
@property
def current_cover_position(self):
def current_cover_position(self) -> int:
"""Return vents current position as a percentage."""
if self._zone["state"] == ADVANTAGE_AIR_STATE_OPEN:
return self._zone["value"]
return 0
async def async_open_cover(self, **kwargs):
async def async_open_cover(self, **kwargs: Any) -> None:
"""Fully open zone vent."""
await self.async_change(
{
@ -79,7 +81,7 @@ class AdvantageAirZoneVent(AdvantageAirEntity, CoverEntity):
}
)
async def async_close_cover(self, **kwargs):
async def async_close_cover(self, **kwargs: Any) -> None:
"""Fully close zone vent."""
await self.async_change(
{
@ -89,7 +91,7 @@ class AdvantageAirZoneVent(AdvantageAirEntity, CoverEntity):
}
)
async def async_set_cover_position(self, **kwargs):
async def async_set_cover_position(self, **kwargs: Any) -> None:
"""Change vent position."""
position = round(kwargs[ATTR_POSITION] / 5) * 5
if position == 0:

View File

@ -1,4 +1,6 @@
"""Config flow for AEMET OpenData."""
from __future__ import annotations
from aemet_opendata import AEMET
import voluptuous as vol
@ -50,7 +52,9 @@ class AemetConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
@staticmethod
@callback
def async_get_options_flow(config_entry):
def async_get_options_flow(
config_entry: config_entries.ConfigEntry,
) -> OptionsFlowHandler:
"""Get the options flow for this handler."""
return OptionsFlowHandler(config_entry)

View File

@ -17,14 +17,6 @@ from homeassistant.components.weather import (
ATTR_CONDITION_RAINY,
ATTR_CONDITION_SNOWY,
ATTR_CONDITION_SUNNY,
ATTR_FORECAST_CONDITION,
ATTR_FORECAST_PRECIPITATION,
ATTR_FORECAST_PRECIPITATION_PROBABILITY,
ATTR_FORECAST_TEMP,
ATTR_FORECAST_TEMP_LOW,
ATTR_FORECAST_TIME,
ATTR_FORECAST_WIND_BEARING,
ATTR_FORECAST_WIND_SPEED,
)
from homeassistant.const import (
DEGREE,
@ -45,8 +37,16 @@ ENTRY_NAME = "name"
ENTRY_WEATHER_COORDINATOR = "weather_coordinator"
ATTR_API_CONDITION = "condition"
ATTR_API_FORECAST_CONDITION = "condition"
ATTR_API_FORECAST_DAILY = "forecast-daily"
ATTR_API_FORECAST_HOURLY = "forecast-hourly"
ATTR_API_FORECAST_PRECIPITATION = "precipitation"
ATTR_API_FORECAST_PRECIPITATION_PROBABILITY = "precipitation_probability"
ATTR_API_FORECAST_TEMP = "temperature"
ATTR_API_FORECAST_TEMP_LOW = "templow"
ATTR_API_FORECAST_TIME = "datetime"
ATTR_API_FORECAST_WIND_BEARING = "wind_bearing"
ATTR_API_FORECAST_WIND_SPEED = "wind_speed"
ATTR_API_HUMIDITY = "humidity"
ATTR_API_PRESSURE = "pressure"
ATTR_API_RAIN = "rain"
@ -158,14 +158,14 @@ CONDITIONS_MAP = {
}
FORECAST_MONITORED_CONDITIONS = [
ATTR_FORECAST_CONDITION,
ATTR_FORECAST_PRECIPITATION,
ATTR_FORECAST_PRECIPITATION_PROBABILITY,
ATTR_FORECAST_TEMP,
ATTR_FORECAST_TEMP_LOW,
ATTR_FORECAST_TIME,
ATTR_FORECAST_WIND_BEARING,
ATTR_FORECAST_WIND_SPEED,
ATTR_API_FORECAST_CONDITION,
ATTR_API_FORECAST_PRECIPITATION,
ATTR_API_FORECAST_PRECIPITATION_PROBABILITY,
ATTR_API_FORECAST_TEMP,
ATTR_API_FORECAST_TEMP_LOW,
ATTR_API_FORECAST_TIME,
ATTR_API_FORECAST_WIND_BEARING,
ATTR_API_FORECAST_WIND_SPEED,
]
MONITORED_CONDITIONS = [
ATTR_API_CONDITION,
@ -202,43 +202,43 @@ FORECAST_MODE_ATTR_API = {
FORECAST_SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
SensorEntityDescription(
key=ATTR_FORECAST_CONDITION,
key=ATTR_API_FORECAST_CONDITION,
name="Condition",
),
SensorEntityDescription(
key=ATTR_FORECAST_PRECIPITATION,
key=ATTR_API_FORECAST_PRECIPITATION,
name="Precipitation",
native_unit_of_measurement=PRECIPITATION_MILLIMETERS_PER_HOUR,
),
SensorEntityDescription(
key=ATTR_FORECAST_PRECIPITATION_PROBABILITY,
key=ATTR_API_FORECAST_PRECIPITATION_PROBABILITY,
name="Precipitation probability",
native_unit_of_measurement=PERCENTAGE,
),
SensorEntityDescription(
key=ATTR_FORECAST_TEMP,
key=ATTR_API_FORECAST_TEMP,
name="Temperature",
native_unit_of_measurement=TEMP_CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE,
),
SensorEntityDescription(
key=ATTR_FORECAST_TEMP_LOW,
key=ATTR_API_FORECAST_TEMP_LOW,
name="Temperature Low",
native_unit_of_measurement=TEMP_CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE,
),
SensorEntityDescription(
key=ATTR_FORECAST_TIME,
key=ATTR_API_FORECAST_TIME,
name="Time",
device_class=SensorDeviceClass.TIMESTAMP,
),
SensorEntityDescription(
key=ATTR_FORECAST_WIND_BEARING,
key=ATTR_API_FORECAST_WIND_BEARING,
name="Wind bearing",
native_unit_of_measurement=DEGREE,
),
SensorEntityDescription(
key=ATTR_FORECAST_WIND_SPEED,
key=ATTR_API_FORECAST_WIND_SPEED,
name="Wind speed",
native_unit_of_measurement=SPEED_KILOMETERS_PER_HOUR,
),

View File

@ -10,7 +10,7 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.util import dt as dt_util
from .const import (
ATTR_FORECAST_TIME,
ATTR_API_FORECAST_TIME,
ATTRIBUTION,
DOMAIN,
ENTRY_NAME,
@ -45,17 +45,13 @@ async def async_setup_entry(
entities.extend(
[
AemetForecastSensor(
name_prefix,
unique_id_prefix,
f"{domain_data[ENTRY_NAME]} {mode} Forecast",
f"{unique_id}-forecast-{mode}",
weather_coordinator,
mode,
description,
)
for mode in FORECAST_MODES
if (
(name_prefix := f"{domain_data[ENTRY_NAME]} {mode} Forecast")
and (unique_id_prefix := f"{unique_id}-forecast-{mode}")
)
for description in FORECAST_SENSOR_TYPES
if description.key in FORECAST_MONITORED_CONDITIONS
]
@ -89,14 +85,14 @@ class AemetSensor(AbstractAemetSensor):
def __init__(
self,
name,
unique_id,
unique_id_prefix,
weather_coordinator: WeatherUpdateCoordinator,
description: SensorEntityDescription,
):
"""Initialize the sensor."""
super().__init__(
name=name,
unique_id=f"{unique_id}-{description.key}",
unique_id=f"{unique_id_prefix}-{description.key}",
coordinator=weather_coordinator,
description=description,
)
@ -113,7 +109,7 @@ class AemetForecastSensor(AbstractAemetSensor):
def __init__(
self,
name,
unique_id,
unique_id_prefix,
weather_coordinator: WeatherUpdateCoordinator,
forecast_mode,
description: SensorEntityDescription,
@ -121,7 +117,7 @@ class AemetForecastSensor(AbstractAemetSensor):
"""Initialize the sensor."""
super().__init__(
name=name,
unique_id=f"{unique_id}-{description.key}",
unique_id=f"{unique_id_prefix}-{description.key}",
coordinator=weather_coordinator,
description=description,
)
@ -139,6 +135,6 @@ class AemetForecastSensor(AbstractAemetSensor):
)
if forecasts:
forecast = forecasts[0].get(self.entity_description.key)
if self.entity_description.key == ATTR_FORECAST_TIME:
if self.entity_description.key == ATTR_API_FORECAST_TIME:
forecast = dt_util.parse_datetime(forecast)
return forecast

View File

@ -1,5 +1,8 @@
{
"config": {
"abort": {
"already_configured": "\u041c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u0442\u043e \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e"
},
"error": {
"invalid_api_key": "\u041d\u0435\u0432\u0430\u043b\u0438\u0434\u0435\u043d API \u043a\u043b\u044e\u0447"
},

View File

@ -0,0 +1,11 @@
{
"config": {
"step": {
"user": {
"data": {
"api_key": "API-nyckel"
}
}
}
}
}

View File

@ -1,13 +1,36 @@
"""Support for the AEMET OpenData service."""
from homeassistant.components.weather import WeatherEntity
from homeassistant.components.weather import (
ATTR_FORECAST_CONDITION,
ATTR_FORECAST_NATIVE_PRECIPITATION,
ATTR_FORECAST_NATIVE_TEMP,
ATTR_FORECAST_NATIVE_TEMP_LOW,
ATTR_FORECAST_NATIVE_WIND_SPEED,
ATTR_FORECAST_PRECIPITATION_PROBABILITY,
ATTR_FORECAST_TIME,
ATTR_FORECAST_WIND_BEARING,
WeatherEntity,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import PRESSURE_HPA, SPEED_KILOMETERS_PER_HOUR, TEMP_CELSIUS
from homeassistant.const import (
LENGTH_MILLIMETERS,
PRESSURE_HPA,
SPEED_KILOMETERS_PER_HOUR,
TEMP_CELSIUS,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import (
ATTR_API_CONDITION,
ATTR_API_FORECAST_CONDITION,
ATTR_API_FORECAST_PRECIPITATION,
ATTR_API_FORECAST_PRECIPITATION_PROBABILITY,
ATTR_API_FORECAST_TEMP,
ATTR_API_FORECAST_TEMP_LOW,
ATTR_API_FORECAST_TIME,
ATTR_API_FORECAST_WIND_BEARING,
ATTR_API_FORECAST_WIND_SPEED,
ATTR_API_HUMIDITY,
ATTR_API_PRESSURE,
ATTR_API_TEMPERATURE,
@ -19,10 +42,32 @@ from .const import (
ENTRY_WEATHER_COORDINATOR,
FORECAST_MODE_ATTR_API,
FORECAST_MODE_DAILY,
FORECAST_MODE_HOURLY,
FORECAST_MODES,
)
from .weather_update_coordinator import WeatherUpdateCoordinator
FORECAST_MAP = {
FORECAST_MODE_DAILY: {
ATTR_API_FORECAST_CONDITION: ATTR_FORECAST_CONDITION,
ATTR_API_FORECAST_PRECIPITATION_PROBABILITY: ATTR_FORECAST_PRECIPITATION_PROBABILITY,
ATTR_API_FORECAST_TEMP_LOW: ATTR_FORECAST_NATIVE_TEMP_LOW,
ATTR_API_FORECAST_TEMP: ATTR_FORECAST_NATIVE_TEMP,
ATTR_API_FORECAST_TIME: ATTR_FORECAST_TIME,
ATTR_API_FORECAST_WIND_BEARING: ATTR_FORECAST_WIND_BEARING,
ATTR_API_FORECAST_WIND_SPEED: ATTR_FORECAST_NATIVE_WIND_SPEED,
},
FORECAST_MODE_HOURLY: {
ATTR_API_FORECAST_CONDITION: ATTR_FORECAST_CONDITION,
ATTR_API_FORECAST_PRECIPITATION_PROBABILITY: ATTR_FORECAST_PRECIPITATION_PROBABILITY,
ATTR_API_FORECAST_PRECIPITATION: ATTR_FORECAST_NATIVE_PRECIPITATION,
ATTR_API_FORECAST_TEMP: ATTR_FORECAST_NATIVE_TEMP,
ATTR_API_FORECAST_TIME: ATTR_FORECAST_TIME,
ATTR_API_FORECAST_WIND_BEARING: ATTR_FORECAST_WIND_BEARING,
ATTR_API_FORECAST_WIND_SPEED: ATTR_FORECAST_NATIVE_WIND_SPEED,
},
}
async def async_setup_entry(
hass: HomeAssistant,
@ -47,9 +92,10 @@ class AemetWeather(CoordinatorEntity[WeatherUpdateCoordinator], WeatherEntity):
"""Implementation of an AEMET OpenData sensor."""
_attr_attribution = ATTRIBUTION
_attr_temperature_unit = TEMP_CELSIUS
_attr_pressure_unit = PRESSURE_HPA
_attr_wind_speed_unit = SPEED_KILOMETERS_PER_HOUR
_attr_native_precipitation_unit = LENGTH_MILLIMETERS
_attr_native_pressure_unit = PRESSURE_HPA
_attr_native_temperature_unit = TEMP_CELSIUS
_attr_native_wind_speed_unit = SPEED_KILOMETERS_PER_HOUR
def __init__(
self,
@ -75,7 +121,12 @@ class AemetWeather(CoordinatorEntity[WeatherUpdateCoordinator], WeatherEntity):
@property
def forecast(self):
"""Return the forecast array."""
return self.coordinator.data[FORECAST_MODE_ATTR_API[self._forecast_mode]]
forecasts = self.coordinator.data[FORECAST_MODE_ATTR_API[self._forecast_mode]]
forecast_map = FORECAST_MAP[self._forecast_mode]
return [
{ha_key: forecast[api_key] for api_key, ha_key in forecast_map.items()}
for forecast in forecasts
]
@property
def humidity(self):
@ -83,12 +134,12 @@ class AemetWeather(CoordinatorEntity[WeatherUpdateCoordinator], WeatherEntity):
return self.coordinator.data[ATTR_API_HUMIDITY]
@property
def pressure(self):
def native_pressure(self):
"""Return the pressure."""
return self.coordinator.data[ATTR_API_PRESSURE]
@property
def temperature(self):
def native_temperature(self):
"""Return the temperature."""
return self.coordinator.data[ATTR_API_TEMPERATURE]
@ -98,6 +149,6 @@ class AemetWeather(CoordinatorEntity[WeatherUpdateCoordinator], WeatherEntity):
return self.coordinator.data[ATTR_API_WIND_BEARING]
@property
def wind_speed(self):
def native_wind_speed(self):
"""Return the wind speed."""
return self.coordinator.data[ATTR_API_WIND_SPEED]

View File

@ -42,23 +42,21 @@ from aemet_opendata.helpers import (
)
import async_timeout
from homeassistant.components.weather import (
ATTR_FORECAST_CONDITION,
ATTR_FORECAST_PRECIPITATION,
ATTR_FORECAST_PRECIPITATION_PROBABILITY,
ATTR_FORECAST_TEMP,
ATTR_FORECAST_TEMP_LOW,
ATTR_FORECAST_TIME,
ATTR_FORECAST_WIND_BEARING,
ATTR_FORECAST_WIND_SPEED,
)
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from homeassistant.util import dt as dt_util
from .const import (
ATTR_API_CONDITION,
ATTR_API_FORECAST_CONDITION,
ATTR_API_FORECAST_DAILY,
ATTR_API_FORECAST_HOURLY,
ATTR_API_FORECAST_PRECIPITATION,
ATTR_API_FORECAST_PRECIPITATION_PROBABILITY,
ATTR_API_FORECAST_TEMP,
ATTR_API_FORECAST_TEMP_LOW,
ATTR_API_FORECAST_TIME,
ATTR_API_FORECAST_WIND_BEARING,
ATTR_API_FORECAST_WIND_SPEED,
ATTR_API_HUMIDITY,
ATTR_API_PRESSURE,
ATTR_API_RAIN,
@ -402,15 +400,15 @@ class WeatherUpdateCoordinator(DataUpdateCoordinator):
return None
return {
ATTR_FORECAST_CONDITION: condition,
ATTR_FORECAST_PRECIPITATION_PROBABILITY: self._get_precipitation_prob_day(
ATTR_API_FORECAST_CONDITION: condition,
ATTR_API_FORECAST_PRECIPITATION_PROBABILITY: self._get_precipitation_prob_day(
day
),
ATTR_FORECAST_TEMP: self._get_temperature_day(day),
ATTR_FORECAST_TEMP_LOW: self._get_temperature_low_day(day),
ATTR_FORECAST_TIME: dt_util.as_utc(date).isoformat(),
ATTR_FORECAST_WIND_SPEED: self._get_wind_speed_day(day),
ATTR_FORECAST_WIND_BEARING: self._get_wind_bearing_day(day),
ATTR_API_FORECAST_TEMP: self._get_temperature_day(day),
ATTR_API_FORECAST_TEMP_LOW: self._get_temperature_low_day(day),
ATTR_API_FORECAST_TIME: dt_util.as_utc(date).isoformat(),
ATTR_API_FORECAST_WIND_SPEED: self._get_wind_speed_day(day),
ATTR_API_FORECAST_WIND_BEARING: self._get_wind_bearing_day(day),
}
def _convert_forecast_hour(self, date, day, hour):
@ -420,15 +418,15 @@ class WeatherUpdateCoordinator(DataUpdateCoordinator):
forecast_dt = date.replace(hour=hour, minute=0, second=0)
return {
ATTR_FORECAST_CONDITION: condition,
ATTR_FORECAST_PRECIPITATION: self._calc_precipitation(day, hour),
ATTR_FORECAST_PRECIPITATION_PROBABILITY: self._calc_precipitation_prob(
ATTR_API_FORECAST_CONDITION: condition,
ATTR_API_FORECAST_PRECIPITATION: self._calc_precipitation(day, hour),
ATTR_API_FORECAST_PRECIPITATION_PROBABILITY: self._calc_precipitation_prob(
day, hour
),
ATTR_FORECAST_TEMP: self._get_temperature(day, hour),
ATTR_FORECAST_TIME: dt_util.as_utc(forecast_dt).isoformat(),
ATTR_FORECAST_WIND_SPEED: self._get_wind_speed(day, hour),
ATTR_FORECAST_WIND_BEARING: self._get_wind_bearing(day, hour),
ATTR_API_FORECAST_TEMP: self._get_temperature(day, hour),
ATTR_API_FORECAST_TIME: dt_util.as_utc(forecast_dt).isoformat(),
ATTR_API_FORECAST_WIND_SPEED: self._get_wind_speed(day, hour),
ATTR_API_FORECAST_WIND_BEARING: self._get_wind_bearing(day, hour),
}
def _calc_precipitation(self, day, hour):

View File

@ -1,4 +1,6 @@
"""Support for Agent DVR Alarm Control Panels."""
from __future__ import annotations
from homeassistant.components.alarm_control_panel import (
AlarmControlPanelEntity,
AlarmControlPanelEntityFeature,
@ -58,7 +60,7 @@ class AgentBaseStation(AlarmControlPanelEntity):
sw_version=client.version,
)
async def async_update(self):
async def async_update(self) -> None:
"""Update the state of the device."""
await self._client.update()
self._attr_available = self._client.is_available
@ -76,24 +78,24 @@ class AgentBaseStation(AlarmControlPanelEntity):
else:
self._attr_state = STATE_ALARM_DISARMED
async def async_alarm_disarm(self, code=None):
async def async_alarm_disarm(self, code: str | None = None) -> None:
"""Send disarm command."""
await self._client.disarm()
self._attr_state = STATE_ALARM_DISARMED
async def async_alarm_arm_away(self, code=None):
async def async_alarm_arm_away(self, code: str | None = None) -> None:
"""Send arm away command. Uses custom mode."""
await self._client.arm()
await self._client.set_active_profile(CONF_AWAY_MODE_NAME)
self._attr_state = STATE_ALARM_ARMED_AWAY
async def async_alarm_arm_home(self, code=None):
async def async_alarm_arm_home(self, code: str | None = None) -> None:
"""Send arm home command. Uses custom mode."""
await self._client.arm()
await self._client.set_active_profile(CONF_HOME_MODE_NAME)
self._attr_state = STATE_ALARM_ARMED_HOME
async def async_alarm_arm_night(self, code=None):
async def async_alarm_arm_night(self, code: str | None = None) -> None:
"""Send arm night command. Uses custom mode."""
await self._client.arm()
await self._client.set_active_profile(CONF_NIGHT_MODE_NAME)

View File

@ -3,6 +3,9 @@
"abort": {
"already_configured": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e"
},
"error": {
"cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435"
},
"step": {
"user": {
"data": {

View File

@ -4,6 +4,7 @@
"already_configured": "Airly-integrationen f\u00f6r dessa koordinater \u00e4r redan konfigurerad."
},
"error": {
"invalid_api_key": "Ogiltig API-nyckel",
"wrong_location": "Inga Airly m\u00e4tstationer i detta omr\u00e5de."
},
"step": {

View File

@ -0,0 +1,11 @@
{
"config": {
"step": {
"user": {
"data": {
"api_key": "API-nyckel"
}
}
}
}
}

View File

@ -2,6 +2,8 @@
from __future__ import annotations
import asyncio
from collections.abc import Mapping
from typing import Any
from pyairvisual import CloudAPI, NodeSamba
from pyairvisual.errors import (
@ -70,7 +72,7 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
def __init__(self) -> None:
"""Initialize the config flow."""
self._entry_data_for_reauth: dict[str, str] = {}
self._entry_data_for_reauth: Mapping[str, Any] = {}
self._geo_id: str | None = None
@property
@ -219,10 +221,10 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
data={**user_input, CONF_INTEGRATION_TYPE: INTEGRATION_TYPE_NODE_PRO},
)
async def async_step_reauth(self, data: dict[str, str]) -> FlowResult:
async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult:
"""Handle configuration by re-auth."""
self._entry_data_for_reauth = data
self._geo_id = async_get_geography_id(data)
self._entry_data_for_reauth = entry_data
self._geo_id = async_get_geography_id(entry_data)
return await self.async_step_reauth_confirm()
async def async_step_reauth_confirm(

View File

@ -11,7 +11,8 @@
"step": {
"geography_by_coords": {
"data": {
"api_key": "API \u043a\u043b\u044e\u0447"
"api_key": "API \u043a\u043b\u044e\u0447",
"longitude": "\u0413\u0435\u043e\u0433\u0440\u0430\u0444\u0441\u043a\u0430 \u0434\u044a\u043b\u0436\u0438\u043d\u0430"
}
},
"geography_by_name": {

View File

@ -1,5 +1,8 @@
{
"state": {
"airvisual__pollutant_label": {
"p1": "PM10"
},
"airvisual__pollutant_level": {
"good": "\u05d8\u05d5\u05d1",
"unhealthy": "\u05dc\u05d0 \u05d1\u05e8\u05d9\u05d0",

View File

@ -0,0 +1,7 @@
{
"state": {
"airvisual__pollutant_label": {
"co": "Kolmonoxid"
}
}
}

View File

@ -5,6 +5,11 @@
"invalid_api_key": "Ogiltig API-nyckel"
},
"step": {
"geography_by_name": {
"data": {
"api_key": "API-nyckel"
}
},
"node_pro": {
"data": {
"ip_address": "Enhets IP-adress / v\u00e4rdnamn",

View File

@ -3,7 +3,7 @@
"name": "Airzone",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/airzone",
"requirements": ["aioairzone==0.4.4"],
"requirements": ["aioairzone==0.4.5"],
"codeowners": ["@Noltari"],
"iot_class": "local_polling",
"loggers": ["aioairzone"]

View File

@ -1,13 +1,16 @@
"""The aladdin_connect component."""
import asyncio
import logging
from typing import Final
from aladdin_connect import AladdinConnectClient
from AIOAladdinConnect import AladdinConnectClient
from aiohttp import ClientConnectionError
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import DOMAIN
@ -20,9 +23,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up platform from a ConfigEntry."""
username = entry.data[CONF_USERNAME]
password = entry.data[CONF_PASSWORD]
acc = AladdinConnectClient(username, password)
if not await hass.async_add_executor_job(acc.login):
raise ConfigEntryAuthFailed("Incorrect Password")
acc = AladdinConnectClient(username, password, async_get_clientsession(hass))
try:
if not await acc.login():
raise ConfigEntryAuthFailed("Incorrect Password")
except (ClientConnectionError, asyncio.TimeoutError) as ex:
raise ConfigEntryNotReady("Can not connect to host") from ex
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = acc
hass.config_entries.async_setup_platforms(entry, PLATFORMS)

View File

@ -1,10 +1,14 @@
"""Config flow for Aladdin Connect cover integration."""
from __future__ import annotations
import asyncio
from collections.abc import Mapping
import logging
from typing import Any
from aladdin_connect import AladdinConnectClient
from AIOAladdinConnect import AladdinConnectClient
from aiohttp import ClientError
from aiohttp.client_exceptions import ClientConnectionError
import voluptuous as vol
from homeassistant import config_entries
@ -12,6 +16,7 @@ from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResult
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import DOMAIN
@ -32,8 +37,11 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> None:
Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user.
"""
acc = AladdinConnectClient(data[CONF_USERNAME], data[CONF_PASSWORD])
login = await hass.async_add_executor_job(acc.login)
acc = AladdinConnectClient(
data[CONF_USERNAME], data[CONF_PASSWORD], async_get_clientsession(hass)
)
login = await acc.login()
await acc.close()
if not login:
raise InvalidAuth
@ -44,9 +52,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
VERSION = 1
entry: config_entries.ConfigEntry | None
async def async_step_reauth(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult:
"""Handle re-authentication with Aladdin Connect."""
self.entry = self.hass.config_entries.async_get_entry(self.context["entry_id"])
@ -68,8 +74,13 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
try:
await validate_input(self.hass, data)
except InvalidAuth:
errors["base"] = "invalid_auth"
except (ClientConnectionError, asyncio.TimeoutError, ClientError):
errors["base"] = "cannot_connect"
else:
self.hass.config_entries.async_update_entry(
@ -104,6 +115,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
except InvalidAuth:
errors["base"] = "invalid_auth"
except (ClientConnectionError, asyncio.TimeoutError, ClientError):
errors["base"] = "cannot_connect"
else:
await self.async_set_unique_id(
user_input["username"].lower(), raise_on_progress=False

View File

@ -1,10 +1,11 @@
"""Platform for the Aladdin Connect cover component."""
from __future__ import annotations
from datetime import timedelta
import logging
from typing import Any, Final
from aladdin_connect import AladdinConnectClient
from AIOAladdinConnect import AladdinConnectClient
import voluptuous as vol
from homeassistant.components.cover import (
@ -34,6 +35,7 @@ _LOGGER: Final = logging.getLogger(__name__)
PLATFORM_SCHEMA: Final = BASE_PLATFORM_SCHEMA.extend(
{vol.Required(CONF_USERNAME): cv.string, vol.Required(CONF_PASSWORD): cv.string}
)
SCAN_INTERVAL = timedelta(seconds=300)
async def async_setup_platform(
@ -62,14 +64,12 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the Aladdin Connect platform."""
acc = hass.data[DOMAIN][config_entry.entry_id]
doors = await hass.async_add_executor_job(acc.get_doors)
acc: AladdinConnectClient = hass.data[DOMAIN][config_entry.entry_id]
doors = await acc.get_doors()
if doors is None:
raise PlatformNotReady("Error from Aladdin Connect getting doors")
async_add_entities(
(AladdinDevice(acc, door) for door in doors),
update_before_add=True,
(AladdinDevice(acc, door, config_entry) for door in doors),
)
@ -79,27 +79,63 @@ class AladdinDevice(CoverEntity):
_attr_device_class = CoverDeviceClass.GARAGE
_attr_supported_features = SUPPORTED_FEATURES
def __init__(self, acc: AladdinConnectClient, device: DoorDevice) -> None:
def __init__(
self, acc: AladdinConnectClient, device: DoorDevice, entry: ConfigEntry
) -> None:
"""Initialize the Aladdin Connect cover."""
self._acc = acc
self._device_id = device["device_id"]
self._number = device["door_number"]
self._attr_name = device["name"]
self._attr_unique_id = f"{self._device_id}-{self._number}"
def close_cover(self, **kwargs: Any) -> None:
async def async_added_to_hass(self) -> None:
"""Connect Aladdin Connect to the cloud."""
async def update_callback() -> None:
"""Schedule a state update."""
self.async_write_ha_state()
self._acc.register_callback(update_callback, self._number)
await self._acc.get_doors(self._number)
async def async_will_remove_from_hass(self) -> None:
"""Close Aladdin Connect before removing."""
await self._acc.close()
async def async_close_cover(self, **kwargs: Any) -> None:
"""Issue close command to cover."""
self._acc.close_door(self._device_id, self._number)
await self._acc.close_door(self._device_id, self._number)
def open_cover(self, **kwargs: Any) -> None:
async def async_open_cover(self, **kwargs: Any) -> None:
"""Issue open command to cover."""
self._acc.open_door(self._device_id, self._number)
await self._acc.open_door(self._device_id, self._number)
def update(self) -> None:
async def async_update(self) -> None:
"""Update status of cover."""
status = STATES_MAP.get(
self._acc.get_door_status(self._device_id, self._number)
await self._acc.get_doors(self._number)
@property
def is_closed(self) -> bool | None:
"""Update is closed attribute."""
value = STATES_MAP.get(self._acc.get_door_status(self._device_id, self._number))
if value is None:
return None
return value == STATE_CLOSED
@property
def is_closing(self) -> bool:
"""Update is closing attribute."""
return (
STATES_MAP.get(self._acc.get_door_status(self._device_id, self._number))
== STATE_CLOSING
)
@property
def is_opening(self) -> bool:
"""Update is opening attribute."""
return (
STATES_MAP.get(self._acc.get_door_status(self._device_id, self._number))
== STATE_OPENING
)
self._attr_is_opening = status == STATE_OPENING
self._attr_is_closing = status == STATE_CLOSING
self._attr_is_closed = None if status is None else status == STATE_CLOSED

View File

@ -2,7 +2,7 @@
"domain": "aladdin_connect",
"name": "Aladdin Connect",
"documentation": "https://www.home-assistant.io/integrations/aladdin_connect",
"requirements": ["aladdin_connect==0.4"],
"requirements": ["AIOAladdinConnect==0.1.21"],
"codeowners": ["@mkmer"],
"iot_class": "cloud_polling",
"loggers": ["aladdin_connect"],

View File

@ -13,6 +13,7 @@
"data": {
"password": "Contrase\u00f1a"
},
"description": "La integraci\u00f3n de Aladdin Connect necesita volver a autenticar su cuenta",
"title": "Reautenticaci\u00f3n de la integraci\u00f3n"
},
"user": {

View File

@ -1,11 +1,11 @@
{
"config": {
"abort": {
"already_configured": "O dispositivo j\u00e1 est\u00e1 configurado",
"already_configured": "Dispositivo j\u00e1 est\u00e1 configurado",
"reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida"
},
"error": {
"cannot_connect": "Falhou ao conectar",
"cannot_connect": "Falha ao conectar",
"invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida"
},
"step": {
@ -19,7 +19,7 @@
"user": {
"data": {
"password": "Senha",
"username": "Nome de usu\u00e1rio"
"username": "Usu\u00e1rio"
}
}
}

View File

@ -0,0 +1,26 @@
{
"config": {
"abort": {
"already_configured": "Enheten \u00e4r redan konfigurerad",
"reauth_successful": "\u00c5terautentisering lyckades"
},
"error": {
"cannot_connect": "Det gick inte att ansluta.",
"invalid_auth": "Ogiltig autentisering"
},
"step": {
"reauth_confirm": {
"data": {
"password": "L\u00f6senord"
},
"title": "\u00c5terautenticera integration"
},
"user": {
"data": {
"password": "L\u00f6senord",
"username": "Anv\u00e4ndarnamn"
}
}
}
}
}

View File

@ -7,6 +7,9 @@
"disarm": "Avlarma {entity_name}",
"trigger": "Utl\u00f6sare {entity_name}"
},
"condition_type": {
"is_triggered": "har utl\u00f6sts"
},
"trigger_type": {
"armed_away": "{entity_name} larmad borta",
"armed_home": "{entity_name} larmad hemma",

View File

@ -4,6 +4,7 @@
"arm_away": "{entity_name} \u79bb\u5bb6\u8b66\u6212",
"arm_home": "{entity_name} \u5728\u5bb6\u8b66\u6212",
"arm_night": "{entity_name} \u591c\u95f4\u8b66\u6212",
"arm_vacation": "{entity_name} \u5ea6\u5047\u8b66\u6212",
"disarm": "\u89e3\u9664 {entity_name} \u8b66\u6212",
"trigger": "\u89e6\u53d1 {entity_name}"
},
@ -11,6 +12,7 @@
"is_armed_away": "{entity_name} \u79bb\u5bb6\u8b66\u6212",
"is_armed_home": "{entity_name} \u5728\u5bb6\u8b66\u6212",
"is_armed_night": "{entity_name} \u591c\u95f4\u8b66\u6212",
"is_armed_vacation": "{entity_name} \u5ea6\u5047\u8b66\u6212",
"is_disarmed": "{entity_name} \u8b66\u6212\u5df2\u89e3\u9664",
"is_triggered": "{entity_name} \u8b66\u62a5\u5df2\u89e6\u53d1"
},
@ -18,6 +20,7 @@
"armed_away": "{entity_name} \u79bb\u5bb6\u8b66\u6212",
"armed_home": "{entity_name} \u5728\u5bb6\u8b66\u6212",
"armed_night": "{entity_name} \u591c\u95f4\u8b66\u6212",
"armed_vacation": "{entity_name} \u5ea6\u5047\u8b66\u6212",
"disarmed": "{entity_name} \u8b66\u6212\u89e3\u9664",
"triggered": "{entity_name} \u89e6\u53d1\u8b66\u62a5"
}

View File

@ -1,4 +1,6 @@
"""Support for AlarmDecoder-based alarm control panels (Honeywell/DSC)."""
from __future__ import annotations
import voluptuous as vol
from homeassistant.components.alarm_control_panel import (
@ -91,7 +93,7 @@ class AlarmDecoderAlarmPanel(AlarmControlPanelEntity):
self._attr_code_arm_required = code_arm_required
self._alt_night_mode = alt_night_mode
async def async_added_to_hass(self):
async def async_added_to_hass(self) -> None:
"""Register callbacks."""
self.async_on_remove(
async_dispatcher_connect(
@ -126,12 +128,12 @@ class AlarmDecoderAlarmPanel(AlarmControlPanelEntity):
}
self.schedule_update_ha_state()
def alarm_disarm(self, code=None):
def alarm_disarm(self, code: str | None = None) -> None:
"""Send disarm command."""
if code:
self._client.send(f"{code!s}1")
def alarm_arm_away(self, code=None):
def alarm_arm_away(self, code: str | None = None) -> None:
"""Send arm away command."""
self._client.arm_away(
code=code,
@ -139,7 +141,7 @@ class AlarmDecoderAlarmPanel(AlarmControlPanelEntity):
auto_bypass=self._auto_bypass,
)
def alarm_arm_home(self, code=None):
def alarm_arm_home(self, code: str | None = None) -> None:
"""Send arm home command."""
self._client.arm_home(
code=code,
@ -147,7 +149,7 @@ class AlarmDecoderAlarmPanel(AlarmControlPanelEntity):
auto_bypass=self._auto_bypass,
)
def alarm_arm_night(self, code=None):
def alarm_arm_night(self, code: str | None = None) -> None:
"""Send arm night command."""
self._client.arm_night(
code=code,

View File

@ -1,4 +1,6 @@
"""Config flow for AlarmDecoder."""
from __future__ import annotations
import logging
from adext import AdExt
@ -58,7 +60,9 @@ class AlarmDecoderFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
@staticmethod
@callback
def async_get_options_flow(config_entry):
def async_get_options_flow(
config_entry: config_entries.ConfigEntry,
) -> AlarmDecoderOptionsFlowHandler:
"""Get the options flow for AlarmDecoder."""
return AlarmDecoderOptionsFlowHandler(config_entry)

View File

@ -4,9 +4,11 @@ from __future__ import annotations
import logging
from homeassistant.components import (
button,
cover,
fan,
image_processing,
input_button,
input_number,
light,
timer,
@ -1044,6 +1046,8 @@ class AlexaThermostatController(AlexaCapability):
if preset in API_THERMOSTAT_PRESETS:
mode = API_THERMOSTAT_PRESETS[preset]
elif self.entity.state == STATE_UNKNOWN:
return None
else:
mode = API_THERMOSTAT_MODES.get(self.entity.state)
if mode is None:
@ -1891,7 +1895,10 @@ class AlexaEventDetectionSensor(AlexaCapability):
if self.entity.domain == image_processing.DOMAIN:
if int(state):
human_presence = "DETECTED"
elif state == STATE_ON:
elif state == STATE_ON or self.entity.domain in [
input_button.DOMAIN,
button.DOMAIN,
]:
human_presence = "DETECTED"
return {"value": human_presence}
@ -1903,7 +1910,8 @@ class AlexaEventDetectionSensor(AlexaCapability):
"detectionModes": {
"humanPresence": {
"featureAvailability": "ENABLED",
"supportsNotDetected": True,
"supportsNotDetected": self.entity.domain
not in [input_button.DOMAIN, button.DOMAIN],
}
},
}

View File

@ -382,7 +382,6 @@ def async_get_entities(hass, config) -> list[AlexaEntity]:
@ENTITY_ADAPTERS.register(alert.DOMAIN)
@ENTITY_ADAPTERS.register(automation.DOMAIN)
@ENTITY_ADAPTERS.register(group.DOMAIN)
@ENTITY_ADAPTERS.register(input_boolean.DOMAIN)
class GenericCapabilities(AlexaEntity):
"""A generic, on/off device.
@ -405,12 +404,16 @@ class GenericCapabilities(AlexaEntity):
]
@ENTITY_ADAPTERS.register(input_boolean.DOMAIN)
@ENTITY_ADAPTERS.register(switch.DOMAIN)
class SwitchCapabilities(AlexaEntity):
"""Class to represent Switch capabilities."""
def default_display_categories(self):
"""Return the display categories for this entity."""
if self.entity.domain == input_boolean.DOMAIN:
return [DisplayCategory.OTHER]
device_class = self.entity.attributes.get(ATTR_DEVICE_CLASS)
if device_class == switch.SwitchDeviceClass.OUTLET:
return [DisplayCategory.SMARTPLUG]
@ -421,6 +424,7 @@ class SwitchCapabilities(AlexaEntity):
"""Yield the supported interfaces."""
return [
AlexaPowerController(self.entity),
AlexaContactSensor(self.hass, self.entity),
AlexaEndpointHealth(self.hass, self.entity),
Alexa(self.hass),
]
@ -439,6 +443,8 @@ class ButtonCapabilities(AlexaEntity):
"""Yield the supported interfaces."""
return [
AlexaSceneController(self.entity, supports_deactivation=False),
AlexaEventDetectionSensor(self.hass, self.entity),
AlexaEndpointHealth(self.hass, self.entity),
Alexa(self.hass),
]

View File

@ -127,11 +127,6 @@ async def async_handle_message(hass, message):
@HANDLERS.register("SessionEndedRequest")
async def async_handle_session_end(hass, message):
"""Handle a session end request."""
return None
@HANDLERS.register("IntentRequest")
@HANDLERS.register("LaunchRequest")
async def async_handle_intent(hass, message):
@ -151,6 +146,11 @@ async def async_handle_intent(hass, message):
intent_name = (
message.get("session", {}).get("application", {}).get("applicationId")
)
elif req["type"] == "SessionEndedRequest":
app_id = message.get("session", {}).get("application", {}).get("applicationId")
intent_name = f"{app_id}.{req['type']}"
alexa_response.variables["reason"] = req["reason"]
alexa_response.variables["error"] = req.get("error")
else:
intent_name = alexa_intent_info["name"]

View File

@ -64,7 +64,7 @@ class AlmondFlowHandler(
"""Handle authorize step."""
result = await super().async_step_auth(user_input)
if result["type"] == data_entry_flow.RESULT_TYPE_EXTERNAL_STEP:
if result["type"] == data_entry_flow.FlowResultType.EXTERNAL_STEP:
self.host = str(URL(result["url"]).with_path("me"))
return result

View File

@ -1,6 +1,7 @@
"""Config flow to configure the Ambee integration."""
from __future__ import annotations
from collections.abc import Mapping
from typing import Any
from ambee import Ambee, AmbeeAuthenticationError, AmbeeError
@ -71,7 +72,7 @@ class AmbeeFlowHandler(ConfigFlow, domain=DOMAIN):
errors=errors,
)
async def async_step_reauth(self, data: dict[str, Any]) -> FlowResult:
async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult:
"""Handle initiation of re-authentication with Ambee."""
self.entry = self.hass.config_entries.async_get_entry(self.context["entry_id"])
return await self.async_step_reauth_confirm()

View File

@ -0,0 +1,16 @@
{
"config": {
"step": {
"reauth_confirm": {
"data": {
"api_key": "API-nyckel"
}
},
"user": {
"data": {
"api_key": "API-nyckel"
}
}
}
}
}

View File

@ -1,5 +1,8 @@
{
"config": {
"abort": {
"already_configured": "\u0423\u0441\u043b\u0443\u0433\u0430\u0442\u0430 \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u0430"
},
"error": {
"invalid_key": "\u041d\u0435\u0432\u0430\u043b\u0438\u0434\u0435\u043d API \u043a\u043b\u044e\u0447 \u0438/\u0438\u043b\u0438 Application \u043a\u043b\u044e\u0447",
"no_devices": "\u041d\u0435 \u0441\u0430 \u043d\u0430\u043c\u0435\u0440\u0435\u043d\u0438 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0432 \u043f\u0440\u043e\u0444\u0438\u043b\u0430"

View File

@ -60,7 +60,7 @@ def get_androidtv_mac(dev_props: dict[str, Any]) -> str | None:
def _setup_androidtv(
hass: HomeAssistant, config: dict[str, Any]
hass: HomeAssistant, config: Mapping[str, Any]
) -> tuple[str, PythonRSASigner | None, str]:
"""Generate an ADB key (if needed) and load it."""
adbkey: str = config.get(

View File

@ -1,7 +1,6 @@
"""Rest API for Home Assistant."""
import asyncio
from http import HTTPStatus
import json
import logging
from aiohttp import web
@ -29,7 +28,7 @@ import homeassistant.core as ha
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceNotFound, TemplateError, Unauthorized
from homeassistant.helpers import template
from homeassistant.helpers.json import JSONEncoder
from homeassistant.helpers.json import json_dumps, json_loads
from homeassistant.helpers.service import async_get_all_descriptions
from homeassistant.helpers.typing import ConfigType
@ -108,7 +107,7 @@ class APIEventStream(HomeAssistantView):
if event.event_type == EVENT_HOMEASSISTANT_STOP:
data = stop_obj
else:
data = json.dumps(event, cls=JSONEncoder)
data = json_dumps(event)
await to_write.put(data)
@ -261,7 +260,7 @@ class APIEventView(HomeAssistantView):
raise Unauthorized()
body = await request.text()
try:
event_data = json.loads(body) if body else None
event_data = json_loads(body) if body else None
except ValueError:
return self.json_message(
"Event data should be valid JSON.", HTTPStatus.BAD_REQUEST
@ -314,7 +313,7 @@ class APIDomainServicesView(HomeAssistantView):
hass: ha.HomeAssistant = request.app["hass"]
body = await request.text()
try:
data = json.loads(body) if body else None
data = json_loads(body) if body else None
except ValueError:
return self.json_message(
"Data should be valid JSON.", HTTPStatus.BAD_REQUEST

View File

@ -23,6 +23,7 @@ from homeassistant.const import (
Platform,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.dispatcher import (
@ -49,6 +50,13 @@ PLATFORMS = [Platform.MEDIA_PLAYER, Platform.REMOTE]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up a config entry for Apple TV."""
manager = AppleTVManager(hass, entry)
if manager.is_on:
await manager.connect_once(raise_missing_credentials=True)
if not manager.atv:
address = entry.data[CONF_ADDRESS]
raise ConfigEntryNotReady(f"Not found at {address}, waiting for discovery")
hass.data.setdefault(DOMAIN, {})[entry.unique_id] = manager
async def on_hass_stop(event):
@ -115,6 +123,10 @@ class AppleTVEntity(Entity):
self.atv = None
self.async_write_ha_state()
if self.manager.atv:
# ATV is already connected
_async_connected(self.manager.atv)
self.async_on_remove(
async_dispatcher_connect(
self.hass, f"{SIGNAL_CONNECTED}_{self.unique_id}", _async_connected
@ -148,14 +160,14 @@ class AppleTVManager:
self.config_entry = config_entry
self.hass = hass
self.atv = None
self._is_on = not config_entry.options.get(CONF_START_OFF, False)
self.is_on = not config_entry.options.get(CONF_START_OFF, False)
self._connection_attempts = 0
self._connection_was_lost = False
self._task = None
async def init(self):
"""Initialize power management."""
if self._is_on:
if self.is_on:
await self.connect()
def connection_lost(self, _):
@ -186,13 +198,13 @@ class AppleTVManager:
async def connect(self):
"""Connect to device."""
self._is_on = True
self.is_on = True
self._start_connect_loop()
async def disconnect(self):
"""Disconnect from device."""
_LOGGER.debug("Disconnecting from device")
self._is_on = False
self.is_on = False
try:
if self.atv:
self.atv.close()
@ -205,50 +217,53 @@ class AppleTVManager:
def _start_connect_loop(self):
"""Start background connect loop to device."""
if not self._task and self.atv is None and self._is_on:
if not self._task and self.atv is None and self.is_on:
self._task = asyncio.create_task(self._connect_loop())
else:
_LOGGER.debug(
"Not starting connect loop (%s, %s)", self.atv is None, self._is_on
"Not starting connect loop (%s, %s)", self.atv is None, self.is_on
)
async def connect_once(self, raise_missing_credentials):
"""Try to connect once."""
try:
if conf := await self._scan():
await self._connect(conf, raise_missing_credentials)
except exceptions.AuthenticationError:
self.config_entry.async_start_reauth(self.hass)
asyncio.create_task(self.disconnect())
_LOGGER.exception(
"Authentication failed for %s, try reconfiguring device",
self.config_entry.data[CONF_NAME],
)
return
except asyncio.CancelledError:
pass
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Failed to connect")
self.atv = None
async def _connect_loop(self):
"""Connect loop background task function."""
_LOGGER.debug("Starting connect loop")
# Try to find device and connect as long as the user has said that
# we are allowed to connect and we are not already connected.
while self._is_on and self.atv is None:
try:
conf = await self._scan()
if conf:
await self._connect(conf)
except exceptions.AuthenticationError:
self.config_entry.async_start_reauth(self.hass)
asyncio.create_task(self.disconnect())
_LOGGER.exception(
"Authentication failed for %s, try reconfiguring device",
self.config_entry.data[CONF_NAME],
)
while self.is_on and self.atv is None:
await self.connect_once(raise_missing_credentials=False)
if self.atv is not None:
break
except asyncio.CancelledError:
pass
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Failed to connect")
self.atv = None
self._connection_attempts += 1
backoff = min(
max(
BACKOFF_TIME_LOWER_LIMIT,
randrange(2**self._connection_attempts),
),
BACKOFF_TIME_UPPER_LIMIT,
)
if self.atv is None:
self._connection_attempts += 1
backoff = min(
max(
BACKOFF_TIME_LOWER_LIMIT,
randrange(2**self._connection_attempts),
),
BACKOFF_TIME_UPPER_LIMIT,
)
_LOGGER.debug("Reconnecting in %d seconds", backoff)
await asyncio.sleep(backoff)
_LOGGER.debug("Reconnecting in %d seconds", backoff)
await asyncio.sleep(backoff)
_LOGGER.debug("Connect loop ended")
self._task = None
@ -287,23 +302,33 @@ class AppleTVManager:
# it will update the address and reload the config entry when the device is found.
return None
async def _connect(self, conf):
async def _connect(self, conf, raise_missing_credentials):
"""Connect to device."""
credentials = self.config_entry.data[CONF_CREDENTIALS]
session = async_get_clientsession(self.hass)
name = self.config_entry.data[CONF_NAME]
missing_protocols = []
for protocol_int, creds in credentials.items():
protocol = Protocol(int(protocol_int))
if conf.get_service(protocol) is not None:
conf.set_credentials(protocol, creds)
else:
_LOGGER.warning(
"Protocol %s not found for %s, functionality will be reduced",
protocol.name,
self.config_entry.data[CONF_NAME],
missing_protocols.append(protocol.name)
if missing_protocols:
missing_protocols_str = ", ".join(missing_protocols)
if raise_missing_credentials:
raise ConfigEntryNotReady(
f"Protocol(s) {missing_protocols_str} not yet found for {name}, waiting for discovery."
)
_LOGGER.info(
"Protocol(s) %s not yet found for %s, trying later",
missing_protocols_str,
name,
)
return
_LOGGER.debug("Connecting to device %s", self.config_entry.data[CONF_NAME])
session = async_get_clientsession(self.hass)
self.atv = await connect(conf, self.hass.loop, session=session)
self.atv.listener = self

View File

@ -21,8 +21,8 @@ def build_app_list(app_list):
media_content_id="apps",
media_content_type=MEDIA_TYPE_APPS,
title="Apps",
can_play=True,
can_expand=False,
can_play=False,
can_expand=True,
children=[item_payload(item) for item in app_list],
children_media_class=MEDIA_CLASS_APP,
)

View File

@ -3,9 +3,11 @@ from __future__ import annotations
import asyncio
from collections import deque
from collections.abc import Mapping
from ipaddress import ip_address
import logging
from random import randrange
from typing import Any
from pyatv import exceptions, pair, scan
from pyatv.const import DeviceModel, PairingRequirement, Protocol
@ -13,10 +15,11 @@ from pyatv.convert import model_str, protocol_str
from pyatv.helpers import get_unique_id
import voluptuous as vol
from homeassistant import config_entries, data_entry_flow
from homeassistant import config_entries
from homeassistant.components import zeroconf
from homeassistant.const import CONF_ADDRESS, CONF_NAME, CONF_PIN
from homeassistant.core import callback
from homeassistant.data_entry_flow import AbortFlow, FlowResult
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.util.network import is_ipv6_address
@ -71,7 +74,9 @@ class AppleTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
@staticmethod
@callback
def async_get_options_flow(config_entry):
def async_get_options_flow(
config_entry: config_entries.ConfigEntry,
) -> AppleTVOptionsFlow:
"""Get options flow for this handler."""
return AppleTVOptionsFlow(config_entry)
@ -116,10 +121,10 @@ class AppleTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
return entry.unique_id
return None
async def async_step_reauth(self, user_input=None):
async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult:
"""Handle initial step when updating invalid credentials."""
self.context["title_placeholders"] = {
"name": user_input[CONF_NAME],
"name": entry_data[CONF_NAME],
"type": "Apple TV",
}
self.scan_filter = self.unique_id
@ -164,7 +169,7 @@ class AppleTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
async def async_step_zeroconf(
self, discovery_info: zeroconf.ZeroconfServiceInfo
) -> data_entry_flow.FlowResult:
) -> FlowResult:
"""Handle device found via zeroconf."""
host = discovery_info.host
if is_ipv6_address(host):
@ -248,7 +253,7 @@ class AppleTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
):
# Add potentially new identifiers from this device to the existing flow
context["all_identifiers"].append(unique_id)
raise data_entry_flow.AbortFlow("already_in_progress")
raise AbortFlow("already_in_progress")
async def async_found_zeroconf_device(self, user_input=None):
"""Handle device found after Zeroconf discovery."""
@ -523,7 +528,7 @@ class AppleTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
class AppleTVOptionsFlow(config_entries.OptionsFlow):
"""Handle Apple TV options."""
def __init__(self, config_entry):
def __init__(self, config_entry: config_entries.ConfigEntry) -> None:
"""Initialize Apple TV options flow."""
self.config_entry = config_entry
self.options = dict(config_entry.options)

View File

@ -3,7 +3,7 @@
"name": "Apple TV",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/apple_tv",
"requirements": ["pyatv==0.10.0"],
"requirements": ["pyatv==0.10.2"],
"dependencies": ["zeroconf"],
"zeroconf": [
"_mediaremotetv._tcp.local.",

View File

@ -282,22 +282,20 @@ class AppleTvMediaPlayer(AppleTVEntity, MediaPlayerEntity):
# RAOP. Otherwise try to play it with regular AirPlay.
if media_type == MEDIA_TYPE_APP:
await self.atv.apps.launch_app(media_id)
return
if media_source.is_media_source_id(media_id):
play_item = await media_source.async_resolve_media(
self.hass, media_id, self.entity_id
)
media_id = play_item.url
media_id = async_process_play_media_url(self.hass, play_item.url)
media_type = MEDIA_TYPE_MUSIC
media_id = async_process_play_media_url(self.hass, media_id)
if self._is_feature_available(FeatureName.StreamFile) and (
media_type == MEDIA_TYPE_MUSIC or await is_streamable(media_id)
):
_LOGGER.debug("Streaming %s via RAOP", media_id)
await self.atv.stream.stream_file(media_id)
elif self._is_feature_available(FeatureName.PlayUrl):
_LOGGER.debug("Playing %s via AirPlay", media_id)
await self.atv.stream.play_url(media_id)

View File

@ -7,6 +7,7 @@
"device_did_not_pair": "No se ha intentado finalizar el proceso de emparejamiento desde el dispositivo.",
"device_not_found": "No se ha encontrado el dispositivo durante la detecci\u00f3n, por favor, intente a\u00f1adirlo de nuevo.",
"inconsistent_device": "No se encontraron los protocolos esperados durante el descubrimiento. Esto normalmente indica un problema con el DNS de multidifusi\u00f3n (Zeroconf). Por favor, intente a\u00f1adir el dispositivo de nuevo.",
"ipv6_not_supported": "IPv6 no est\u00e1 soportado.",
"no_devices_found": "No se encontraron dispositivos en la red",
"reauth_successful": "La reautenticaci\u00f3n se realiz\u00f3 correctamente",
"setup_failed": "No se ha podido configurar el dispositivo.",

View File

@ -253,6 +253,11 @@ class ApplicationCredentialsProtocol(Protocol):
) -> config_entry_oauth2_flow.AbstractOAuth2Implementation:
"""Return a custom auth implementation."""
async def async_get_description_placeholders(
self, hass: HomeAssistant
) -> dict[str, str]:
"""Return description placeholders for the credentials dialog."""
async def _get_platform(
hass: HomeAssistant, integration_domain: str
@ -282,6 +287,14 @@ async def _get_platform(
return platform
async def _async_integration_config(hass: HomeAssistant, domain: str) -> dict[str, Any]:
platform = await _get_platform(hass, domain)
if platform and hasattr(platform, "async_get_description_placeholders"):
placeholders = await platform.async_get_description_placeholders(hass)
return {"description_placeholders": placeholders}
return {}
@websocket_api.websocket_command(
{vol.Required("type"): "application_credentials/config"}
)
@ -290,6 +303,11 @@ async def handle_integration_list(
hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
) -> None:
"""Handle integrations command."""
connection.send_result(
msg["id"], {"domains": await async_get_application_credentials(hass)}
)
domains = await async_get_application_credentials(hass)
result = {
"domains": domains,
"integrations": {
domain: await _async_integration_config(hass, domain) for domain in domains
},
}
connection.send_result(msg["id"], result)

View File

@ -2,7 +2,7 @@
"domain": "apprise",
"name": "Apprise",
"documentation": "https://www.home-assistant.io/integrations/apprise",
"requirements": ["apprise==0.9.8.3"],
"requirements": ["apprise==0.9.9"],
"codeowners": ["@caronc"],
"iot_class": "cloud_push",
"loggers": ["apprise"]

View File

@ -3,5 +3,10 @@
"abort": {
"cannot_connect": "\u8fde\u63a5\u5931\u8d25"
}
},
"device_automation": {
"trigger_type": {
"turn_on": "{entity_name} \u88ab\u8981\u6c42\u6253\u5f00"
}
}
}

View File

@ -1,11 +1,13 @@
{
"config": {
"error": {
"cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435",
"unknown": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430"
},
"step": {
"user": {
"data": {
"mode": "\u0420\u0435\u0436\u0438\u043c",
"name": "\u0418\u043c\u0435",
"password": "\u041f\u0430\u0440\u043e\u043b\u0430",
"port": "\u041f\u043e\u0440\u0442",

View File

@ -1,7 +1,8 @@
{
"config": {
"abort": {
"invalid_unique_id": "No se pudo determinar ning\u00fan identificador \u00fanico v\u00e1lido del dispositivo"
"invalid_unique_id": "No se pudo determinar ning\u00fan identificador \u00fanico v\u00e1lido del dispositivo",
"no_unique_id": "Un dispositivo sin una identificaci\u00f3n \u00fanica v\u00e1lida ya est\u00e1 configurado. La configuraci\u00f3n de una instancia m\u00faltiple no es posible"
},
"error": {
"cannot_connect": "No se pudo conectar",

View File

@ -2,7 +2,7 @@
"config": {
"abort": {
"invalid_unique_id": "Imposs\u00edvel determinar um ID exclusivo v\u00e1lido para o dispositivo",
"no_unique_id": "[%key:component::asuswrt::config::abort::not_unique_id_exist%]"
"no_unique_id": "Um dispositivo sem um ID exclusivo v\u00e1lido j\u00e1 est\u00e1 configurado. A configura\u00e7\u00e3o de v\u00e1rias inst\u00e2ncias n\u00e3o \u00e9 poss\u00edvel"
},
"error": {
"cannot_connect": "Falha ao conectar",

View File

@ -31,7 +31,7 @@
"step": {
"init": {
"data": {
"consider_home": "Sekunder att v\u00e4nta tills attt en enhet anses borta",
"consider_home": "Sekunder att v\u00e4nta tills att en enhet anses borta",
"dnsmasq": "Platsen i routern f\u00f6r dnsmasq.leases-filerna",
"interface": "Gr\u00e4nssnittet som du vill ha statistik fr\u00e5n (t.ex. eth0, eth1 etc)",
"require_ip": "Enheterna m\u00e5ste ha IP (f\u00f6r accesspunktsl\u00e4ge)",

View File

@ -3,6 +3,9 @@
"abort": {
"already_configured": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e"
},
"error": {
"cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0445 \u043f\u0440\u0438 \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435"
},
"step": {
"user": {
"data": {

View File

@ -21,6 +21,7 @@ from homeassistant.exceptions import (
ConfigEntryNotReady,
HomeAssistantError,
)
from homeassistant.helpers import device_registry as dr
from .activity import ActivityStream
from .const import DOMAIN, MIN_TIME_BETWEEN_DETAIL_UPDATES, PLATFORMS
@ -283,12 +284,15 @@ class AugustData(AugustSubscriberMixin):
device.device_id,
)
def _get_device_name(self, device_id):
def get_device(self, device_id: str) -> Doorbell | Lock | None:
"""Get a device by id."""
return self._locks_by_id.get(device_id) or self._doorbells_by_id.get(device_id)
def _get_device_name(self, device_id: str) -> str | None:
"""Return doorbell or lock name as August has it stored."""
if device_id in self._locks_by_id:
return self._locks_by_id[device_id].device_name
if device_id in self._doorbells_by_id:
return self._doorbells_by_id[device_id].device_name
if device := self.get_device(device_id):
return device.device_name
return None
async def async_lock(self, device_id):
"""Lock the device."""
@ -403,3 +407,15 @@ def _restore_live_attrs(lock_detail, attrs):
"""Restore the non-cache attributes after a cached update."""
for attr, value in attrs.items():
setattr(lock_detail, attr, value)
async def async_remove_config_entry_device(
hass: HomeAssistant, config_entry: ConfigEntry, device_entry: dr.DeviceEntry
) -> bool:
"""Remove august config entry from a device if its no longer present."""
data: AugustData = hass.data[DOMAIN][config_entry.entry_id]
return not any(
identifier
for identifier in device_entry.identifiers
if identifier[0] == DOMAIN and data.get_device(identifier[1])
)

View File

@ -30,7 +30,7 @@ class AugustWakeLockButton(AugustEntityMixin, ButtonEntity):
self._attr_name = f"{device.device_name} Wake"
self._attr_unique_id = f"{self._device_id}_wake"
async def async_press(self, **kwargs):
async def async_press(self) -> None:
"""Wake the device."""
await self._data.async_status_async(self._device_id, self._hyper_bridge)

View File

@ -1,11 +1,14 @@
"""Config flow for August integration."""
from collections.abc import Mapping
import logging
from typing import Any
import voluptuous as vol
from yalexs.authenticator import ValidationResult
from homeassistant import config_entries
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.data_entry_flow import FlowResult
from .const import CONF_LOGIN_METHOD, DOMAIN, LOGIN_METHODS, VERIFICATION_CODE_KEY
from .exceptions import CannotConnect, InvalidAuth, RequireValidation
@ -109,9 +112,9 @@ class AugustConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
},
)
async def async_step_reauth(self, data):
async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult:
"""Handle configuration by re-auth."""
self._user_auth_details = dict(data)
self._user_auth_details = dict(entry_data)
self._mode = "reauth"
self._needs_reset = True
self._august_gateway = AugustGateway(self.hass)

View File

@ -1,5 +1,6 @@
"""Support for August lock."""
import logging
from typing import Any
from aiohttp import ClientResponseError
from yalexs.activity import SOURCE_PUBNUB, ActivityType
@ -44,14 +45,14 @@ class AugustLock(AugustEntityMixin, RestoreEntity, LockEntity):
self._attr_unique_id = f"{self._device_id:s}_lock"
self._update_from_data()
async def async_lock(self, **kwargs):
async def async_lock(self, **kwargs: Any) -> None:
"""Lock the device."""
if self._data.activity_stream.pubnub.connected:
await self._data.async_lock_async(self._device_id, self._hyper_bridge)
return
await self._call_lock_operation(self._data.async_lock)
async def async_unlock(self, **kwargs):
async def async_unlock(self, **kwargs: Any) -> None:
"""Unlock the device."""
if self._data.activity_stream.pubnub.connected:
await self._data.async_unlock_async(self._device_id, self._hyper_bridge)
@ -126,7 +127,7 @@ class AugustLock(AugustEntityMixin, RestoreEntity, LockEntity):
"keypad_battery_level"
] = self._detail.keypad.battery_level
async def async_added_to_hass(self):
async def async_added_to_hass(self) -> None:
"""Restore ATTR_CHANGED_BY on startup since it is likely no longer in the activity log."""
await super().async_added_to_hass()

View File

@ -0,0 +1,14 @@
{
"config": {
"error": {
"invalid_auth": "\u041d\u0435\u0432\u0430\u043b\u0438\u0434\u043d\u043e \u0443\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u044f\u0432\u0430\u043d\u0435"
},
"step": {
"user_validate": {
"data": {
"password": "\u041f\u0430\u0440\u043e\u043b\u0430"
}
}
}
}
}

View File

@ -1,4 +1,6 @@
"""Config flow for SpaceX Launches and Starman."""
from __future__ import annotations
import logging
from aiohttp import ClientError
@ -22,7 +24,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
@staticmethod
@callback
def async_get_options_flow(config_entry):
def async_get_options_flow(
config_entry: config_entries.ConfigEntry,
) -> OptionsFlowHandler:
"""Get the options flow for this handler."""
return OptionsFlowHandler(config_entry)
@ -82,7 +86,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
class OptionsFlowHandler(config_entries.OptionsFlow):
"""Handle options flow changes."""
def __init__(self, config_entry):
def __init__(self, config_entry: config_entries.ConfigEntry) -> None:
"""Initialize options flow."""
self.config_entry = config_entry

View File

@ -17,7 +17,7 @@
"step": {
"init": {
"data": {
"threshold": "\u3057\u304d\u3044\u5024(%)"
"threshold": "\u95be\u5024(%)"
}
}
}

View File

@ -10,15 +10,13 @@
import logging
from aurorapy.client import AuroraError, AuroraSerialClient
from aurorapy.client import AuroraSerialClient
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_ADDRESS, CONF_PORT, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from .config_flow import validate_and_connect
from .const import ATTR_SERIAL_NUMBER, DOMAIN
from .const import DOMAIN
PLATFORMS = [Platform.SENSOR]
@ -31,42 +29,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
comport = entry.data[CONF_PORT]
address = entry.data[CONF_ADDRESS]
ser_client = AuroraSerialClient(address, comport, parity="N", timeout=1)
# To handle yaml import attempts in darkness, (re)try connecting only if
# unique_id not yet assigned.
if entry.unique_id is None:
try:
res = await hass.async_add_executor_job(
validate_and_connect, hass, entry.data
)
except AuroraError as error:
if "No response after" in str(error):
raise ConfigEntryNotReady("No response (could be dark)") from error
_LOGGER.error("Failed to connect to inverter: %s", error)
return False
except OSError as error:
if error.errno == 19: # No such device.
_LOGGER.error("Failed to connect to inverter: no such COM port")
return False
_LOGGER.error("Failed to connect to inverter: %s", error)
return False
else:
# If we got here, the device is now communicating (maybe after
# being in darkness). But there's a small risk that the user has
# configured via the UI since we last attempted the yaml setup,
# which means we'd get a duplicate unique ID.
new_id = res[ATTR_SERIAL_NUMBER]
# Check if this unique_id has already been used
for existing_entry in hass.config_entries.async_entries(DOMAIN):
if existing_entry.unique_id == new_id:
_LOGGER.debug(
"Remove already configured config entry for id %s", new_id
)
hass.async_create_task(
hass.config_entries.async_remove(entry.entry_id)
)
return False
hass.config_entries.async_update_entry(entry, unique_id=new_id)
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = ser_client
hass.config_entries.async_setup_platforms(entry, PLATFORMS)

View File

@ -3,7 +3,7 @@
"name": "Aurora ABB PowerOne Solar PV",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/aurora_abb_powerone",
"requirements": ["aurorapy==0.2.6"],
"requirements": ["aurorapy==0.2.7"],
"codeowners": ["@davet2001"],
"iot_class": "local_polling",
"loggers": ["aurorapy"]

View File

@ -5,7 +5,7 @@ from collections.abc import Mapping
import logging
from typing import Any
from aurorapy.client import AuroraError, AuroraSerialClient
from aurorapy.client import AuroraError, AuroraSerialClient, AuroraTimeoutError
from homeassistant.components.sensor import (
SensorDeviceClass,
@ -102,22 +102,16 @@ class AuroraSensor(AuroraEntity, SensorEntity):
self._attr_native_value = round(energy_wh / 1000, 2)
self._attr_available = True
except AuroraTimeoutError:
self._attr_state = None
self._attr_native_value = None
self._attr_available = False
_LOGGER.debug("No response from inverter (could be dark)")
except AuroraError as error:
self._attr_state = None
self._attr_native_value = None
self._attr_available = False
# aurorapy does not have different exceptions (yet) for dealing
# with timeout vs other comms errors.
# This means the (normal) situation of no response during darkness
# raises an exception.
# aurorapy (gitlab) pull request merged 29/5/2019. When >0.2.6 is
# released, this could be modified to :
# except AuroraTimeoutError as e:
# Workaround: look at the text of the exception
if "No response after" in str(error):
_LOGGER.debug("No response from inverter (could be dark)")
else:
raise error
raise error
finally:
if self._attr_available != self.available_prev:
if self._attr_available:

View File

@ -0,0 +1,17 @@
{
"config": {
"abort": {
"no_serial_ports": "Inga com portar funna. M\u00e5ste ha en RS485 enhet f\u00f6r att kommunicera"
},
"error": {
"cannot_open_serial_port": "Kan inte \u00f6ppna serieporten, kontrollera och f\u00f6rs\u00f6k igen."
},
"step": {
"user": {
"data": {
"port": "RS485 eller USB-RS485 adapter port"
}
}
}
}
}

View File

@ -1,6 +1,7 @@
"""Config flow for Aussie Broadband integration."""
from __future__ import annotations
from collections.abc import Mapping
from typing import Any
from aiohttp import ClientError
@ -76,9 +77,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
errors=errors,
)
async def async_step_reauth(self, user_input: dict[str, str]) -> FlowResult:
async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult:
"""Handle reauth on credential failure."""
self._reauth_username = user_input[CONF_USERNAME]
self._reauth_username = entry_data[CONF_USERNAME]
return await self.async_step_reauth_confirm()

View File

@ -1,12 +1,14 @@
{
"config": {
"abort": {
"already_configured": "La cuenta ya est\u00e1 configurada",
"no_services_found": "No se han encontrado servicios para esta cuenta",
"reauth_successful": "Re-autenticaci\u00f3n realizada correctamente"
},
"error": {
"cannot_connect": "Fall\u00f3 la conexi\u00f3n",
"invalid_auth": "Autenticaci\u00f3n no v\u00e1lida"
"invalid_auth": "Autenticaci\u00f3n no v\u00e1lida",
"unknown": "Error inesperado"
},
"step": {
"reauth_confirm": {
@ -31,8 +33,16 @@
}
},
"options": {
"abort": {
"cannot_connect": "Fallo en la conexi\u00f3n",
"invalid_auth": "Autenticaci\u00f3n no v\u00e1lida",
"unknown": "Error inesperado"
},
"step": {
"init": {
"data": {
"services": "Servicios"
},
"title": "Selecciona servicios"
}
}

View File

@ -150,44 +150,6 @@ from homeassistant.util import dt as dt_util
from . import indieauth, login_flow, mfa_setup_flow
DOMAIN = "auth"
WS_TYPE_CURRENT_USER = "auth/current_user"
SCHEMA_WS_CURRENT_USER = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend(
{vol.Required("type"): WS_TYPE_CURRENT_USER}
)
WS_TYPE_LONG_LIVED_ACCESS_TOKEN = "auth/long_lived_access_token"
SCHEMA_WS_LONG_LIVED_ACCESS_TOKEN = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend(
{
vol.Required("type"): WS_TYPE_LONG_LIVED_ACCESS_TOKEN,
vol.Required("lifespan"): int, # days
vol.Required("client_name"): str,
vol.Optional("client_icon"): str,
}
)
WS_TYPE_REFRESH_TOKENS = "auth/refresh_tokens"
SCHEMA_WS_REFRESH_TOKENS = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend(
{vol.Required("type"): WS_TYPE_REFRESH_TOKENS}
)
WS_TYPE_DELETE_REFRESH_TOKEN = "auth/delete_refresh_token"
SCHEMA_WS_DELETE_REFRESH_TOKEN = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend(
{
vol.Required("type"): WS_TYPE_DELETE_REFRESH_TOKEN,
vol.Required("refresh_token_id"): str,
}
)
WS_TYPE_SIGN_PATH = "auth/sign_path"
SCHEMA_WS_SIGN_PATH = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend(
{
vol.Required("type"): WS_TYPE_SIGN_PATH,
vol.Required("path"): str,
vol.Optional("expires", default=30): int,
}
)
RESULT_TYPE_CREDENTIALS = "credentials"
@bind_hass
@ -206,27 +168,11 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
hass.http.register_view(LinkUserView(retrieve_result))
hass.http.register_view(OAuth2AuthorizeCallbackView())
websocket_api.async_register_command(
hass, WS_TYPE_CURRENT_USER, websocket_current_user, SCHEMA_WS_CURRENT_USER
)
websocket_api.async_register_command(
hass,
WS_TYPE_LONG_LIVED_ACCESS_TOKEN,
websocket_create_long_lived_access_token,
SCHEMA_WS_LONG_LIVED_ACCESS_TOKEN,
)
websocket_api.async_register_command(
hass, WS_TYPE_REFRESH_TOKENS, websocket_refresh_tokens, SCHEMA_WS_REFRESH_TOKENS
)
websocket_api.async_register_command(
hass,
WS_TYPE_DELETE_REFRESH_TOKEN,
websocket_delete_refresh_token,
SCHEMA_WS_DELETE_REFRESH_TOKEN,
)
websocket_api.async_register_command(
hass, WS_TYPE_SIGN_PATH, websocket_sign_path, SCHEMA_WS_SIGN_PATH
)
websocket_api.async_register_command(hass, websocket_current_user)
websocket_api.async_register_command(hass, websocket_create_long_lived_access_token)
websocket_api.async_register_command(hass, websocket_refresh_tokens)
websocket_api.async_register_command(hass, websocket_delete_refresh_token)
websocket_api.async_register_command(hass, websocket_sign_path)
await login_flow.async_setup(hass, store_result)
await mfa_setup_flow.async_setup(hass)
@ -487,6 +433,7 @@ def _create_auth_code_store():
return store_result, retrieve_result
@websocket_api.websocket_command({vol.Required("type"): "auth/current_user"})
@websocket_api.ws_require_user()
@websocket_api.async_response
async def websocket_current_user(
@ -524,6 +471,14 @@ async def websocket_current_user(
)
@websocket_api.websocket_command(
{
vol.Required("type"): "auth/long_lived_access_token",
vol.Required("lifespan"): int, # days
vol.Required("client_name"): str,
vol.Optional("client_icon"): str,
}
)
@websocket_api.ws_require_user()
@websocket_api.async_response
async def websocket_create_long_lived_access_token(
@ -541,13 +496,13 @@ async def websocket_create_long_lived_access_token(
try:
access_token = hass.auth.async_create_access_token(refresh_token)
except InvalidAuthError as exc:
return websocket_api.error_message(
msg["id"], websocket_api.const.ERR_UNAUTHORIZED, str(exc)
)
connection.send_error(msg["id"], websocket_api.const.ERR_UNAUTHORIZED, str(exc))
return
connection.send_message(websocket_api.result_message(msg["id"], access_token))
connection.send_result(msg["id"], access_token)
@websocket_api.websocket_command({vol.Required("type"): "auth/refresh_tokens"})
@websocket_api.ws_require_user()
@callback
def websocket_refresh_tokens(
@ -555,27 +510,38 @@ def websocket_refresh_tokens(
):
"""Return metadata of users refresh tokens."""
current_id = connection.refresh_token_id
connection.send_message(
websocket_api.result_message(
msg["id"],
[
{
"id": refresh.id,
"client_id": refresh.client_id,
"client_name": refresh.client_name,
"client_icon": refresh.client_icon,
"type": refresh.token_type,
"created_at": refresh.created_at,
"is_current": refresh.id == current_id,
"last_used_at": refresh.last_used_at,
"last_used_ip": refresh.last_used_ip,
}
for refresh in connection.user.refresh_tokens.values()
],
tokens = []
for refresh in connection.user.refresh_tokens.values():
if refresh.credential:
auth_provider_type = refresh.credential.auth_provider_type
else:
auth_provider_type = None
tokens.append(
{
"id": refresh.id,
"client_id": refresh.client_id,
"client_name": refresh.client_name,
"client_icon": refresh.client_icon,
"type": refresh.token_type,
"created_at": refresh.created_at,
"is_current": refresh.id == current_id,
"last_used_at": refresh.last_used_at,
"last_used_ip": refresh.last_used_ip,
"auth_provider_type": auth_provider_type,
}
)
)
connection.send_result(msg["id"], tokens)
@websocket_api.websocket_command(
{
vol.Required("type"): "auth/delete_refresh_token",
vol.Required("refresh_token_id"): str,
}
)
@websocket_api.ws_require_user()
@websocket_api.async_response
async def websocket_delete_refresh_token(
@ -585,15 +551,21 @@ async def websocket_delete_refresh_token(
refresh_token = connection.user.refresh_tokens.get(msg["refresh_token_id"])
if refresh_token is None:
return websocket_api.error_message(
msg["id"], "invalid_token_id", "Received invalid token"
)
connection.send_error(msg["id"], "invalid_token_id", "Received invalid token")
return
await hass.auth.async_remove_refresh_token(refresh_token)
connection.send_message(websocket_api.result_message(msg["id"], {}))
connection.send_result(msg["id"], {})
@websocket_api.websocket_command(
{
vol.Required("type"): "auth/sign_path",
vol.Required("path"): str,
vol.Optional("expires", default=30): int,
}
)
@websocket_api.ws_require_user()
@callback
def websocket_sign_path(

View File

@ -52,7 +52,7 @@ flow for details.
Progress the flow. Most flows will be 1 page, but could optionally add extra
login challenges, like TFA. Once the flow has finished, the returned step will
have type RESULT_TYPE_CREATE_ENTRY and "result" key will contain an authorization code.
have type FlowResultType.CREATE_ENTRY and "result" key will contain an authorization code.
The authorization code associated with an authorized user by default, it will
associate with an credential if "type" set to "link_user" in
"/auth/login_flow"
@ -123,13 +123,13 @@ class AuthProvidersView(HomeAssistantView):
def _prepare_result_json(result):
"""Convert result to JSON."""
if result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY:
if result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY:
data = result.copy()
data.pop("result")
data.pop("data")
return data
if result["type"] != data_entry_flow.RESULT_TYPE_FORM:
if result["type"] != data_entry_flow.FlowResultType.FORM:
return result
data = result.copy()
@ -154,11 +154,11 @@ class LoginFlowBaseView(HomeAssistantView):
async def _async_flow_result_to_response(self, request, client_id, result):
"""Convert the flow result to a response."""
if result["type"] != data_entry_flow.RESULT_TYPE_CREATE_ENTRY:
if result["type"] != data_entry_flow.FlowResultType.CREATE_ENTRY:
# @log_invalid_auth does not work here since it returns HTTP 200.
# We need to manually log failed login attempts.
if (
result["type"] == data_entry_flow.RESULT_TYPE_FORM
result["type"] == data_entry_flow.FlowResultType.FORM
and (errors := result.get("errors"))
and errors.get("base")
in (

View File

@ -129,11 +129,11 @@ def websocket_depose_mfa(
def _prepare_result_json(result):
"""Convert result to JSON."""
if result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY:
if result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY:
data = result.copy()
return data
if result["type"] != data_entry_flow.RESULT_TYPE_FORM:
if result["type"] != data_entry_flow.FlowResultType.FORM:
return result
data = result.copy()

View File

@ -1,12 +1,16 @@
"""Config flow for Awair."""
from __future__ import annotations
from collections.abc import Mapping
from typing import Any
from python_awair import Awair
from python_awair.exceptions import AuthError, AwairError
import voluptuous as vol
from homeassistant.config_entries import ConfigFlow
from homeassistant.const import CONF_ACCESS_TOKEN
from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import DOMAIN, LOGGER
@ -17,7 +21,9 @@ class AwairFlowHandler(ConfigFlow, domain=DOMAIN):
VERSION = 1
async def async_step_user(self, user_input: dict | None = None):
async def async_step_user(
self, user_input: dict[str, str] | None = None
) -> FlowResult:
"""Handle a flow initialized by the user."""
errors = {}
@ -42,8 +48,14 @@ class AwairFlowHandler(ConfigFlow, domain=DOMAIN):
errors=errors,
)
async def async_step_reauth(self, user_input: dict | None = None):
async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult:
"""Handle re-auth if token invalid."""
return await self.async_step_reauth_confirm()
async def async_step_reauth_confirm(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Confirm reauth dialog."""
errors = {}
if user_input is not None:
@ -62,7 +74,7 @@ class AwairFlowHandler(ConfigFlow, domain=DOMAIN):
errors = {CONF_ACCESS_TOKEN: error}
return self.async_show_form(
step_id="reauth",
step_id="reauth_confirm",
data_schema=vol.Schema({vol.Required(CONF_ACCESS_TOKEN): str}),
errors=errors,
)

View File

@ -8,7 +8,7 @@
"email": "[%key:common::config_flow::data::email%]"
}
},
"reauth": {
"reauth_confirm": {
"description": "Please re-enter your Awair developer access token.",
"data": {
"access_token": "[%key:common::config_flow::data::access_token%]",

View File

@ -17,6 +17,13 @@
},
"description": "Torna a introduir el token d'acc\u00e9s de desenvolupador d'Awair."
},
"reauth_confirm": {
"data": {
"access_token": "Token d'acc\u00e9s",
"email": "Correu electr\u00f2nic"
},
"description": "Torna a introduir el 'token' d'acc\u00e9s de desenvolupador d'Awair."
},
"user": {
"data": {
"access_token": "Token d'acc\u00e9s",

View File

@ -15,7 +15,14 @@
"access_token": "Zugangstoken",
"email": "E-Mail"
},
"description": "Bitte gib dein Awair-Entwicklerzugriffstoken erneut ein."
"description": "Bitte gib deinen Awair-Entwicklerzugriffstoken erneut ein."
},
"reauth_confirm": {
"data": {
"access_token": "Zugangstoken",
"email": "E-Mail"
},
"description": "Bitte gib deinen Awair-Entwicklerzugriffstoken erneut ein."
},
"user": {
"data": {

View File

@ -17,6 +17,13 @@
},
"description": "Please re-enter your Awair developer access token."
},
"reauth_confirm": {
"data": {
"access_token": "Access Token",
"email": "Email"
},
"description": "Please re-enter your Awair developer access token."
},
"user": {
"data": {
"access_token": "Access Token",

View File

@ -17,6 +17,13 @@
},
"description": "Taassisesta oma Awairi arendaja juurdep\u00e4\u00e4suluba."
},
"reauth_confirm": {
"data": {
"access_token": "Juurdep\u00e4\u00e4sut\u00f5end",
"email": "Meiliaadress"
},
"description": "Taassisesta oma Awairi arendaja juurdep\u00e4\u00e4suluba."
},
"user": {
"data": {
"access_token": "Juurdep\u00e4\u00e4sut\u00f5end",

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