mirror of
https://github.com/home-assistant/core.git
synced 2025-07-25 14:17:45 +00:00
Merge pull request #74522 from home-assistant/rc
This commit is contained in:
commit
eb0f8f9542
@ -132,7 +132,7 @@ requirements: &requirements
|
||||
- homeassistant/package_constraints.txt
|
||||
- script/pip_check
|
||||
- requirements*.txt
|
||||
- setup.cfg
|
||||
- pyproject.toml
|
||||
|
||||
any:
|
||||
- *base_platforms
|
||||
|
54
.coveragerc
54
.coveragerc
@ -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
|
||||
|
11
.github/workflows/builder.yml
vendored
11
.github/workflows/builder.yml
vendored
@ -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 \
|
||||
|
65
.github/workflows/ci.yaml
vendored
65
.github/workflows/ci.yaml
vendored
@ -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 \
|
||||
|
4
.github/workflows/translations.yaml
vendored
4
.github/workflows/translations.yaml
vendored
@ -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 }}
|
||||
|
||||
|
57
.github/workflows/wheels.yml
vendored
57
.github/workflows/wheels.yml
vendored
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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.*
|
||||
|
40
CODEOWNERS
40
CODEOWNERS
@ -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
|
||||
|
@ -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.
|
||||
|
15
Dockerfile
15
Dockerfile
@ -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 /
|
||||
|
||||
|
@ -18,6 +18,7 @@ RUN \
|
||||
libavfilter-dev \
|
||||
libpcap-dev \
|
||||
libturbojpeg0 \
|
||||
libyaml-dev \
|
||||
libxml2 \
|
||||
git \
|
||||
cmake \
|
||||
|
10
build.yaml
10
build.yaml
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
11
homeassistant/components/accuweather/translations/sv.json
Normal file
11
homeassistant/components/accuweather/translations/sv.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"api_key": "API-nyckel"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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,
|
||||
),
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
},
|
||||
|
11
homeassistant/components/aemet/translations/sv.json
Normal file
11
homeassistant/components/aemet/translations/sv.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"api_key": "API-nyckel"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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]
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
@ -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": {
|
||||
|
@ -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": {
|
||||
|
11
homeassistant/components/airnow/translations/sv.json
Normal file
11
homeassistant/components/airnow/translations/sv.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"api_key": "API-nyckel"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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(
|
||||
|
@ -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": {
|
||||
|
@ -1,5 +1,8 @@
|
||||
{
|
||||
"state": {
|
||||
"airvisual__pollutant_label": {
|
||||
"p1": "PM10"
|
||||
},
|
||||
"airvisual__pollutant_level": {
|
||||
"good": "\u05d8\u05d5\u05d1",
|
||||
"unhealthy": "\u05dc\u05d0 \u05d1\u05e8\u05d9\u05d0",
|
||||
|
@ -0,0 +1,7 @@
|
||||
{
|
||||
"state": {
|
||||
"airvisual__pollutant_label": {
|
||||
"co": "Kolmonoxid"
|
||||
}
|
||||
}
|
||||
}
|
@ -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",
|
||||
|
@ -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"]
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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"],
|
||||
|
@ -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": {
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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",
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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],
|
||||
}
|
||||
},
|
||||
}
|
||||
|
@ -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),
|
||||
]
|
||||
|
||||
|
@ -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"]
|
||||
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
16
homeassistant/components/ambee/translations/sv.json
Normal file
16
homeassistant/components/ambee/translations/sv.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"reauth_confirm": {
|
||||
"data": {
|
||||
"api_key": "API-nyckel"
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"data": {
|
||||
"api_key": "API-nyckel"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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,
|
||||
)
|
||||
|
@ -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)
|
||||
|
@ -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.",
|
||||
|
@ -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)
|
||||
|
@ -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.",
|
||||
|
@ -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)
|
||||
|
@ -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"]
|
||||
|
@ -3,5 +3,10 @@
|
||||
"abort": {
|
||||
"cannot_connect": "\u8fde\u63a5\u5931\u8d25"
|
||||
}
|
||||
},
|
||||
"device_automation": {
|
||||
"trigger_type": {
|
||||
"turn_on": "{entity_name} \u88ab\u8981\u6c42\u6253\u5f00"
|
||||
}
|
||||
}
|
||||
}
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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)",
|
||||
|
@ -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": {
|
||||
|
@ -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])
|
||||
)
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
||||
|
14
homeassistant/components/august/translations/bg.json
Normal file
14
homeassistant/components/august/translations/bg.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
|
||||
|
@ -17,7 +17,7 @@
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"threshold": "\u3057\u304d\u3044\u5024(%)"
|
||||
"threshold": "\u95be\u5024(%)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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"]
|
||||
|
@ -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:
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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()
|
||||
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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 (
|
||||
|
@ -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()
|
||||
|
@ -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,
|
||||
)
|
||||
|
@ -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%]",
|
||||
|
@ -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",
|
||||
|
@ -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": {
|
||||
|
@ -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",
|
||||
|
@ -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
Loading…
x
Reference in New Issue
Block a user