Merge pull request #76119 from home-assistant/rc

This commit is contained in:
Franck Nijhof 2022-08-03 14:47:19 +02:00 committed by GitHub
commit 8ef3ca2daf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3160 changed files with 60249 additions and 22098 deletions

View File

@ -52,6 +52,7 @@ components: &components
- homeassistant/components/auth/** - homeassistant/components/auth/**
- homeassistant/components/automation/** - homeassistant/components/automation/**
- homeassistant/components/backup/** - homeassistant/components/backup/**
- homeassistant/components/bluetooth/**
- homeassistant/components/cloud/** - homeassistant/components/cloud/**
- homeassistant/components/config/** - homeassistant/components/config/**
- homeassistant/components/configurator/** - homeassistant/components/configurator/**
@ -87,6 +88,7 @@ components: &components
- homeassistant/components/persistent_notification/** - homeassistant/components/persistent_notification/**
- homeassistant/components/person/** - homeassistant/components/person/**
- homeassistant/components/recorder/** - homeassistant/components/recorder/**
- homeassistant/components/repairs/**
- homeassistant/components/safe_mode/** - homeassistant/components/safe_mode/**
- homeassistant/components/script/** - homeassistant/components/script/**
- homeassistant/components/shopping_list/** - homeassistant/components/shopping_list/**

View File

@ -23,6 +23,7 @@ omit =
homeassistant/components/adax/climate.py homeassistant/components/adax/climate.py
homeassistant/components/adguard/__init__.py homeassistant/components/adguard/__init__.py
homeassistant/components/adguard/const.py homeassistant/components/adguard/const.py
homeassistant/components/adguard/entity.py
homeassistant/components/adguard/sensor.py homeassistant/components/adguard/sensor.py
homeassistant/components/adguard/switch.py homeassistant/components/adguard/switch.py
homeassistant/components/ads/* homeassistant/components/ads/*
@ -136,6 +137,7 @@ omit =
homeassistant/components/bosch_shc/switch.py homeassistant/components/bosch_shc/switch.py
homeassistant/components/braviatv/__init__.py homeassistant/components/braviatv/__init__.py
homeassistant/components/braviatv/const.py homeassistant/components/braviatv/const.py
homeassistant/components/braviatv/entity.py
homeassistant/components/braviatv/media_player.py homeassistant/components/braviatv/media_player.py
homeassistant/components/braviatv/remote.py homeassistant/components/braviatv/remote.py
homeassistant/components/broadlink/__init__.py homeassistant/components/broadlink/__init__.py
@ -210,7 +212,6 @@ omit =
homeassistant/components/denonavr/media_player.py homeassistant/components/denonavr/media_player.py
homeassistant/components/denonavr/receiver.py homeassistant/components/denonavr/receiver.py
homeassistant/components/deutsche_bahn/sensor.py homeassistant/components/deutsche_bahn/sensor.py
homeassistant/components/devolo_home_control/light.py
homeassistant/components/devolo_home_control/sensor.py homeassistant/components/devolo_home_control/sensor.py
homeassistant/components/devolo_home_control/switch.py homeassistant/components/devolo_home_control/switch.py
homeassistant/components/digital_ocean/* homeassistant/components/digital_ocean/*
@ -267,6 +268,7 @@ omit =
homeassistant/components/eliqonline/sensor.py homeassistant/components/eliqonline/sensor.py
homeassistant/components/elkm1/__init__.py homeassistant/components/elkm1/__init__.py
homeassistant/components/elkm1/alarm_control_panel.py homeassistant/components/elkm1/alarm_control_panel.py
homeassistant/components/elkm1/binary_sensor.py
homeassistant/components/elkm1/climate.py homeassistant/components/elkm1/climate.py
homeassistant/components/elkm1/discovery.py homeassistant/components/elkm1/discovery.py
homeassistant/components/elkm1/light.py homeassistant/components/elkm1/light.py
@ -276,6 +278,7 @@ omit =
homeassistant/components/elmax/__init__.py homeassistant/components/elmax/__init__.py
homeassistant/components/elmax/common.py homeassistant/components/elmax/common.py
homeassistant/components/elmax/const.py homeassistant/components/elmax/const.py
homeassistant/components/elmax/binary_sensor.py
homeassistant/components/elmax/switch.py homeassistant/components/elmax/switch.py
homeassistant/components/elv/* homeassistant/components/elv/*
homeassistant/components/emby/media_player.py homeassistant/components/emby/media_player.py
@ -439,7 +442,6 @@ omit =
homeassistant/components/google_cloud/tts.py homeassistant/components/google_cloud/tts.py
homeassistant/components/google_maps/device_tracker.py homeassistant/components/google_maps/device_tracker.py
homeassistant/components/google_pubsub/__init__.py homeassistant/components/google_pubsub/__init__.py
homeassistant/components/gpmdp/media_player.py
homeassistant/components/gpsd/sensor.py homeassistant/components/gpsd/sensor.py
homeassistant/components/greenwave/light.py homeassistant/components/greenwave/light.py
homeassistant/components/group/notify.py homeassistant/components/group/notify.py
@ -449,6 +451,7 @@ omit =
homeassistant/components/gtfs/sensor.py homeassistant/components/gtfs/sensor.py
homeassistant/components/guardian/__init__.py homeassistant/components/guardian/__init__.py
homeassistant/components/guardian/binary_sensor.py homeassistant/components/guardian/binary_sensor.py
homeassistant/components/guardian/button.py
homeassistant/components/guardian/sensor.py homeassistant/components/guardian/sensor.py
homeassistant/components/guardian/switch.py homeassistant/components/guardian/switch.py
homeassistant/components/guardian/util.py homeassistant/components/guardian/util.py
@ -555,6 +558,7 @@ omit =
homeassistant/components/insteon/utils.py homeassistant/components/insteon/utils.py
homeassistant/components/intellifire/__init__.py homeassistant/components/intellifire/__init__.py
homeassistant/components/intellifire/coordinator.py homeassistant/components/intellifire/coordinator.py
homeassistant/components/intellifire/climate.py
homeassistant/components/intellifire/binary_sensor.py homeassistant/components/intellifire/binary_sensor.py
homeassistant/components/intellifire/sensor.py homeassistant/components/intellifire/sensor.py
homeassistant/components/intellifire/switch.py homeassistant/components/intellifire/switch.py
@ -642,9 +646,6 @@ omit =
homeassistant/components/life360/const.py homeassistant/components/life360/const.py
homeassistant/components/life360/coordinator.py homeassistant/components/life360/coordinator.py
homeassistant/components/life360/device_tracker.py homeassistant/components/life360/device_tracker.py
homeassistant/components/lifx/__init__.py
homeassistant/components/lifx/const.py
homeassistant/components/lifx/light.py
homeassistant/components/lifx_cloud/scene.py homeassistant/components/lifx_cloud/scene.py
homeassistant/components/lightwave/* homeassistant/components/lightwave/*
homeassistant/components/limitlessled/light.py homeassistant/components/limitlessled/light.py
@ -716,7 +717,6 @@ omit =
homeassistant/components/microsoft/tts.py homeassistant/components/microsoft/tts.py
homeassistant/components/miflora/sensor.py homeassistant/components/miflora/sensor.py
homeassistant/components/mikrotik/hub.py homeassistant/components/mikrotik/hub.py
homeassistant/components/mikrotik/device_tracker.py
homeassistant/components/mill/climate.py homeassistant/components/mill/climate.py
homeassistant/components/mill/const.py homeassistant/components/mill/const.py
homeassistant/components/mill/sensor.py homeassistant/components/mill/sensor.py
@ -927,7 +927,6 @@ omit =
homeassistant/components/plex/cast.py homeassistant/components/plex/cast.py
homeassistant/components/plex/media_player.py homeassistant/components/plex/media_player.py
homeassistant/components/plex/view.py homeassistant/components/plex/view.py
homeassistant/components/plugwise/select.py
homeassistant/components/plum_lightpad/light.py homeassistant/components/plum_lightpad/light.py
homeassistant/components/pocketcasts/sensor.py homeassistant/components/pocketcasts/sensor.py
homeassistant/components/point/__init__.py homeassistant/components/point/__init__.py
@ -1109,7 +1108,6 @@ omit =
homeassistant/components/smtp/notify.py homeassistant/components/smtp/notify.py
homeassistant/components/snapcast/* homeassistant/components/snapcast/*
homeassistant/components/snmp/* homeassistant/components/snmp/*
homeassistant/components/sochain/sensor.py
homeassistant/components/solaredge/__init__.py homeassistant/components/solaredge/__init__.py
homeassistant/components/solaredge/coordinator.py homeassistant/components/solaredge/coordinator.py
homeassistant/components/solaredge/sensor.py homeassistant/components/solaredge/sensor.py
@ -1494,6 +1492,7 @@ omit =
homeassistant/components/yolink/climate.py homeassistant/components/yolink/climate.py
homeassistant/components/yolink/const.py homeassistant/components/yolink/const.py
homeassistant/components/yolink/coordinator.py homeassistant/components/yolink/coordinator.py
homeassistant/components/yolink/cover.py
homeassistant/components/yolink/entity.py homeassistant/components/yolink/entity.py
homeassistant/components/yolink/lock.py homeassistant/components/yolink/lock.py
homeassistant/components/yolink/sensor.py homeassistant/components/yolink/sensor.py
@ -1521,7 +1520,6 @@ omit =
homeassistant/components/zha/light.py homeassistant/components/zha/light.py
homeassistant/components/zha/sensor.py homeassistant/components/zha/sensor.py
homeassistant/components/zhong_hong/climate.py homeassistant/components/zhong_hong/climate.py
homeassistant/components/xbee/*
homeassistant/components/ziggo_mediabox_xl/media_player.py homeassistant/components/ziggo_mediabox_xl/media_player.py
homeassistant/components/zoneminder/* homeassistant/components/zoneminder/*
homeassistant/components/supla/* homeassistant/components/supla/*

View File

@ -33,6 +33,7 @@
- [ ] Bugfix (non-breaking change which fixes an issue) - [ ] Bugfix (non-breaking change which fixes an issue)
- [ ] New integration (thank you!) - [ ] New integration (thank you!)
- [ ] New feature (which adds functionality to an existing integration) - [ ] New feature (which adds functionality to an existing integration)
- [ ] Deprecation (breaking change to happen in the future)
- [ ] Breaking change (fix/feature causing existing functionality to break) - [ ] Breaking change (fix/feature causing existing functionality to break)
- [ ] Code quality improvements to existing code or addition of tests - [ ] Code quality improvements to existing code or addition of tests

View File

@ -29,7 +29,7 @@ jobs:
fetch-depth: 0 fetch-depth: 0
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v4.0.0 uses: actions/setup-python@v4.1.0
with: with:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
@ -70,7 +70,7 @@ jobs:
uses: actions/checkout@v3.0.2 uses: actions/checkout@v3.0.2
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v4.0.0 uses: actions/setup-python@v4.1.0
with: with:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
@ -102,9 +102,20 @@ jobs:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v3.0.2 uses: actions/checkout@v3.0.2
- name: Download nightly wheels of frontend
if: needs.init.outputs.channel == 'dev'
uses: dawidd6/action-download-artifact@v2
with:
github_token: ${{secrets.GITHUB_TOKEN}}
repo: home-assistant/frontend
branch: dev
workflow: nightly.yaml
workflow_conclusion: success
name: wheels
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
if: needs.init.outputs.channel == 'dev' if: needs.init.outputs.channel == 'dev'
uses: actions/setup-python@v4.0.0 uses: actions/setup-python@v4.1.0
with: with:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
@ -112,10 +123,23 @@ jobs:
if: needs.init.outputs.channel == 'dev' if: needs.init.outputs.channel == 'dev'
shell: bash shell: bash
run: | run: |
python3 -m pip install packaging python3 -m pip install packaging tomli
python3 -m pip install --use-deprecated=legacy-resolver . python3 -m pip install --use-deprecated=legacy-resolver .
version="$(python3 script/version_bump.py nightly)" version="$(python3 script/version_bump.py nightly)"
if [[ "$(ls home_assistant_frontend*.whl)" =~ ^home_assistant_frontend-(.*)-py3-none-any.whl$ ]]; then
echo "Found frontend wheel, setting version to: ${BASH_REMATCH[1]}"
frontend_version="${BASH_REMATCH[1]}" yq \
--inplace e -o json \
'.requirements = ["home-assistant-frontend=="+env(frontend_version)]' \
homeassistant/components/frontend/manifest.json
sed -i "s|home-assistant-frontend==.*|home-assistant-frontend==${BASH_REMATCH[1]}|" \
homeassistant/package_constraints.txt
python -m script.gen_requirements_all
fi
- name: Write meta info file - name: Write meta info file
shell: bash shell: bash
run: | run: |
@ -135,7 +159,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Build base image - name: Build base image
uses: home-assistant/builder@2022.06.2 uses: home-assistant/builder@2022.07.0
with: with:
args: | args: |
$BUILD_ARGS \ $BUILD_ARGS \
@ -201,7 +225,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Build base image - name: Build base image
uses: home-assistant/builder@2022.06.2 uses: home-assistant/builder@2022.07.0
with: with:
args: | args: |
$BUILD_ARGS \ $BUILD_ARGS \

View File

@ -20,9 +20,9 @@ on:
type: boolean type: boolean
env: env:
CACHE_VERSION: 10 CACHE_VERSION: 1
PIP_CACHE_VERSION: 4 PIP_CACHE_VERSION: 1
HA_SHORT_VERSION: 2022.7 HA_SHORT_VERSION: 2022.8
DEFAULT_PYTHON: 3.9 DEFAULT_PYTHON: 3.9
PRE_COMMIT_CACHE: ~/.cache/pre-commit PRE_COMMIT_CACHE: ~/.cache/pre-commit
PIP_CACHE: /tmp/pip-cache PIP_CACHE: /tmp/pip-cache
@ -35,24 +35,38 @@ concurrency:
cancel-in-progress: true cancel-in-progress: true
jobs: jobs:
changes: info:
name: Determine what has changed name: Collect information & changes data
outputs: outputs:
# In case of issues with the partial run, use the following line instead: # In case of issues with the partial run, use the following line instead:
# test_full_suite: 'true' # test_full_suite: 'true'
test_full_suite: ${{ steps.info.outputs.test_full_suite }}
core: ${{ steps.core.outputs.changes }} core: ${{ steps.core.outputs.changes }}
integrations: ${{ steps.integrations.outputs.changes }}
integrations_glob: ${{ steps.info.outputs.integrations_glob }} integrations_glob: ${{ steps.info.outputs.integrations_glob }}
tests: ${{ steps.info.outputs.tests }} integrations: ${{ steps.integrations.outputs.changes }}
tests_glob: ${{ steps.info.outputs.tests_glob }} pre-commit_cache_key: ${{ steps.generate_pre-commit_cache_key.outputs.key }}
test_groups: ${{ steps.info.outputs.test_groups }} python_cache_key: ${{ steps.generate_python_cache_key.outputs.key }}
test_group_count: ${{ steps.info.outputs.test_group_count }}
requirements: ${{ steps.core.outputs.requirements }} requirements: ${{ steps.core.outputs.requirements }}
runs-on: ubuntu-latest test_full_suite: ${{ steps.info.outputs.test_full_suite }}
test_group_count: ${{ steps.info.outputs.test_group_count }}
test_groups: ${{ steps.info.outputs.test_groups }}
tests_glob: ${{ steps.info.outputs.tests_glob }}
tests: ${{ steps.info.outputs.tests }}
runs-on: ubuntu-20.04
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v3.0.2 uses: actions/checkout@v3.0.2
- name: Generate partial Python venv restore key
id: generate_python_cache_key
run: >-
echo "::set-output name=key::venv-${{ env.CACHE_VERSION }}-${{
hashFiles('requirements_test.txt') }}-${{
hashFiles('requirements_all.txt') }}-${{
hashFiles('homeassistant/package_constraints.txt') }}"
- name: Generate partial pre-commit restore key
id: generate_pre-commit_cache_key
run: >-
echo "::set-output name=key::${{ env.CACHE_VERSION }}-${{ env.DEFAULT_PYTHON }}-${{
hashFiles('.pre-commit-config.yaml') }}"
- name: Filter for core changes - name: Filter for core changes
uses: dorny/paths-filter@v2.10.2 uses: dorny/paths-filter@v2.10.2
id: core id: core
@ -79,8 +93,8 @@ jobs:
# Defaults # Defaults
integrations_glob="" integrations_glob=""
test_full_suite="true" test_full_suite="true"
test_groups="[1, 2, 3, 4, 5, 6]" test_groups="[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]"
test_group_count=6 test_group_count=10
tests="[]" tests="[]"
tests_glob="" tests_glob=""
@ -123,8 +137,8 @@ jobs:
|| [[ "${{ github.event.inputs.full }}" == "true" ]] \ || [[ "${{ github.event.inputs.full }}" == "true" ]] \
|| [[ "${{ contains(github.event.pull_request.labels.*.name, 'ci-full-run') }}" == "true" ]]; || [[ "${{ contains(github.event.pull_request.labels.*.name, 'ci-full-run') }}" == "true" ]];
then then
test_groups="[1, 2, 3, 4, 5, 6]" test_groups="[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]"
test_group_count=6 test_group_count=10
test_full_suite="true" test_full_suite="true"
fi fi
@ -142,84 +156,39 @@ jobs:
echo "tests_glob: ${tests_glob}" echo "tests_glob: ${tests_glob}"
echo "::set-output name=tests_glob::${tests_glob}" echo "::set-output name=tests_glob::${tests_glob}"
# Separate job to pre-populate the base dependency cache pre-commit:
# This prevent upcoming jobs to do the same individually name: Prepare pre-commit base
prepare-base: runs-on: ubuntu-20.04
name: Prepare base dependencies needs:
runs-on: ubuntu-latest - info
timeout-minutes: 20
outputs:
python-key: ${{ steps.generate-python-key.outputs.key }}
pre-commit-key: ${{ steps.generate-pre-commit-key.outputs.key }}
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v3.0.2 uses: actions/checkout@v3.0.2
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python id: python
uses: actions/setup-python@v4.0.0 uses: actions/setup-python@v4.1.0
with: with:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
- name: Generate partial Python venv restore key cache: "pip"
id: generate-python-key
run: >-
echo "::set-output name=key::base-venv-${{ env.CACHE_VERSION }}-${{
hashFiles('requirements.txt') }}-${{
hashFiles('requirements_test.txt') }}-${{
hashFiles('homeassistant/package_constraints.txt') }}"
- name: Generate partial pip restore key
id: generate-pip-key
run: >-
echo "::set-output name=key::base-pip-${{ env.PIP_CACHE_VERSION }}-${{
env.HA_SHORT_VERSION }}-$(date -u '+%Y-%m-%dT%H:%M:%s')"
- name: Restore base Python virtual environment - name: Restore base Python virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v3.0.4 uses: actions/cache@v3.0.5
with: with:
path: venv path: venv
key: >- key: ${{ runner.os }}-venv-${{ needs.info.outputs.pre-commit_cache_key }}
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
steps.generate-python-key.outputs.key }}
# Temporary disabling the restore of environments when bumping
# a dependency. It seems that we are experiencing issues with
# restoring environments in GitHub Actions, although unclear why.
# First attempt: https://github.com/home-assistant/core/pull/62383
#
# restore-keys: |
# ${{ runner.os }}-${{ steps.python.outputs.python-version }}-base-venv-${{ env.CACHE_VERSION }}-${{ hashFiles('requirements.txt') }}-${{ hashFiles('requirements_test.txt') }}-
# ${{ runner.os }}-${{ steps.python.outputs.python-version }}-base-venv-${{ env.CACHE_VERSION }}-${{ hashFiles('requirements.txt') }}-
# ${{ 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.4
with:
path: ${{ env.PIP_CACHE }}
key: >-
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
steps.generate-pip-key.outputs.key }}
restore-keys: |
${{ runner.os }}-${{ steps.python.outputs.python-version }}-base-pip-${{ env.PIP_CACHE_VERSION }}-${{ env.HA_SHORT_VERSION }}-
- name: Create Python virtual environment - name: Create Python virtual environment
if: steps.cache-venv.outputs.cache-hit != 'true' if: steps.cache-venv.outputs.cache-hit != 'true'
run: | run: |
python -m venv venv python -m venv venv
. venv/bin/activate . venv/bin/activate
python --version python --version
pip install --cache-dir=$PIP_CACHE -U "pip>=21.0,<22.2" setuptools wheel pip install "$(cat requirements_test.txt | grep pre-commit)"
pip install --cache-dir=$PIP_CACHE -r requirements.txt -r requirements_test.txt --use-deprecated=legacy-resolver
- name: Generate partial pre-commit restore key
id: generate-pre-commit-key
run: >-
echo "::set-output name=key::pre-commit-${{ env.CACHE_VERSION }}-${{
hashFiles('.pre-commit-config.yaml') }}"
- name: Restore pre-commit environment from cache - name: Restore pre-commit environment from cache
id: cache-precommit id: cache-precommit
uses: actions/cache@v3.0.4 uses: actions/cache@v3.0.5
with: with:
path: ${{ env.PRE_COMMIT_CACHE }} path: ${{ env.PRE_COMMIT_CACHE }}
key: >- key: ${{ runner.os }}-pre-commit-${{ needs.info.outputs.pre-commit_cache_key }}
${{ runner.os }}-${{ steps.generate-pre-commit-key.outputs.key }}
restore-keys: |
${{ runner.os }}-pre-commit-${{ env.CACHE_VERSION }}-
- name: Install pre-commit dependencies - name: Install pre-commit dependencies
if: steps.cache-precommit.outputs.cache-hit != 'true' if: steps.cache-precommit.outputs.cache-hit != 'true'
run: | run: |
@ -228,25 +197,24 @@ jobs:
lint-black: lint-black:
name: Check black name: Check black
runs-on: ubuntu-latest runs-on: ubuntu-20.04
needs: needs:
- changes - info
- prepare-base - pre-commit
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v3.0.2 uses: actions/checkout@v3.0.2
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v4.0.0 uses: actions/setup-python@v4.1.0
id: python id: python
with: with:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore base Python virtual environment - name: Restore base Python virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v3.0.4 uses: actions/cache@v3.0.5
with: with:
path: venv path: venv
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ key: ${{ runner.os }}-venv-${{ needs.info.outputs.pre-commit_cache_key }}
needs.prepare-base.outputs.python-key }}
- name: Fail job if Python cache restore failed - name: Fail job if Python cache restore failed
if: steps.cache-venv.outputs.cache-hit != 'true' if: steps.cache-venv.outputs.cache-hit != 'true'
run: | run: |
@ -254,49 +222,48 @@ jobs:
exit 1 exit 1
- name: Restore pre-commit environment from cache - name: Restore pre-commit environment from cache
id: cache-precommit id: cache-precommit
uses: actions/cache@v3.0.4 uses: actions/cache@v3.0.5
with: with:
path: ${{ env.PRE_COMMIT_CACHE }} path: ${{ env.PRE_COMMIT_CACHE }}
key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }} key: ${{ runner.os }}-pre-commit-${{ needs.info.outputs.pre-commit_cache_key }}
- name: Fail job if pre-commit cache restore failed - name: Fail job if pre-commit cache restore failed
if: steps.cache-precommit.outputs.cache-hit != 'true' if: steps.cache-precommit.outputs.cache-hit != 'true'
run: | run: |
echo "Failed to restore pre-commit environment from cache" echo "Failed to restore pre-commit environment from cache"
exit 1 exit 1
- name: Run black (fully) - name: Run black (fully)
if: needs.changes.outputs.test_full_suite == 'true' if: needs.info.outputs.test_full_suite == 'true'
run: | run: |
. venv/bin/activate . venv/bin/activate
pre-commit run --hook-stage manual black --all-files --show-diff-on-failure pre-commit run --hook-stage manual black --all-files --show-diff-on-failure
- name: Run black (partially) - name: Run black (partially)
if: needs.changes.outputs.test_full_suite == 'false' if: needs.info.outputs.test_full_suite == 'false'
shell: bash shell: bash
run: | run: |
. venv/bin/activate . venv/bin/activate
shopt -s globstar shopt -s globstar
pre-commit run --hook-stage manual black --files {homeassistant,tests}/components/${{ needs.changes.outputs.integrations_glob }}/**/* --show-diff-on-failure pre-commit run --hook-stage manual black --files {homeassistant,tests}/components/${{ needs.info.outputs.integrations_glob }}/**/* --show-diff-on-failure
lint-flake8: lint-flake8:
name: Check flake8 name: Check flake8
runs-on: ubuntu-latest runs-on: ubuntu-20.04
needs: needs:
- changes - info
- prepare-base - pre-commit
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v3.0.2 uses: actions/checkout@v3.0.2
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v4.0.0 uses: actions/setup-python@v4.1.0
id: python id: python
with: with:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore base Python virtual environment - name: Restore base Python virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v3.0.4 uses: actions/cache@v3.0.5
with: with:
path: venv path: venv
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ key: ${{ runner.os }}-venv-${{ needs.info.outputs.pre-commit_cache_key }}
needs.prepare-base.outputs.python-key }}
- name: Fail job if Python cache restore failed - name: Fail job if Python cache restore failed
if: steps.cache-venv.outputs.cache-hit != 'true' if: steps.cache-venv.outputs.cache-hit != 'true'
run: | run: |
@ -304,10 +271,10 @@ jobs:
exit 1 exit 1
- name: Restore pre-commit environment from cache - name: Restore pre-commit environment from cache
id: cache-precommit id: cache-precommit
uses: actions/cache@v3.0.4 uses: actions/cache@v3.0.5
with: with:
path: ${{ env.PRE_COMMIT_CACHE }} path: ${{ env.PRE_COMMIT_CACHE }}
key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }} key: ${{ runner.os }}-pre-commit-${{ needs.info.outputs.pre-commit_cache_key }}
- name: Fail job if pre-commit cache restore failed - name: Fail job if pre-commit cache restore failed
if: steps.cache-precommit.outputs.cache-hit != 'true' if: steps.cache-precommit.outputs.cache-hit != 'true'
run: | run: |
@ -317,37 +284,38 @@ jobs:
run: | run: |
echo "::add-matcher::.github/workflows/matchers/flake8.json" echo "::add-matcher::.github/workflows/matchers/flake8.json"
- name: Run flake8 (fully) - name: Run flake8 (fully)
if: needs.changes.outputs.test_full_suite == 'true' if: needs.info.outputs.test_full_suite == 'true'
run: | run: |
. venv/bin/activate . venv/bin/activate
pre-commit run --hook-stage manual flake8 --all-files pre-commit run --hook-stage manual flake8 --all-files
- name: Run flake8 (partially) - name: Run flake8 (partially)
if: needs.changes.outputs.test_full_suite == 'false' if: needs.info.outputs.test_full_suite == 'false'
shell: bash shell: bash
run: | run: |
. venv/bin/activate . venv/bin/activate
shopt -s globstar shopt -s globstar
pre-commit run --hook-stage manual flake8 --files {homeassistant,tests}/components/${{ needs.changes.outputs.integrations_glob }}/**/* pre-commit run --hook-stage manual flake8 --files {homeassistant,tests}/components/${{ needs.info.outputs.integrations_glob }}/**/*
lint-isort: lint-isort:
name: Check isort name: Check isort
runs-on: ubuntu-latest runs-on: ubuntu-20.04
needs: prepare-base needs:
- info
- pre-commit
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v3.0.2 uses: actions/checkout@v3.0.2
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v4.0.0 uses: actions/setup-python@v4.1.0
id: python id: python
with: with:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore base Python virtual environment - name: Restore base Python virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v3.0.4 uses: actions/cache@v3.0.5
with: with:
path: venv path: venv
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ key: ${{ runner.os }}-venv-${{ needs.info.outputs.pre-commit_cache_key }}
needs.prepare-base.outputs.python-key }}
- name: Fail job if Python cache restore failed - name: Fail job if Python cache restore failed
if: steps.cache-venv.outputs.cache-hit != 'true' if: steps.cache-venv.outputs.cache-hit != 'true'
run: | run: |
@ -355,10 +323,10 @@ jobs:
exit 1 exit 1
- name: Restore pre-commit environment from cache - name: Restore pre-commit environment from cache
id: cache-precommit id: cache-precommit
uses: actions/cache@v3.0.4 uses: actions/cache@v3.0.5
with: with:
path: ${{ env.PRE_COMMIT_CACHE }} path: ${{ env.PRE_COMMIT_CACHE }}
key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }} key: ${{ runner.os }}-pre-commit-${{ needs.info.outputs.pre-commit_cache_key }}
- name: Fail job if pre-commit cache restore failed - name: Fail job if pre-commit cache restore failed
if: steps.cache-precommit.outputs.cache-hit != 'true' if: steps.cache-precommit.outputs.cache-hit != 'true'
run: | run: |
@ -371,25 +339,24 @@ jobs:
lint-other: lint-other:
name: Check other linters name: Check other linters
runs-on: ubuntu-latest runs-on: ubuntu-20.04
needs: needs:
- changes - info
- prepare-base - pre-commit
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v3.0.2 uses: actions/checkout@v3.0.2
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v4.0.0 uses: actions/setup-python@v4.1.0
id: python id: python
with: with:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore base Python virtual environment - name: Restore base Python virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v3.0.4 uses: actions/cache@v3.0.5
with: with:
path: venv path: venv
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ key: ${{ runner.os }}-venv-${{ needs.info.outputs.pre-commit_cache_key }}
needs.prepare-base.outputs.python-key }}
- name: Fail job if Python cache restore failed - name: Fail job if Python cache restore failed
if: steps.cache-venv.outputs.cache-hit != 'true' if: steps.cache-venv.outputs.cache-hit != 'true'
run: | run: |
@ -397,10 +364,10 @@ jobs:
exit 1 exit 1
- name: Restore pre-commit environment from cache - name: Restore pre-commit environment from cache
id: cache-precommit id: cache-precommit
uses: actions/cache@v3.0.4 uses: actions/cache@v3.0.5
with: with:
path: ${{ env.PRE_COMMIT_CACHE }} path: ${{ env.PRE_COMMIT_CACHE }}
key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }} key: ${{ runner.os }}-pre-commit-${{ needs.info.outputs.pre-commit_cache_key }}
- name: Fail job if pre-commit cache restore failed - name: Fail job if pre-commit cache restore failed
if: steps.cache-precommit.outputs.cache-hit != 'true' if: steps.cache-precommit.outputs.cache-hit != 'true'
run: | run: |
@ -408,17 +375,17 @@ jobs:
exit 1 exit 1
- name: Run pyupgrade (fully) - name: Run pyupgrade (fully)
if: needs.changes.outputs.test_full_suite == 'true' if: needs.info.outputs.test_full_suite == 'true'
run: | run: |
. venv/bin/activate . venv/bin/activate
pre-commit run --hook-stage manual pyupgrade --all-files --show-diff-on-failure pre-commit run --hook-stage manual pyupgrade --all-files --show-diff-on-failure
- name: Run pyupgrade (partially) - name: Run pyupgrade (partially)
if: needs.changes.outputs.test_full_suite == 'false' if: needs.info.outputs.test_full_suite == 'false'
shell: bash shell: bash
run: | run: |
. venv/bin/activate . venv/bin/activate
shopt -s globstar shopt -s globstar
pre-commit run --hook-stage manual pyupgrade --files {homeassistant,tests}/components/${{ needs.changes.outputs.integrations_glob }}/**/* --show-diff-on-failure pre-commit run --hook-stage manual pyupgrade --files {homeassistant,tests}/components/${{ needs.info.outputs.integrations_glob }}/**/* --show-diff-on-failure
- name: Register yamllint problem matcher - name: Register yamllint problem matcher
run: | run: |
@ -437,17 +404,17 @@ jobs:
pre-commit run --hook-stage manual check-json --all-files pre-commit run --hook-stage manual check-json --all-files
- name: Run prettier (fully) - name: Run prettier (fully)
if: needs.changes.outputs.test_full_suite == 'true' if: needs.info.outputs.test_full_suite == 'true'
run: | run: |
. venv/bin/activate . venv/bin/activate
pre-commit run --hook-stage manual prettier --all-files pre-commit run --hook-stage manual prettier --all-files
- name: Run prettier (partially) - name: Run prettier (partially)
if: needs.changes.outputs.test_full_suite == 'false' if: needs.info.outputs.test_full_suite == 'false'
shell: bash shell: bash
run: | run: |
. venv/bin/activate . venv/bin/activate
pre-commit run --hook-stage manual prettier --files {homeassistant,tests}/components/${{ needs.changes.outputs.integrations_glob }}/**/* pre-commit run --hook-stage manual prettier --files {homeassistant,tests}/components/${{ needs.info.outputs.integrations_glob }}/**/*
- name: Register check executables problem matcher - name: Register check executables problem matcher
run: | run: |
@ -478,36 +445,105 @@ jobs:
args: hadolint Dockerfile.dev args: hadolint Dockerfile.dev
- name: Run bandit (fully) - name: Run bandit (fully)
if: needs.changes.outputs.test_full_suite == 'true' if: needs.info.outputs.test_full_suite == 'true'
run: | run: |
. venv/bin/activate . venv/bin/activate
pre-commit run --hook-stage manual bandit --all-files --show-diff-on-failure pre-commit run --hook-stage manual bandit --all-files --show-diff-on-failure
- name: Run bandit (partially) - name: Run bandit (partially)
if: needs.changes.outputs.test_full_suite == 'false' if: needs.info.outputs.test_full_suite == 'false'
shell: bash shell: bash
run: | run: |
. venv/bin/activate . venv/bin/activate
shopt -s globstar shopt -s globstar
pre-commit run --hook-stage manual bandit --files {homeassistant,tests}/components/${{ needs.changes.outputs.integrations_glob }}/**/* --show-diff-on-failure pre-commit run --hook-stage manual bandit --files {homeassistant,tests}/components/${{ needs.info.outputs.integrations_glob }}/**/* --show-diff-on-failure
hassfest: base:
name: Check hassfest name: Prepare dependencies
runs-on: ubuntu-latest runs-on: ubuntu-20.04
needs: prepare-tests needs: info
timeout-minutes: 60
strategy: strategy:
matrix: matrix:
python-version: [3.9] python-version: ["3.9", "3.10"]
container: homeassistant/ci-azure:${{ matrix.python-version }}
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v3.0.2 uses: actions/checkout@v3.0.2
- name: Restore full Python ${{ matrix.python-version }} virtual environment - name: Set up Python ${{ matrix.python-version }}
id: python
uses: actions/setup-python@v4.1.0
with:
python-version: ${{ matrix.python-version }}
- name: Generate partial pip restore key
id: generate-pip-key
run: >-
echo "::set-output name=key::pip-${{ env.PIP_CACHE_VERSION }}-${{
env.HA_SHORT_VERSION }}-$(date -u '+%Y-%m-%dT%H:%M:%s')"
- name: Restore base Python virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v3.0.4 uses: actions/cache@v3.0.5
with: with:
path: venv path: venv
key: ${{ runner.os }}-${{ matrix.python-version }}-${{ key: >-
needs.prepare-tests.outputs.python-key }} ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
needs.info.outputs.python_cache_key }}
- name: Restore pip wheel cache
if: steps.cache-venv.outputs.cache-hit != 'true'
uses: actions/cache@v3.0.5
with:
path: ${{ env.PIP_CACHE }}
key: >-
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
steps.generate-pip-key.outputs.key }}
restore-keys: |
${{ runner.os }}-${{ steps.python.outputs.python-version }}-pip-${{ env.PIP_CACHE_VERSION }}-${{ env.HA_SHORT_VERSION }}-
- name: Install additional OS dependencies
if: steps.cache-venv.outputs.cache-hit != 'true'
run: |
sudo apt-get update
sudo apt-get -y install \
bluez \
ffmpeg \
libavcodec-dev \
libavdevice-dev \
libavfilter-dev \
libavformat-dev \
libavutil-dev \
libswresample-dev \
libswscale-dev \
libudev-dev
- name: Create Python virtual environment
if: steps.cache-venv.outputs.cache-hit != 'true'
run: |
python -m venv venv
. venv/bin/activate
python --version
pip install --cache-dir=$PIP_CACHE -U "pip>=21.0,<22.3" setuptools wheel
pip install --cache-dir=$PIP_CACHE -r requirements_all.txt --use-deprecated=legacy-resolver
pip install --cache-dir=$PIP_CACHE -r requirements_test.txt --use-deprecated=legacy-resolver
pip install -e .
hassfest:
name: Check hassfest
runs-on: ubuntu-20.04
needs:
- info
- base
steps:
- name: Check out code from GitHub
uses: actions/checkout@v3.0.2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v4.1.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment
id: cache-venv
uses: actions/cache@v3.0.5
with:
path: venv
key: >-
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
needs.info.outputs.python_cache_key }}
- name: Fail job if Python cache restore failed - name: Fail job if Python cache restore failed
if: steps.cache-venv.outputs.cache-hit != 'true' if: steps.cache-venv.outputs.cache-hit != 'true'
run: | run: |
@ -520,23 +556,26 @@ jobs:
gen-requirements-all: gen-requirements-all:
name: Check all requirements name: Check all requirements
runs-on: ubuntu-latest runs-on: ubuntu-20.04
needs: prepare-base needs:
- info
- base
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v3.0.2 uses: actions/checkout@v3.0.2
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v4.0.0
id: python id: python
uses: actions/setup-python@v4.1.0
with: with:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore base Python virtual environment - name: Restore base Python virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v3.0.4 uses: actions/cache@v3.0.5
with: with:
path: venv path: venv
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ key: >-
needs.prepare-base.outputs.python-key }} ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
needs.info.outputs.python_cache_key }}
- name: Fail job if Python cache restore failed - name: Fail job if Python cache restore failed
if: steps.cache-venv.outputs.cache-hit != 'true' if: steps.cache-venv.outputs.cache-hit != 'true'
run: | run: |
@ -547,94 +586,29 @@ jobs:
. venv/bin/activate . venv/bin/activate
python -m script.gen_requirements_all validate python -m script.gen_requirements_all validate
prepare-tests:
name: Prepare tests for Python ${{ matrix.python-version }}
runs-on: ubuntu-latest
timeout-minutes: 60
strategy:
matrix:
python-version: ["3.9", "3.10"]
outputs:
python-key: ${{ steps.generate-python-key.outputs.key }}
container: homeassistant/ci-azure:${{ matrix.python-version }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@v3.0.2
- name: Generate partial Python venv restore key
id: generate-python-key
run: >-
echo "::set-output name=key::venv-${{ env.CACHE_VERSION }}-${{
hashFiles('requirements_test.txt') }}-${{
hashFiles('requirements_all.txt') }}-${{
hashFiles('homeassistant/package_constraints.txt') }}"
- name: Generate partial pip restore key
id: generate-pip-key
run: >-
echo "::set-output name=key::pip-${{ env.PIP_CACHE_VERSION }}-${{
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.4
with:
path: venv
key: >-
${{ runner.os }}-${{ matrix.python-version }}-${{
steps.generate-python-key.outputs.key }}
# Temporary disabling the restore of environments when bumping
# a dependency. It seems that we are experiencing issues with
# restoring environments in GitHub Actions, although unclear why.
# First attempt: https://github.com/home-assistant/core/pull/62383
#
# restore-keys: |
# ${{ runner.os }}-${{ matrix.python-version }}-venv-${{ env.CACHE_VERSION }}-${{ hashFiles('requirements_test.txt') }}-${{ hashFiles('requirements_all.txt') }}-
# ${{ runner.os }}-${{ matrix.python-version }}-venv-${{ env.CACHE_VERSION }}-${{ hashFiles('requirements_test.txt') }}-
# ${{ 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.4
with:
path: ${{ env.PIP_CACHE }}
key: >-
${{ runner.os }}-${{ matrix.python-version }}-${{
steps.generate-pip-key.outputs.key }}
restore-keys: |
${{ runner.os }}-${{ matrix.python-version }}-pip-${{ env.PIP_CACHE_VERSION }}-${{ env.HA_SHORT_VERSION }}-
- name: Create full Python ${{ matrix.python-version }} virtual environment
if: steps.cache-venv.outputs.cache-hit != 'true'
run: |
# Temporary addition of cmake, needed to build some Python 3.9 packages
apt-get update
apt-get -y install cmake
python -m venv venv
. venv/bin/activate
python --version
pip install --cache-dir=$PIP_CACHE -U "pip>=21.0,<22.2" setuptools wheel
pip install --cache-dir=$PIP_CACHE -r requirements_all.txt --use-deprecated=legacy-resolver
pip install --cache-dir=$PIP_CACHE -r requirements_test.txt --use-deprecated=legacy-resolver
pip install -e .
pylint: pylint:
name: Check pylint name: Check pylint
runs-on: ubuntu-latest runs-on: ubuntu-20.04
timeout-minutes: 20 timeout-minutes: 20
needs: needs:
- changes - info
- prepare-tests - base
strategy:
matrix:
python-version: [3.9]
container: homeassistant/ci-azure:${{ matrix.python-version }}
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v3.0.2 uses: actions/checkout@v3.0.2
- name: Restore full Python ${{ matrix.python-version }} virtual environment - name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v4.1.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v3.0.4 uses: actions/cache@v3.0.5
with: with:
path: venv path: venv
key: ${{ runner.os }}-${{ matrix.python-version }}-${{ key: >-
needs.prepare-tests.outputs.python-key }} ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
needs.info.outputs.python_cache_key }}
- name: Fail job if Python cache restore failed - name: Fail job if Python cache restore failed
if: steps.cache-venv.outputs.cache-hit != 'true' if: steps.cache-venv.outputs.cache-hit != 'true'
run: | run: |
@ -644,39 +618,41 @@ jobs:
run: | run: |
echo "::add-matcher::.github/workflows/matchers/pylint.json" echo "::add-matcher::.github/workflows/matchers/pylint.json"
- name: Run pylint (fully) - name: Run pylint (fully)
if: needs.changes.outputs.test_full_suite == 'true' if: needs.info.outputs.test_full_suite == 'true'
run: | run: |
. venv/bin/activate . venv/bin/activate
python --version python --version
pylint homeassistant pylint --ignore-missing-annotations=y homeassistant
- name: Run pylint (partially) - name: Run pylint (partially)
if: needs.changes.outputs.test_full_suite == 'false' if: needs.info.outputs.test_full_suite == 'false'
shell: bash shell: bash
run: | run: |
. venv/bin/activate . venv/bin/activate
python --version python --version
pylint homeassistant/components/${{ needs.changes.outputs.integrations_glob }} pylint --ignore-missing-annotations=y homeassistant/components/${{ needs.info.outputs.integrations_glob }}
mypy: mypy:
name: Check mypy name: Check mypy
runs-on: ubuntu-latest runs-on: ubuntu-20.04
needs: needs:
- changes - info
- prepare-tests - base
strategy:
matrix:
python-version: [3.9]
container: homeassistant/ci-azure:${{ matrix.python-version }}
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v3.0.2 uses: actions/checkout@v3.0.2
- name: Restore full Python ${{ matrix.python-version }} virtual environment - name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v4.1.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v3.0.4 uses: actions/cache@v3.0.5
with: with:
path: venv path: venv
key: ${{ runner.os }}-${{ matrix.python-version }}-${{ key: >-
needs.prepare-tests.outputs.python-key }} ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
needs.info.outputs.python_cache_key }}
- name: Fail job if Python cache restore failed - name: Fail job if Python cache restore failed
if: steps.cache-venv.outputs.cache-hit != 'true' if: steps.cache-venv.outputs.cache-hit != 'true'
run: | run: |
@ -686,41 +662,45 @@ jobs:
run: | run: |
echo "::add-matcher::.github/workflows/matchers/mypy.json" echo "::add-matcher::.github/workflows/matchers/mypy.json"
- name: Run mypy (fully) - name: Run mypy (fully)
if: needs.changes.outputs.test_full_suite == 'true' if: needs.info.outputs.test_full_suite == 'true'
run: | run: |
. venv/bin/activate . venv/bin/activate
python --version python --version
mypy homeassistant pylint mypy homeassistant pylint
- name: Run mypy (partially) - name: Run mypy (partially)
if: needs.changes.outputs.test_full_suite == 'false' if: needs.info.outputs.test_full_suite == 'false'
shell: bash shell: bash
run: | run: |
. venv/bin/activate . venv/bin/activate
python --version python --version
mypy homeassistant/components/${{ needs.changes.outputs.integrations_glob }} mypy homeassistant/components/${{ needs.info.outputs.integrations_glob }}
pip-check: pip-check:
runs-on: ubuntu-latest runs-on: ubuntu-20.04
if: needs.changes.outputs.requirements == 'true' || github.event.inputs.full == 'true'
needs: needs:
- changes - info
- prepare-tests - base
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
python-version: [3.9] python-version: ["3.9", "3.10"]
name: Run pip check ${{ matrix.python-version }} name: Run pip check ${{ matrix.python-version }}
container: homeassistant/ci-azure:${{ matrix.python-version }}
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v3.0.2 uses: actions/checkout@v3.0.2
- name: Set up Python ${{ matrix.python-version }}
id: python
uses: actions/setup-python@v4.1.0
with:
python-version: ${{ matrix.python-version }}
- name: Restore full Python ${{ matrix.python-version }} virtual environment - name: Restore full Python ${{ matrix.python-version }} virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v3.0.4 uses: actions/cache@v3.0.5
with: with:
path: venv path: venv
key: ${{ runner.os }}-${{ matrix.python-version }}-${{ key: >-
needs.prepare-tests.outputs.python-key }} ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
needs.info.outputs.python_cache_key }}
- name: Fail job if Python cache restore failed - name: Fail job if Python cache restore failed
if: steps.cache-venv.outputs.cache-hit != 'true' if: steps.cache-venv.outputs.cache-hit != 'true'
run: | run: |
@ -732,38 +712,48 @@ jobs:
./script/pip_check $PIP_CACHE ./script/pip_check $PIP_CACHE
pytest: pytest:
runs-on: ubuntu-latest runs-on: ubuntu-20.04
if: | if: |
(github.event_name != 'push' || github.event.repository.full_name == 'home-assistant/core') (github.event_name != 'push' || github.event.repository.full_name == 'home-assistant/core')
&& github.event.inputs.lint-only != 'true' && github.event.inputs.lint-only != 'true'
&& (needs.changes.outputs.test_full_suite == 'true' || needs.changes.outputs.tests_glob) && (needs.info.outputs.test_full_suite == 'true' || needs.info.outputs.tests_glob)
needs: needs:
- changes - info
- base
- gen-requirements-all - gen-requirements-all
- hassfest - hassfest
- lint-black - lint-black
- lint-other - lint-other
- lint-isort - lint-isort
- mypy - mypy
- prepare-tests
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
group: ${{ fromJson(needs.changes.outputs.test_groups) }} group: ${{ fromJson(needs.info.outputs.test_groups) }}
python-version: ["3.9", "3.10"] python-version: ["3.9", "3.10"]
name: >- name: >-
Run tests Python ${{ matrix.python-version }} (${{ matrix.group }}) Run tests Python ${{ matrix.python-version }} (${{ matrix.group }})
container: homeassistant/ci-azure:${{ matrix.python-version }}
steps: steps:
- name: Install additional OS dependencies
run: |
sudo apt-get update
sudo apt-get -y install \
bluez \
ffmpeg
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v3.0.2 uses: actions/checkout@v3.0.2
- name: Set up Python ${{ matrix.python-version }}
id: python
uses: actions/setup-python@v4.1.0
with:
python-version: ${{ matrix.python-version }}
- name: Restore full Python ${{ matrix.python-version }} virtual environment - name: Restore full Python ${{ matrix.python-version }} virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v3.0.4 uses: actions/cache@v3.0.5
with: with:
path: venv path: venv
key: ${{ runner.os }}-${{ matrix.python-version }}-${{ key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
needs.prepare-tests.outputs.python-key }} needs.info.outputs.python_cache_key }}
- name: Fail job if Python cache restore failed - name: Fail job if Python cache restore failed
if: steps.cache-venv.outputs.cache-hit != 'true' if: steps.cache-venv.outputs.cache-hit != 'true'
run: | run: |
@ -783,7 +773,7 @@ jobs:
run: | run: |
echo "::add-matcher::.github/workflows/matchers/pytest-slow.json" echo "::add-matcher::.github/workflows/matchers/pytest-slow.json"
- name: Run pytest (fully) - name: Run pytest (fully)
if: needs.changes.outputs.test_full_suite == 'true' if: needs.info.outputs.test_full_suite == 'true'
timeout-minutes: 60 timeout-minutes: 60
run: | run: |
. venv/bin/activate . venv/bin/activate
@ -794,7 +784,7 @@ jobs:
--durations=10 \ --durations=10 \
-n auto \ -n auto \
--dist=loadfile \ --dist=loadfile \
--test-group-count ${{ needs.changes.outputs.test_group_count }} \ --test-group-count ${{ needs.info.outputs.test_group_count }} \
--test-group=${{ matrix.group }} \ --test-group=${{ matrix.group }} \
--cov="homeassistant" \ --cov="homeassistant" \
--cov-report=xml \ --cov-report=xml \
@ -802,8 +792,8 @@ jobs:
-p no:sugar \ -p no:sugar \
tests tests
- name: Run pytest (partially) - name: Run pytest (partially)
if: needs.changes.outputs.test_full_suite == 'false' if: needs.info.outputs.test_full_suite == 'false'
timeout-minutes: 20 timeout-minutes: 10
shell: bash shell: bash
run: | run: |
. venv/bin/activate . venv/bin/activate
@ -838,9 +828,9 @@ jobs:
coverage: coverage:
name: Upload test coverage to Codecov name: Upload test coverage to Codecov
runs-on: ubuntu-latest runs-on: ubuntu-20.04
needs: needs:
- changes - info
- pytest - pytest
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
@ -848,10 +838,10 @@ jobs:
- name: Download all coverage artifacts - name: Download all coverage artifacts
uses: actions/download-artifact@v3 uses: actions/download-artifact@v3
- name: Upload coverage to Codecov (full coverage) - name: Upload coverage to Codecov (full coverage)
if: needs.changes.outputs.test_full_suite == 'true' if: needs.info.outputs.test_full_suite == 'true'
uses: codecov/codecov-action@v3.1.0 uses: codecov/codecov-action@v3.1.0
with: with:
flags: full-suite flags: full-suite
- name: Upload coverage to Codecov (partial coverage) - name: Upload coverage to Codecov (partial coverage)
if: needs.changes.outputs.test_full_suite == 'false' if: needs.info.outputs.test_full_suite == 'false'
uses: codecov/codecov-action@v3.1.0 uses: codecov/codecov-action@v3.1.0

View File

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

View File

@ -157,6 +157,9 @@ jobs:
echo "cmake==3.22.2" echo "cmake==3.22.2"
) >> homeassistant/package_constraints.txt ) >> homeassistant/package_constraints.txt
# Do not pin numpy in wheels building
sed -i "/numpy/d" homeassistant/package_constraints.txt
- name: Build wheels - name: Build wheels
uses: home-assistant/wheels@2022.06.7 uses: home-assistant/wheels@2022.06.7
with: with:

View File

@ -1,6 +0,0 @@
# Patterns matched in this file will be ignored by supported search utilities
# Ignore generated html and javascript files
/homeassistant/components/frontend/www_static/*.html
/homeassistant/components/frontend/www_static/*.js
/homeassistant/components/frontend/www_static/panels/*.html

View File

@ -1,11 +1,11 @@
repos: repos:
- repo: https://github.com/asottile/pyupgrade - repo: https://github.com/asottile/pyupgrade
rev: v2.34.0 rev: v2.37.2
hooks: hooks:
- id: pyupgrade - id: pyupgrade
args: [--py39-plus] args: [--py39-plus]
- repo: https://github.com/psf/black - repo: https://github.com/psf/black
rev: 22.3.0 rev: 22.6.0
hooks: hooks:
- id: black - id: black
args: args:
@ -21,7 +21,7 @@ repos:
- --skip="./.*,*.csv,*.json" - --skip="./.*,*.csv,*.json"
- --quiet-level=2 - --quiet-level=2
exclude_types: [csv, json] exclude_types: [csv, json]
exclude: ^tests/fixtures/ exclude: ^tests/fixtures/|homeassistant/generated/
- repo: https://github.com/PyCQA/flake8 - repo: https://github.com/PyCQA/flake8
rev: 4.0.1 rev: 4.0.1
hooks: hooks:
@ -31,8 +31,8 @@ repos:
- pyflakes==2.4.0 - pyflakes==2.4.0
- flake8-docstrings==1.6.0 - flake8-docstrings==1.6.0
- pydocstyle==6.1.1 - pydocstyle==6.1.1
- flake8-comprehensions==3.8.0 - flake8-comprehensions==3.10.0
- flake8-noqa==1.2.1 - flake8-noqa==1.2.5
- mccabe==0.6.1 - mccabe==0.6.1
files: ^(homeassistant|script|tests)/.+\.py$ files: ^(homeassistant|script|tests)/.+\.py$
- repo: https://github.com/PyCQA/bandit - repo: https://github.com/PyCQA/bandit
@ -61,7 +61,7 @@ repos:
- --branch=master - --branch=master
- --branch=rc - --branch=rc
- repo: https://github.com/adrienverge/yamllint.git - repo: https://github.com/adrienverge/yamllint.git
rev: v1.26.3 rev: v1.27.1
hooks: hooks:
- id: yamllint - id: yamllint
- repo: https://github.com/pre-commit/mirrors-prettier - repo: https://github.com/pre-commit/mirrors-prettier
@ -96,7 +96,7 @@ repos:
files: ^(homeassistant|pylint)/.+\.py$ files: ^(homeassistant|pylint)/.+\.py$
- id: pylint - id: pylint
name: pylint name: pylint
entry: script/run-in-env.sh pylint -j 0 entry: script/run-in-env.sh pylint -j 0 --ignore-missing-annotations=y
language: script language: script
types: [python] types: [python]
files: ^homeassistant/.+\.py$ files: ^homeassistant/.+\.py$

View File

@ -15,12 +15,17 @@ homeassistant.auth.auth_store
homeassistant.auth.providers.* homeassistant.auth.providers.*
homeassistant.helpers.area_registry homeassistant.helpers.area_registry
homeassistant.helpers.condition homeassistant.helpers.condition
homeassistant.helpers.debounce
homeassistant.helpers.deprecation
homeassistant.helpers.discovery homeassistant.helpers.discovery
homeassistant.helpers.dispatcher
homeassistant.helpers.entity homeassistant.helpers.entity
homeassistant.helpers.entity_platform
homeassistant.helpers.entity_values homeassistant.helpers.entity_values
homeassistant.helpers.event homeassistant.helpers.event
homeassistant.helpers.reload homeassistant.helpers.reload
homeassistant.helpers.script_variables homeassistant.helpers.script_variables
homeassistant.helpers.singleton
homeassistant.helpers.sun homeassistant.helpers.sun
homeassistant.helpers.translation homeassistant.helpers.translation
homeassistant.util.async_ homeassistant.util.async_
@ -57,6 +62,7 @@ homeassistant.components.automation.*
homeassistant.components.backup.* homeassistant.components.backup.*
homeassistant.components.baf.* homeassistant.components.baf.*
homeassistant.components.binary_sensor.* homeassistant.components.binary_sensor.*
homeassistant.components.bluetooth.*
homeassistant.components.bluetooth_tracker.* homeassistant.components.bluetooth_tracker.*
homeassistant.components.bmw_connected_drive.* homeassistant.components.bmw_connected_drive.*
homeassistant.components.bond.* homeassistant.components.bond.*
@ -109,6 +115,7 @@ homeassistant.components.group.*
homeassistant.components.guardian.* homeassistant.components.guardian.*
homeassistant.components.history.* homeassistant.components.history.*
homeassistant.components.homeassistant.triggers.event homeassistant.components.homeassistant.triggers.event
homeassistant.components.homeassistant_alerts.*
homeassistant.components.homekit homeassistant.components.homekit
homeassistant.components.homekit.accessories homeassistant.components.homekit.accessories
homeassistant.components.homekit.aidmanager homeassistant.components.homekit.aidmanager
@ -145,6 +152,8 @@ homeassistant.components.lametric.*
homeassistant.components.laundrify.* homeassistant.components.laundrify.*
homeassistant.components.lcn.* homeassistant.components.lcn.*
homeassistant.components.light.* homeassistant.components.light.*
homeassistant.components.lifx.*
homeassistant.components.litterrobot.*
homeassistant.components.local_ip.* homeassistant.components.local_ip.*
homeassistant.components.lock.* homeassistant.components.lock.*
homeassistant.components.logbook.* homeassistant.components.logbook.*
@ -153,6 +162,7 @@ homeassistant.components.luftdaten.*
homeassistant.components.mailbox.* homeassistant.components.mailbox.*
homeassistant.components.media_player.* homeassistant.components.media_player.*
homeassistant.components.media_source.* homeassistant.components.media_source.*
homeassistant.components.metoffice.*
homeassistant.components.mjpeg.* homeassistant.components.mjpeg.*
homeassistant.components.modbus.* homeassistant.components.modbus.*
homeassistant.components.modem_callerid.* homeassistant.components.modem_callerid.*
@ -190,6 +200,8 @@ homeassistant.components.recollect_waste.*
homeassistant.components.recorder.* homeassistant.components.recorder.*
homeassistant.components.remote.* homeassistant.components.remote.*
homeassistant.components.renault.* homeassistant.components.renault.*
homeassistant.components.repairs.*
homeassistant.components.rhasspy.*
homeassistant.components.ridwell.* homeassistant.components.ridwell.*
homeassistant.components.rituals_perfume_genie.* homeassistant.components.rituals_perfume_genie.*
homeassistant.components.roku.* homeassistant.components.roku.*

8
.vscode/launch.json vendored
View File

@ -12,6 +12,14 @@
"justMyCode": false, "justMyCode": false,
"args": ["--debug", "-c", "config"] "args": ["--debug", "-c", "config"]
}, },
{
"name": "Home Assistant (skip pip)",
"type": "python",
"request": "launch",
"module": "homeassistant",
"justMyCode": false,
"args": ["--debug", "-c", "config", "--skip-pip"]
},
{ {
// Debug by attaching to local Home Asistant server using Remote Python Debugger. // Debug by attaching to local Home Asistant server using Remote Python Debugger.
// See https://www.home-assistant.io/integrations/debugpy/ // See https://www.home-assistant.io/integrations/debugpy/

View File

@ -74,6 +74,8 @@ build.json @home-assistant/supervisor
/tests/components/analytics/ @home-assistant/core @ludeeus /tests/components/analytics/ @home-assistant/core @ludeeus
/homeassistant/components/androidtv/ @JeffLIrion @ollo69 /homeassistant/components/androidtv/ @JeffLIrion @ollo69
/tests/components/androidtv/ @JeffLIrion @ollo69 /tests/components/androidtv/ @JeffLIrion @ollo69
/homeassistant/components/anthemav/ @hyralex
/tests/components/anthemav/ @hyralex
/homeassistant/components/apache_kafka/ @bachya /homeassistant/components/apache_kafka/ @bachya
/tests/components/apache_kafka/ @bachya /tests/components/apache_kafka/ @bachya
/homeassistant/components/api/ @home-assistant/core /homeassistant/components/api/ @home-assistant/core
@ -136,6 +138,8 @@ build.json @home-assistant/supervisor
/homeassistant/components/blueprint/ @home-assistant/core /homeassistant/components/blueprint/ @home-assistant/core
/tests/components/blueprint/ @home-assistant/core /tests/components/blueprint/ @home-assistant/core
/homeassistant/components/bluesound/ @thrawnarn /homeassistant/components/bluesound/ @thrawnarn
/homeassistant/components/bluetooth/ @bdraco
/tests/components/bluetooth/ @bdraco
/homeassistant/components/bmw_connected_drive/ @gerard33 @rikroe /homeassistant/components/bmw_connected_drive/ @gerard33 @rikroe
/tests/components/bmw_connected_drive/ @gerard33 @rikroe /tests/components/bmw_connected_drive/ @gerard33 @rikroe
/homeassistant/components/bond/ @bdraco @prystupa @joshs85 @marciogranzotto /homeassistant/components/bond/ @bdraco @prystupa @joshs85 @marciogranzotto
@ -407,6 +411,8 @@ build.json @home-assistant/supervisor
/homeassistant/components/google_cloud/ @lufton /homeassistant/components/google_cloud/ @lufton
/homeassistant/components/google_travel_time/ @eifinger /homeassistant/components/google_travel_time/ @eifinger
/tests/components/google_travel_time/ @eifinger /tests/components/google_travel_time/ @eifinger
/homeassistant/components/govee_ble/ @bdraco
/tests/components/govee_ble/ @bdraco
/homeassistant/components/gpsd/ @fabaff /homeassistant/components/gpsd/ @fabaff
/homeassistant/components/gree/ @cmroche /homeassistant/components/gree/ @cmroche
/tests/components/gree/ @cmroche /tests/components/gree/ @cmroche
@ -449,6 +455,8 @@ build.json @home-assistant/supervisor
/tests/components/home_plus_control/ @chemaaa /tests/components/home_plus_control/ @chemaaa
/homeassistant/components/homeassistant/ @home-assistant/core /homeassistant/components/homeassistant/ @home-assistant/core
/tests/components/homeassistant/ @home-assistant/core /tests/components/homeassistant/ @home-assistant/core
/homeassistant/components/homeassistant_alerts/ @home-assistant/core
/tests/components/homeassistant_alerts/ @home-assistant/core
/homeassistant/components/homeassistant_yellow/ @home-assistant/core /homeassistant/components/homeassistant_yellow/ @home-assistant/core
/tests/components/homeassistant_yellow/ @home-assistant/core /tests/components/homeassistant_yellow/ @home-assistant/core
/homeassistant/components/homekit/ @bdraco /homeassistant/components/homekit/ @bdraco
@ -494,6 +502,8 @@ build.json @home-assistant/supervisor
/homeassistant/components/incomfort/ @zxdavb /homeassistant/components/incomfort/ @zxdavb
/homeassistant/components/influxdb/ @mdegat01 /homeassistant/components/influxdb/ @mdegat01
/tests/components/influxdb/ @mdegat01 /tests/components/influxdb/ @mdegat01
/homeassistant/components/inkbird/ @bdraco
/tests/components/inkbird/ @bdraco
/homeassistant/components/input_boolean/ @home-assistant/core /homeassistant/components/input_boolean/ @home-assistant/core
/tests/components/input_boolean/ @home-assistant/core /tests/components/input_boolean/ @home-assistant/core
/homeassistant/components/input_button/ @home-assistant/core /homeassistant/components/input_button/ @home-assistant/core
@ -573,7 +583,8 @@ build.json @home-assistant/supervisor
/homeassistant/components/lg_netcast/ @Drafteed /homeassistant/components/lg_netcast/ @Drafteed
/homeassistant/components/life360/ @pnbruckner /homeassistant/components/life360/ @pnbruckner
/tests/components/life360/ @pnbruckner /tests/components/life360/ @pnbruckner
/homeassistant/components/lifx/ @Djelibeybi /homeassistant/components/lifx/ @bdraco @Djelibeybi
/tests/components/lifx/ @bdraco @Djelibeybi
/homeassistant/components/light/ @home-assistant/core /homeassistant/components/light/ @home-assistant/core
/tests/components/light/ @home-assistant/core /tests/components/light/ @home-assistant/core
/homeassistant/components/linux_battery/ @fabaff /homeassistant/components/linux_battery/ @fabaff
@ -628,8 +639,8 @@ build.json @home-assistant/supervisor
/homeassistant/components/meteoalarm/ @rolfberkenbosch /homeassistant/components/meteoalarm/ @rolfberkenbosch
/homeassistant/components/meteoclimatic/ @adrianmo /homeassistant/components/meteoclimatic/ @adrianmo
/tests/components/meteoclimatic/ @adrianmo /tests/components/meteoclimatic/ @adrianmo
/homeassistant/components/metoffice/ @MrHarcombe /homeassistant/components/metoffice/ @MrHarcombe @avee87
/tests/components/metoffice/ @MrHarcombe /tests/components/metoffice/ @MrHarcombe @avee87
/homeassistant/components/miflora/ @danielhiversen @basnijholt /homeassistant/components/miflora/ @danielhiversen @basnijholt
/homeassistant/components/mikrotik/ @engrbm87 /homeassistant/components/mikrotik/ @engrbm87
/tests/components/mikrotik/ @engrbm87 /tests/components/mikrotik/ @engrbm87
@ -641,6 +652,8 @@ build.json @home-assistant/supervisor
/tests/components/minecraft_server/ @elmurato /tests/components/minecraft_server/ @elmurato
/homeassistant/components/minio/ @tkislan /homeassistant/components/minio/ @tkislan
/tests/components/minio/ @tkislan /tests/components/minio/ @tkislan
/homeassistant/components/moat/ @bdraco
/tests/components/moat/ @bdraco
/homeassistant/components/mobile_app/ @home-assistant/core /homeassistant/components/mobile_app/ @home-assistant/core
/tests/components/mobile_app/ @home-assistant/core /tests/components/mobile_app/ @home-assistant/core
/homeassistant/components/modbus/ @adamchengtkc @janiversen @vzahradnik /homeassistant/components/modbus/ @adamchengtkc @janiversen @vzahradnik
@ -696,6 +709,8 @@ build.json @home-assistant/supervisor
/homeassistant/components/nextbus/ @vividboarder /homeassistant/components/nextbus/ @vividboarder
/tests/components/nextbus/ @vividboarder /tests/components/nextbus/ @vividboarder
/homeassistant/components/nextcloud/ @meichthys /homeassistant/components/nextcloud/ @meichthys
/homeassistant/components/nextdns/ @bieniu
/tests/components/nextdns/ @bieniu
/homeassistant/components/nfandroidtv/ @tkdrob /homeassistant/components/nfandroidtv/ @tkdrob
/tests/components/nfandroidtv/ @tkdrob /tests/components/nfandroidtv/ @tkdrob
/homeassistant/components/nightscout/ @marciogranzotto /homeassistant/components/nightscout/ @marciogranzotto
@ -852,11 +867,15 @@ build.json @home-assistant/supervisor
/tests/components/remote/ @home-assistant/core /tests/components/remote/ @home-assistant/core
/homeassistant/components/renault/ @epenet /homeassistant/components/renault/ @epenet
/tests/components/renault/ @epenet /tests/components/renault/ @epenet
/homeassistant/components/repairs/ @home-assistant/core
/tests/components/repairs/ @home-assistant/core
/homeassistant/components/repetier/ @MTrab @ShadowBr0ther /homeassistant/components/repetier/ @MTrab @ShadowBr0ther
/homeassistant/components/rflink/ @javicalle /homeassistant/components/rflink/ @javicalle
/tests/components/rflink/ @javicalle /tests/components/rflink/ @javicalle
/homeassistant/components/rfxtrx/ @danielhiversen @elupus @RobBie1221 /homeassistant/components/rfxtrx/ @danielhiversen @elupus @RobBie1221
/tests/components/rfxtrx/ @danielhiversen @elupus @RobBie1221 /tests/components/rfxtrx/ @danielhiversen @elupus @RobBie1221
/homeassistant/components/rhasspy/ @balloob @synesthesiam
/tests/components/rhasspy/ @balloob @synesthesiam
/homeassistant/components/ridwell/ @bachya /homeassistant/components/ridwell/ @bachya
/tests/components/ridwell/ @bachya /tests/components/ridwell/ @bachya
/homeassistant/components/ring/ @balloob /homeassistant/components/ring/ @balloob
@ -911,6 +930,8 @@ build.json @home-assistant/supervisor
/tests/components/sensibo/ @andrey-git @gjohansson-ST /tests/components/sensibo/ @andrey-git @gjohansson-ST
/homeassistant/components/sensor/ @home-assistant/core /homeassistant/components/sensor/ @home-assistant/core
/tests/components/sensor/ @home-assistant/core /tests/components/sensor/ @home-assistant/core
/homeassistant/components/sensorpush/ @bdraco
/tests/components/sensorpush/ @bdraco
/homeassistant/components/sentry/ @dcramer @frenck /homeassistant/components/sentry/ @dcramer @frenck
/tests/components/sentry/ @dcramer @frenck /tests/components/sentry/ @dcramer @frenck
/homeassistant/components/senz/ @milanmeu /homeassistant/components/senz/ @milanmeu
@ -977,6 +998,8 @@ build.json @home-assistant/supervisor
/tests/components/songpal/ @rytilahti @shenxn /tests/components/songpal/ @rytilahti @shenxn
/homeassistant/components/sonos/ @cgtobi @jjlawren /homeassistant/components/sonos/ @cgtobi @jjlawren
/tests/components/sonos/ @cgtobi @jjlawren /tests/components/sonos/ @cgtobi @jjlawren
/homeassistant/components/soundtouch/ @kroimon
/tests/components/soundtouch/ @kroimon
/homeassistant/components/spaceapi/ @fabaff /homeassistant/components/spaceapi/ @fabaff
/tests/components/spaceapi/ @fabaff /tests/components/spaceapi/ @fabaff
/homeassistant/components/speedtestdotnet/ @rohankapoorcom @engrbm87 /homeassistant/components/speedtestdotnet/ @rohankapoorcom @engrbm87
@ -1021,11 +1044,11 @@ build.json @home-assistant/supervisor
/tests/components/switch/ @home-assistant/core /tests/components/switch/ @home-assistant/core
/homeassistant/components/switch_as_x/ @home-assistant/core /homeassistant/components/switch_as_x/ @home-assistant/core
/tests/components/switch_as_x/ @home-assistant/core /tests/components/switch_as_x/ @home-assistant/core
/homeassistant/components/switchbot/ @danielhiversen @RenierM26 /homeassistant/components/switchbot/ @bdraco @danielhiversen @RenierM26 @murtas @Eloston
/tests/components/switchbot/ @danielhiversen @RenierM26 /tests/components/switchbot/ @bdraco @danielhiversen @RenierM26 @murtas @Eloston
/homeassistant/components/switcher_kis/ @tomerfi @thecode /homeassistant/components/switcher_kis/ @tomerfi @thecode
/tests/components/switcher_kis/ @tomerfi @thecode /tests/components/switcher_kis/ @tomerfi @thecode
/homeassistant/components/switchmate/ @danielhiversen /homeassistant/components/switchmate/ @danielhiversen @qiz-li
/homeassistant/components/syncthing/ @zhulik /homeassistant/components/syncthing/ @zhulik
/tests/components/syncthing/ @zhulik /tests/components/syncthing/ @zhulik
/homeassistant/components/syncthru/ @nielstron /homeassistant/components/syncthru/ @nielstron
@ -1203,6 +1226,8 @@ build.json @home-assistant/supervisor
/homeassistant/components/xbox_live/ @MartinHjelmare /homeassistant/components/xbox_live/ @MartinHjelmare
/homeassistant/components/xiaomi_aqara/ @danielhiversen @syssi /homeassistant/components/xiaomi_aqara/ @danielhiversen @syssi
/tests/components/xiaomi_aqara/ @danielhiversen @syssi /tests/components/xiaomi_aqara/ @danielhiversen @syssi
/homeassistant/components/xiaomi_ble/ @Jc2k @Ernst79
/tests/components/xiaomi_ble/ @Jc2k @Ernst79
/homeassistant/components/xiaomi_miio/ @rytilahti @syssi @starkillerOG @bieniu /homeassistant/components/xiaomi_miio/ @rytilahti @syssi @starkillerOG @bieniu
/tests/components/xiaomi_miio/ @rytilahti @syssi @starkillerOG @bieniu /tests/components/xiaomi_miio/ @rytilahti @syssi @starkillerOG @bieniu
/homeassistant/components/xiaomi_tv/ @simse /homeassistant/components/xiaomi_tv/ @simse
@ -1226,8 +1251,8 @@ build.json @home-assistant/supervisor
/tests/components/zeroconf/ @bdraco /tests/components/zeroconf/ @bdraco
/homeassistant/components/zerproc/ @emlove /homeassistant/components/zerproc/ @emlove
/tests/components/zerproc/ @emlove /tests/components/zerproc/ @emlove
/homeassistant/components/zha/ @dmulcahey @adminiuga /homeassistant/components/zha/ @dmulcahey @adminiuga @puddly
/tests/components/zha/ @dmulcahey @adminiuga /tests/components/zha/ @dmulcahey @adminiuga @puddly
/homeassistant/components/zodiac/ @JulienTant /homeassistant/components/zodiac/ @JulienTant
/tests/components/zodiac/ @JulienTant /tests/components/zodiac/ @JulienTant
/homeassistant/components/zone/ @home-assistant/core /homeassistant/components/zone/ @home-assistant/core

View File

@ -13,9 +13,12 @@ COPY homeassistant/package_constraints.txt homeassistant/homeassistant/
RUN \ RUN \
pip3 install --no-cache-dir --no-index --only-binary=:all: --find-links "${WHEELS_LINKS}" \ pip3 install --no-cache-dir --no-index --only-binary=:all: --find-links "${WHEELS_LINKS}" \
-r homeassistant/requirements.txt --use-deprecated=legacy-resolver -r homeassistant/requirements.txt --use-deprecated=legacy-resolver
COPY requirements_all.txt homeassistant/ COPY requirements_all.txt home_assistant_frontend-* homeassistant/
RUN \ RUN \
pip3 install --no-cache-dir --no-index --only-binary=:all: --find-links "${WHEELS_LINKS}" \ if ls homeassistant/home_assistant_frontend*.whl 1> /dev/null 2>&1; then \
pip3 install --no-cache-dir --no-index homeassistant/home_assistant_frontend-*.whl; \
fi \
&& pip3 install --no-cache-dir --no-index --only-binary=:all: --find-links "${WHEELS_LINKS}" \
-r homeassistant/requirements_all.txt --use-deprecated=legacy-resolver -r homeassistant/requirements_all.txt --use-deprecated=legacy-resolver
## Setup Home Assistant Core ## Setup Home Assistant Core

View File

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

View File

@ -46,7 +46,7 @@ class AuthStore:
self._users: dict[str, models.User] | None = None self._users: dict[str, models.User] | None = None
self._groups: dict[str, models.Group] | None = None self._groups: dict[str, models.Group] | None = None
self._perm_lookup: PermissionLookup | None = None self._perm_lookup: PermissionLookup | None = None
self._store = Store( self._store = Store[dict[str, list[dict[str, Any]]]](
hass, STORAGE_VERSION, STORAGE_KEY, private=True, atomic_writes=True hass, STORAGE_VERSION, STORAGE_KEY, private=True, atomic_writes=True
) )
self._lock = asyncio.Lock() self._lock = asyncio.Lock()
@ -483,9 +483,10 @@ class AuthStore:
jwt_key=rt_dict["jwt_key"], jwt_key=rt_dict["jwt_key"],
last_used_at=last_used_at, last_used_at=last_used_at,
last_used_ip=rt_dict.get("last_used_ip"), last_used_ip=rt_dict.get("last_used_ip"),
credential=credentials.get(rt_dict.get("credential_id")),
version=rt_dict.get("version"), version=rt_dict.get("version"),
) )
if "credential_id" in rt_dict:
token.credential = credentials.get(rt_dict["credential_id"])
users[rt_dict["user_id"]].refresh_tokens[token.id] = token users[rt_dict["user_id"]].refresh_tokens[token.id] = token
self._groups = groups self._groups = groups

View File

@ -7,7 +7,7 @@ from __future__ import annotations
import asyncio import asyncio
from collections import OrderedDict from collections import OrderedDict
import logging import logging
from typing import Any from typing import Any, cast
import attr import attr
import voluptuous as vol import voluptuous as vol
@ -100,7 +100,7 @@ class NotifyAuthModule(MultiFactorAuthModule):
"""Initialize the user data store.""" """Initialize the user data store."""
super().__init__(hass, config) super().__init__(hass, config)
self._user_settings: _UsersDict | None = None self._user_settings: _UsersDict | None = None
self._user_store = Store( self._user_store = Store[dict[str, dict[str, Any]]](
hass, STORAGE_VERSION, STORAGE_KEY, private=True, atomic_writes=True hass, STORAGE_VERSION, STORAGE_KEY, private=True, atomic_writes=True
) )
self._include = config.get(CONF_INCLUDE, []) self._include = config.get(CONF_INCLUDE, [])
@ -119,10 +119,8 @@ class NotifyAuthModule(MultiFactorAuthModule):
if self._user_settings is not None: if self._user_settings is not None:
return return
if (data := await self._user_store.async_load()) is None or not isinstance( if (data := await self._user_store.async_load()) is None:
data, dict data = cast(dict[str, dict[str, Any]], {STORAGE_USERS: {}})
):
data = {STORAGE_USERS: {}}
self._user_settings = { self._user_settings = {
user_id: NotifySetting(**setting) user_id: NotifySetting(**setting)
@ -322,6 +320,7 @@ class NotifySetupFlow(SetupFlow):
errors: dict[str, str] = {} errors: dict[str, str] = {}
hass = self._auth_module.hass hass = self._auth_module.hass
assert self._secret and self._count
if user_input: if user_input:
verified = await hass.async_add_executor_job( verified = await hass.async_add_executor_job(
_verify_otp, self._secret, user_input["code"], self._count _verify_otp, self._secret, user_input["code"], self._count
@ -336,7 +335,6 @@ class NotifySetupFlow(SetupFlow):
errors["base"] = "invalid_code" errors["base"] = "invalid_code"
# generate code every time, no retry logic # generate code every time, no retry logic
assert self._secret and self._count
code = await hass.async_add_executor_job( code = await hass.async_add_executor_job(
_generate_otp, self._secret, self._count _generate_otp, self._secret, self._count
) )

View File

@ -3,7 +3,7 @@ from __future__ import annotations
import asyncio import asyncio
from io import BytesIO from io import BytesIO
from typing import Any from typing import Any, cast
import voluptuous as vol import voluptuous as vol
@ -77,7 +77,7 @@ class TotpAuthModule(MultiFactorAuthModule):
"""Initialize the user data store.""" """Initialize the user data store."""
super().__init__(hass, config) super().__init__(hass, config)
self._users: dict[str, str] | None = None self._users: dict[str, str] | None = None
self._user_store = Store( self._user_store = Store[dict[str, dict[str, str]]](
hass, STORAGE_VERSION, STORAGE_KEY, private=True, atomic_writes=True hass, STORAGE_VERSION, STORAGE_KEY, private=True, atomic_writes=True
) )
self._init_lock = asyncio.Lock() self._init_lock = asyncio.Lock()
@ -93,16 +93,14 @@ class TotpAuthModule(MultiFactorAuthModule):
if self._users is not None: if self._users is not None:
return return
if (data := await self._user_store.async_load()) is None or not isinstance( if (data := await self._user_store.async_load()) is None:
data, dict data = cast(dict[str, dict[str, str]], {STORAGE_USERS: {}})
):
data = {STORAGE_USERS: {}}
self._users = data.get(STORAGE_USERS, {}) self._users = data.get(STORAGE_USERS, {})
async def _async_save(self) -> None: async def _async_save(self) -> None:
"""Save data.""" """Save data."""
await self._user_store.async_save({STORAGE_USERS: self._users}) await self._user_store.async_save({STORAGE_USERS: self._users or {}})
def _add_ota_secret(self, user_id: str, secret: str | None = None) -> str: def _add_ota_secret(self, user_id: str, secret: str | None = None) -> str:
"""Create a ota_secret for user.""" """Create a ota_secret for user."""

View File

@ -61,10 +61,10 @@ class Data:
def __init__(self, hass: HomeAssistant) -> None: def __init__(self, hass: HomeAssistant) -> None:
"""Initialize the user data store.""" """Initialize the user data store."""
self.hass = hass self.hass = hass
self._store = Store( self._store = Store[dict[str, list[dict[str, str]]]](
hass, STORAGE_VERSION, STORAGE_KEY, private=True, atomic_writes=True hass, STORAGE_VERSION, STORAGE_KEY, private=True, atomic_writes=True
) )
self._data: dict[str, Any] | None = None self._data: dict[str, list[dict[str, str]]] | None = None
# Legacy mode will allow usernames to start/end with whitespace # Legacy mode will allow usernames to start/end with whitespace
# and will compare usernames case-insensitive. # and will compare usernames case-insensitive.
# Remove in 2020 or when we launch 1.0. # Remove in 2020 or when we launch 1.0.
@ -80,10 +80,8 @@ class Data:
async def async_load(self) -> None: async def async_load(self) -> None:
"""Load stored data.""" """Load stored data."""
if (data := await self._store.async_load()) is None or not isinstance( if (data := await self._store.async_load()) is None:
data, dict data = cast(dict[str, list[dict[str, str]]], {"users": []})
):
data = {"users": []}
seen: set[str] = set() seen: set[str] = set()
@ -123,7 +121,8 @@ class Data:
@property @property
def users(self) -> list[dict[str, str]]: def users(self) -> list[dict[str, str]]:
"""Return users.""" """Return users."""
return self._data["users"] # type: ignore[index,no-any-return] assert self._data is not None
return self._data["users"]
def validate_login(self, username: str, password: str) -> None: def validate_login(self, username: str, password: str) -> None:
"""Validate a username and password. """Validate a username and password.

View File

@ -4,15 +4,15 @@ from __future__ import annotations
from enum import Enum from enum import Enum
from typing import Any, TypeVar from typing import Any, TypeVar
_StrEnumT = TypeVar("_StrEnumT", bound="StrEnum") _StrEnumSelfT = TypeVar("_StrEnumSelfT", bound="StrEnum")
class StrEnum(str, Enum): class StrEnum(str, Enum):
"""Partial backport of Python 3.11's StrEnum for our basic use cases.""" """Partial backport of Python 3.11's StrEnum for our basic use cases."""
def __new__( def __new__(
cls: type[_StrEnumT], value: str, *args: Any, **kwargs: Any cls: type[_StrEnumSelfT], value: str, *args: Any, **kwargs: Any
) -> _StrEnumT: ) -> _StrEnumSelfT:
"""Create a new StrEnum instance.""" """Create a new StrEnum instance."""
if not isinstance(value, str): if not isinstance(value, str):
raise TypeError(f"{value!r} is not a string") raise TypeError(f"{value!r} is not a string")

View File

@ -24,7 +24,7 @@ from .const import (
SIGNAL_BOOTSTRAP_INTEGRATONS, SIGNAL_BOOTSTRAP_INTEGRATONS,
) )
from .exceptions import HomeAssistantError from .exceptions import HomeAssistantError
from .helpers import area_registry, device_registry, entity_registry from .helpers import area_registry, device_registry, entity_registry, recorder
from .helpers.dispatcher import async_dispatcher_send from .helpers.dispatcher import async_dispatcher_send
from .helpers.typing import ConfigType from .helpers.typing import ConfigType
from .setup import ( from .setup import (
@ -35,7 +35,6 @@ from .setup import (
async_setup_component, async_setup_component,
) )
from .util import dt as dt_util from .util import dt as dt_util
from .util.async_ import gather_with_concurrency
from .util.logging import async_activate_log_queue_handler from .util.logging import async_activate_log_queue_handler
from .util.package import async_get_user_site, is_virtual_env from .util.package import async_get_user_site, is_virtual_env
@ -67,10 +66,19 @@ LOGGING_INTEGRATIONS = {
# Error logging # Error logging
"system_log", "system_log",
"sentry", "sentry",
}
FRONTEND_INTEGRATIONS = {
# Get the frontend up and running as soon as possible so problem
# integrations can be removed and database migration status is
# visible in frontend
"frontend",
}
RECORDER_INTEGRATIONS = {
# Setup after frontend
# To record data # To record data
"recorder", "recorder",
} }
DISCOVERY_INTEGRATIONS = ("dhcp", "ssdp", "usb", "zeroconf") DISCOVERY_INTEGRATIONS = ("bluetooth", "dhcp", "ssdp", "usb", "zeroconf")
STAGE_1_INTEGRATIONS = { STAGE_1_INTEGRATIONS = {
# We need to make sure discovery integrations # We need to make sure discovery integrations
# update their deps before stage 2 integrations # update their deps before stage 2 integrations
@ -84,10 +92,6 @@ STAGE_1_INTEGRATIONS = {
"cloud", "cloud",
# Ensure supervisor is available # Ensure supervisor is available
"hassio", "hassio",
# Get the frontend up and running as soon
# as possible so problem integrations can
# be removed
"frontend",
} }
@ -285,7 +289,9 @@ def async_enable_logging(
This method must be run in the event loop. This method must be run in the event loop.
""" """
fmt = "%(asctime)s %(levelname)s (%(threadName)s) [%(name)s] %(message)s" fmt = (
"%(asctime)s.%(msecs)03d %(levelname)s (%(threadName)s) [%(name)s] %(message)s"
)
datefmt = "%Y-%m-%d %H:%M:%S" datefmt = "%Y-%m-%d %H:%M:%S"
if not log_no_color: if not log_no_color:
@ -477,14 +483,9 @@ async def _async_set_up_integrations(
integrations_to_process = [ integrations_to_process = [
int_or_exc int_or_exc
for int_or_exc in await gather_with_concurrency( for int_or_exc in (
loader.MAX_LOAD_CONCURRENTLY, await loader.async_get_integrations(hass, old_to_resolve)
*( ).values()
loader.async_get_integration(hass, domain)
for domain in old_to_resolve
),
return_exceptions=True,
)
if isinstance(int_or_exc, loader.Integration) if isinstance(int_or_exc, loader.Integration)
] ]
resolve_dependencies_tasks = [ resolve_dependencies_tasks = [
@ -508,11 +509,43 @@ async def _async_set_up_integrations(
_LOGGER.info("Domains to be set up: %s", domains_to_setup) _LOGGER.info("Domains to be set up: %s", domains_to_setup)
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 and cache the result of platform.uname().processor
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),
)
# Initialize recorder
if "recorder" in domains_to_setup:
recorder.async_initialize_recorder(hass)
# Load logging as soon as possible # Load logging as soon as possible
if logging_domains := domains_to_setup & LOGGING_INTEGRATIONS: if logging_domains := domains_to_setup & LOGGING_INTEGRATIONS:
_LOGGER.info("Setting up logging: %s", logging_domains) _LOGGER.info("Setting up logging: %s", logging_domains)
await async_setup_multi_components(hass, logging_domains, config) await async_setup_multi_components(hass, logging_domains, config)
# Setup frontend
if frontend_domains := domains_to_setup & FRONTEND_INTEGRATIONS:
_LOGGER.info("Setting up frontend: %s", frontend_domains)
await async_setup_multi_components(hass, frontend_domains, config)
# Setup recorder
if recorder_domains := domains_to_setup & RECORDER_INTEGRATIONS:
_LOGGER.info("Setting up recorder: %s", recorder_domains)
await async_setup_multi_components(hass, recorder_domains, config)
# Start up debuggers. Start these first in case they want to wait. # Start up debuggers. Start these first in case they want to wait.
if debuggers := domains_to_setup & DEBUGGER_INTEGRATIONS: if debuggers := domains_to_setup & DEBUGGER_INTEGRATIONS:
_LOGGER.debug("Setting up debuggers: %s", debuggers) _LOGGER.debug("Setting up debuggers: %s", debuggers)
@ -522,7 +555,8 @@ async def _async_set_up_integrations(
stage_1_domains: set[str] = set() stage_1_domains: set[str] = set()
# Find all dependencies of any dependency of any stage 1 integration that # Find all dependencies of any dependency of any stage 1 integration that
# we plan on loading and promote them to stage 1 # we plan on loading and promote them to stage 1. This is done only to not
# get misleading log messages
deps_promotion: set[str] = STAGE_1_INTEGRATIONS deps_promotion: set[str] = STAGE_1_INTEGRATIONS
while deps_promotion: while deps_promotion:
old_deps_promotion = deps_promotion old_deps_promotion = deps_promotion
@ -539,24 +573,13 @@ async def _async_set_up_integrations(
deps_promotion.update(dep_itg.all_dependencies) deps_promotion.update(dep_itg.all_dependencies)
stage_2_domains = domains_to_setup - logging_domains - debuggers - stage_1_domains stage_2_domains = (
domains_to_setup
def _cache_uname_processor() -> None: - logging_domains
"""Cache the result of platform.uname().processor in the executor. - frontend_domains
- recorder_domains
Multiple modules call this function at startup which - debuggers
executes a blocking subprocess call. This is a problem for the - stage_1_domains
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 # Start setup

View File

@ -103,7 +103,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
hass.data[DOMAIN] = AbodeSystem(abode, polling) hass.data[DOMAIN] = AbodeSystem(abode, polling)
hass.config_entries.async_setup_platforms(entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
await setup_hass_events(hass) await setup_hass_events(hass)
await hass.async_add_executor_job(setup_hass_services, hass) await hass.async_add_executor_job(setup_hass_services, hass)

View File

@ -2,7 +2,7 @@
"config": { "config": {
"abort": { "abort": {
"reauth_successful": "\u518d\u8a8d\u8a3c\u306b\u6210\u529f\u3057\u307e\u3057\u305f", "reauth_successful": "\u518d\u8a8d\u8a3c\u306b\u6210\u529f\u3057\u307e\u3057\u305f",
"single_instance_allowed": "\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059\u3002\u5358\u4e00\u306e\u8a2d\u5b9a\u3057\u304b\u3067\u304d\u307e\u305b\u3093\u3002" "single_instance_allowed": "\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059\u3002\u8a2d\u5b9a\u3067\u304d\u308b\u306e\u306f1\u3064\u3060\u3051\u3067\u3059\u3002"
}, },
"error": { "error": {
"cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f",

View File

@ -18,7 +18,7 @@
"user": { "user": {
"data": { "data": {
"password": "Palavra-passe", "password": "Palavra-passe",
"username": "Endere\u00e7o de e-mail" "username": "Email"
} }
} }
} }

View File

@ -11,12 +11,14 @@ from aiohttp.client_exceptions import ClientConnectorError
from async_timeout import timeout from async_timeout import timeout
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_API_KEY, Platform from homeassistant.const import CONF_API_KEY, CONF_NAME, Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.device_registry import DeviceEntryType
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import ATTR_FORECAST, CONF_FORECAST, DOMAIN from .const import ATTR_FORECAST, CONF_FORECAST, DOMAIN, MANUFACTURER
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -26,6 +28,7 @@ PLATFORMS = [Platform.SENSOR, Platform.WEATHER]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up AccuWeather as config entry.""" """Set up AccuWeather as config entry."""
api_key: str = entry.data[CONF_API_KEY] api_key: str = entry.data[CONF_API_KEY]
name: str = entry.data[CONF_NAME]
assert entry.unique_id is not None assert entry.unique_id is not None
location_key = entry.unique_id location_key = entry.unique_id
forecast: bool = entry.options.get(CONF_FORECAST, False) forecast: bool = entry.options.get(CONF_FORECAST, False)
@ -35,7 +38,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
websession = async_get_clientsession(hass) websession = async_get_clientsession(hass)
coordinator = AccuWeatherDataUpdateCoordinator( coordinator = AccuWeatherDataUpdateCoordinator(
hass, websession, api_key, location_key, forecast hass, websession, api_key, location_key, forecast, name
) )
await coordinator.async_config_entry_first_refresh() await coordinator.async_config_entry_first_refresh()
@ -43,7 +46,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
hass.config_entries.async_setup_platforms(entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True return True
@ -73,12 +76,27 @@ class AccuWeatherDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
api_key: str, api_key: str,
location_key: str, location_key: str,
forecast: bool, forecast: bool,
name: str,
) -> None: ) -> None:
"""Initialize.""" """Initialize."""
self.location_key = location_key self.location_key = location_key
self.forecast = forecast self.forecast = forecast
self.is_metric = hass.config.units.is_metric self.is_metric = hass.config.units.is_metric
self.accuweather = AccuWeather(api_key, session, location_key=self.location_key) self.accuweather = AccuWeather(api_key, session, location_key=location_key)
self.device_info = DeviceInfo(
entry_type=DeviceEntryType.SERVICE,
identifiers={(DOMAIN, location_key)},
manufacturer=MANUFACTURER,
name=name,
# You don't need to provide specific details for the URL,
# so passing in _ characters is fine if the location key
# is correct
configuration_url=(
"http://accuweather.com/en/"
f"_/_/{location_key}/"
f"weather-forecast/{location_key}/"
),
)
# Enabling the forecast download increases the number of requests per data # Enabling the forecast download increases the number of requests per data
# update, we use 40 minutes for current condition only and 80 minutes for # update, we use 40 minutes for current condition only and 80 minutes for

View File

@ -3,7 +3,6 @@ from __future__ import annotations
from typing import Final from typing import Final
from homeassistant.components.sensor import SensorDeviceClass, SensorStateClass
from homeassistant.components.weather import ( from homeassistant.components.weather import (
ATTR_CONDITION_CLEAR_NIGHT, ATTR_CONDITION_CLEAR_NIGHT,
ATTR_CONDITION_CLOUDY, ATTR_CONDITION_CLOUDY,
@ -20,22 +19,6 @@ from homeassistant.components.weather import (
ATTR_CONDITION_SUNNY, ATTR_CONDITION_SUNNY,
ATTR_CONDITION_WINDY, ATTR_CONDITION_WINDY,
) )
from homeassistant.const import (
CONCENTRATION_PARTS_PER_CUBIC_METER,
LENGTH_FEET,
LENGTH_INCHES,
LENGTH_METERS,
LENGTH_MILLIMETERS,
PERCENTAGE,
SPEED_KILOMETERS_PER_HOUR,
SPEED_MILES_PER_HOUR,
TEMP_CELSIUS,
TEMP_FAHRENHEIT,
TIME_HOURS,
UV_INDEX,
)
from .model import AccuWeatherSensorDescription
API_IMPERIAL: Final = "Imperial" API_IMPERIAL: Final = "Imperial"
API_METRIC: Final = "Metric" API_METRIC: Final = "Metric"
@ -45,7 +28,6 @@ CONF_FORECAST: Final = "forecast"
DOMAIN: Final = "accuweather" DOMAIN: Final = "accuweather"
MANUFACTURER: Final = "AccuWeather, Inc." MANUFACTURER: Final = "AccuWeather, Inc."
MAX_FORECAST_DAYS: Final = 4 MAX_FORECAST_DAYS: Final = 4
NAME: Final = "AccuWeather"
CONDITION_CLASSES: Final[dict[str, list[int]]] = { CONDITION_CLASSES: Final[dict[str, list[int]]] = {
ATTR_CONDITION_CLEAR_NIGHT: [33, 34, 37], ATTR_CONDITION_CLEAR_NIGHT: [33, 34, 37],
@ -63,264 +45,3 @@ CONDITION_CLASSES: Final[dict[str, list[int]]] = {
ATTR_CONDITION_SUNNY: [1, 2, 5], ATTR_CONDITION_SUNNY: [1, 2, 5],
ATTR_CONDITION_WINDY: [32], ATTR_CONDITION_WINDY: [32],
} }
FORECAST_SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
AccuWeatherSensorDescription(
key="CloudCoverDay",
icon="mdi:weather-cloudy",
name="Cloud Cover Day",
unit_metric=PERCENTAGE,
unit_imperial=PERCENTAGE,
entity_registry_enabled_default=False,
),
AccuWeatherSensorDescription(
key="CloudCoverNight",
icon="mdi:weather-cloudy",
name="Cloud Cover Night",
unit_metric=PERCENTAGE,
unit_imperial=PERCENTAGE,
entity_registry_enabled_default=False,
),
AccuWeatherSensorDescription(
key="Grass",
icon="mdi:grass",
name="Grass Pollen",
unit_metric=CONCENTRATION_PARTS_PER_CUBIC_METER,
unit_imperial=CONCENTRATION_PARTS_PER_CUBIC_METER,
entity_registry_enabled_default=False,
),
AccuWeatherSensorDescription(
key="HoursOfSun",
icon="mdi:weather-partly-cloudy",
name="Hours Of Sun",
unit_metric=TIME_HOURS,
unit_imperial=TIME_HOURS,
),
AccuWeatherSensorDescription(
key="Mold",
icon="mdi:blur",
name="Mold Pollen",
unit_metric=CONCENTRATION_PARTS_PER_CUBIC_METER,
unit_imperial=CONCENTRATION_PARTS_PER_CUBIC_METER,
entity_registry_enabled_default=False,
),
AccuWeatherSensorDescription(
key="Ozone",
icon="mdi:vector-triangle",
name="Ozone",
unit_metric=None,
unit_imperial=None,
entity_registry_enabled_default=False,
),
AccuWeatherSensorDescription(
key="Ragweed",
icon="mdi:sprout",
name="Ragweed Pollen",
unit_metric=CONCENTRATION_PARTS_PER_CUBIC_METER,
unit_imperial=CONCENTRATION_PARTS_PER_CUBIC_METER,
entity_registry_enabled_default=False,
),
AccuWeatherSensorDescription(
key="RealFeelTemperatureMax",
device_class=SensorDeviceClass.TEMPERATURE,
name="RealFeel Temperature Max",
unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT,
),
AccuWeatherSensorDescription(
key="RealFeelTemperatureMin",
device_class=SensorDeviceClass.TEMPERATURE,
name="RealFeel Temperature Min",
unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT,
),
AccuWeatherSensorDescription(
key="RealFeelTemperatureShadeMax",
device_class=SensorDeviceClass.TEMPERATURE,
name="RealFeel Temperature Shade Max",
unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT,
entity_registry_enabled_default=False,
),
AccuWeatherSensorDescription(
key="RealFeelTemperatureShadeMin",
device_class=SensorDeviceClass.TEMPERATURE,
name="RealFeel Temperature Shade Min",
unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT,
entity_registry_enabled_default=False,
),
AccuWeatherSensorDescription(
key="ThunderstormProbabilityDay",
icon="mdi:weather-lightning",
name="Thunderstorm Probability Day",
unit_metric=PERCENTAGE,
unit_imperial=PERCENTAGE,
),
AccuWeatherSensorDescription(
key="ThunderstormProbabilityNight",
icon="mdi:weather-lightning",
name="Thunderstorm Probability Night",
unit_metric=PERCENTAGE,
unit_imperial=PERCENTAGE,
),
AccuWeatherSensorDescription(
key="Tree",
icon="mdi:tree-outline",
name="Tree Pollen",
unit_metric=CONCENTRATION_PARTS_PER_CUBIC_METER,
unit_imperial=CONCENTRATION_PARTS_PER_CUBIC_METER,
entity_registry_enabled_default=False,
),
AccuWeatherSensorDescription(
key="UVIndex",
icon="mdi:weather-sunny",
name="UV Index",
unit_metric=UV_INDEX,
unit_imperial=UV_INDEX,
),
AccuWeatherSensorDescription(
key="WindGustDay",
icon="mdi:weather-windy",
name="Wind Gust Day",
unit_metric=SPEED_KILOMETERS_PER_HOUR,
unit_imperial=SPEED_MILES_PER_HOUR,
entity_registry_enabled_default=False,
),
AccuWeatherSensorDescription(
key="WindGustNight",
icon="mdi:weather-windy",
name="Wind Gust Night",
unit_metric=SPEED_KILOMETERS_PER_HOUR,
unit_imperial=SPEED_MILES_PER_HOUR,
entity_registry_enabled_default=False,
),
AccuWeatherSensorDescription(
key="WindDay",
icon="mdi:weather-windy",
name="Wind Day",
unit_metric=SPEED_KILOMETERS_PER_HOUR,
unit_imperial=SPEED_MILES_PER_HOUR,
),
AccuWeatherSensorDescription(
key="WindNight",
icon="mdi:weather-windy",
name="Wind Night",
unit_metric=SPEED_KILOMETERS_PER_HOUR,
unit_imperial=SPEED_MILES_PER_HOUR,
),
)
SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
AccuWeatherSensorDescription(
key="ApparentTemperature",
device_class=SensorDeviceClass.TEMPERATURE,
name="Apparent Temperature",
unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT,
entity_registry_enabled_default=False,
state_class=SensorStateClass.MEASUREMENT,
),
AccuWeatherSensorDescription(
key="Ceiling",
icon="mdi:weather-fog",
name="Cloud Ceiling",
unit_metric=LENGTH_METERS,
unit_imperial=LENGTH_FEET,
state_class=SensorStateClass.MEASUREMENT,
),
AccuWeatherSensorDescription(
key="CloudCover",
icon="mdi:weather-cloudy",
name="Cloud Cover",
unit_metric=PERCENTAGE,
unit_imperial=PERCENTAGE,
entity_registry_enabled_default=False,
state_class=SensorStateClass.MEASUREMENT,
),
AccuWeatherSensorDescription(
key="DewPoint",
device_class=SensorDeviceClass.TEMPERATURE,
name="Dew Point",
unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT,
entity_registry_enabled_default=False,
state_class=SensorStateClass.MEASUREMENT,
),
AccuWeatherSensorDescription(
key="RealFeelTemperature",
device_class=SensorDeviceClass.TEMPERATURE,
name="RealFeel Temperature",
unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT,
state_class=SensorStateClass.MEASUREMENT,
),
AccuWeatherSensorDescription(
key="RealFeelTemperatureShade",
device_class=SensorDeviceClass.TEMPERATURE,
name="RealFeel Temperature Shade",
unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT,
entity_registry_enabled_default=False,
state_class=SensorStateClass.MEASUREMENT,
),
AccuWeatherSensorDescription(
key="Precipitation",
icon="mdi:weather-rainy",
name="Precipitation",
unit_metric=LENGTH_MILLIMETERS,
unit_imperial=LENGTH_INCHES,
state_class=SensorStateClass.MEASUREMENT,
),
AccuWeatherSensorDescription(
key="PressureTendency",
device_class="accuweather__pressure_tendency",
icon="mdi:gauge",
name="Pressure Tendency",
unit_metric=None,
unit_imperial=None,
),
AccuWeatherSensorDescription(
key="UVIndex",
icon="mdi:weather-sunny",
name="UV Index",
unit_metric=UV_INDEX,
unit_imperial=UV_INDEX,
state_class=SensorStateClass.MEASUREMENT,
),
AccuWeatherSensorDescription(
key="WetBulbTemperature",
device_class=SensorDeviceClass.TEMPERATURE,
name="Wet Bulb Temperature",
unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT,
entity_registry_enabled_default=False,
state_class=SensorStateClass.MEASUREMENT,
),
AccuWeatherSensorDescription(
key="WindChillTemperature",
device_class=SensorDeviceClass.TEMPERATURE,
name="Wind Chill Temperature",
unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT,
entity_registry_enabled_default=False,
state_class=SensorStateClass.MEASUREMENT,
),
AccuWeatherSensorDescription(
key="Wind",
icon="mdi:weather-windy",
name="Wind",
unit_metric=SPEED_KILOMETERS_PER_HOUR,
unit_imperial=SPEED_MILES_PER_HOUR,
state_class=SensorStateClass.MEASUREMENT,
),
AccuWeatherSensorDescription(
key="WindGust",
icon="mdi:weather-windy",
name="Wind Gust",
unit_metric=SPEED_KILOMETERS_PER_HOUR,
unit_imperial=SPEED_MILES_PER_HOUR,
entity_registry_enabled_default=False,
state_class=SensorStateClass.MEASUREMENT,
),
)

View File

@ -1,14 +0,0 @@
"""Type definitions for AccuWeather integration."""
from __future__ import annotations
from dataclasses import dataclass
from homeassistant.components.sensor import SensorEntityDescription
@dataclass
class AccuWeatherSensorDescription(SensorEntityDescription):
"""Class describing AccuWeather sensor entities."""
unit_metric: str | None = None
unit_imperial: str | None = None

View File

@ -1,14 +1,31 @@
"""Support for the AccuWeather service.""" """Support for the AccuWeather service."""
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass
from typing import Any, cast from typing import Any, cast
from homeassistant.components.sensor import SensorDeviceClass, SensorEntity from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_NAME from homeassistant.const import (
CONCENTRATION_PARTS_PER_CUBIC_METER,
LENGTH_FEET,
LENGTH_INCHES,
LENGTH_METERS,
LENGTH_MILLIMETERS,
PERCENTAGE,
SPEED_KILOMETERS_PER_HOUR,
SPEED_MILES_PER_HOUR,
TEMP_CELSIUS,
TEMP_FAHRENHEIT,
TIME_HOURS,
UV_INDEX,
)
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.device_registry import DeviceEntryType
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType from homeassistant.helpers.typing import StateType
from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.helpers.update_coordinator import CoordinatorEntity
@ -20,28 +37,292 @@ from .const import (
ATTR_FORECAST, ATTR_FORECAST,
ATTRIBUTION, ATTRIBUTION,
DOMAIN, DOMAIN,
FORECAST_SENSOR_TYPES,
MANUFACTURER,
MAX_FORECAST_DAYS, MAX_FORECAST_DAYS,
NAME,
SENSOR_TYPES,
) )
from .model import AccuWeatherSensorDescription
PARALLEL_UPDATES = 1 PARALLEL_UPDATES = 1
@dataclass
class AccuWeatherSensorDescription(SensorEntityDescription):
"""Class describing AccuWeather sensor entities."""
unit_metric: str | None = None
unit_imperial: str | None = None
FORECAST_SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
AccuWeatherSensorDescription(
key="CloudCoverDay",
icon="mdi:weather-cloudy",
name="Cloud cover day",
unit_metric=PERCENTAGE,
unit_imperial=PERCENTAGE,
entity_registry_enabled_default=False,
),
AccuWeatherSensorDescription(
key="CloudCoverNight",
icon="mdi:weather-cloudy",
name="Cloud cover night",
unit_metric=PERCENTAGE,
unit_imperial=PERCENTAGE,
entity_registry_enabled_default=False,
),
AccuWeatherSensorDescription(
key="Grass",
icon="mdi:grass",
name="Grass pollen",
unit_metric=CONCENTRATION_PARTS_PER_CUBIC_METER,
unit_imperial=CONCENTRATION_PARTS_PER_CUBIC_METER,
entity_registry_enabled_default=False,
),
AccuWeatherSensorDescription(
key="HoursOfSun",
icon="mdi:weather-partly-cloudy",
name="Hours of sun",
unit_metric=TIME_HOURS,
unit_imperial=TIME_HOURS,
),
AccuWeatherSensorDescription(
key="Mold",
icon="mdi:blur",
name="Mold pollen",
unit_metric=CONCENTRATION_PARTS_PER_CUBIC_METER,
unit_imperial=CONCENTRATION_PARTS_PER_CUBIC_METER,
entity_registry_enabled_default=False,
),
AccuWeatherSensorDescription(
key="Ozone",
icon="mdi:vector-triangle",
name="Ozone",
unit_metric=None,
unit_imperial=None,
entity_registry_enabled_default=False,
),
AccuWeatherSensorDescription(
key="Ragweed",
icon="mdi:sprout",
name="Ragweed pollen",
unit_metric=CONCENTRATION_PARTS_PER_CUBIC_METER,
unit_imperial=CONCENTRATION_PARTS_PER_CUBIC_METER,
entity_registry_enabled_default=False,
),
AccuWeatherSensorDescription(
key="RealFeelTemperatureMax",
device_class=SensorDeviceClass.TEMPERATURE,
name="RealFeel temperature max",
unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT,
),
AccuWeatherSensorDescription(
key="RealFeelTemperatureMin",
device_class=SensorDeviceClass.TEMPERATURE,
name="RealFeel temperature min",
unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT,
),
AccuWeatherSensorDescription(
key="RealFeelTemperatureShadeMax",
device_class=SensorDeviceClass.TEMPERATURE,
name="RealFeel temperature shade max",
unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT,
entity_registry_enabled_default=False,
),
AccuWeatherSensorDescription(
key="RealFeelTemperatureShadeMin",
device_class=SensorDeviceClass.TEMPERATURE,
name="RealFeel temperature shade min",
unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT,
entity_registry_enabled_default=False,
),
AccuWeatherSensorDescription(
key="ThunderstormProbabilityDay",
icon="mdi:weather-lightning",
name="Thunderstorm probability day",
unit_metric=PERCENTAGE,
unit_imperial=PERCENTAGE,
),
AccuWeatherSensorDescription(
key="ThunderstormProbabilityNight",
icon="mdi:weather-lightning",
name="Thunderstorm probability night",
unit_metric=PERCENTAGE,
unit_imperial=PERCENTAGE,
),
AccuWeatherSensorDescription(
key="Tree",
icon="mdi:tree-outline",
name="Tree pollen",
unit_metric=CONCENTRATION_PARTS_PER_CUBIC_METER,
unit_imperial=CONCENTRATION_PARTS_PER_CUBIC_METER,
entity_registry_enabled_default=False,
),
AccuWeatherSensorDescription(
key="UVIndex",
icon="mdi:weather-sunny",
name="UV index",
unit_metric=UV_INDEX,
unit_imperial=UV_INDEX,
),
AccuWeatherSensorDescription(
key="WindGustDay",
icon="mdi:weather-windy",
name="Wind gust day",
unit_metric=SPEED_KILOMETERS_PER_HOUR,
unit_imperial=SPEED_MILES_PER_HOUR,
entity_registry_enabled_default=False,
),
AccuWeatherSensorDescription(
key="WindGustNight",
icon="mdi:weather-windy",
name="Wind gust night",
unit_metric=SPEED_KILOMETERS_PER_HOUR,
unit_imperial=SPEED_MILES_PER_HOUR,
entity_registry_enabled_default=False,
),
AccuWeatherSensorDescription(
key="WindDay",
icon="mdi:weather-windy",
name="Wind day",
unit_metric=SPEED_KILOMETERS_PER_HOUR,
unit_imperial=SPEED_MILES_PER_HOUR,
),
AccuWeatherSensorDescription(
key="WindNight",
icon="mdi:weather-windy",
name="Wind night",
unit_metric=SPEED_KILOMETERS_PER_HOUR,
unit_imperial=SPEED_MILES_PER_HOUR,
),
)
SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
AccuWeatherSensorDescription(
key="ApparentTemperature",
device_class=SensorDeviceClass.TEMPERATURE,
name="Apparent temperature",
unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT,
entity_registry_enabled_default=False,
state_class=SensorStateClass.MEASUREMENT,
),
AccuWeatherSensorDescription(
key="Ceiling",
icon="mdi:weather-fog",
name="Cloud ceiling",
unit_metric=LENGTH_METERS,
unit_imperial=LENGTH_FEET,
state_class=SensorStateClass.MEASUREMENT,
),
AccuWeatherSensorDescription(
key="CloudCover",
icon="mdi:weather-cloudy",
name="Cloud cover",
unit_metric=PERCENTAGE,
unit_imperial=PERCENTAGE,
entity_registry_enabled_default=False,
state_class=SensorStateClass.MEASUREMENT,
),
AccuWeatherSensorDescription(
key="DewPoint",
device_class=SensorDeviceClass.TEMPERATURE,
name="Dew point",
unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT,
entity_registry_enabled_default=False,
state_class=SensorStateClass.MEASUREMENT,
),
AccuWeatherSensorDescription(
key="RealFeelTemperature",
device_class=SensorDeviceClass.TEMPERATURE,
name="RealFeel temperature",
unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT,
state_class=SensorStateClass.MEASUREMENT,
),
AccuWeatherSensorDescription(
key="RealFeelTemperatureShade",
device_class=SensorDeviceClass.TEMPERATURE,
name="RealFeel temperature shade",
unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT,
entity_registry_enabled_default=False,
state_class=SensorStateClass.MEASUREMENT,
),
AccuWeatherSensorDescription(
key="Precipitation",
icon="mdi:weather-rainy",
name="Precipitation",
unit_metric=LENGTH_MILLIMETERS,
unit_imperial=LENGTH_INCHES,
state_class=SensorStateClass.MEASUREMENT,
),
AccuWeatherSensorDescription(
key="PressureTendency",
device_class="accuweather__pressure_tendency",
icon="mdi:gauge",
name="Pressure tendency",
unit_metric=None,
unit_imperial=None,
),
AccuWeatherSensorDescription(
key="UVIndex",
icon="mdi:weather-sunny",
name="UV index",
unit_metric=UV_INDEX,
unit_imperial=UV_INDEX,
state_class=SensorStateClass.MEASUREMENT,
),
AccuWeatherSensorDescription(
key="WetBulbTemperature",
device_class=SensorDeviceClass.TEMPERATURE,
name="Wet bulb temperature",
unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT,
entity_registry_enabled_default=False,
state_class=SensorStateClass.MEASUREMENT,
),
AccuWeatherSensorDescription(
key="WindChillTemperature",
device_class=SensorDeviceClass.TEMPERATURE,
name="Wind chill temperature",
unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT,
entity_registry_enabled_default=False,
state_class=SensorStateClass.MEASUREMENT,
),
AccuWeatherSensorDescription(
key="Wind",
icon="mdi:weather-windy",
name="Wind",
unit_metric=SPEED_KILOMETERS_PER_HOUR,
unit_imperial=SPEED_MILES_PER_HOUR,
state_class=SensorStateClass.MEASUREMENT,
),
AccuWeatherSensorDescription(
key="WindGust",
icon="mdi:weather-windy",
name="Wind gust",
unit_metric=SPEED_KILOMETERS_PER_HOUR,
unit_imperial=SPEED_MILES_PER_HOUR,
entity_registry_enabled_default=False,
state_class=SensorStateClass.MEASUREMENT,
),
)
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None: ) -> None:
"""Add AccuWeather entities from a config_entry.""" """Add AccuWeather entities from a config_entry."""
name: str = entry.data[CONF_NAME]
coordinator: AccuWeatherDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] coordinator: AccuWeatherDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
sensors: list[AccuWeatherSensor] = [] sensors: list[AccuWeatherSensor] = []
for description in SENSOR_TYPES: for description in SENSOR_TYPES:
sensors.append(AccuWeatherSensor(name, coordinator, description)) sensors.append(AccuWeatherSensor(coordinator, description))
if coordinator.forecast: if coordinator.forecast:
for description in FORECAST_SENSOR_TYPES: for description in FORECAST_SENSOR_TYPES:
@ -50,9 +331,7 @@ async def async_setup_entry(
# locations. # locations.
if description.key in coordinator.data[ATTR_FORECAST][0]: if description.key in coordinator.data[ATTR_FORECAST][0]:
sensors.append( sensors.append(
AccuWeatherSensor( AccuWeatherSensor(coordinator, description, forecast_day=day)
name, coordinator, description, forecast_day=day
)
) )
async_add_entities(sensors) async_add_entities(sensors)
@ -64,11 +343,11 @@ class AccuWeatherSensor(
"""Define an AccuWeather entity.""" """Define an AccuWeather entity."""
_attr_attribution = ATTRIBUTION _attr_attribution = ATTRIBUTION
_attr_has_entity_name = True
entity_description: AccuWeatherSensorDescription entity_description: AccuWeatherSensorDescription
def __init__( def __init__(
self, self,
name: str,
coordinator: AccuWeatherDataUpdateCoordinator, coordinator: AccuWeatherDataUpdateCoordinator,
description: AccuWeatherSensorDescription, description: AccuWeatherSensorDescription,
forecast_day: int | None = None, forecast_day: int | None = None,
@ -81,12 +360,11 @@ class AccuWeatherSensor(
) )
self._attrs: dict[str, Any] = {} self._attrs: dict[str, Any] = {}
if forecast_day is not None: if forecast_day is not None:
self._attr_name = f"{name} {description.name} {forecast_day}d" self._attr_name = f"{description.name} {forecast_day}d"
self._attr_unique_id = ( self._attr_unique_id = (
f"{coordinator.location_key}-{description.key}-{forecast_day}".lower() f"{coordinator.location_key}-{description.key}-{forecast_day}".lower()
) )
else: else:
self._attr_name = f"{name} {description.name}"
self._attr_unique_id = ( self._attr_unique_id = (
f"{coordinator.location_key}-{description.key}".lower() f"{coordinator.location_key}-{description.key}".lower()
) )
@ -96,12 +374,7 @@ class AccuWeatherSensor(
else: else:
self._unit_system = API_IMPERIAL self._unit_system = API_IMPERIAL
self._attr_native_unit_of_measurement = description.unit_imperial self._attr_native_unit_of_measurement = description.unit_imperial
self._attr_device_info = DeviceInfo( self._attr_device_info = coordinator.device_info
entry_type=DeviceEntryType.SERVICE,
identifiers={(DOMAIN, coordinator.location_key)},
manufacturer=MANUFACTURER,
name=NAME,
)
self.forecast_day = forecast_day self.forecast_day = forecast_day
@property @property

View File

@ -1,10 +1,10 @@
{ {
"config": { "config": {
"abort": { "abort": {
"single_instance_allowed": "\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059\u3002\u5358\u4e00\u306e\u8a2d\u5b9a\u3057\u304b\u3067\u304d\u307e\u305b\u3093\u3002" "single_instance_allowed": "\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059\u3002\u8a2d\u5b9a\u3067\u304d\u308b\u306e\u306f1\u3064\u3060\u3051\u3067\u3059\u3002"
}, },
"create_entry": { "create_entry": {
"default": "\u4e00\u90e8\u306e\u30bb\u30f3\u30b5\u30fc\u306f\u3001\u30c7\u30d5\u30a9\u30eb\u30c8\u3067\u306f\u6709\u52b9\u306b\u306a\u3063\u3066\u3044\u307e\u305b\u3093\u3002\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u306e\u69cb\u6210\u5f8c\u3001\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3\u30ec\u30b8\u30b9\u30c8\u30ea\u3067\u305d\u308c\u3089\u3092\u6709\u52b9\u306b\u3067\u304d\u307e\u3059\u3002\n\u5929\u6c17\u4e88\u5831\u306f\u30c7\u30d5\u30a9\u30eb\u30c8\u3067\u306f\u6709\u52b9\u306b\u306a\u3063\u3066\u3044\u307e\u305b\u3093\u3002\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u306e\u30aa\u30d7\u30b7\u30e7\u30f3\u3067\u6709\u52b9\u306b\u3067\u304d\u307e\u3059\u3002" "default": "\u4e00\u90e8\u306e\u30bb\u30f3\u30b5\u30fc\u306f\u3001\u30c7\u30d5\u30a9\u30eb\u30c8\u3067\u306f\u6709\u52b9\u306b\u306a\u3063\u3066\u3044\u307e\u305b\u3093\u3002\u7d71\u5408\u306e\u69cb\u6210\u5f8c\u3001\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3\u30ec\u30b8\u30b9\u30c8\u30ea\u3067\u305d\u308c\u3089\u3092\u6709\u52b9\u306b\u3067\u304d\u307e\u3059\u3002\n\u5929\u6c17\u4e88\u5831\u306f\u30c7\u30d5\u30a9\u30eb\u30c8\u3067\u306f\u6709\u52b9\u306b\u306a\u3063\u3066\u3044\u307e\u305b\u3093\u3002\u7d71\u5408\u306e\u30aa\u30d7\u30b7\u30e7\u30f3\u3067\u6709\u52b9\u306b\u3067\u304d\u307e\u3059\u3002"
}, },
"error": { "error": {
"cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f",

View File

@ -34,7 +34,7 @@
}, },
"system_health": { "system_health": {
"info": { "info": {
"can_reach_server": "Dost\u0119p do serwera AccuWeather", "can_reach_server": "Dost\u0119p do serwera",
"remaining_requests": "Pozosta\u0142o dozwolonych \u017c\u0105da\u0144" "remaining_requests": "Pozosta\u0142o dozwolonych \u017c\u0105da\u0144"
} }
} }

View File

@ -3,6 +3,9 @@
"abort": { "abort": {
"single_instance_allowed": "J\u00e1 configurado. Apenas uma \u00fanica configura\u00e7\u00e3o \u00e9 poss\u00edvel." "single_instance_allowed": "J\u00e1 configurado. Apenas uma \u00fanica configura\u00e7\u00e3o \u00e9 poss\u00edvel."
}, },
"create_entry": {
"default": "Alguns sensores n\u00e3o s\u00e3o ativados por defeito. Podem ser ativados no registo da entidade ap\u00f3s a configura\u00e7\u00e3o da integra\u00e7\u00e3o.\nA previs\u00e3o do tempo n\u00e3o est\u00e1 ativada por defeito. Pode ativ\u00e1-la nas op\u00e7\u00f5es de integra\u00e7\u00e3o."
},
"error": { "error": {
"cannot_connect": "Falha na liga\u00e7\u00e3o", "cannot_connect": "Falha na liga\u00e7\u00e3o",
"invalid_api_key": "Chave de API inv\u00e1lida" "invalid_api_key": "Chave de API inv\u00e1lida"

View File

@ -18,7 +18,6 @@ from homeassistant.components.weather import (
) )
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
CONF_NAME,
LENGTH_INCHES, LENGTH_INCHES,
LENGTH_KILOMETERS, LENGTH_KILOMETERS,
LENGTH_MILES, LENGTH_MILES,
@ -31,8 +30,6 @@ from homeassistant.const import (
TEMP_FAHRENHEIT, TEMP_FAHRENHEIT,
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceEntryType
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.util.dt import utc_from_timestamp from homeassistant.util.dt import utc_from_timestamp
@ -45,8 +42,6 @@ from .const import (
ATTRIBUTION, ATTRIBUTION,
CONDITION_CLASSES, CONDITION_CLASSES,
DOMAIN, DOMAIN,
MANUFACTURER,
NAME,
) )
PARALLEL_UPDATES = 1 PARALLEL_UPDATES = 1
@ -56,11 +51,10 @@ async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None: ) -> None:
"""Add a AccuWeather weather entity from a config_entry.""" """Add a AccuWeather weather entity from a config_entry."""
name: str = entry.data[CONF_NAME]
coordinator: AccuWeatherDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] coordinator: AccuWeatherDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
async_add_entities([AccuWeatherEntity(name, coordinator)]) async_add_entities([AccuWeatherEntity(coordinator)])
class AccuWeatherEntity( class AccuWeatherEntity(
@ -68,9 +62,9 @@ class AccuWeatherEntity(
): ):
"""Define an AccuWeather entity.""" """Define an AccuWeather entity."""
def __init__( _attr_has_entity_name = True
self, name: str, coordinator: AccuWeatherDataUpdateCoordinator
) -> None: def __init__(self, coordinator: AccuWeatherDataUpdateCoordinator) -> None:
"""Initialize.""" """Initialize."""
super().__init__(coordinator) super().__init__(coordinator)
# Coordinator data is used also for sensors which don't have units automatically # Coordinator data is used also for sensors which don't have units automatically
@ -90,21 +84,9 @@ class AccuWeatherEntity(
self._attr_native_temperature_unit = TEMP_FAHRENHEIT self._attr_native_temperature_unit = TEMP_FAHRENHEIT
self._attr_native_visibility_unit = LENGTH_MILES self._attr_native_visibility_unit = LENGTH_MILES
self._attr_native_wind_speed_unit = SPEED_MILES_PER_HOUR self._attr_native_wind_speed_unit = SPEED_MILES_PER_HOUR
self._attr_name = name
self._attr_unique_id = coordinator.location_key self._attr_unique_id = coordinator.location_key
self._attr_attribution = ATTRIBUTION self._attr_attribution = ATTRIBUTION
self._attr_device_info = DeviceInfo( self._attr_device_info = coordinator.device_info
entry_type=DeviceEntryType.SERVICE,
identifiers={(DOMAIN, coordinator.location_key)},
manufacturer=MANUFACTURER,
name=NAME,
# You don't need to provide specific details for the URL,
# so passing in _ characters is fine if the location key
# is correct
configuration_url="http://accuweather.com/en/"
f"_/_/{coordinator.location_key}/"
f"weather-forecast/{coordinator.location_key}/",
)
@property @property
def condition(self) -> str | None: def condition(self) -> str | None:

View File

@ -19,7 +19,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
return False return False
hass.data.setdefault(DOMAIN, {})[config_entry.entry_id] = hub hass.data.setdefault(DOMAIN, {})[config_entry.entry_id] = hub
hass.config_entries.async_setup_platforms(config_entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
return True return True

View File

@ -10,7 +10,7 @@ PLATFORMS = [Platform.CLIMATE]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Adax from a config entry.""" """Set up Adax from a config entry."""
hass.config_entries.async_setup_platforms(entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True return True

View File

@ -0,0 +1,17 @@
{
"config": {
"abort": {
"invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida"
},
"error": {
"cannot_connect": "Falha na liga\u00e7\u00e3o"
},
"step": {
"cloud": {
"data": {
"password": "Palavra-passe"
}
}
}
}
}

View File

@ -1,12 +1,10 @@
"""Support for AdGuard Home.""" """Support for AdGuard Home."""
from __future__ import annotations from __future__ import annotations
import logging from adguardhome import AdGuardHome, AdGuardHomeConnectionError
from adguardhome import AdGuardHome, AdGuardHomeConnectionError, AdGuardHomeError
import voluptuous as vol import voluptuous as vol
from homeassistant.config_entries import SOURCE_HASSIO, ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
CONF_HOST, CONF_HOST,
CONF_NAME, CONF_NAME,
@ -22,13 +20,10 @@ from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import config_validation as cv from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.device_registry import DeviceEntryType
from homeassistant.helpers.entity import DeviceInfo, Entity
from .const import ( from .const import (
CONF_FORCE, CONF_FORCE,
DATA_ADGUARD_CLIENT, DATA_ADGUARD_CLIENT,
DATA_ADGUARD_VERSION,
DOMAIN, DOMAIN,
SERVICE_ADD_URL, SERVICE_ADD_URL,
SERVICE_DISABLE_URL, SERVICE_DISABLE_URL,
@ -37,8 +32,6 @@ from .const import (
SERVICE_REMOVE_URL, SERVICE_REMOVE_URL,
) )
_LOGGER = logging.getLogger(__name__)
SERVICE_URL_SCHEMA = vol.Schema({vol.Required(CONF_URL): cv.url}) SERVICE_URL_SCHEMA = vol.Schema({vol.Required(CONF_URL): cv.url})
SERVICE_ADD_URL_SCHEMA = vol.Schema( SERVICE_ADD_URL_SCHEMA = vol.Schema(
{vol.Required(CONF_NAME): cv.string, vol.Required(CONF_URL): cv.url} {vol.Required(CONF_NAME): cv.string, vol.Required(CONF_URL): cv.url}
@ -70,7 +63,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
except AdGuardHomeConnectionError as exception: except AdGuardHomeConnectionError as exception:
raise ConfigEntryNotReady from exception raise ConfigEntryNotReady from exception
hass.config_entries.async_setup_platforms(entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
async def add_url(call: ServiceCall) -> None: async def add_url(call: ServiceCall) -> None:
"""Service call to add a new filter subscription to AdGuard Home.""" """Service call to add a new filter subscription to AdGuard Home."""
@ -127,91 +120,3 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
del hass.data[DOMAIN] del hass.data[DOMAIN]
return unload_ok return unload_ok
class AdGuardHomeEntity(Entity):
"""Defines a base AdGuard Home entity."""
def __init__(
self,
adguard: AdGuardHome,
entry: ConfigEntry,
name: str,
icon: str,
enabled_default: bool = True,
) -> None:
"""Initialize the AdGuard Home entity."""
self._available = True
self._enabled_default = enabled_default
self._icon = icon
self._name = name
self._entry = entry
self.adguard = adguard
@property
def name(self) -> str:
"""Return the name of the entity."""
return self._name
@property
def icon(self) -> str:
"""Return the mdi icon of the entity."""
return self._icon
@property
def entity_registry_enabled_default(self) -> bool:
"""Return if the entity should be enabled when first added to the entity registry."""
return self._enabled_default
@property
def available(self) -> bool:
"""Return True if entity is available."""
return self._available
async def async_update(self) -> None:
"""Update AdGuard Home entity."""
if not self.enabled:
return
try:
await self._adguard_update()
self._available = True
except AdGuardHomeError:
if self._available:
_LOGGER.debug(
"An error occurred while updating AdGuard Home sensor",
exc_info=True,
)
self._available = False
async def _adguard_update(self) -> None:
"""Update AdGuard Home entity."""
raise NotImplementedError()
class AdGuardHomeDeviceEntity(AdGuardHomeEntity):
"""Defines a AdGuard Home device entity."""
@property
def device_info(self) -> DeviceInfo:
"""Return device information about this AdGuard Home instance."""
if self._entry.source == SOURCE_HASSIO:
config_url = "homeassistant://hassio/ingress/a0d7b954_adguard"
else:
if self.adguard.tls:
config_url = f"https://{self.adguard.host}:{self.adguard.port}"
else:
config_url = f"http://{self.adguard.host}:{self.adguard.port}"
return DeviceInfo(
entry_type=DeviceEntryType.SERVICE,
identifiers={
(DOMAIN, self.adguard.host, self.adguard.port, self.adguard.base_path) # type: ignore[arg-type]
},
manufacturer="AdGuard Team",
name="AdGuard Home",
sw_version=self.hass.data[DOMAIN][self._entry.entry_id].get(
DATA_ADGUARD_VERSION
),
configuration_url=config_url,
)

View File

@ -1,7 +1,10 @@
"""Constants for the AdGuard Home integration.""" """Constants for the AdGuard Home integration."""
import logging
DOMAIN = "adguard" DOMAIN = "adguard"
LOGGER = logging.getLogger(__package__)
DATA_ADGUARD_CLIENT = "adguard_client" DATA_ADGUARD_CLIENT = "adguard_client"
DATA_ADGUARD_VERSION = "adguard_version" DATA_ADGUARD_VERSION = "adguard_version"

View File

@ -0,0 +1,69 @@
"""AdGuard Home base entity."""
from __future__ import annotations
from adguardhome import AdGuardHome, AdGuardHomeError
from homeassistant.config_entries import SOURCE_HASSIO, ConfigEntry
from homeassistant.helpers.device_registry import DeviceEntryType
from homeassistant.helpers.entity import DeviceInfo, Entity
from .const import DATA_ADGUARD_VERSION, DOMAIN, LOGGER
class AdGuardHomeEntity(Entity):
"""Defines a base AdGuard Home entity."""
_attr_has_entity_name = True
_attr_available = True
def __init__(
self,
adguard: AdGuardHome,
entry: ConfigEntry,
) -> None:
"""Initialize the AdGuard Home entity."""
self._entry = entry
self.adguard = adguard
async def async_update(self) -> None:
"""Update AdGuard Home entity."""
if not self.enabled:
return
try:
await self._adguard_update()
self._attr_available = True
except AdGuardHomeError:
if self._attr_available:
LOGGER.debug(
"An error occurred while updating AdGuard Home sensor",
exc_info=True,
)
self._attr_available = False
async def _adguard_update(self) -> None:
"""Update AdGuard Home entity."""
raise NotImplementedError()
@property
def device_info(self) -> DeviceInfo:
"""Return device information about this AdGuard Home instance."""
if self._entry.source == SOURCE_HASSIO:
config_url = "homeassistant://hassio/ingress/a0d7b954_adguard"
elif self.adguard.tls:
config_url = f"https://{self.adguard.host}:{self.adguard.port}"
else:
config_url = f"http://{self.adguard.host}:{self.adguard.port}"
return DeviceInfo(
entry_type=DeviceEntryType.SERVICE,
identifiers={
(DOMAIN, self.adguard.host, self.adguard.port, self.adguard.base_path) # type: ignore[arg-type]
},
manufacturer="AdGuard Team",
name="AdGuard Home",
sw_version=self.hass.data[DOMAIN][self._entry.entry_id].get(
DATA_ADGUARD_VERSION
),
configuration_url=config_url,
)

View File

@ -1,24 +1,102 @@
"""Support for AdGuard Home sensors.""" """Support for AdGuard Home sensors."""
from __future__ import annotations from __future__ import annotations
from collections.abc import Callable, Coroutine
from dataclasses import dataclass
from datetime import timedelta from datetime import timedelta
from typing import Any
from adguardhome import AdGuardHome, AdGuardHomeConnectionError from adguardhome import AdGuardHome, AdGuardHomeConnectionError
from homeassistant.components.sensor import SensorEntity from homeassistant.components.sensor import SensorEntity, SensorEntityDescription
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import PERCENTAGE, TIME_MILLISECONDS from homeassistant.const import PERCENTAGE, TIME_MILLISECONDS
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import PlatformNotReady from homeassistant.exceptions import PlatformNotReady
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import AdGuardHomeDeviceEntity
from .const import DATA_ADGUARD_CLIENT, DATA_ADGUARD_VERSION, DOMAIN from .const import DATA_ADGUARD_CLIENT, DATA_ADGUARD_VERSION, DOMAIN
from .entity import AdGuardHomeEntity
SCAN_INTERVAL = timedelta(seconds=300) SCAN_INTERVAL = timedelta(seconds=300)
PARALLEL_UPDATES = 4 PARALLEL_UPDATES = 4
@dataclass
class AdGuardHomeEntityDescriptionMixin:
"""Mixin for required keys."""
value_fn: Callable[[AdGuardHome], Coroutine[Any, Any, int | float]]
@dataclass
class AdGuardHomeEntityDescription(
SensorEntityDescription, AdGuardHomeEntityDescriptionMixin
):
"""Describes AdGuard Home sensor entity."""
SENSORS: tuple[AdGuardHomeEntityDescription, ...] = (
AdGuardHomeEntityDescription(
key="dns_queries",
name="DNS queries",
icon="mdi:magnify",
native_unit_of_measurement="queries",
value_fn=lambda adguard: adguard.stats.dns_queries(),
),
AdGuardHomeEntityDescription(
key="blocked_filtering",
name="DNS queries blocked",
icon="mdi:magnify-close",
native_unit_of_measurement="queries",
value_fn=lambda adguard: adguard.stats.blocked_filtering(),
),
AdGuardHomeEntityDescription(
key="blocked_percentage",
name="DNS queries blocked ratio",
icon="mdi:magnify-close",
native_unit_of_measurement=PERCENTAGE,
value_fn=lambda adguard: adguard.stats.blocked_percentage(),
),
AdGuardHomeEntityDescription(
key="blocked_parental",
name="Parental control blocked",
icon="mdi:human-male-girl",
native_unit_of_measurement="requests",
value_fn=lambda adguard: adguard.stats.replaced_parental(),
),
AdGuardHomeEntityDescription(
key="blocked_safebrowsing",
name="Safe browsing blocked",
icon="mdi:shield-half-full",
native_unit_of_measurement="requests",
value_fn=lambda adguard: adguard.stats.replaced_safebrowsing(),
),
AdGuardHomeEntityDescription(
key="enforced_safesearch",
name="Safe searches enforced",
icon="mdi:shield-search",
native_unit_of_measurement="requests",
value_fn=lambda adguard: adguard.stats.replaced_safesearch(),
),
AdGuardHomeEntityDescription(
key="average_speed",
name="Average processing speed",
icon="mdi:speedometer",
native_unit_of_measurement=TIME_MILLISECONDS,
value_fn=lambda adguard: adguard.stats.avg_processing_time(),
),
AdGuardHomeEntityDescription(
key="rules_count",
name="Rules count",
icon="mdi:counter",
native_unit_of_measurement="rules",
value_fn=lambda adguard: adguard.filtering.rules_count(allowlist=False),
entity_registry_enabled_default=False,
),
)
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
entry: ConfigEntry, entry: ConfigEntry,
@ -34,215 +112,39 @@ async def async_setup_entry(
hass.data[DOMAIN][entry.entry_id][DATA_ADGUARD_VERSION] = version hass.data[DOMAIN][entry.entry_id][DATA_ADGUARD_VERSION] = version
sensors = [ async_add_entities(
AdGuardHomeDNSQueriesSensor(adguard, entry), [AdGuardHomeSensor(adguard, entry, description) for description in SENSORS],
AdGuardHomeBlockedFilteringSensor(adguard, entry), True,
AdGuardHomePercentageBlockedSensor(adguard, entry), )
AdGuardHomeReplacedParentalSensor(adguard, entry),
AdGuardHomeReplacedSafeBrowsingSensor(adguard, entry),
AdGuardHomeReplacedSafeSearchSensor(adguard, entry),
AdGuardHomeAverageProcessingTimeSensor(adguard, entry),
AdGuardHomeRulesCountSensor(adguard, entry),
]
async_add_entities(sensors, True)
class AdGuardHomeSensor(AdGuardHomeDeviceEntity, SensorEntity): class AdGuardHomeSensor(AdGuardHomeEntity, SensorEntity):
"""Defines a AdGuard Home sensor.""" """Defines a AdGuard Home sensor."""
entity_description: AdGuardHomeEntityDescription
def __init__( def __init__(
self, self,
adguard: AdGuardHome, adguard: AdGuardHome,
entry: ConfigEntry, entry: ConfigEntry,
name: str, description: AdGuardHomeEntityDescription,
icon: str,
measurement: str,
unit_of_measurement: str,
enabled_default: bool = True,
) -> None: ) -> None:
"""Initialize AdGuard Home sensor.""" """Initialize AdGuard Home sensor."""
self._state: int | str | None = None super().__init__(adguard, entry)
self._unit_of_measurement = unit_of_measurement self.entity_description = description
self.measurement = measurement self._attr_unique_id = "_".join(
super().__init__(adguard, entry, name, icon, enabled_default)
@property
def unique_id(self) -> str:
"""Return the unique ID for this sensor."""
return "_".join(
[ [
DOMAIN, DOMAIN,
self.adguard.host, adguard.host,
str(self.adguard.port), str(adguard.port),
"sensor", "sensor",
self.measurement, description.key,
] ]
) )
@property
def native_value(self) -> int | str | None:
"""Return the state of the sensor."""
return self._state
@property
def native_unit_of_measurement(self) -> str | None:
"""Return the unit this state is expressed in."""
return self._unit_of_measurement
class AdGuardHomeDNSQueriesSensor(AdGuardHomeSensor):
"""Defines a AdGuard Home DNS Queries sensor."""
def __init__(self, adguard: AdGuardHome, entry: ConfigEntry) -> None:
"""Initialize AdGuard Home sensor."""
super().__init__(
adguard,
entry,
"AdGuard DNS Queries",
"mdi:magnify",
"dns_queries",
"queries",
)
async def _adguard_update(self) -> None: async def _adguard_update(self) -> None:
"""Update AdGuard Home entity.""" """Update AdGuard Home entity."""
self._state = await self.adguard.stats.dns_queries() value = await self.entity_description.value_fn(self.adguard)
self._attr_native_value = value
if isinstance(value, float):
class AdGuardHomeBlockedFilteringSensor(AdGuardHomeSensor): self._attr_native_value = f"{value:.2f}"
"""Defines a AdGuard Home blocked by filtering sensor."""
def __init__(self, adguard: AdGuardHome, entry: ConfigEntry) -> None:
"""Initialize AdGuard Home sensor."""
super().__init__(
adguard,
entry,
"AdGuard DNS Queries Blocked",
"mdi:magnify-close",
"blocked_filtering",
"queries",
enabled_default=False,
)
async def _adguard_update(self) -> None:
"""Update AdGuard Home entity."""
self._state = await self.adguard.stats.blocked_filtering()
class AdGuardHomePercentageBlockedSensor(AdGuardHomeSensor):
"""Defines a AdGuard Home blocked percentage sensor."""
def __init__(self, adguard: AdGuardHome, entry: ConfigEntry) -> None:
"""Initialize AdGuard Home sensor."""
super().__init__(
adguard,
entry,
"AdGuard DNS Queries Blocked Ratio",
"mdi:magnify-close",
"blocked_percentage",
PERCENTAGE,
)
async def _adguard_update(self) -> None:
"""Update AdGuard Home entity."""
percentage = await self.adguard.stats.blocked_percentage()
self._state = f"{percentage:.2f}"
class AdGuardHomeReplacedParentalSensor(AdGuardHomeSensor):
"""Defines a AdGuard Home replaced by parental control sensor."""
def __init__(self, adguard: AdGuardHome, entry: ConfigEntry) -> None:
"""Initialize AdGuard Home sensor."""
super().__init__(
adguard,
entry,
"AdGuard Parental Control Blocked",
"mdi:human-male-girl",
"blocked_parental",
"requests",
)
async def _adguard_update(self) -> None:
"""Update AdGuard Home entity."""
self._state = await self.adguard.stats.replaced_parental()
class AdGuardHomeReplacedSafeBrowsingSensor(AdGuardHomeSensor):
"""Defines a AdGuard Home replaced by safe browsing sensor."""
def __init__(self, adguard: AdGuardHome, entry: ConfigEntry) -> None:
"""Initialize AdGuard Home sensor."""
super().__init__(
adguard,
entry,
"AdGuard Safe Browsing Blocked",
"mdi:shield-half-full",
"blocked_safebrowsing",
"requests",
)
async def _adguard_update(self) -> None:
"""Update AdGuard Home entity."""
self._state = await self.adguard.stats.replaced_safebrowsing()
class AdGuardHomeReplacedSafeSearchSensor(AdGuardHomeSensor):
"""Defines a AdGuard Home replaced by safe search sensor."""
def __init__(self, adguard: AdGuardHome, entry: ConfigEntry) -> None:
"""Initialize AdGuard Home sensor."""
super().__init__(
adguard,
entry,
"AdGuard Safe Searches Enforced",
"mdi:shield-search",
"enforced_safesearch",
"requests",
)
async def _adguard_update(self) -> None:
"""Update AdGuard Home entity."""
self._state = await self.adguard.stats.replaced_safesearch()
class AdGuardHomeAverageProcessingTimeSensor(AdGuardHomeSensor):
"""Defines a AdGuard Home average processing time sensor."""
def __init__(self, adguard: AdGuardHome, entry: ConfigEntry) -> None:
"""Initialize AdGuard Home sensor."""
super().__init__(
adguard,
entry,
"AdGuard Average Processing Speed",
"mdi:speedometer",
"average_speed",
TIME_MILLISECONDS,
)
async def _adguard_update(self) -> None:
"""Update AdGuard Home entity."""
average = await self.adguard.stats.avg_processing_time()
self._state = f"{average:.2f}"
class AdGuardHomeRulesCountSensor(AdGuardHomeSensor):
"""Defines a AdGuard Home rules count sensor."""
def __init__(self, adguard: AdGuardHome, entry: ConfigEntry) -> None:
"""Initialize AdGuard Home sensor."""
super().__init__(
adguard,
entry,
"AdGuard Rules Count",
"mdi:counter",
"rules_count",
"rules",
enabled_default=False,
)
async def _adguard_update(self) -> None:
"""Update AdGuard Home entity."""
self._state = await self.adguard.filtering.rules_count(allowlist=False)

View File

@ -1,27 +1,94 @@
"""Support for AdGuard Home switches.""" """Support for AdGuard Home switches."""
from __future__ import annotations from __future__ import annotations
from collections.abc import Callable, Coroutine
from dataclasses import dataclass
from datetime import timedelta from datetime import timedelta
import logging
from typing import Any from typing import Any
from adguardhome import AdGuardHome, AdGuardHomeConnectionError, AdGuardHomeError from adguardhome import AdGuardHome, AdGuardHomeConnectionError, AdGuardHomeError
from homeassistant.components.switch import SwitchEntity from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import PlatformNotReady from homeassistant.exceptions import PlatformNotReady
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import AdGuardHomeDeviceEntity from .const import DATA_ADGUARD_CLIENT, DATA_ADGUARD_VERSION, DOMAIN, LOGGER
from .const import DATA_ADGUARD_CLIENT, DATA_ADGUARD_VERSION, DOMAIN from .entity import AdGuardHomeEntity
_LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(seconds=10) SCAN_INTERVAL = timedelta(seconds=10)
PARALLEL_UPDATES = 1 PARALLEL_UPDATES = 1
@dataclass
class AdGuardHomeSwitchEntityDescriptionMixin:
"""Mixin for required keys."""
is_on_fn: Callable[[AdGuardHome], Callable[[], Coroutine[Any, Any, bool]]]
turn_on_fn: Callable[[AdGuardHome], Callable[[], Coroutine[Any, Any, None]]]
turn_off_fn: Callable[[AdGuardHome], Callable[[], Coroutine[Any, Any, None]]]
@dataclass
class AdGuardHomeSwitchEntityDescription(
SwitchEntityDescription, AdGuardHomeSwitchEntityDescriptionMixin
):
"""Describes AdGuard Home switch entity."""
SWITCHES: tuple[AdGuardHomeSwitchEntityDescription, ...] = (
AdGuardHomeSwitchEntityDescription(
key="protection",
name="Protection",
icon="mdi:shield-check",
is_on_fn=lambda adguard: adguard.protection_enabled,
turn_on_fn=lambda adguard: adguard.enable_protection,
turn_off_fn=lambda adguard: adguard.disable_protection,
),
AdGuardHomeSwitchEntityDescription(
key="parental",
name="Parental control",
icon="mdi:shield-check",
is_on_fn=lambda adguard: adguard.parental.enabled,
turn_on_fn=lambda adguard: adguard.parental.enable,
turn_off_fn=lambda adguard: adguard.parental.disable,
),
AdGuardHomeSwitchEntityDescription(
key="safesearch",
name="Safe search",
icon="mdi:shield-check",
is_on_fn=lambda adguard: adguard.safesearch.enabled,
turn_on_fn=lambda adguard: adguard.safesearch.enable,
turn_off_fn=lambda adguard: adguard.safesearch.disable,
),
AdGuardHomeSwitchEntityDescription(
key="safebrowsing",
name="Safe browsing",
icon="mdi:shield-check",
is_on_fn=lambda adguard: adguard.safebrowsing.enabled,
turn_on_fn=lambda adguard: adguard.safebrowsing.enable,
turn_off_fn=lambda adguard: adguard.safebrowsing.disable,
),
AdGuardHomeSwitchEntityDescription(
key="filtering",
name="Filtering",
icon="mdi:shield-check",
is_on_fn=lambda adguard: adguard.filtering.enabled,
turn_on_fn=lambda adguard: adguard.filtering.enable,
turn_off_fn=lambda adguard: adguard.filtering.disable,
),
AdGuardHomeSwitchEntityDescription(
key="querylog",
name="Query log",
icon="mdi:shield-check",
is_on_fn=lambda adguard: adguard.querylog.enabled,
turn_on_fn=lambda adguard: adguard.querylog.enable,
turn_off_fn=lambda adguard: adguard.querylog.disable,
),
)
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
entry: ConfigEntry, entry: ConfigEntry,
@ -37,203 +104,46 @@ async def async_setup_entry(
hass.data[DOMAIN][entry.entry_id][DATA_ADGUARD_VERSION] = version hass.data[DOMAIN][entry.entry_id][DATA_ADGUARD_VERSION] = version
switches = [ async_add_entities(
AdGuardHomeProtectionSwitch(adguard, entry), [AdGuardHomeSwitch(adguard, entry, description) for description in SWITCHES],
AdGuardHomeFilteringSwitch(adguard, entry), True,
AdGuardHomeParentalSwitch(adguard, entry), )
AdGuardHomeSafeBrowsingSwitch(adguard, entry),
AdGuardHomeSafeSearchSwitch(adguard, entry),
AdGuardHomeQueryLogSwitch(adguard, entry),
]
async_add_entities(switches, True)
class AdGuardHomeSwitch(AdGuardHomeDeviceEntity, SwitchEntity): class AdGuardHomeSwitch(AdGuardHomeEntity, SwitchEntity):
"""Defines a AdGuard Home switch.""" """Defines a AdGuard Home switch."""
entity_description: AdGuardHomeSwitchEntityDescription
def __init__( def __init__(
self, self,
adguard: AdGuardHome, adguard: AdGuardHome,
entry: ConfigEntry, entry: ConfigEntry,
name: str, description: AdGuardHomeSwitchEntityDescription,
icon: str,
key: str,
enabled_default: bool = True,
) -> None: ) -> None:
"""Initialize AdGuard Home switch.""" """Initialize AdGuard Home switch."""
self._state = False super().__init__(adguard, entry)
self._key = key self.entity_description = description
super().__init__(adguard, entry, name, icon, enabled_default) self._attr_unique_id = "_".join(
[DOMAIN, adguard.host, str(adguard.port), "switch", description.key]
@property
def unique_id(self) -> str:
"""Return the unique ID for this sensor."""
return "_".join(
[DOMAIN, self.adguard.host, str(self.adguard.port), "switch", self._key]
) )
@property
def is_on(self) -> bool:
"""Return the state of the switch."""
return self._state
async def async_turn_off(self, **kwargs: Any) -> None: async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn off the switch.""" """Turn off the switch."""
try: try:
await self._adguard_turn_off() await self.entity_description.turn_off_fn(self.adguard)()
except AdGuardHomeError: except AdGuardHomeError:
_LOGGER.error("An error occurred while turning off AdGuard Home switch") LOGGER.error("An error occurred while turning off AdGuard Home switch")
self._available = False self._attr_available = False
async def _adguard_turn_off(self) -> None:
"""Turn off the switch."""
raise NotImplementedError()
async def async_turn_on(self, **kwargs: Any) -> None: async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn on the switch.""" """Turn on the switch."""
try: try:
await self._adguard_turn_on() await self.entity_description.turn_on_fn(self.adguard)()
except AdGuardHomeError: except AdGuardHomeError:
_LOGGER.error("An error occurred while turning on AdGuard Home switch") LOGGER.error("An error occurred while turning on AdGuard Home switch")
self._available = False self._attr_available = False
async def _adguard_turn_on(self) -> None:
"""Turn on the switch."""
raise NotImplementedError()
class AdGuardHomeProtectionSwitch(AdGuardHomeSwitch):
"""Defines a AdGuard Home protection switch."""
def __init__(self, adguard: AdGuardHome, entry: ConfigEntry) -> None:
"""Initialize AdGuard Home switch."""
super().__init__(
adguard, entry, "AdGuard Protection", "mdi:shield-check", "protection"
)
async def _adguard_turn_off(self) -> None:
"""Turn off the switch."""
await self.adguard.disable_protection()
async def _adguard_turn_on(self) -> None:
"""Turn on the switch."""
await self.adguard.enable_protection()
async def _adguard_update(self) -> None: async def _adguard_update(self) -> None:
"""Update AdGuard Home entity.""" """Update AdGuard Home entity."""
self._state = await self.adguard.protection_enabled() self._attr_is_on = await self.entity_description.is_on_fn(self.adguard)()
class AdGuardHomeParentalSwitch(AdGuardHomeSwitch):
"""Defines a AdGuard Home parental control switch."""
def __init__(self, adguard: AdGuardHome, entry: ConfigEntry) -> None:
"""Initialize AdGuard Home switch."""
super().__init__(
adguard, entry, "AdGuard Parental Control", "mdi:shield-check", "parental"
)
async def _adguard_turn_off(self) -> None:
"""Turn off the switch."""
await self.adguard.parental.disable()
async def _adguard_turn_on(self) -> None:
"""Turn on the switch."""
await self.adguard.parental.enable()
async def _adguard_update(self) -> None:
"""Update AdGuard Home entity."""
self._state = await self.adguard.parental.enabled()
class AdGuardHomeSafeSearchSwitch(AdGuardHomeSwitch):
"""Defines a AdGuard Home safe search switch."""
def __init__(self, adguard: AdGuardHome, entry: ConfigEntry) -> None:
"""Initialize AdGuard Home switch."""
super().__init__(
adguard, entry, "AdGuard Safe Search", "mdi:shield-check", "safesearch"
)
async def _adguard_turn_off(self) -> None:
"""Turn off the switch."""
await self.adguard.safesearch.disable()
async def _adguard_turn_on(self) -> None:
"""Turn on the switch."""
await self.adguard.safesearch.enable()
async def _adguard_update(self) -> None:
"""Update AdGuard Home entity."""
self._state = await self.adguard.safesearch.enabled()
class AdGuardHomeSafeBrowsingSwitch(AdGuardHomeSwitch):
"""Defines a AdGuard Home safe search switch."""
def __init__(self, adguard: AdGuardHome, entry: ConfigEntry) -> None:
"""Initialize AdGuard Home switch."""
super().__init__(
adguard, entry, "AdGuard Safe Browsing", "mdi:shield-check", "safebrowsing"
)
async def _adguard_turn_off(self) -> None:
"""Turn off the switch."""
await self.adguard.safebrowsing.disable()
async def _adguard_turn_on(self) -> None:
"""Turn on the switch."""
await self.adguard.safebrowsing.enable()
async def _adguard_update(self) -> None:
"""Update AdGuard Home entity."""
self._state = await self.adguard.safebrowsing.enabled()
class AdGuardHomeFilteringSwitch(AdGuardHomeSwitch):
"""Defines a AdGuard Home filtering switch."""
def __init__(self, adguard: AdGuardHome, entry: ConfigEntry) -> None:
"""Initialize AdGuard Home switch."""
super().__init__(
adguard, entry, "AdGuard Filtering", "mdi:shield-check", "filtering"
)
async def _adguard_turn_off(self) -> None:
"""Turn off the switch."""
await self.adguard.filtering.disable()
async def _adguard_turn_on(self) -> None:
"""Turn on the switch."""
await self.adguard.filtering.enable()
async def _adguard_update(self) -> None:
"""Update AdGuard Home entity."""
self._state = await self.adguard.filtering.enabled()
class AdGuardHomeQueryLogSwitch(AdGuardHomeSwitch):
"""Defines a AdGuard Home query log switch."""
def __init__(self, adguard: AdGuardHome, entry: ConfigEntry) -> None:
"""Initialize AdGuard Home switch."""
super().__init__(
adguard,
entry,
"AdGuard Query Log",
"mdi:shield-check",
"querylog",
enabled_default=False,
)
async def _adguard_turn_off(self) -> None:
"""Turn off the switch."""
await self.adguard.querylog.disable()
async def _adguard_turn_on(self) -> None:
"""Turn on the switch."""
await self.adguard.querylog.enable()
async def _adguard_update(self) -> None:
"""Update AdGuard Home entity."""
self._state = await self.adguard.querylog.enabled()

View File

@ -2,7 +2,7 @@
"config": { "config": {
"abort": { "abort": {
"already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van", "already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van",
"existing_instance_updated": "Friss\u00edtette a megl\u00e9v\u0151 konfigur\u00e1ci\u00f3t." "existing_instance_updated": "A megl\u00e9v\u0151 konfigur\u00e1ci\u00f3 friss\u00edtve."
}, },
"error": { "error": {
"cannot_connect": "Sikertelen csatlakoz\u00e1s" "cannot_connect": "Sikertelen csatlakoz\u00e1s"

View File

@ -1,5 +1,8 @@
{ {
"config": { "config": {
"abort": {
"already_configured": "O servi\u00e7o j\u00e1 est\u00e1 configurado"
},
"error": { "error": {
"cannot_connect": "Falha na liga\u00e7\u00e3o" "cannot_connect": "Falha na liga\u00e7\u00e3o"
}, },
@ -14,7 +17,7 @@
"port": "Porta", "port": "Porta",
"ssl": "Utiliza um certificado SSL", "ssl": "Utiliza um certificado SSL",
"username": "Nome de Utilizador", "username": "Nome de Utilizador",
"verify_ssl": "Verificar certificado SSL" "verify_ssl": "Verificar o certificado SSL"
} }
} }
} }

View File

@ -65,7 +65,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"async_change": async_change, "async_change": async_change,
} }
hass.config_entries.async_setup_platforms(entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True return True

View File

@ -11,7 +11,7 @@ from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN as ADVANTAGE_AIR_DOMAIN from .const import DOMAIN as ADVANTAGE_AIR_DOMAIN
from .entity import AdvantageAirEntity from .entity import AdvantageAirAcEntity, AdvantageAirZoneEntity
PARALLEL_UPDATES = 0 PARALLEL_UPDATES = 0
@ -21,13 +21,13 @@ async def async_setup_entry(
config_entry: ConfigEntry, config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up AdvantageAir motion platform.""" """Set up AdvantageAir Binary Sensor platform."""
instance = hass.data[ADVANTAGE_AIR_DOMAIN][config_entry.entry_id] instance = hass.data[ADVANTAGE_AIR_DOMAIN][config_entry.entry_id]
entities: list[BinarySensorEntity] = [] entities: list[BinarySensorEntity] = []
for ac_key, ac_device in instance["coordinator"].data["aircons"].items(): for ac_key, ac_device in instance["coordinator"].data["aircons"].items():
entities.append(AdvantageAirZoneFilter(instance, ac_key)) entities.append(AdvantageAirFilter(instance, ac_key))
for zone_key, zone in ac_device["zones"].items(): for zone_key, zone in ac_device["zones"].items():
# Only add motion sensor when motion is enabled # Only add motion sensor when motion is enabled
if zone["motionConfig"] >= 2: if zone["motionConfig"] >= 2:
@ -38,19 +38,17 @@ async def async_setup_entry(
async_add_entities(entities) async_add_entities(entities)
class AdvantageAirZoneFilter(AdvantageAirEntity, BinarySensorEntity): class AdvantageAirFilter(AdvantageAirAcEntity, BinarySensorEntity):
"""Advantage Air Filter.""" """Advantage Air Filter sensor."""
_attr_device_class = BinarySensorDeviceClass.PROBLEM _attr_device_class = BinarySensorDeviceClass.PROBLEM
_attr_entity_category = EntityCategory.DIAGNOSTIC _attr_entity_category = EntityCategory.DIAGNOSTIC
_attr_name = "Filter"
def __init__(self, instance, ac_key): def __init__(self, instance, ac_key):
"""Initialize an Advantage Air Filter.""" """Initialize an Advantage Air Filter sensor."""
super().__init__(instance, ac_key) super().__init__(instance, ac_key)
self._attr_name = f'{self._ac["name"]} Filter' self._attr_unique_id += "-filter"
self._attr_unique_id = (
f'{self.coordinator.data["system"]["rid"]}-{ac_key}-filter'
)
@property @property
def is_on(self): def is_on(self):
@ -58,18 +56,16 @@ class AdvantageAirZoneFilter(AdvantageAirEntity, BinarySensorEntity):
return self._ac["filterCleanStatus"] return self._ac["filterCleanStatus"]
class AdvantageAirZoneMotion(AdvantageAirEntity, BinarySensorEntity): class AdvantageAirZoneMotion(AdvantageAirZoneEntity, BinarySensorEntity):
"""Advantage Air Zone Motion.""" """Advantage Air Zone Motion sensor."""
_attr_device_class = BinarySensorDeviceClass.MOTION _attr_device_class = BinarySensorDeviceClass.MOTION
def __init__(self, instance, ac_key, zone_key): def __init__(self, instance, ac_key, zone_key):
"""Initialize an Advantage Air Zone Motion.""" """Initialize an Advantage Air Zone Motion sensor."""
super().__init__(instance, ac_key, zone_key) super().__init__(instance, ac_key, zone_key)
self._attr_name = f'{self._zone["name"]} Motion' self._attr_name = f'{self._zone["name"]} motion'
self._attr_unique_id = ( self._attr_unique_id += "-motion"
f'{self.coordinator.data["system"]["rid"]}-{ac_key}-{zone_key}-motion'
)
@property @property
def is_on(self): def is_on(self):
@ -77,19 +73,17 @@ class AdvantageAirZoneMotion(AdvantageAirEntity, BinarySensorEntity):
return self._zone["motion"] == 20 return self._zone["motion"] == 20
class AdvantageAirZoneMyZone(AdvantageAirEntity, BinarySensorEntity): class AdvantageAirZoneMyZone(AdvantageAirZoneEntity, BinarySensorEntity):
"""Advantage Air Zone MyZone.""" """Advantage Air Zone MyZone sensor."""
_attr_entity_registry_enabled_default = False _attr_entity_registry_enabled_default = False
_attr_entity_category = EntityCategory.DIAGNOSTIC _attr_entity_category = EntityCategory.DIAGNOSTIC
def __init__(self, instance, ac_key, zone_key): def __init__(self, instance, ac_key, zone_key):
"""Initialize an Advantage Air Zone MyZone.""" """Initialize an Advantage Air Zone MyZone sensor."""
super().__init__(instance, ac_key, zone_key) super().__init__(instance, ac_key, zone_key)
self._attr_name = f'{self._zone["name"]} MyZone' self._attr_name = f'{self._zone["name"]} myZone'
self._attr_unique_id = ( self._attr_unique_id += "-myzone"
f'{self.coordinator.data["system"]["rid"]}-{ac_key}-{zone_key}-myzone'
)
@property @property
def is_on(self): def is_on(self):

View File

@ -15,7 +15,6 @@ from homeassistant.components.climate.const import (
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_TEMPERATURE, PRECISION_WHOLE, TEMP_CELSIUS from homeassistant.const import ATTR_TEMPERATURE, PRECISION_WHOLE, TEMP_CELSIUS
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_platform
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import ( from .const import (
@ -25,7 +24,7 @@ from .const import (
ADVANTAGE_AIR_STATE_OPEN, ADVANTAGE_AIR_STATE_OPEN,
DOMAIN as ADVANTAGE_AIR_DOMAIN, DOMAIN as ADVANTAGE_AIR_DOMAIN,
) )
from .entity import AdvantageAirEntity from .entity import AdvantageAirAcEntity, AdvantageAirZoneEntity
ADVANTAGE_AIR_HVAC_MODES = { ADVANTAGE_AIR_HVAC_MODES = {
"heat": HVACMode.HEAT, "heat": HVACMode.HEAT,
@ -53,7 +52,6 @@ ADVANTAGE_AIR_FAN_MODES = {
HASS_FAN_MODES = {v: k for k, v in ADVANTAGE_AIR_FAN_MODES.items()} HASS_FAN_MODES = {v: k for k, v in ADVANTAGE_AIR_FAN_MODES.items()}
FAN_SPEEDS = {FAN_LOW: 30, FAN_MEDIUM: 60, FAN_HIGH: 100} FAN_SPEEDS = {FAN_LOW: 30, FAN_MEDIUM: 60, FAN_HIGH: 100}
ADVANTAGE_AIR_SERVICE_SET_MYZONE = "set_myzone"
ZONE_HVAC_MODES = [HVACMode.OFF, HVACMode.HEAT_COOL] ZONE_HVAC_MODES = [HVACMode.OFF, HVACMode.HEAT_COOL]
PARALLEL_UPDATES = 0 PARALLEL_UPDATES = 0
@ -79,26 +77,14 @@ async def async_setup_entry(
entities.append(AdvantageAirZone(instance, ac_key, zone_key)) entities.append(AdvantageAirZone(instance, ac_key, zone_key))
async_add_entities(entities) async_add_entities(entities)
platform = entity_platform.async_get_current_platform()
platform.async_register_entity_service(
ADVANTAGE_AIR_SERVICE_SET_MYZONE,
{},
"set_myzone",
)
class AdvantageAirAC(AdvantageAirAcEntity, ClimateEntity):
class AdvantageAirClimateEntity(AdvantageAirEntity, ClimateEntity): """AdvantageAir AC unit."""
"""AdvantageAir Climate class."""
_attr_temperature_unit = TEMP_CELSIUS _attr_temperature_unit = TEMP_CELSIUS
_attr_target_temperature_step = PRECISION_WHOLE _attr_target_temperature_step = PRECISION_WHOLE
_attr_max_temp = 32 _attr_max_temp = 32
_attr_min_temp = 16 _attr_min_temp = 16
class AdvantageAirAC(AdvantageAirClimateEntity):
"""AdvantageAir AC unit."""
_attr_fan_modes = [FAN_AUTO, FAN_LOW, FAN_MEDIUM, FAN_HIGH] _attr_fan_modes = [FAN_AUTO, FAN_LOW, FAN_MEDIUM, FAN_HIGH]
_attr_hvac_modes = AC_HVAC_MODES _attr_hvac_modes = AC_HVAC_MODES
_attr_supported_features = ( _attr_supported_features = (
@ -108,8 +94,6 @@ class AdvantageAirAC(AdvantageAirClimateEntity):
def __init__(self, instance, ac_key): def __init__(self, instance, ac_key):
"""Initialize an AdvantageAir AC unit.""" """Initialize an AdvantageAir AC unit."""
super().__init__(instance, ac_key) super().__init__(instance, ac_key)
self._attr_name = self._ac["name"]
self._attr_unique_id = f'{self.coordinator.data["system"]["rid"]}-{ac_key}'
if self._ac.get("myAutoModeEnabled"): if self._ac.get("myAutoModeEnabled"):
self._attr_hvac_modes = AC_HVAC_MODES + [HVACMode.AUTO] self._attr_hvac_modes = AC_HVAC_MODES + [HVACMode.AUTO]
@ -160,9 +144,13 @@ class AdvantageAirAC(AdvantageAirClimateEntity):
await self.async_change({self.ac_key: {"info": {"setTemp": temp}}}) await self.async_change({self.ac_key: {"info": {"setTemp": temp}}})
class AdvantageAirZone(AdvantageAirClimateEntity): class AdvantageAirZone(AdvantageAirZoneEntity, ClimateEntity):
"""AdvantageAir Zone control.""" """AdvantageAir Zone control."""
_attr_temperature_unit = TEMP_CELSIUS
_attr_target_temperature_step = PRECISION_WHOLE
_attr_max_temp = 32
_attr_min_temp = 16
_attr_hvac_modes = ZONE_HVAC_MODES _attr_hvac_modes = ZONE_HVAC_MODES
_attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE _attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
@ -216,12 +204,3 @@ class AdvantageAirZone(AdvantageAirClimateEntity):
await self.async_change( await self.async_change(
{self.ac_key: {"zones": {self.zone_key: {"setTemp": temp}}}} {self.ac_key: {"zones": {self.zone_key: {"setTemp": temp}}}}
) )
async def set_myzone(self, **kwargs):
"""Set this zone as the 'MyZone'."""
_LOGGER.warning(
"The advantage_air.set_myzone service has been deprecated and will be removed in a future version, please use the select.select_option service on the MyZone entity"
)
await self.async_change(
{self.ac_key: {"info": {"myZone": self._zone["number"]}}}
)

View File

@ -16,7 +16,7 @@ from .const import (
ADVANTAGE_AIR_STATE_OPEN, ADVANTAGE_AIR_STATE_OPEN,
DOMAIN as ADVANTAGE_AIR_DOMAIN, DOMAIN as ADVANTAGE_AIR_DOMAIN,
) )
from .entity import AdvantageAirEntity from .entity import AdvantageAirZoneEntity
PARALLEL_UPDATES = 0 PARALLEL_UPDATES = 0
@ -39,8 +39,8 @@ async def async_setup_entry(
async_add_entities(entities) async_add_entities(entities)
class AdvantageAirZoneVent(AdvantageAirEntity, CoverEntity): class AdvantageAirZoneVent(AdvantageAirZoneEntity, CoverEntity):
"""Advantage Air Cover Class.""" """Advantage Air Zone Vent."""
_attr_device_class = CoverDeviceClass.DAMPER _attr_device_class = CoverDeviceClass.DAMPER
_attr_supported_features = ( _attr_supported_features = (
@ -50,12 +50,9 @@ class AdvantageAirZoneVent(AdvantageAirEntity, CoverEntity):
) )
def __init__(self, instance, ac_key, zone_key): def __init__(self, instance, ac_key, zone_key):
"""Initialize an Advantage Air Cover Class.""" """Initialize an Advantage Air Zone Vent."""
super().__init__(instance, ac_key, zone_key) super().__init__(instance, ac_key, zone_key)
self._attr_name = f'{self._zone["name"]}' self._attr_name = self._zone["name"]
self._attr_unique_id = (
f'{self.coordinator.data["system"]["rid"]}-{ac_key}-{zone_key}'
)
@property @property
def is_closed(self) -> bool: def is_closed(self) -> bool:

View File

@ -9,24 +9,46 @@ from .const import DOMAIN
class AdvantageAirEntity(CoordinatorEntity): class AdvantageAirEntity(CoordinatorEntity):
"""Parent class for Advantage Air Entities.""" """Parent class for Advantage Air Entities."""
def __init__(self, instance, ac_key, zone_key=None): _attr_has_entity_name = True
"""Initialize common aspects of an Advantage Air sensor."""
def __init__(self, instance):
"""Initialize common aspects of an Advantage Air entity."""
super().__init__(instance["coordinator"]) super().__init__(instance["coordinator"])
self._attr_unique_id = self.coordinator.data["system"]["rid"]
class AdvantageAirAcEntity(AdvantageAirEntity):
"""Parent class for Advantage Air AC Entities."""
def __init__(self, instance, ac_key):
"""Initialize common aspects of an Advantage Air ac entity."""
super().__init__(instance)
self.async_change = instance["async_change"] self.async_change = instance["async_change"]
self.ac_key = ac_key self.ac_key = ac_key
self.zone_key = zone_key self._attr_unique_id += f"-{ac_key}"
self._attr_device_info = DeviceInfo( self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, self.coordinator.data["system"]["rid"])}, via_device=(DOMAIN, self.coordinator.data["system"]["rid"]),
identifiers={(DOMAIN, self._attr_unique_id)},
manufacturer="Advantage Air", manufacturer="Advantage Air",
model=self.coordinator.data["system"]["sysType"], model=self.coordinator.data["system"]["sysType"],
name=self.coordinator.data["system"]["name"], name=self.coordinator.data["aircons"][self.ac_key]["info"]["name"],
sw_version=self.coordinator.data["system"]["myAppRev"],
) )
@property @property
def _ac(self): def _ac(self):
return self.coordinator.data["aircons"][self.ac_key]["info"] return self.coordinator.data["aircons"][self.ac_key]["info"]
class AdvantageAirZoneEntity(AdvantageAirAcEntity):
"""Parent class for Advantage Air Zone Entities."""
def __init__(self, instance, ac_key, zone_key):
"""Initialize common aspects of an Advantage Air zone entity."""
super().__init__(instance, ac_key)
self.zone_key = zone_key
self._attr_unique_id += f"-{zone_key}"
@property @property
def _zone(self): def _zone(self):
return self.coordinator.data["aircons"][self.ac_key]["zones"][self.zone_key] return self.coordinator.data["aircons"][self.ac_key]["zones"][self.zone_key]

View File

@ -5,7 +5,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN as ADVANTAGE_AIR_DOMAIN from .const import DOMAIN as ADVANTAGE_AIR_DOMAIN
from .entity import AdvantageAirEntity from .entity import AdvantageAirAcEntity
ADVANTAGE_AIR_INACTIVE = "Inactive" ADVANTAGE_AIR_INACTIVE = "Inactive"
@ -15,7 +15,7 @@ async def async_setup_entry(
config_entry: ConfigEntry, config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up AdvantageAir toggle platform.""" """Set up AdvantageAir select platform."""
instance = hass.data[ADVANTAGE_AIR_DOMAIN][config_entry.entry_id] instance = hass.data[ADVANTAGE_AIR_DOMAIN][config_entry.entry_id]
@ -25,21 +25,19 @@ async def async_setup_entry(
async_add_entities(entities) async_add_entities(entities)
class AdvantageAirMyZone(AdvantageAirEntity, SelectEntity): class AdvantageAirMyZone(AdvantageAirAcEntity, SelectEntity):
"""Representation of Advantage Air MyZone control.""" """Representation of Advantage Air MyZone control."""
_attr_icon = "mdi:home-thermometer" _attr_icon = "mdi:home-thermometer"
_attr_options = [ADVANTAGE_AIR_INACTIVE] _attr_options = [ADVANTAGE_AIR_INACTIVE]
_number_to_name = {0: ADVANTAGE_AIR_INACTIVE} _number_to_name = {0: ADVANTAGE_AIR_INACTIVE}
_name_to_number = {ADVANTAGE_AIR_INACTIVE: 0} _name_to_number = {ADVANTAGE_AIR_INACTIVE: 0}
_attr_name = "MyZone"
def __init__(self, instance, ac_key): def __init__(self, instance, ac_key):
"""Initialize an Advantage Air MyZone control.""" """Initialize an Advantage Air MyZone control."""
super().__init__(instance, ac_key) super().__init__(instance, ac_key)
self._attr_name = f'{self._ac["name"]} MyZone' self._attr_unique_id += "-myzone"
self._attr_unique_id = (
f'{self.coordinator.data["system"]["rid"]}-{ac_key}-myzone'
)
for zone in instance["coordinator"].data["aircons"][ac_key]["zones"].values(): for zone in instance["coordinator"].data["aircons"][ac_key]["zones"].values():
if zone["type"] > 0: if zone["type"] > 0:
@ -49,7 +47,7 @@ class AdvantageAirMyZone(AdvantageAirEntity, SelectEntity):
@property @property
def current_option(self): def current_option(self):
"""Return the fresh air status.""" """Return the current MyZone."""
return self._number_to_name[self._ac["myZone"]] return self._number_to_name[self._ac["myZone"]]
async def async_select_option(self, option): async def async_select_option(self, option):

View File

@ -16,7 +16,7 @@ from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import ADVANTAGE_AIR_STATE_OPEN, DOMAIN as ADVANTAGE_AIR_DOMAIN from .const import ADVANTAGE_AIR_STATE_OPEN, DOMAIN as ADVANTAGE_AIR_DOMAIN
from .entity import AdvantageAirEntity from .entity import AdvantageAirAcEntity, AdvantageAirZoneEntity
ADVANTAGE_AIR_SET_COUNTDOWN_VALUE = "minutes" ADVANTAGE_AIR_SET_COUNTDOWN_VALUE = "minutes"
ADVANTAGE_AIR_SET_COUNTDOWN_UNIT = "min" ADVANTAGE_AIR_SET_COUNTDOWN_UNIT = "min"
@ -56,7 +56,7 @@ async def async_setup_entry(
) )
class AdvantageAirTimeTo(AdvantageAirEntity, SensorEntity): class AdvantageAirTimeTo(AdvantageAirAcEntity, SensorEntity):
"""Representation of Advantage Air timer control.""" """Representation of Advantage Air timer control."""
_attr_native_unit_of_measurement = ADVANTAGE_AIR_SET_COUNTDOWN_UNIT _attr_native_unit_of_measurement = ADVANTAGE_AIR_SET_COUNTDOWN_UNIT
@ -67,10 +67,8 @@ class AdvantageAirTimeTo(AdvantageAirEntity, SensorEntity):
super().__init__(instance, ac_key) super().__init__(instance, ac_key)
self.action = action self.action = action
self._time_key = f"countDownTo{action}" self._time_key = f"countDownTo{action}"
self._attr_name = f'{self._ac["name"]} Time To {action}' self._attr_name = f"Time to {action}"
self._attr_unique_id = ( self._attr_unique_id += f"-timeto{action}"
f'{self.coordinator.data["system"]["rid"]}-{self.ac_key}-timeto{action}'
)
@property @property
def native_value(self): def native_value(self):
@ -90,7 +88,7 @@ class AdvantageAirTimeTo(AdvantageAirEntity, SensorEntity):
await self.async_change({self.ac_key: {"info": {self._time_key: value}}}) await self.async_change({self.ac_key: {"info": {self._time_key: value}}})
class AdvantageAirZoneVent(AdvantageAirEntity, SensorEntity): class AdvantageAirZoneVent(AdvantageAirZoneEntity, SensorEntity):
"""Representation of Advantage Air Zone Vent Sensor.""" """Representation of Advantage Air Zone Vent Sensor."""
_attr_native_unit_of_measurement = PERCENTAGE _attr_native_unit_of_measurement = PERCENTAGE
@ -100,10 +98,8 @@ class AdvantageAirZoneVent(AdvantageAirEntity, SensorEntity):
def __init__(self, instance, ac_key, zone_key): def __init__(self, instance, ac_key, zone_key):
"""Initialize an Advantage Air Zone Vent Sensor.""" """Initialize an Advantage Air Zone Vent Sensor."""
super().__init__(instance, ac_key, zone_key=zone_key) super().__init__(instance, ac_key, zone_key=zone_key)
self._attr_name = f'{self._zone["name"]} Vent' self._attr_name = f'{self._zone["name"]} vent'
self._attr_unique_id = ( self._attr_unique_id += "-vent"
f'{self.coordinator.data["system"]["rid"]}-{ac_key}-{zone_key}-vent'
)
@property @property
def native_value(self): def native_value(self):
@ -120,7 +116,7 @@ class AdvantageAirZoneVent(AdvantageAirEntity, SensorEntity):
return "mdi:fan-off" return "mdi:fan-off"
class AdvantageAirZoneSignal(AdvantageAirEntity, SensorEntity): class AdvantageAirZoneSignal(AdvantageAirZoneEntity, SensorEntity):
"""Representation of Advantage Air Zone wireless signal sensor.""" """Representation of Advantage Air Zone wireless signal sensor."""
_attr_native_unit_of_measurement = PERCENTAGE _attr_native_unit_of_measurement = PERCENTAGE
@ -130,10 +126,8 @@ class AdvantageAirZoneSignal(AdvantageAirEntity, SensorEntity):
def __init__(self, instance, ac_key, zone_key): def __init__(self, instance, ac_key, zone_key):
"""Initialize an Advantage Air Zone wireless signal sensor.""" """Initialize an Advantage Air Zone wireless signal sensor."""
super().__init__(instance, ac_key, zone_key) super().__init__(instance, ac_key, zone_key)
self._attr_name = f'{self._zone["name"]} Signal' self._attr_name = f'{self._zone["name"]} signal'
self._attr_unique_id = ( self._attr_unique_id += "-signal"
f'{self.coordinator.data["system"]["rid"]}-{ac_key}-{zone_key}-signal'
)
@property @property
def native_value(self): def native_value(self):
@ -154,7 +148,7 @@ class AdvantageAirZoneSignal(AdvantageAirEntity, SensorEntity):
return "mdi:wifi-strength-outline" return "mdi:wifi-strength-outline"
class AdvantageAirZoneTemp(AdvantageAirEntity, SensorEntity): class AdvantageAirZoneTemp(AdvantageAirZoneEntity, SensorEntity):
"""Representation of Advantage Air Zone temperature sensor.""" """Representation of Advantage Air Zone temperature sensor."""
_attr_native_unit_of_measurement = TEMP_CELSIUS _attr_native_unit_of_measurement = TEMP_CELSIUS
@ -166,10 +160,8 @@ class AdvantageAirZoneTemp(AdvantageAirEntity, SensorEntity):
def __init__(self, instance, ac_key, zone_key): def __init__(self, instance, ac_key, zone_key):
"""Initialize an Advantage Air Zone Temp Sensor.""" """Initialize an Advantage Air Zone Temp Sensor."""
super().__init__(instance, ac_key, zone_key) super().__init__(instance, ac_key, zone_key)
self._attr_name = f'{self._zone["name"]} Temperature' self._attr_name = f'{self._zone["name"]} temperature'
self._attr_unique_id = ( self._attr_unique_id += "-temp"
f'{self.coordinator.data["system"]["rid"]}-{ac_key}-{zone_key}-temp'
)
@property @property
def native_value(self): def native_value(self):

View File

@ -15,11 +15,3 @@ set_time_to:
min: 0 min: 0
max: 1440 max: 1440
unit_of_measurement: minutes unit_of_measurement: minutes
set_myzone:
name: Set MyZone
description: Change which zone is set as the reference for temperature control (deprecated)
target:
entity:
integration: advantage_air
domain: climate

View File

@ -9,7 +9,7 @@ from .const import (
ADVANTAGE_AIR_STATE_ON, ADVANTAGE_AIR_STATE_ON,
DOMAIN as ADVANTAGE_AIR_DOMAIN, DOMAIN as ADVANTAGE_AIR_DOMAIN,
) )
from .entity import AdvantageAirEntity from .entity import AdvantageAirAcEntity
async def async_setup_entry( async def async_setup_entry(
@ -17,7 +17,7 @@ async def async_setup_entry(
config_entry: ConfigEntry, config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up AdvantageAir toggle platform.""" """Set up AdvantageAir switch platform."""
instance = hass.data[ADVANTAGE_AIR_DOMAIN][config_entry.entry_id] instance = hass.data[ADVANTAGE_AIR_DOMAIN][config_entry.entry_id]
@ -28,18 +28,16 @@ async def async_setup_entry(
async_add_entities(entities) async_add_entities(entities)
class AdvantageAirFreshAir(AdvantageAirEntity, SwitchEntity): class AdvantageAirFreshAir(AdvantageAirAcEntity, SwitchEntity):
"""Representation of Advantage Air fresh air control.""" """Representation of Advantage Air fresh air control."""
_attr_icon = "mdi:air-filter" _attr_icon = "mdi:air-filter"
_attr_name = "Fresh air"
def __init__(self, instance, ac_key): def __init__(self, instance, ac_key):
"""Initialize an Advantage Air fresh air control.""" """Initialize an Advantage Air fresh air control."""
super().__init__(instance, ac_key) super().__init__(instance, ac_key)
self._attr_name = f'{self._ac["name"]} Fresh Air' self._attr_unique_id += "-freshair"
self._attr_unique_id = (
f'{self.coordinator.data["system"]["rid"]}-{ac_key}-freshair'
)
@property @property
def is_on(self): def is_on(self):

View File

@ -40,7 +40,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
ENTRY_WEATHER_COORDINATOR: weather_coordinator, ENTRY_WEATHER_COORDINATOR: weather_coordinator,
} }
hass.config_entries.async_setup_platforms(entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
entry.async_on_unload(entry.add_update_listener(async_update_options)) entry.async_on_unload(entry.add_update_listener(async_update_options))

View File

@ -12,9 +12,9 @@
"api_key": "API\u30ad\u30fc", "api_key": "API\u30ad\u30fc",
"latitude": "\u7def\u5ea6", "latitude": "\u7def\u5ea6",
"longitude": "\u7d4c\u5ea6", "longitude": "\u7d4c\u5ea6",
"name": "\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u306e\u540d\u524d" "name": "\u7d71\u5408\u306e\u540d\u524d"
}, },
"description": "AEMET OpenData\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u3002 API\u30ad\u30fc\u3092\u751f\u6210\u3059\u308b\u306b\u306f\u3001https://opendata.aemet.es/centrodedescargas/altaUsuario \u306b\u30a2\u30af\u30bb\u30b9\u3057\u3066\u304f\u3060\u3055\u3044" "description": "AEMET OpenData\u7d71\u5408\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u3002 API\u30ad\u30fc\u3092\u751f\u6210\u3059\u308b\u306b\u306f\u3001https://opendata.aemet.es/centrodedescargas/altaUsuario \u306b\u30a2\u30af\u30bb\u30b9\u3057\u3066\u304f\u3060\u3055\u3044"
} }
} }
}, },

View File

@ -0,0 +1,7 @@
{
"config": {
"error": {
"invalid_api_key": "Chave de API inv\u00e1lida"
}
}
}

View File

@ -48,7 +48,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
sw_version=agent_client.version, sw_version=agent_client.version,
) )
hass.config_entries.async_setup_platforms(config_entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
return True return True

View File

@ -110,7 +110,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
hass.data.setdefault(DOMAIN, {}) hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.entry_id] = coordinator hass.data[DOMAIN][entry.entry_id] = coordinator
hass.config_entries.async_setup_platforms(entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
# Remove air_quality entities from registry if they exist # Remove air_quality entities from registry if they exist
ent_reg = er.async_get(hass) ent_reg = er.async_get(hass)

View File

@ -25,7 +25,6 @@ SUFFIX_LIMIT: Final = "LIMIT"
ATTRIBUTION: Final = "Data provided by Airly" ATTRIBUTION: Final = "Data provided by Airly"
CONF_USE_NEAREST: Final = "use_nearest" CONF_USE_NEAREST: Final = "use_nearest"
DEFAULT_NAME: Final = "Airly"
DOMAIN: Final = "airly" DOMAIN: Final = "airly"
LABEL_ADVICE: Final = "advice" LABEL_ADVICE: Final = "advice"
MANUFACTURER: Final = "Airly sp. z o.o." MANUFACTURER: Final = "Airly sp. z o.o."

View File

@ -45,7 +45,6 @@ from .const import (
ATTR_LIMIT, ATTR_LIMIT,
ATTR_PERCENT, ATTR_PERCENT,
ATTRIBUTION, ATTRIBUTION,
DEFAULT_NAME,
DOMAIN, DOMAIN,
MANUFACTURER, MANUFACTURER,
SUFFIX_LIMIT, SUFFIX_LIMIT,
@ -137,6 +136,7 @@ async def async_setup_entry(
class AirlySensor(CoordinatorEntity[AirlyDataUpdateCoordinator], SensorEntity): class AirlySensor(CoordinatorEntity[AirlyDataUpdateCoordinator], SensorEntity):
"""Define an Airly sensor.""" """Define an Airly sensor."""
_attr_has_entity_name = True
entity_description: AirlySensorEntityDescription entity_description: AirlySensorEntityDescription
def __init__( def __init__(
@ -151,12 +151,11 @@ class AirlySensor(CoordinatorEntity[AirlyDataUpdateCoordinator], SensorEntity):
entry_type=DeviceEntryType.SERVICE, entry_type=DeviceEntryType.SERVICE,
identifiers={(DOMAIN, f"{coordinator.latitude}-{coordinator.longitude}")}, identifiers={(DOMAIN, f"{coordinator.latitude}-{coordinator.longitude}")},
manufacturer=MANUFACTURER, manufacturer=MANUFACTURER,
name=DEFAULT_NAME, name=name,
configuration_url=URL.format( configuration_url=URL.format(
latitude=coordinator.latitude, longitude=coordinator.longitude latitude=coordinator.latitude, longitude=coordinator.longitude
), ),
) )
self._attr_name = f"{name} {description.name}"
self._attr_unique_id = ( self._attr_unique_id = (
f"{coordinator.latitude}-{coordinator.longitude}-{description.key}".lower() f"{coordinator.latitude}-{coordinator.longitude}-{description.key}".lower()
) )

View File

@ -15,7 +15,7 @@
"longitude": "\u7d4c\u5ea6", "longitude": "\u7d4c\u5ea6",
"name": "\u540d\u524d" "name": "\u540d\u524d"
}, },
"description": "Airly\u306e\u7a7a\u6c17\u54c1\u8cea\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u3002 API\u30ad\u30fc\u3092\u751f\u6210\u3059\u308b\u306b\u306f\u3001https://developer.airly.eu/register \u306b\u30a2\u30af\u30bb\u30b9\u3057\u3066\u304f\u3060\u3055\u3044" "description": "API\u30ad\u30fc\u3092\u751f\u6210\u3059\u308b\u306b\u306f\u3001https://developer.airly.eu/register \u306b\u30a2\u30af\u30bb\u30b9\u3057\u3066\u304f\u3060\u3055\u3044"
} }
} }
}, },

View File

@ -21,7 +21,7 @@
}, },
"system_health": { "system_health": {
"info": { "info": {
"can_reach_server": "Dost\u0119p do serwera Airly", "can_reach_server": "Dost\u0119p do serwera",
"requests_per_day": "Dozwolone dzienne \u017c\u0105dania", "requests_per_day": "Dozwolone dzienne \u017c\u0105dania",
"requests_remaining": "Pozosta\u0142o dozwolonych \u017c\u0105da\u0144" "requests_remaining": "Pozosta\u0142o dozwolonych \u017c\u0105da\u0144"
} }

View File

@ -65,7 +65,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
hass.data.setdefault(DOMAIN, {}) hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.entry_id] = coordinator hass.data[DOMAIN][entry.entry_id] = coordinator
hass.config_entries.async_setup_platforms(entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True return True

View File

@ -17,7 +17,7 @@
"longitude": "\u7d4c\u5ea6", "longitude": "\u7d4c\u5ea6",
"radius": "\u30b9\u30c6\u30fc\u30b7\u30e7\u30f3\u534a\u5f84(\u30de\u30a4\u30eb; \u30aa\u30d7\u30b7\u30e7\u30f3)" "radius": "\u30b9\u30c6\u30fc\u30b7\u30e7\u30f3\u534a\u5f84(\u30de\u30a4\u30eb; \u30aa\u30d7\u30b7\u30e7\u30f3)"
}, },
"description": "AirNow\u306e\u7a7a\u6c17\u54c1\u8cea\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u3002 API\u30ad\u30fc\u3092\u751f\u6210\u3059\u308b\u306b\u306f\u3001https://docs.airnowapi.org/account/request/ \u306b\u30a2\u30af\u30bb\u30b9\u3057\u3066\u304f\u3060\u3055\u3044" "description": "AirNow\u306e\u7a7a\u6c17\u54c1\u8cea\u7d71\u5408\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u3002 API\u30ad\u30fc\u3092\u751f\u6210\u3059\u308b\u306b\u306f\u3001https://docs.airnowapi.org/account/request/ \u306b\u30a2\u30af\u30bb\u30b9\u3057\u3066\u304f\u3060\u3055\u3044"
} }
} }
} }

View File

@ -48,7 +48,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
hass.data[DOMAIN][entry.entry_id] = coordinator hass.data[DOMAIN][entry.entry_id] = coordinator
hass.config_entries.async_setup_platforms(entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True return True

View File

@ -0,0 +1,7 @@
{
"config": {
"error": {
"cannot_connect": "Falha na liga\u00e7\u00e3o"
}
}
}

View File

@ -31,7 +31,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
await coordinator.async_config_entry_first_refresh() await coordinator.async_config_entry_first_refresh()
hass.data[DOMAIN][entry.entry_id] = coordinator hass.data[DOMAIN][entry.entry_id] = coordinator
hass.config_entries.async_setup_platforms(entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True return True

View File

@ -0,0 +1,11 @@
{
"config": {
"step": {
"user": {
"data": {
"host": "Anfitri\u00e3o"
}
}
}
}
}

View File

@ -273,7 +273,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
if CONF_API_KEY in entry.data: if CONF_API_KEY in entry.data:
async_sync_geo_coordinator_update_intervals(hass, entry.data[CONF_API_KEY]) async_sync_geo_coordinator_update_intervals(hass, entry.data[CONF_API_KEY])
hass.config_entries.async_setup_platforms(entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True return True

View File

@ -62,20 +62,20 @@ SENSOR_KIND_VOC = "voc"
GEOGRAPHY_SENSOR_DESCRIPTIONS = ( GEOGRAPHY_SENSOR_DESCRIPTIONS = (
SensorEntityDescription( SensorEntityDescription(
key=SENSOR_KIND_LEVEL, key=SENSOR_KIND_LEVEL,
name="Air Pollution Level", name="Air pollution level",
device_class=DEVICE_CLASS_POLLUTANT_LEVEL, device_class=DEVICE_CLASS_POLLUTANT_LEVEL,
icon="mdi:gauge", icon="mdi:gauge",
), ),
SensorEntityDescription( SensorEntityDescription(
key=SENSOR_KIND_AQI, key=SENSOR_KIND_AQI,
name="Air Quality Index", name="Air quality index",
device_class=SensorDeviceClass.AQI, device_class=SensorDeviceClass.AQI,
native_unit_of_measurement="AQI", native_unit_of_measurement="AQI",
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
), ),
SensorEntityDescription( SensorEntityDescription(
key=SENSOR_KIND_POLLUTANT, key=SENSOR_KIND_POLLUTANT,
name="Main Pollutant", name="Main pollutant",
device_class=DEVICE_CLASS_POLLUTANT_LABEL, device_class=DEVICE_CLASS_POLLUTANT_LABEL,
icon="mdi:chemical-weapon", icon="mdi:chemical-weapon",
), ),
@ -85,7 +85,7 @@ GEOGRAPHY_SENSOR_LOCALES = {"cn": "Chinese", "us": "U.S."}
NODE_PRO_SENSOR_DESCRIPTIONS = ( NODE_PRO_SENSOR_DESCRIPTIONS = (
SensorEntityDescription( SensorEntityDescription(
key=SENSOR_KIND_AQI, key=SENSOR_KIND_AQI,
name="Air Quality Index", name="Air quality index",
device_class=SensorDeviceClass.AQI, device_class=SensorDeviceClass.AQI,
native_unit_of_measurement="AQI", native_unit_of_measurement="AQI",
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
@ -292,6 +292,8 @@ class AirVisualGeographySensor(AirVisualEntity, SensorEntity):
class AirVisualNodeProSensor(AirVisualEntity, SensorEntity): class AirVisualNodeProSensor(AirVisualEntity, SensorEntity):
"""Define an AirVisual sensor related to a Node/Pro unit.""" """Define an AirVisual sensor related to a Node/Pro unit."""
_attr_has_entity_name = True
def __init__( def __init__(
self, self,
coordinator: DataUpdateCoordinator, coordinator: DataUpdateCoordinator,
@ -301,9 +303,6 @@ class AirVisualNodeProSensor(AirVisualEntity, SensorEntity):
"""Initialize.""" """Initialize."""
super().__init__(coordinator, entry, description) super().__init__(coordinator, entry, description)
self._attr_name = (
f"{coordinator.data['settings']['node_name']} Node/Pro: {description.name}"
)
self._attr_unique_id = f"{coordinator.data['serial_number']}_{description.key}" self._attr_unique_id = f"{coordinator.data['serial_number']}_{description.key}"
@property @property

View File

@ -10,6 +10,11 @@
"invalid_api_key": "Chave de API inv\u00e1lida" "invalid_api_key": "Chave de API inv\u00e1lida"
}, },
"step": { "step": {
"geography_by_coords": {
"data": {
"latitude": "Latitude"
}
},
"node_pro": { "node_pro": {
"data": { "data": {
"ip_address": "Servidor", "ip_address": "Servidor",
@ -18,7 +23,7 @@
}, },
"reauth_confirm": { "reauth_confirm": {
"data": { "data": {
"api_key": "" "api_key": "Chave da API"
} }
} }
} }

View File

@ -0,0 +1,13 @@
{
"state": {
"airvisual__pollutant_label": {
"co": "Mon\u00f3xido de carbono",
"n2": "Di\u00f3xido de nitrog\u00e9nio",
"o3": "Ozono",
"p1": "PM10"
},
"airvisual__pollutant_level": {
"moderate": "Moderado"
}
}
}

View File

@ -79,7 +79,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
hass.config_entries.async_setup_platforms(entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True return True

View File

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

View File

@ -13,7 +13,7 @@
"host": "\u30db\u30b9\u30c8", "host": "\u30db\u30b9\u30c8",
"port": "\u30dd\u30fc\u30c8" "port": "\u30dd\u30fc\u30c8"
}, },
"description": "Airzone\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7" "description": "Airzone\u7d71\u5408\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7"
} }
} }
} }

View File

@ -0,0 +1,12 @@
{
"config": {
"step": {
"user": {
"data": {
"host": "Servidor",
"port": "Porta"
}
}
}
}
}

View File

@ -16,7 +16,7 @@ from .const import CLIENT_ID, DOMAIN
_LOGGER: Final = logging.getLogger(__name__) _LOGGER: Final = logging.getLogger(__name__)
PLATFORMS: list[Platform] = [Platform.COVER] PLATFORMS: list[Platform] = [Platform.COVER, Platform.SENSOR]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
@ -33,7 +33,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
raise ConfigEntryNotReady("Can not connect to host") from ex raise ConfigEntryNotReady("Can not connect to host") from ex
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = acc hass.data.setdefault(DOMAIN, {})[entry.entry_id] = acc
hass.config_entries.async_setup_platforms(entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True return True

View File

@ -24,6 +24,7 @@ from homeassistant.const import (
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import PlatformNotReady from homeassistant.exceptions import PlatformNotReady
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
@ -87,9 +88,19 @@ class AladdinDevice(CoverEntity):
self._device_id = device["device_id"] self._device_id = device["device_id"]
self._number = device["door_number"] self._number = device["door_number"]
self._attr_name = device["name"] self._name = device["name"]
self._serial = device["serial"] self._serial = device["serial"]
self._attr_unique_id = f"{self._device_id}-{self._number}" self._attr_unique_id = f"{self._device_id}-{self._number}"
self._attr_has_entity_name = True
@property
def device_info(self) -> DeviceInfo | None:
"""Device information for Aladdin Connect cover."""
return DeviceInfo(
identifiers={(DOMAIN, self._device_id)},
name=self._name,
manufacturer="Overhead Door",
)
async def async_added_to_hass(self) -> None: async def async_added_to_hass(self) -> None:
"""Connect Aladdin Connect to the cloud.""" """Connect Aladdin Connect to the cloud."""

View File

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

View File

@ -0,0 +1,116 @@
"""Support for Aladdin Connect Garage Door sensors."""
from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
from typing import cast
from AIOAladdinConnect import AladdinConnectClient
from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import PERCENTAGE, SIGNAL_STRENGTH_DECIBELS
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN
from .model import DoorDevice
@dataclass
class AccSensorEntityDescriptionMixin:
"""Mixin for required keys."""
value_fn: Callable
@dataclass
class AccSensorEntityDescription(
SensorEntityDescription, AccSensorEntityDescriptionMixin
):
"""Describes AladdinConnect sensor entity."""
SENSORS: tuple[AccSensorEntityDescription, ...] = (
AccSensorEntityDescription(
key="battery_level",
name="Battery level",
device_class=SensorDeviceClass.BATTERY,
entity_registry_enabled_default=False,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
value_fn=AladdinConnectClient.get_battery_status,
),
AccSensorEntityDescription(
key="rssi",
name="Wi-Fi RSSI",
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
entity_registry_enabled_default=False,
native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS,
state_class=SensorStateClass.MEASUREMENT,
value_fn=AladdinConnectClient.get_rssi_status,
),
)
async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up Aladdin Connect sensor devices."""
acc: AladdinConnectClient = hass.data[DOMAIN][entry.entry_id]
entities = []
doors = await acc.get_doors()
for door in doors:
entities.extend(
[AladdinConnectSensor(acc, door, description) for description in SENSORS]
)
async_add_entities(entities)
class AladdinConnectSensor(SensorEntity):
"""A sensor implementation for Aladdin Connect devices."""
_device: AladdinConnectSensor
entity_description: AccSensorEntityDescription
def __init__(
self,
acc: AladdinConnectClient,
device: DoorDevice,
description: AccSensorEntityDescription,
) -> None:
"""Initialize a sensor for an Abode device."""
self._device_id = device["device_id"]
self._number = device["door_number"]
self._name = device["name"]
self._acc = acc
self.entity_description = description
self._attr_unique_id = f"{self._device_id}-{self._number}-{description.key}"
self._attr_has_entity_name = True
@property
def device_info(self) -> DeviceInfo | None:
"""Device information for Aladdin Connect sensors."""
return DeviceInfo(
identifiers={(DOMAIN, self._device_id)},
name=self._name,
manufacturer="Overhead Door",
)
@property
def native_value(self) -> float | None:
"""Return the state of the sensor."""
return cast(
float,
self.entity_description.value_fn(self._acc, self._device_id, self._number),
)

View File

@ -13,8 +13,8 @@
"data": { "data": {
"password": "\u30d1\u30b9\u30ef\u30fc\u30c9" "password": "\u30d1\u30b9\u30ef\u30fc\u30c9"
}, },
"description": "Aladdin Connect\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3067\u306f\u3001\u30a2\u30ab\u30a6\u30f3\u30c8\u3092\u518d\u8a8d\u8a3c\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059", "description": "Aladdin Connect\u7d71\u5408\u3067\u306f\u3001\u30a2\u30ab\u30a6\u30f3\u30c8\u3092\u518d\u8a8d\u8a3c\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059",
"title": "\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u306e\u518d\u8a8d\u8a3c" "title": "\u7d71\u5408\u306e\u518d\u8a8d\u8a3c"
}, },
"user": { "user": {
"data": { "data": {

View File

@ -0,0 +1,17 @@
{
"config": {
"abort": {
"reauth_successful": "Reautentica\u00e7\u00e3o bem sucedida"
},
"error": {
"invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida"
},
"step": {
"user": {
"data": {
"username": "Nome de Utilizador"
}
}
}
}
}

View File

@ -13,6 +13,7 @@
"armed_custom_bypass": "Armado com desvio personalizado", "armed_custom_bypass": "Armado com desvio personalizado",
"armed_home": "Armado Casa", "armed_home": "Armado Casa",
"armed_night": "Armado noite", "armed_night": "Armado noite",
"armed_vacation": "Armado f\u00e9rias",
"arming": "A armar", "arming": "A armar",
"disarmed": "Desarmado", "disarmed": "Desarmado",
"disarming": "A desarmar", "disarming": "A desarmar",

View File

@ -131,7 +131,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
await open_connection() await open_connection()
hass.config_entries.async_setup_platforms(entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True return True

View File

@ -3,6 +3,9 @@
"abort": { "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" "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": { "step": {
"protocol": { "protocol": {
"data": { "data": {

View File

@ -903,6 +903,7 @@ class AlexaContactSensor(AlexaCapability):
"en-CA", "en-CA",
"en-IN", "en-IN",
"en-US", "en-US",
"en-GB",
"es-ES", "es-ES",
"it-IT", "it-IT",
"ja-JP", "ja-JP",
@ -951,6 +952,7 @@ class AlexaMotionSensor(AlexaCapability):
"en-CA", "en-CA",
"en-IN", "en-IN",
"en-US", "en-US",
"en-GB",
"es-ES", "es-ES",
"it-IT", "it-IT",
"ja-JP", "ja-JP",

View File

@ -65,6 +65,7 @@ class AbstractConfig(ABC):
async def async_enable_proactive_mode(self): async def async_enable_proactive_mode(self):
"""Enable proactive mode.""" """Enable proactive mode."""
_LOGGER.debug("Enable proactive mode")
if self._unsub_proactive_report is None: if self._unsub_proactive_report is None:
self._unsub_proactive_report = self.hass.async_create_task( self._unsub_proactive_report = self.hass.async_create_task(
async_enable_proactive_mode(self.hass, self) async_enable_proactive_mode(self.hass, self)
@ -77,6 +78,7 @@ class AbstractConfig(ABC):
async def async_disable_proactive_mode(self): async def async_disable_proactive_mode(self):
"""Disable proactive mode.""" """Disable proactive mode."""
_LOGGER.debug("Disable proactive mode")
if unsub_func := await self._unsub_proactive_report: if unsub_func := await self._unsub_proactive_report:
unsub_func() unsub_func()
self._unsub_proactive_report = None self._unsub_proactive_report = None
@ -113,7 +115,6 @@ class AbstractConfig(ABC):
self._store.set_authorized(authorized) self._store.set_authorized(authorized)
if self.should_report_state != self.is_reporting_states: if self.should_report_state != self.is_reporting_states:
if self.should_report_state: if self.should_report_state:
_LOGGER.debug("Enable proactive mode")
try: try:
await self.async_enable_proactive_mode() await self.async_enable_proactive_mode()
except Exception: except Exception:
@ -121,7 +122,6 @@ class AbstractConfig(ABC):
self._store.set_authorized(False) self._store.set_authorized(False)
raise raise
else: else:
_LOGGER.debug("Disable proactive mode")
await self.async_disable_proactive_mode() await self.async_disable_proactive_mode()

View File

@ -12,7 +12,6 @@ from .auth import Auth
from .config import AbstractConfig from .config import AbstractConfig
from .const import CONF_ENDPOINT, CONF_ENTITY_CONFIG, CONF_FILTER, CONF_LOCALE from .const import CONF_ENDPOINT, CONF_ENTITY_CONFIG, CONF_FILTER, CONF_LOCALE
from .smart_home import async_handle_message from .smart_home import async_handle_message
from .state_report import async_enable_proactive_mode
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
SMART_HOME_HTTP_ENDPOINT = "/api/alexa/smart_home" SMART_HOME_HTTP_ENDPOINT = "/api/alexa/smart_home"
@ -104,7 +103,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> None:
hass.http.register_view(SmartHomeView(smart_home_config)) hass.http.register_view(SmartHomeView(smart_home_config))
if smart_home_config.should_report_state: if smart_home_config.should_report_state:
await async_enable_proactive_mode(hass, smart_home_config) await smart_home_config.async_enable_proactive_mode()
class SmartHomeView(HomeAssistantView): class SmartHomeView(HomeAssistantView):

View File

@ -5,7 +5,7 @@ import asyncio
from datetime import timedelta from datetime import timedelta
import logging import logging
import time import time
from typing import Optional, cast from typing import Any
from aiohttp import ClientError, ClientSession from aiohttp import ClientError, ClientSession
import async_timeout import async_timeout
@ -167,8 +167,8 @@ async def _configure_almond_for_ha(
return return
_LOGGER.debug("Configuring Almond to connect to Home Assistant at %s", hass_url) _LOGGER.debug("Configuring Almond to connect to Home Assistant at %s", hass_url)
store = storage.Store(hass, STORAGE_VERSION, STORAGE_KEY) store = storage.Store[dict[str, Any]](hass, STORAGE_VERSION, STORAGE_KEY)
data = cast(Optional[dict], await store.async_load()) data = await store.async_load()
if data is None: if data is None:
data = {} data = {}

View File

@ -4,7 +4,7 @@
"cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f",
"missing_configuration": "\u30b3\u30f3\u30dd\u30fc\u30cd\u30f3\u30c8\u304c\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8\u306b\u5f93\u3063\u3066\u304f\u3060\u3055\u3044\u3002", "missing_configuration": "\u30b3\u30f3\u30dd\u30fc\u30cd\u30f3\u30c8\u304c\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8\u306b\u5f93\u3063\u3066\u304f\u3060\u3055\u3044\u3002",
"no_url_available": "\u4f7f\u7528\u53ef\u80fd\u306aURL\u304c\u3042\u308a\u307e\u305b\u3093\u3002\u3053\u306e\u30a8\u30e9\u30fc\u306e\u8a73\u7d30\u306b\u3064\u3044\u3066\u306f\u3001[\u30d8\u30eb\u30d7\u30bb\u30af\u30b7\u30e7\u30f3\u3092\u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044]({docs_url})", "no_url_available": "\u4f7f\u7528\u53ef\u80fd\u306aURL\u304c\u3042\u308a\u307e\u305b\u3093\u3002\u3053\u306e\u30a8\u30e9\u30fc\u306e\u8a73\u7d30\u306b\u3064\u3044\u3066\u306f\u3001[\u30d8\u30eb\u30d7\u30bb\u30af\u30b7\u30e7\u30f3\u3092\u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044]({docs_url})",
"single_instance_allowed": "\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059\u3002\u5358\u4e00\u306e\u8a2d\u5b9a\u3057\u304b\u3067\u304d\u307e\u305b\u3093\u3002" "single_instance_allowed": "\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059\u3002\u8a2d\u5b9a\u3067\u304d\u308b\u306e\u306f1\u3064\u3060\u3051\u3067\u3059\u3002"
}, },
"step": { "step": {
"hassio_confirm": { "hassio_confirm": {

View File

@ -3,10 +3,16 @@ from __future__ import annotations
from ambee import AirQuality, Ambee, AmbeeAuthenticationError, Pollen from ambee import AirQuality, Ambee, AmbeeAuthenticationError, Pollen
from homeassistant.components.repairs import (
IssueSeverity,
async_create_issue,
async_delete_issue,
)
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, Platform from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers.typing import ConfigType
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from .const import DOMAIN, LOGGER, SCAN_INTERVAL, SERVICE_AIR_QUALITY, SERVICE_POLLEN from .const import DOMAIN, LOGGER, SCAN_INTERVAL, SERVICE_AIR_QUALITY, SERVICE_POLLEN
@ -14,6 +20,20 @@ from .const import DOMAIN, LOGGER, SCAN_INTERVAL, SERVICE_AIR_QUALITY, SERVICE_P
PLATFORMS = [Platform.SENSOR] PLATFORMS = [Platform.SENSOR]
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the Ambee integration."""
async_create_issue(
hass,
DOMAIN,
"pending_removal",
breaks_in_ha_version="2022.10.0",
is_fixable=False,
severity=IssueSeverity.WARNING,
translation_key="pending_removal",
)
return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Ambee from a config entry.""" """Set up Ambee from a config entry."""
hass.data.setdefault(DOMAIN, {}).setdefault(entry.entry_id, {}) hass.data.setdefault(DOMAIN, {}).setdefault(entry.entry_id, {})
@ -58,7 +78,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
await pollen.async_config_entry_first_refresh() await pollen.async_config_entry_first_refresh()
hass.data[DOMAIN][entry.entry_id][SERVICE_POLLEN] = pollen hass.data[DOMAIN][entry.entry_id][SERVICE_POLLEN] = pollen
hass.config_entries.async_setup_platforms(entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True return True
@ -67,4 +87,6 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok: if unload_ok:
del hass.data[DOMAIN][entry.entry_id] del hass.data[DOMAIN][entry.entry_id]
if not hass.data[DOMAIN]:
async_delete_issue(hass, DOMAIN, "pending_removal")
return unload_ok return unload_ok

View File

@ -27,7 +27,7 @@ SERVICE_AIR_QUALITY: Final = "air_quality"
SERVICE_POLLEN: Final = "pollen" SERVICE_POLLEN: Final = "pollen"
SERVICES: dict[str, str] = { SERVICES: dict[str, str] = {
SERVICE_AIR_QUALITY: "Air Quality", SERVICE_AIR_QUALITY: "Air quality",
SERVICE_POLLEN: "Pollen", SERVICE_POLLEN: "Pollen",
} }
@ -35,25 +35,25 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
SERVICE_AIR_QUALITY: [ SERVICE_AIR_QUALITY: [
SensorEntityDescription( SensorEntityDescription(
key="particulate_matter_2_5", key="particulate_matter_2_5",
name="Particulate Matter < 2.5 μm", name="Particulate matter < 2.5 μm",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
), ),
SensorEntityDescription( SensorEntityDescription(
key="particulate_matter_10", key="particulate_matter_10",
name="Particulate Matter < 10 μm", name="Particulate matter < 10 μm",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
), ),
SensorEntityDescription( SensorEntityDescription(
key="sulphur_dioxide", key="sulphur_dioxide",
name="Sulphur Dioxide (SO2)", name="Sulphur dioxide (SO2)",
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION, native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
), ),
SensorEntityDescription( SensorEntityDescription(
key="nitrogen_dioxide", key="nitrogen_dioxide",
name="Nitrogen Dioxide (NO2)", name="Nitrogen dioxide (NO2)",
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION, native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
), ),
@ -65,60 +65,60 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
), ),
SensorEntityDescription( SensorEntityDescription(
key="carbon_monoxide", key="carbon_monoxide",
name="Carbon Monoxide (CO)", name="Carbon monoxide (CO)",
device_class=SensorDeviceClass.CO, device_class=SensorDeviceClass.CO,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION, native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
), ),
SensorEntityDescription( SensorEntityDescription(
key="air_quality_index", key="air_quality_index",
name="Air Quality Index (AQI)", name="Air quality index (AQI)",
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
), ),
], ],
SERVICE_POLLEN: [ SERVICE_POLLEN: [
SensorEntityDescription( SensorEntityDescription(
key="grass", key="grass",
name="Grass Pollen", name="Grass",
icon="mdi:grass", icon="mdi:grass",
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER, native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
), ),
SensorEntityDescription( SensorEntityDescription(
key="tree", key="tree",
name="Tree Pollen", name="Tree",
icon="mdi:tree", icon="mdi:tree",
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER, native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
), ),
SensorEntityDescription( SensorEntityDescription(
key="weed", key="weed",
name="Weed Pollen", name="Weed",
icon="mdi:sprout", icon="mdi:sprout",
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER, native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
), ),
SensorEntityDescription( SensorEntityDescription(
key="grass_risk", key="grass_risk",
name="Grass Pollen Risk", name="Grass risk",
icon="mdi:grass", icon="mdi:grass",
device_class=DEVICE_CLASS_AMBEE_RISK, device_class=DEVICE_CLASS_AMBEE_RISK,
), ),
SensorEntityDescription( SensorEntityDescription(
key="tree_risk", key="tree_risk",
name="Tree Pollen Risk", name="Tree risk",
icon="mdi:tree", icon="mdi:tree",
device_class=DEVICE_CLASS_AMBEE_RISK, device_class=DEVICE_CLASS_AMBEE_RISK,
), ),
SensorEntityDescription( SensorEntityDescription(
key="weed_risk", key="weed_risk",
name="Weed Pollen Risk", name="Weed risk",
icon="mdi:sprout", icon="mdi:sprout",
device_class=DEVICE_CLASS_AMBEE_RISK, device_class=DEVICE_CLASS_AMBEE_RISK,
), ),
SensorEntityDescription( SensorEntityDescription(
key="grass_poaceae", key="grass_poaceae",
name="Poaceae Grass Pollen", name="Poaceae grass",
icon="mdi:grass", icon="mdi:grass",
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER, native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
@ -126,7 +126,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
), ),
SensorEntityDescription( SensorEntityDescription(
key="tree_alder", key="tree_alder",
name="Alder Tree Pollen", name="Alder tree",
icon="mdi:tree", icon="mdi:tree",
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER, native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
@ -134,7 +134,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
), ),
SensorEntityDescription( SensorEntityDescription(
key="tree_birch", key="tree_birch",
name="Birch Tree Pollen", name="Birch tree",
icon="mdi:tree", icon="mdi:tree",
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER, native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
@ -142,7 +142,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
), ),
SensorEntityDescription( SensorEntityDescription(
key="tree_cypress", key="tree_cypress",
name="Cypress Tree Pollen", name="Cypress tree",
icon="mdi:tree", icon="mdi:tree",
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER, native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
@ -150,7 +150,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
), ),
SensorEntityDescription( SensorEntityDescription(
key="tree_elm", key="tree_elm",
name="Elm Tree Pollen", name="Elm tree",
icon="mdi:tree", icon="mdi:tree",
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER, native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
@ -158,7 +158,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
), ),
SensorEntityDescription( SensorEntityDescription(
key="tree_hazel", key="tree_hazel",
name="Hazel Tree Pollen", name="Hazel tree",
icon="mdi:tree", icon="mdi:tree",
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER, native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
@ -166,7 +166,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
), ),
SensorEntityDescription( SensorEntityDescription(
key="tree_oak", key="tree_oak",
name="Oak Tree Pollen", name="Oak tree",
icon="mdi:tree", icon="mdi:tree",
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER, native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
@ -174,7 +174,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
), ),
SensorEntityDescription( SensorEntityDescription(
key="tree_pine", key="tree_pine",
name="Pine Tree Pollen", name="Pine tree",
icon="mdi:tree", icon="mdi:tree",
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER, native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
@ -182,7 +182,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
), ),
SensorEntityDescription( SensorEntityDescription(
key="tree_plane", key="tree_plane",
name="Plane Tree Pollen", name="Plane tree",
icon="mdi:tree", icon="mdi:tree",
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER, native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
@ -190,7 +190,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
), ),
SensorEntityDescription( SensorEntityDescription(
key="tree_poplar", key="tree_poplar",
name="Poplar Tree Pollen", name="Poplar tree",
icon="mdi:tree", icon="mdi:tree",
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER, native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
@ -198,7 +198,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
), ),
SensorEntityDescription( SensorEntityDescription(
key="weed_chenopod", key="weed_chenopod",
name="Chenopod Weed Pollen", name="Chenopod weed",
icon="mdi:sprout", icon="mdi:sprout",
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER, native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
@ -206,7 +206,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
), ),
SensorEntityDescription( SensorEntityDescription(
key="weed_mugwort", key="weed_mugwort",
name="Mugwort Weed Pollen", name="Mugwort weed",
icon="mdi:sprout", icon="mdi:sprout",
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER, native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
@ -214,7 +214,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
), ),
SensorEntityDescription( SensorEntityDescription(
key="weed_nettle", key="weed_nettle",
name="Nettle Weed Pollen", name="Nettle weed",
icon="mdi:sprout", icon="mdi:sprout",
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER, native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
@ -222,7 +222,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
), ),
SensorEntityDescription( SensorEntityDescription(
key="weed_ragweed", key="weed_ragweed",
name="Ragweed Weed Pollen", name="Ragweed weed",
icon="mdi:sprout", icon="mdi:sprout",
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER, native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,

View File

@ -4,6 +4,7 @@
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/ambee", "documentation": "https://www.home-assistant.io/integrations/ambee",
"requirements": ["ambee==0.4.0"], "requirements": ["ambee==0.4.0"],
"dependencies": ["repairs"],
"codeowners": ["@frenck"], "codeowners": ["@frenck"],
"quality_scale": "platinum", "quality_scale": "platinum",
"iot_class": "cloud_polling" "iot_class": "cloud_polling"

View File

@ -42,6 +42,8 @@ async def async_setup_entry(
class AmbeeSensorEntity(CoordinatorEntity, SensorEntity): class AmbeeSensorEntity(CoordinatorEntity, SensorEntity):
"""Defines an Ambee sensor.""" """Defines an Ambee sensor."""
_attr_has_entity_name = True
def __init__( def __init__(
self, self,
*, *,

View File

@ -24,5 +24,11 @@
"abort": { "abort": {
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]" "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
} }
},
"issues": {
"pending_removal": {
"title": "The Ambee integration is being removed",
"description": "The Ambee integration is pending removal from Home Assistant and will no longer be available as of Home Assistant 2022.10.\n\nThe integration is being removed, because Ambee removed their free (limited) accounts and doesn't provide a way for regular users to sign up for a paid plan anymore.\n\nRemove the Ambee integration entry from your instance to fix this issue."
}
} }
} }

View File

@ -24,5 +24,11 @@
"description": "Richte Ambee f\u00fcr die Integration mit Home Assistant ein." "description": "Richte Ambee f\u00fcr die Integration mit Home Assistant ein."
} }
} }
},
"issues": {
"pending_removal": {
"description": "Die Ambee-Integration ist dabei, aus Home Assistant entfernt zu werden und wird ab Home Assistant 2022.10 nicht mehr verf\u00fcgbar sein.\n\nDie Integration wird entfernt, weil Ambee seine kostenlosen (begrenzten) Konten entfernt hat und keine M\u00f6glichkeit mehr f\u00fcr regul\u00e4re Nutzer bietet, sich f\u00fcr einen kostenpflichtigen Plan anzumelden.\n\nEntferne den Ambee-Integrationseintrag aus deiner Instanz, um dieses Problem zu beheben.",
"title": "Die Ambee-Integration wird entfernt"
}
} }
} }

View File

@ -24,5 +24,11 @@
"description": "Set up Ambee to integrate with Home Assistant." "description": "Set up Ambee to integrate with Home Assistant."
} }
} }
},
"issues": {
"pending_removal": {
"description": "The Ambee integration is pending removal from Home Assistant and will no longer be available as of Home Assistant 2022.10.\n\nThe integration is being removed, because Ambee removed their free (limited) accounts and doesn't provide a way for regular users to sign up for a paid plan anymore.\n\nRemove the Ambee integration entry from your instance to fix this issue.",
"title": "The Ambee integration is being removed"
}
} }
} }

View File

@ -24,5 +24,11 @@
"description": "Configura Ambee per l'integrazione con Home Assistant." "description": "Configura Ambee per l'integrazione con Home Assistant."
} }
} }
},
"issues": {
"pending_removal": {
"description": "L'integrazione Ambee \u00e8 in attesa di rimozione da Home Assistant e non sar\u00e0 pi\u00f9 disponibile a partire da Home Assistant 2022.10. \n\nL'integrazione \u00e8 stata rimossa, perch\u00e9 Ambee ha rimosso i loro account gratuiti (limitati) e non offre pi\u00f9 agli utenti regolari un modo per iscriversi a un piano a pagamento. \n\nRimuovi la voce di integrazione Ambee dalla tua istanza per risolvere questo problema.",
"title": "L'integrazione Ambee verr\u00e0 rimossa"
}
} }
} }

View File

@ -24,5 +24,11 @@
"description": "Skonfiguruj Ambee, aby zintegrowa\u0107 go z Home Assistantem." "description": "Skonfiguruj Ambee, aby zintegrowa\u0107 go z Home Assistantem."
} }
} }
},
"issues": {
"pending_removal": {
"description": "Integracja Ambee oczekuje na usuni\u0119cie z Home Assistanta i nie b\u0119dzie ju\u017c dost\u0119pna od Home Assistant 2022.10. \n\nIntegracja jest usuwana, poniewa\u017c Ambee usun\u0105\u0142 ich bezp\u0142atne (ograniczone) konta i nie zapewnia ju\u017c zwyk\u0142ym u\u017cytkownikom mo\u017cliwo\u015bci zarejestrowania si\u0119 w p\u0142atnym planie. \n\nUsu\u0144 integracj\u0119 Ambee z Home Assistanta, aby rozwi\u0105za\u0107 ten problem.",
"title": "Integracja Ambee zostanie usuni\u0119ta"
}
} }
} }

View File

@ -24,5 +24,11 @@
"description": "Configure o Ambee para integrar com o Home Assistant." "description": "Configure o Ambee para integrar com o Home Assistant."
} }
} }
},
"issues": {
"pending_removal": {
"description": "A integra\u00e7\u00e3o do Ambee est\u00e1 com remo\u00e7\u00e3o pendente do Home Assistant e n\u00e3o estar\u00e1 mais dispon\u00edvel a partir do Home Assistant 2022.10. \n\n A integra\u00e7\u00e3o est\u00e1 sendo removida, porque a Ambee removeu suas contas gratuitas (limitadas) e n\u00e3o oferece mais uma maneira de usu\u00e1rios regulares se inscreverem em um plano pago. \n\n Remova a entrada de integra\u00e7\u00e3o Ambee de sua inst\u00e2ncia para corrigir esse problema.",
"title": "A integra\u00e7\u00e3o Ambee est\u00e1 sendo removida"
}
} }
} }

View File

@ -0,0 +1,17 @@
{
"config": {
"step": {
"reauth_confirm": {
"data": {
"api_key": "Chave da API"
}
},
"user": {
"data": {
"latitude": "Latitude",
"name": "Nome"
}
}
}
}
}

View File

@ -24,5 +24,11 @@
"description": "\u8a2d\u5b9a Ambee \u4ee5\u6574\u5408\u81f3 Home Assistant\u3002" "description": "\u8a2d\u5b9a Ambee \u4ee5\u6574\u5408\u81f3 Home Assistant\u3002"
} }
} }
},
"issues": {
"pending_removal": {
"description": "Ambee \u6574\u5408\u5373\u5c07\u7531 Home Assistant \u4e2d\u79fb\u9664\u3001\u4e26\u65bc Home Assistant 2022.10 \u7248\u5f8c\u7121\u6cd5\u518d\u4f7f\u7528\u3002\n\n\u7531\u65bc Ambee \u79fb\u9664\u4e86\u5176\u514d\u8cbb\uff08\u6709\u9650\uff09\u5e33\u865f\u3001\u4e26\u4e14\u4e0d\u518d\u63d0\u4f9b\u4e00\u822c\u4f7f\u7528\u8005\u8a3b\u518a\u4ed8\u8cbb\u670d\u52d9\u3001\u6574\u5408\u5373\u5c07\u79fb\u9664\u3002\n\n\u7531 configuration.yaml \u6a94\u6848\u4e2d\u79fb\u9664 YAML \u8a2d\u5b9a\u4e26\u91cd\u555f Home Assistant to \u4ee5\u4fee\u6b63\u6b64\u554f\u984c\u3002",
"title": "Ambee \u6574\u5408\u5373\u5c07\u79fb\u9664"
}
} }
} }

View File

@ -19,7 +19,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
coordinator = AmberUpdateCoordinator(hass, api_instance, site_id) coordinator = AmberUpdateCoordinator(hass, api_instance, site_id)
await coordinator.async_config_entry_first_refresh() await coordinator.async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
hass.config_entries.async_setup_platforms(entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True return True

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