Compare commits

..

12 Commits

Author SHA1 Message Date
G Johansson
e20bda33a4 Fix entity test 2025-01-19 21:29:42 +01:00
G Johansson
f2125a1133 Reset tts test 2 2025-01-19 21:29:42 +01:00
G Johansson
557caa7ecb Reset tts test 2025-01-19 21:29:42 +01:00
G Johansson
d973e42c97 Reset kira test 2025-01-19 21:29:42 +01:00
G Johansson
428c7ced94 Reset blackbird test 2025-01-19 21:29:42 +01:00
G Johansson
7b14bd89a0 Fix template preview 2025-01-19 21:29:42 +01:00
G Johansson
f1f45d6b5b Fixes 2025-01-19 21:29:42 +01:00
G Johansson
50e4311416 Remove 2025-01-19 21:29:42 +01:00
G Johansson
1a32a2f9c2 Mod tests 2025-01-19 21:29:42 +01:00
G Johansson
f9cecc0cd5 Fix some tests 2025-01-19 21:29:42 +01:00
G Johansson
6fe2612f1d Fix comment 2025-01-19 21:29:42 +01:00
G Johansson
43fbb2ab7b Don't allow entities without platform 2025-01-19 21:29:42 +01:00
6388 changed files with 45132 additions and 145762 deletions

View File

@@ -46,8 +46,6 @@
- This PR fixes or closes issue: fixes #
- This PR is related to issue:
- Link to documentation pull request:
- Link to developer documentation pull request:
- Link to frontend pull request:
## Checklist
<!--

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 65 KiB

View File

@@ -1,100 +0,0 @@
# Instructions for GitHub Copilot
This repository holds the core of Home Assistant, a Python 3 based home
automation application.
- Python code must be compatible with Python 3.13
- Use the newest Python language features if possible:
- Pattern matching
- Type hints
- f-strings for string formatting over `%` or `.format()`
- Dataclasses
- Walrus operator
- Code quality tools:
- Formatting: Ruff
- Linting: PyLint and Ruff
- Type checking: MyPy
- Testing: pytest with plain functions and fixtures
- Inline code documentation:
- File headers should be short and concise:
```python
"""Integration for Peblar EV chargers."""
```
- Every method and function needs a docstring:
```python
async def async_setup_entry(hass: HomeAssistant, entry: PeblarConfigEntry) -> bool:
"""Set up Peblar from a config entry."""
...
```
- All code and comments and other text are written in American English
- Follow existing code style patterns as much as possible
- Core locations:
- Shared constants: `homeassistant/const.py`, use them instead of hardcoding
strings or creating duplicate integration constants.
- Integration files:
- Constants: `homeassistant/components/{domain}/const.py`
- Models: `homeassistant/components/{domain}/models.py`
- Coordinator: `homeassistant/components/{domain}/coordinator.py`
- Config flow: `homeassistant/components/{domain}/config_flow.py`
- Platform code: `homeassistant/components/{domain}/{platform}.py`
- All external I/O operations must be async
- Async patterns:
- Avoid sleeping in loops
- Avoid awaiting in loops, gather instead
- No blocking calls
- Polling:
- Follow update coordinator pattern, when possible
- Polling interval may not be configurable by the user
- For local network polling, the minimum interval is 5 seconds
- For cloud polling, the minimum interval is 60 seconds
- Error handling:
- Use specific exceptions from `homeassistant.exceptions`
- Setup failures:
- Temporary: Raise `ConfigEntryNotReady`
- Permanent: Use `ConfigEntryError`
- Logging:
- Message format:
- No periods at end
- No integration names or domains (added automatically)
- No sensitive data (keys, tokens, passwords), even when those are incorrect.
- Be very restrictive on the use of logging info messages, use debug for
anything which is not targeting the user.
- Use lazy logging (no f-strings):
```python
_LOGGER.debug("This is a log message with %s", variable)
```
- Entities:
- Ensure unique IDs for state persistence:
- Unique IDs should not contain values that are subject to user or network change.
- An ID needs to be unique per platform, not per integration.
- The ID does not have to contain the integration domain or platform.
- Acceptable examples:
- Serial number of a device
- MAC address of a device formatted using `homeassistant.helpers.device_registry.format_mac`
Do not obtain the MAC address through arp cache of local network access,
only use the MAC address provided by discovery or the device itself.
- Unique identifier that is physically printed on the device or burned into an EEPROM
- Not acceptable examples:
- IP Address
- Device name
- Hostname
- URL
- Email address
- Username
- For entities that are setup by a config entry, the config entry ID
can be used as a last resort if no other Unique ID is available.
For example: `f"{entry.entry_id}-battery"`
- If the state value is unknown, use `None`
- Do not use the `unavailable` string as a state value,
implement the `available()` property method instead
- Do not use the `unknown` string as a state value, use `None` instead
- Extra entity state attributes:
- The keys of all state attributes should always be present
- If the value is unknown, use `None`
- Provide descriptive state attributes
- Testing:
- Test location: `tests/components/{domain}/`
- Use pytest fixtures from `tests.common`
- Mock external dependencies
- Use snapshots for complex data
- Follow existing test patterns

View File

@@ -32,7 +32,7 @@ jobs:
fetch-depth: 0
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v5.4.0
uses: actions/setup-python@v5.3.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
@@ -94,7 +94,7 @@ jobs:
- name: Download nightly wheels of frontend
if: needs.init.outputs.channel == 'dev'
uses: dawidd6/action-download-artifact@v8
uses: dawidd6/action-download-artifact@v7
with:
github_token: ${{secrets.GITHUB_TOKEN}}
repo: home-assistant/frontend
@@ -105,7 +105,7 @@ jobs:
- name: Download nightly wheels of intents
if: needs.init.outputs.channel == 'dev'
uses: dawidd6/action-download-artifact@v8
uses: dawidd6/action-download-artifact@v7
with:
github_token: ${{secrets.GITHUB_TOKEN}}
repo: home-assistant/intents-package
@@ -116,7 +116,7 @@ jobs:
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
if: needs.init.outputs.channel == 'dev'
uses: actions/setup-python@v5.4.0
uses: actions/setup-python@v5.3.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
@@ -324,7 +324,7 @@ jobs:
uses: actions/checkout@v4.2.2
- name: Install Cosign
uses: sigstore/cosign-installer@v3.8.1
uses: sigstore/cosign-installer@v3.7.0
with:
cosign-release: "v2.2.3"
@@ -454,7 +454,7 @@ jobs:
uses: actions/checkout@v4.2.2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v5.4.0
uses: actions/setup-python@v5.3.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
@@ -509,7 +509,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build Docker image
uses: docker/build-push-action@0adf9959216b96bec444f325f1e493d4aa344497 # v6.14.0
uses: docker/build-push-action@b32b51a8eda65d6793cd0494a773d4f6bcef32dc # v6.11.0
with:
context: . # So action will not pull the repository again
file: ./script/hassfest/docker/Dockerfile
@@ -522,7 +522,7 @@ jobs:
- name: Push Docker image
if: needs.init.outputs.channel != 'dev' && needs.init.outputs.publish == 'true'
id: push
uses: docker/build-push-action@0adf9959216b96bec444f325f1e493d4aa344497 # v6.14.0
uses: docker/build-push-action@b32b51a8eda65d6793cd0494a773d4f6bcef32dc # v6.11.0
with:
context: . # So action will not pull the repository again
file: ./script/hassfest/docker/Dockerfile
@@ -531,7 +531,7 @@ jobs:
- name: Generate artifact attestation
if: needs.init.outputs.channel != 'dev' && needs.init.outputs.publish == 'true'
uses: actions/attest-build-provenance@520d128f165991a6c774bcb264f323e3d70747f4 # v2.2.0
uses: actions/attest-build-provenance@7668571508540a607bdfd90a87a560489fe372eb # v2.1.0
with:
subject-name: ${{ env.HASSFEST_IMAGE_NAME }}
subject-digest: ${{ steps.push.outputs.digest }}

View File

@@ -40,7 +40,7 @@ env:
CACHE_VERSION: 11
UV_CACHE_VERSION: 1
MYPY_CACHE_VERSION: 9
HA_SHORT_VERSION: "2025.3"
HA_SHORT_VERSION: "2025.2"
DEFAULT_PYTHON: "3.13"
ALL_PYTHON_VERSIONS: "['3.13']"
# 10.3 is the oldest supported version
@@ -234,13 +234,13 @@ jobs:
uses: actions/checkout@v4.2.2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v5.4.0
uses: actions/setup-python@v5.3.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
check-latest: true
- name: Restore base Python virtual environment
id: cache-venv
uses: actions/cache@v4.2.1
uses: actions/cache@v4.2.0
with:
path: venv
key: >-
@@ -256,7 +256,7 @@ jobs:
uv pip install "$(cat requirements_test.txt | grep pre-commit)"
- name: Restore pre-commit environment from cache
id: cache-precommit
uses: actions/cache@v4.2.1
uses: actions/cache@v4.2.0
with:
path: ${{ env.PRE_COMMIT_CACHE }}
lookup-only: true
@@ -279,14 +279,14 @@ jobs:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v5.4.0
uses: actions/setup-python@v5.3.0
id: python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
check-latest: true
- name: Restore base Python virtual environment
id: cache-venv
uses: actions/cache/restore@v4.2.1
uses: actions/cache/restore@v4.2.0
with:
path: venv
fail-on-cache-miss: true
@@ -295,7 +295,7 @@ jobs:
needs.info.outputs.pre-commit_cache_key }}
- name: Restore pre-commit environment from cache
id: cache-precommit
uses: actions/cache/restore@v4.2.1
uses: actions/cache/restore@v4.2.0
with:
path: ${{ env.PRE_COMMIT_CACHE }}
fail-on-cache-miss: true
@@ -319,14 +319,14 @@ jobs:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v5.4.0
uses: actions/setup-python@v5.3.0
id: python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
check-latest: true
- name: Restore base Python virtual environment
id: cache-venv
uses: actions/cache/restore@v4.2.1
uses: actions/cache/restore@v4.2.0
with:
path: venv
fail-on-cache-miss: true
@@ -335,7 +335,7 @@ jobs:
needs.info.outputs.pre-commit_cache_key }}
- name: Restore pre-commit environment from cache
id: cache-precommit
uses: actions/cache/restore@v4.2.1
uses: actions/cache/restore@v4.2.0
with:
path: ${{ env.PRE_COMMIT_CACHE }}
fail-on-cache-miss: true
@@ -359,14 +359,14 @@ jobs:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v5.4.0
uses: actions/setup-python@v5.3.0
id: python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
check-latest: true
- name: Restore base Python virtual environment
id: cache-venv
uses: actions/cache/restore@v4.2.1
uses: actions/cache/restore@v4.2.0
with:
path: venv
fail-on-cache-miss: true
@@ -375,7 +375,7 @@ jobs:
needs.info.outputs.pre-commit_cache_key }}
- name: Restore pre-commit environment from cache
id: cache-precommit
uses: actions/cache/restore@v4.2.1
uses: actions/cache/restore@v4.2.0
with:
path: ${{ env.PRE_COMMIT_CACHE }}
fail-on-cache-miss: true
@@ -469,7 +469,7 @@ jobs:
uses: actions/checkout@v4.2.2
- name: Set up Python ${{ matrix.python-version }}
id: python
uses: actions/setup-python@v5.4.0
uses: actions/setup-python@v5.3.0
with:
python-version: ${{ matrix.python-version }}
check-latest: true
@@ -482,7 +482,7 @@ jobs:
env.HA_SHORT_VERSION }}-$(date -u '+%Y-%m-%dT%H:%M:%s')" >> $GITHUB_OUTPUT
- name: Restore base Python virtual environment
id: cache-venv
uses: actions/cache@v4.2.1
uses: actions/cache@v4.2.0
with:
path: venv
key: >-
@@ -490,7 +490,7 @@ jobs:
needs.info.outputs.python_cache_key }}
- name: Restore uv wheel cache
if: steps.cache-venv.outputs.cache-hit != 'true'
uses: actions/cache@v4.2.1
uses: actions/cache@v4.2.0
with:
path: ${{ env.UV_CACHE_DIR }}
key: >-
@@ -572,13 +572,13 @@ jobs:
uses: actions/checkout@v4.2.2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v5.4.0
uses: actions/setup-python@v5.3.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
check-latest: true
- name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment
id: cache-venv
uses: actions/cache/restore@v4.2.1
uses: actions/cache/restore@v4.2.0
with:
path: venv
fail-on-cache-miss: true
@@ -605,13 +605,13 @@ jobs:
uses: actions/checkout@v4.2.2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v5.4.0
uses: actions/setup-python@v5.3.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
check-latest: true
- name: Restore base Python virtual environment
id: cache-venv
uses: actions/cache/restore@v4.2.1
uses: actions/cache/restore@v4.2.0
with:
path: venv
fail-on-cache-miss: true
@@ -643,13 +643,13 @@ jobs:
uses: actions/checkout@v4.2.2
- name: Set up Python ${{ matrix.python-version }}
id: python
uses: actions/setup-python@v5.4.0
uses: actions/setup-python@v5.3.0
with:
python-version: ${{ matrix.python-version }}
check-latest: true
- name: Restore full Python ${{ matrix.python-version }} virtual environment
id: cache-venv
uses: actions/cache/restore@v4.2.1
uses: actions/cache/restore@v4.2.0
with:
path: venv
fail-on-cache-miss: true
@@ -686,13 +686,13 @@ jobs:
uses: actions/checkout@v4.2.2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v5.4.0
uses: actions/setup-python@v5.3.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
check-latest: true
- name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment
id: cache-venv
uses: actions/cache/restore@v4.2.1
uses: actions/cache/restore@v4.2.0
with:
path: venv
fail-on-cache-miss: true
@@ -733,13 +733,13 @@ jobs:
uses: actions/checkout@v4.2.2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v5.4.0
uses: actions/setup-python@v5.3.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
check-latest: true
- name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment
id: cache-venv
uses: actions/cache/restore@v4.2.1
uses: actions/cache/restore@v4.2.0
with:
path: venv
fail-on-cache-miss: true
@@ -778,7 +778,7 @@ jobs:
uses: actions/checkout@v4.2.2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v5.4.0
uses: actions/setup-python@v5.3.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
check-latest: true
@@ -791,7 +791,7 @@ jobs:
env.HA_SHORT_VERSION }}-$(date -u '+%Y-%m-%dT%H:%M:%s')" >> $GITHUB_OUTPUT
- name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment
id: cache-venv
uses: actions/cache/restore@v4.2.1
uses: actions/cache/restore@v4.2.0
with:
path: venv
fail-on-cache-miss: true
@@ -799,7 +799,7 @@ jobs:
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
needs.info.outputs.python_cache_key }}
- name: Restore mypy cache
uses: actions/cache@v4.2.1
uses: actions/cache@v4.2.0
with:
path: .mypy_cache
key: >-
@@ -859,13 +859,13 @@ jobs:
uses: actions/checkout@v4.2.2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v5.4.0
uses: actions/setup-python@v5.3.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
check-latest: true
- name: Restore base Python virtual environment
id: cache-venv
uses: actions/cache/restore@v4.2.1
uses: actions/cache/restore@v4.2.0
with:
path: venv
fail-on-cache-miss: true
@@ -923,13 +923,13 @@ jobs:
uses: actions/checkout@v4.2.2
- name: Set up Python ${{ matrix.python-version }}
id: python
uses: actions/setup-python@v5.4.0
uses: actions/setup-python@v5.3.0
with:
python-version: ${{ matrix.python-version }}
check-latest: true
- name: Restore full Python ${{ matrix.python-version }} virtual environment
id: cache-venv
uses: actions/cache/restore@v4.2.1
uses: actions/cache/restore@v4.2.0
with:
path: venv
fail-on-cache-miss: true
@@ -975,7 +975,6 @@ jobs:
${cov_params[@]} \
-o console_output_style=count \
-p no:sugar \
--exclude-warning-annotations \
$(sed -n "${{ matrix.group }},1p" pytest_buckets.txt) \
2>&1 | tee pytest-${{ matrix.python-version }}-${{ matrix.group }}.txt
- name: Upload pytest output
@@ -1045,13 +1044,13 @@ jobs:
uses: actions/checkout@v4.2.2
- name: Set up Python ${{ matrix.python-version }}
id: python
uses: actions/setup-python@v5.4.0
uses: actions/setup-python@v5.3.0
with:
python-version: ${{ matrix.python-version }}
check-latest: true
- name: Restore full Python ${{ matrix.python-version }} virtual environment
id: cache-venv
uses: actions/cache/restore@v4.2.1
uses: actions/cache/restore@v4.2.0
with:
path: venv
fail-on-cache-miss: true
@@ -1099,7 +1098,6 @@ jobs:
-o console_output_style=count \
--durations=10 \
-p no:sugar \
--exclude-warning-annotations \
--dburl=mysql://root:password@127.0.0.1/homeassistant-test \
tests/components/history \
tests/components/logbook \
@@ -1175,13 +1173,13 @@ jobs:
uses: actions/checkout@v4.2.2
- name: Set up Python ${{ matrix.python-version }}
id: python
uses: actions/setup-python@v5.4.0
uses: actions/setup-python@v5.3.0
with:
python-version: ${{ matrix.python-version }}
check-latest: true
- name: Restore full Python ${{ matrix.python-version }} virtual environment
id: cache-venv
uses: actions/cache/restore@v4.2.1
uses: actions/cache/restore@v4.2.0
with:
path: venv
fail-on-cache-miss: true
@@ -1230,7 +1228,6 @@ jobs:
--durations=0 \
--durations-min=10 \
-p no:sugar \
--exclude-warning-annotations \
--dburl=postgresql://postgres:password@127.0.0.1/homeassistant-test \
tests/components/history \
tests/components/logbook \
@@ -1276,7 +1273,7 @@ jobs:
pattern: coverage-*
- name: Upload coverage to Codecov
if: needs.info.outputs.test_full_suite == 'true'
uses: codecov/codecov-action@v5.3.1
uses: codecov/codecov-action@v5.1.2
with:
fail_ci_if_error: true
flags: full-suite
@@ -1322,13 +1319,13 @@ jobs:
uses: actions/checkout@v4.2.2
- name: Set up Python ${{ matrix.python-version }}
id: python
uses: actions/setup-python@v5.4.0
uses: actions/setup-python@v5.3.0
with:
python-version: ${{ matrix.python-version }}
check-latest: true
- name: Restore full Python ${{ matrix.python-version }} virtual environment
id: cache-venv
uses: actions/cache/restore@v4.2.1
uses: actions/cache/restore@v4.2.0
with:
path: venv
fail-on-cache-miss: true
@@ -1377,7 +1374,6 @@ jobs:
--durations=0 \
--durations-min=1 \
-p no:sugar \
--exclude-warning-annotations \
tests/components/${{ matrix.group }} \
2>&1 | tee pytest-${{ matrix.python-version }}-${{ matrix.group }}.txt
- name: Upload pytest output
@@ -1415,7 +1411,7 @@ jobs:
pattern: coverage-*
- name: Upload coverage to Codecov
if: needs.info.outputs.test_full_suite == 'false'
uses: codecov/codecov-action@v5.3.1
uses: codecov/codecov-action@v5.1.2
with:
fail_ci_if_error: true
token: ${{ secrets.CODECOV_TOKEN }}

View File

@@ -24,11 +24,11 @@ jobs:
uses: actions/checkout@v4.2.2
- name: Initialize CodeQL
uses: github/codeql-action/init@v3.28.9
uses: github/codeql-action/init@v3.28.1
with:
languages: python
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3.28.9
uses: github/codeql-action/analyze@v3.28.1
with:
category: "/language:python"

View File

@@ -17,7 +17,7 @@ jobs:
# - No PRs marked as no-stale
# - No issues (-1)
- name: 60 days stale PRs policy
uses: actions/stale@v9.1.0
uses: actions/stale@v9.0.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 60
@@ -57,7 +57,7 @@ jobs:
# - No issues marked as no-stale or help-wanted
# - No PRs (-1)
- name: 90 days stale issues
uses: actions/stale@v9.1.0
uses: actions/stale@v9.0.0
with:
repo-token: ${{ steps.token.outputs.token }}
days-before-stale: 90
@@ -87,7 +87,7 @@ jobs:
# - No Issues marked as no-stale or help-wanted
# - No PRs (-1)
- name: Needs more information stale issues policy
uses: actions/stale@v9.1.0
uses: actions/stale@v9.0.0
with:
repo-token: ${{ steps.token.outputs.token }}
only-labels: "needs-more-information"

View File

@@ -22,7 +22,7 @@ jobs:
uses: actions/checkout@v4.2.2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v5.4.0
uses: actions/setup-python@v5.3.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}

View File

@@ -36,7 +36,7 @@ jobs:
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v5.4.0
uses: actions/setup-python@v5.3.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
check-latest: true
@@ -131,7 +131,7 @@ jobs:
strategy:
fail-fast: false
matrix:
abi: ["cp313"]
abi: ["cp312", "cp313"]
arch: ${{ fromJson(needs.init.outputs.architectures) }}
steps:
- name: Checkout the repository
@@ -180,7 +180,7 @@ jobs:
strategy:
fail-fast: false
matrix:
abi: ["cp313"]
abi: ["cp312", "cp313"]
arch: ${{ fromJson(needs.init.outputs.architectures) }}
steps:
- name: Checkout the repository

View File

@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.9.7
rev: v0.9.1
hooks:
- id: ruff
args:
@@ -8,7 +8,7 @@ repos:
- id: ruff-format
files: ^((homeassistant|pylint|script|tests)/.+)?[^/]+\.(py|pyi)$
- repo: https://github.com/codespell-project/codespell
rev: v2.4.1
rev: v2.3.0
hooks:
- id: codespell
args:
@@ -61,14 +61,13 @@ repos:
name: mypy
entry: script/run-in-env.sh mypy
language: script
require_serial: true
types_or: [python, pyi]
require_serial: true
files: ^(homeassistant|pylint)/.+\.(py|pyi)$
- id: pylint
name: pylint
entry: script/run-in-env.sh pylint --ignore-missing-annotations=y
entry: script/run-in-env.sh pylint -j 0 --ignore-missing-annotations=y
language: script
require_serial: true
types_or: [python, pyi]
files: ^(homeassistant|tests)/.+\.(py|pyi)$
- id: gen_requirements_all

View File

@@ -119,7 +119,6 @@ homeassistant.components.bluetooth_tracker.*
homeassistant.components.bmw_connected_drive.*
homeassistant.components.bond.*
homeassistant.components.braviatv.*
homeassistant.components.bring.*
homeassistant.components.brother.*
homeassistant.components.browser.*
homeassistant.components.bryant_evolution.*
@@ -218,7 +217,6 @@ homeassistant.components.goalzero.*
homeassistant.components.google.*
homeassistant.components.google_assistant_sdk.*
homeassistant.components.google_cloud.*
homeassistant.components.google_drive.*
homeassistant.components.google_photos.*
homeassistant.components.google_sheets.*
homeassistant.components.govee_ble.*
@@ -229,19 +227,16 @@ homeassistant.components.guardian.*
homeassistant.components.habitica.*
homeassistant.components.hardkernel.*
homeassistant.components.hardware.*
homeassistant.components.heos.*
homeassistant.components.here_travel_time.*
homeassistant.components.history.*
homeassistant.components.history_stats.*
homeassistant.components.holiday.*
homeassistant.components.home_connect.*
homeassistant.components.homeassistant.*
homeassistant.components.homeassistant_alerts.*
homeassistant.components.homeassistant_green.*
homeassistant.components.homeassistant_hardware.*
homeassistant.components.homeassistant_sky_connect.*
homeassistant.components.homeassistant_yellow.*
homeassistant.components.homee.*
homeassistant.components.homekit.*
homeassistant.components.homekit_controller
homeassistant.components.homekit_controller.alarm_control_panel
@@ -267,7 +262,6 @@ homeassistant.components.image_processing.*
homeassistant.components.image_upload.*
homeassistant.components.imap.*
homeassistant.components.imgw_pib.*
homeassistant.components.incomfort.*
homeassistant.components.input_button.*
homeassistant.components.input_select.*
homeassistant.components.input_text.*
@@ -313,14 +307,12 @@ homeassistant.components.logbook.*
homeassistant.components.logger.*
homeassistant.components.london_underground.*
homeassistant.components.lookin.*
homeassistant.components.lovelace.*
homeassistant.components.luftdaten.*
homeassistant.components.madvr.*
homeassistant.components.manual.*
homeassistant.components.mastodon.*
homeassistant.components.matrix.*
homeassistant.components.matter.*
homeassistant.components.mcp.*
homeassistant.components.mcp_server.*
homeassistant.components.mealie.*
homeassistant.components.media_extractor.*
@@ -363,7 +355,6 @@ homeassistant.components.number.*
homeassistant.components.nut.*
homeassistant.components.onboarding.*
homeassistant.components.oncue.*
homeassistant.components.onedrive.*
homeassistant.components.onewire.*
homeassistant.components.onkyo.*
homeassistant.components.open_meteo.*
@@ -438,7 +429,6 @@ homeassistant.components.select.*
homeassistant.components.sensibo.*
homeassistant.components.sensirion_ble.*
homeassistant.components.sensor.*
homeassistant.components.sensorpush_cloud.*
homeassistant.components.sensoterra.*
homeassistant.components.senz.*
homeassistant.components.sfr_box.*

10
.vscode/launch.json vendored
View File

@@ -42,14 +42,6 @@
"--picked"
],
},
{
"name": "Home Assistant: Debug Current Test File",
"type": "debugpy",
"request": "launch",
"module": "pytest",
"console": "integratedTerminal",
"args": ["-vv", "${file}"]
},
{
// Debug by attaching to local Home Assistant server using Remote Python Debugger.
// See https://www.home-assistant.io/integrations/debugpy/
@@ -85,4 +77,4 @@
]
}
]
}
}

2
.vscode/tasks.json vendored
View File

@@ -148,7 +148,7 @@
{
"label": "Install all Test Requirements",
"type": "shell",
"command": "uv pip install -r requirements.txt -r requirements_test_all.txt",
"command": "uv pip install -r requirements_test_all.txt",
"group": {
"kind": "build",
"isDefault": true

30
CODEOWNERS generated
View File

@@ -566,8 +566,6 @@ build.json @home-assistant/supervisor
/tests/components/google_assistant_sdk/ @tronikos
/homeassistant/components/google_cloud/ @lufton @tronikos
/tests/components/google_cloud/ @lufton @tronikos
/homeassistant/components/google_drive/ @tronikos
/tests/components/google_drive/ @tronikos
/homeassistant/components/google_generative_ai_conversation/ @tronikos
/tests/components/google_generative_ai_conversation/ @tronikos
/homeassistant/components/google_mail/ @tkdrob
@@ -625,8 +623,8 @@ build.json @home-assistant/supervisor
/tests/components/hlk_sw16/ @jameshilliard
/homeassistant/components/holiday/ @jrieger @gjohansson-ST
/tests/components/holiday/ @jrieger @gjohansson-ST
/homeassistant/components/home_connect/ @DavidMStraub @Diegorro98 @MartinHjelmare
/tests/components/home_connect/ @DavidMStraub @Diegorro98 @MartinHjelmare
/homeassistant/components/home_connect/ @DavidMStraub @Diegorro98
/tests/components/home_connect/ @DavidMStraub @Diegorro98
/homeassistant/components/homeassistant/ @home-assistant/core
/tests/components/homeassistant/ @home-assistant/core
/homeassistant/components/homeassistant_alerts/ @home-assistant/core
@@ -684,6 +682,8 @@ build.json @home-assistant/supervisor
/homeassistant/components/iammeter/ @lewei50
/homeassistant/components/iaqualink/ @flz
/tests/components/iaqualink/ @flz
/homeassistant/components/ibeacon/ @bdraco
/tests/components/ibeacon/ @bdraco
/homeassistant/components/icloud/ @Quentame @nzapponi
/tests/components/icloud/ @Quentame @nzapponi
/homeassistant/components/idasen_desk/ @abmantis
@@ -731,8 +731,6 @@ build.json @home-assistant/supervisor
/homeassistant/components/intent/ @home-assistant/core @synesthesiam
/tests/components/intent/ @home-assistant/core @synesthesiam
/homeassistant/components/intesishome/ @jnimmo
/homeassistant/components/iometer/ @MaestroOnICe
/tests/components/iometer/ @MaestroOnICe
/homeassistant/components/ios/ @robbiet480
/tests/components/ios/ @robbiet480
/homeassistant/components/iotawatt/ @gtdiehl @jyavenard
@@ -767,8 +765,8 @@ build.json @home-assistant/supervisor
/tests/components/ituran/ @shmuelzon
/homeassistant/components/izone/ @Swamp-Ig
/tests/components/izone/ @Swamp-Ig
/homeassistant/components/jellyfin/ @RunC0deRun @ctalkington
/tests/components/jellyfin/ @RunC0deRun @ctalkington
/homeassistant/components/jellyfin/ @j-stienstra @ctalkington
/tests/components/jellyfin/ @j-stienstra @ctalkington
/homeassistant/components/jewish_calendar/ @tsvi
/tests/components/jewish_calendar/ @tsvi
/homeassistant/components/juicenet/ @jesserockz
@@ -895,8 +893,6 @@ build.json @home-assistant/supervisor
/tests/components/matrix/ @PaarthShah
/homeassistant/components/matter/ @home-assistant/matter
/tests/components/matter/ @home-assistant/matter
/homeassistant/components/mcp/ @allenporter
/tests/components/mcp/ @allenporter
/homeassistant/components/mcp_server/ @allenporter
/tests/components/mcp_server/ @allenporter
/homeassistant/components/mealie/ @joostlek @andrew-codechimp
@@ -1075,8 +1071,6 @@ build.json @home-assistant/supervisor
/tests/components/oncue/ @bdraco @peterager
/homeassistant/components/ondilo_ico/ @JeromeHXP
/tests/components/ondilo_ico/ @JeromeHXP
/homeassistant/components/onedrive/ @zweckj
/tests/components/onedrive/ @zweckj
/homeassistant/components/onewire/ @garbled1 @epenet
/tests/components/onewire/ @garbled1 @epenet
/homeassistant/components/onkyo/ @arturpragacz @eclair4151
@@ -1138,8 +1132,6 @@ build.json @home-assistant/supervisor
/tests/components/permobil/ @IsakNyberg
/homeassistant/components/persistent_notification/ @home-assistant/core
/tests/components/persistent_notification/ @home-assistant/core
/homeassistant/components/pglab/ @pglab-electronics
/tests/components/pglab/ @pglab-electronics
/homeassistant/components/philips_js/ @elupus
/tests/components/philips_js/ @elupus
/homeassistant/components/pi_hole/ @shenxn
@@ -1277,8 +1269,8 @@ build.json @home-assistant/supervisor
/tests/components/rituals_perfume_genie/ @milanmeu @frenck
/homeassistant/components/rmvtransport/ @cgtobi
/tests/components/rmvtransport/ @cgtobi
/homeassistant/components/roborock/ @Lash-L @allenporter
/tests/components/roborock/ @Lash-L @allenporter
/homeassistant/components/roborock/ @Lash-L
/tests/components/roborock/ @Lash-L
/homeassistant/components/roku/ @ctalkington
/tests/components/roku/ @ctalkington
/homeassistant/components/romy/ @xeniter
@@ -1342,8 +1334,6 @@ build.json @home-assistant/supervisor
/tests/components/sensorpro/ @bdraco
/homeassistant/components/sensorpush/ @bdraco
/tests/components/sensorpush/ @bdraco
/homeassistant/components/sensorpush_cloud/ @sstallion
/tests/components/sensorpush_cloud/ @sstallion
/homeassistant/components/sensoterra/ @markruys
/tests/components/sensoterra/ @markruys
/homeassistant/components/sentry/ @dcramer @frenck
@@ -1420,8 +1410,8 @@ build.json @home-assistant/supervisor
/homeassistant/components/solaredge_local/ @drobtravels @scheric
/homeassistant/components/solarlog/ @Ernst79 @dontinelli
/tests/components/solarlog/ @Ernst79 @dontinelli
/homeassistant/components/solax/ @squishykid @Darsstar
/tests/components/solax/ @squishykid @Darsstar
/homeassistant/components/solax/ @squishykid
/tests/components/solax/ @squishykid
/homeassistant/components/soma/ @ratsept @sebfortier2288
/tests/components/soma/ @ratsept @sebfortier2288
/homeassistant/components/sonarr/ @ctalkington

38
Dockerfile generated
View File

@@ -12,26 +12,8 @@ ENV \
ARG QEMU_CPU
# Home Assistant S6-Overlay
COPY rootfs /
# Needs to be redefined inside the FROM statement to be set for RUN commands
ARG BUILD_ARCH
# Get go2rtc binary
RUN \
case "${BUILD_ARCH}" in \
"aarch64") go2rtc_suffix='arm64' ;; \
"armhf") go2rtc_suffix='armv6' ;; \
"armv7") go2rtc_suffix='arm' ;; \
*) go2rtc_suffix=${BUILD_ARCH} ;; \
esac \
&& curl -L https://github.com/AlexxIT/go2rtc/releases/download/v1.9.8/go2rtc_linux_${go2rtc_suffix} --output /bin/go2rtc \
&& chmod +x /bin/go2rtc \
# Verify go2rtc can be executed
&& go2rtc --version
# Install uv
RUN pip3 install uv==0.6.1
RUN pip3 install uv==0.5.18
WORKDIR /usr/src
@@ -60,4 +42,22 @@ RUN \
&& python3 -m compileall \
homeassistant/homeassistant
# Home Assistant S6-Overlay
COPY rootfs /
# Needs to be redefined inside the FROM statement to be set for RUN commands
ARG BUILD_ARCH
# Get go2rtc binary
RUN \
case "${BUILD_ARCH}" in \
"aarch64") go2rtc_suffix='arm64' ;; \
"armhf") go2rtc_suffix='armv6' ;; \
"armv7") go2rtc_suffix='arm' ;; \
*) go2rtc_suffix=${BUILD_ARCH} ;; \
esac \
&& curl -L https://github.com/AlexxIT/go2rtc/releases/download/v1.9.8/go2rtc_linux_${go2rtc_suffix} --output /bin/go2rtc \
&& chmod +x /bin/go2rtc \
# Verify go2rtc can be executed
&& go2rtc --version
WORKDIR /config

View File

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

View File

@@ -11,7 +11,7 @@ import uuid
import attr
from attr import Attribute
from attr.setters import validate
from propcache.api import cached_property
from propcache import cached_property
from homeassistant.const import __version__
from homeassistant.data_entry_flow import FlowContext, FlowResult

View File

@@ -21,7 +21,7 @@ import voluptuous as vol
from homeassistant.core import callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.network import is_cloud_connection
from .. import InvalidAuthError

View File

@@ -18,7 +18,6 @@ import securetar
from .const import __version__ as HA_VERSION
RESTORE_BACKUP_FILE = ".HA_RESTORE"
RESTORE_BACKUP_RESULT_FILE = ".HA_RESTORE_RESULT"
KEEP_BACKUPS = ("backups",)
KEEP_DATABASE = (
"home-assistant_v2.db",
@@ -63,10 +62,7 @@ def restore_backup_file_content(config_dir: Path) -> RestoreBackupFileContent |
restore_database=instruction_content["restore_database"],
restore_homeassistant=instruction_content["restore_homeassistant"],
)
except FileNotFoundError:
return None
except (KeyError, json.JSONDecodeError) as err:
_write_restore_result_file(config_dir, False, err)
except (FileNotFoundError, KeyError, json.JSONDecodeError):
return None
finally:
# Always remove the backup instruction file to prevent a boot loop
@@ -146,7 +142,6 @@ def _extract_backup(
config_dir,
dirs_exist_ok=True,
ignore=shutil.ignore_patterns(*(keep)),
ignore_dangling_symlinks=True,
)
elif restore_content.restore_database:
for entry in KEEP_DATABASE:
@@ -164,23 +159,6 @@ def _extract_backup(
)
def _write_restore_result_file(
config_dir: Path, success: bool, error: Exception | None
) -> None:
"""Write the restore result file."""
result_path = config_dir.joinpath(RESTORE_BACKUP_RESULT_FILE)
result_path.write_text(
json.dumps(
{
"success": success,
"error": str(error) if error else None,
"error_type": str(type(error).__name__) if error else None,
}
),
encoding="utf-8",
)
def restore_backup(config_dir_path: str) -> bool:
"""Restore the backup file if any.
@@ -199,14 +177,7 @@ def restore_backup(config_dir_path: str) -> bool:
restore_content=restore_content,
)
except FileNotFoundError as err:
file_not_found = ValueError(f"Backup file {backup_file_path} does not exist")
_write_restore_result_file(config_dir, False, file_not_found)
raise file_not_found from err
except Exception as err:
_write_restore_result_file(config_dir, False, err)
raise
else:
_write_restore_result_file(config_dir, True, None)
raise ValueError(f"Backup file {backup_file_path} does not exist") from err
if restore_content.remove_after_restore:
backup_file_path.unlink(missing_ok=True)
_LOGGER.info("Restore complete, restarting")

View File

@@ -112,11 +112,6 @@ with contextlib.suppress(ImportError):
# Ensure anyio backend is imported to avoid it being imported in the event loop
from anyio._backends import _asyncio # noqa: F401
with contextlib.suppress(ImportError):
# httpx will import trio if it is installed which does
# blocking I/O in the event loop. We want to avoid that.
import trio # noqa: F401
if TYPE_CHECKING:
from .runner import RuntimeConfig
@@ -134,12 +129,14 @@ DATA_REGISTRIES_LOADED: HassKey[None] = HassKey("bootstrap_registries_loaded")
LOG_SLOW_STARTUP_INTERVAL = 60
SLOW_STARTUP_CHECK_INTERVAL = 1
STAGE_0_SUBSTAGE_TIMEOUT = 60
STAGE_1_TIMEOUT = 120
STAGE_2_TIMEOUT = 300
WRAP_UP_TIMEOUT = 300
COOLDOWN_TIME = 60
DEBUGGER_INTEGRATIONS = {"debugpy"}
# Core integrations are unconditionally loaded
CORE_INTEGRATIONS = {"homeassistant", "persistent_notification"}
@@ -150,10 +147,6 @@ LOGGING_AND_HTTP_DEPS_INTEGRATIONS = {
"isal",
# Set log levels
"logger",
# Ensure network config is available
# before hassio or any other integration is
# loaded that might create an aiohttp client session
"network",
# Error logging
"system_log",
"sentry",
@@ -163,38 +156,13 @@ FRONTEND_INTEGRATIONS = {
# integrations can be removed and database migration status is
# visible in frontend
"frontend",
# Hassio is an after dependency of backup, after dependencies
# are not promoted from stage 2 to earlier stages, so we need to
# add it here. Hassio needs to be setup before backup, otherwise
# the backup integration will think we are a container/core install
# when using HAOS or Supervised install.
"hassio",
# Backup is an after dependency of frontend, after dependencies
# are not promoted from stage 2 to earlier stages, so we need to
# add it here.
"backup",
}
# Stage 0 is divided into substages. Each substage has a name, a set of integrations and a timeout.
# The substage containing recorder should have no timeout, as it could cancel a database migration.
# Recorder freezes "recorder" timeout during a migration, but it does not freeze other timeouts.
# The substages preceding it should also have no timeout, until we ensure that the recorder
# is not accidentally promoted as a dependency of any of the integrations in them.
# If we add timeouts to the frontend substages, we should make sure they don't apply in recovery mode.
STAGE_0_INTEGRATIONS = (
# Load logging and http deps as soon as possible
("logging, http deps", LOGGING_AND_HTTP_DEPS_INTEGRATIONS, None),
# Setup frontend
("frontend", FRONTEND_INTEGRATIONS, None),
# Setup recorder
("recorder", {"recorder"}, None),
# Start up debuggers. Start these first in case they want to wait.
("debugger", {"debugpy"}, STAGE_0_SUBSTAGE_TIMEOUT),
# Zeroconf is used for mdns resolution in aiohttp client helper.
("zeroconf", {"zeroconf"}, STAGE_0_SUBSTAGE_TIMEOUT),
)
DISCOVERY_INTEGRATIONS = ("bluetooth", "dhcp", "ssdp", "usb")
# Stage 1 integrations are not to be preimported in bootstrap.
RECORDER_INTEGRATIONS = {
# Setup after frontend
# To record data
"recorder",
}
DISCOVERY_INTEGRATIONS = ("bluetooth", "dhcp", "ssdp", "usb", "zeroconf")
STAGE_1_INTEGRATIONS = {
# We need to make sure discovery integrations
# update their deps before stage 2 integrations
@@ -206,8 +174,9 @@ STAGE_1_INTEGRATIONS = {
"mqtt_eventstream",
# To provide account link implementations
"cloud",
# Ensure supervisor is available
"hassio",
}
DEFAULT_INTEGRATIONS = {
# These integrations are set up unless recovery mode is activated.
#
@@ -248,12 +217,22 @@ DEFAULT_INTEGRATIONS_SUPERVISOR = {
# These integrations are set up if using the Supervisor
"hassio",
}
CRITICAL_INTEGRATIONS = {
# Recovery mode is activated if these integrations fail to set up
"frontend",
}
SETUP_ORDER = (
# Load logging and http deps as soon as possible
("logging, http deps", LOGGING_AND_HTTP_DEPS_INTEGRATIONS),
# Setup frontend
("frontend", FRONTEND_INTEGRATIONS),
# Setup recorder
("recorder", RECORDER_INTEGRATIONS),
# Start up debuggers. Start these first in case they want to wait.
("debugger", DEBUGGER_INTEGRATIONS),
)
#
# Storage keys we are likely to load during startup
# in order of when we expect to load them.
@@ -700,6 +679,7 @@ async def async_mount_local_lib_path(config_dir: str) -> str:
return deps_dir
@core.callback
def _get_domains(hass: core.HomeAssistant, config: dict[str, Any]) -> set[str]:
"""Get domains of components to set up."""
# Filter out the repeating and common config section [homeassistant]
@@ -721,6 +701,109 @@ def _get_domains(hass: core.HomeAssistant, config: dict[str, Any]) -> set[str]:
return domains
class _WatchPendingSetups:
"""Periodic log and dispatch of setups that are pending."""
def __init__(
self,
hass: core.HomeAssistant,
setup_started: dict[tuple[str, str | None], float],
) -> None:
"""Initialize the WatchPendingSetups class."""
self._hass = hass
self._setup_started = setup_started
self._duration_count = 0
self._handle: asyncio.TimerHandle | None = None
self._previous_was_empty = True
self._loop = hass.loop
def _async_watch(self) -> None:
"""Periodic log of setups that are pending."""
now = monotonic()
self._duration_count += SLOW_STARTUP_CHECK_INTERVAL
remaining_with_setup_started: defaultdict[str, float] = defaultdict(float)
for integration_group, start_time in self._setup_started.items():
domain, _ = integration_group
remaining_with_setup_started[domain] += now - start_time
if remaining_with_setup_started:
_LOGGER.debug("Integration remaining: %s", remaining_with_setup_started)
elif waiting_tasks := self._hass._active_tasks: # noqa: SLF001
_LOGGER.debug("Waiting on tasks: %s", waiting_tasks)
self._async_dispatch(remaining_with_setup_started)
if (
self._setup_started
and self._duration_count % LOG_SLOW_STARTUP_INTERVAL == 0
):
# We log every LOG_SLOW_STARTUP_INTERVAL until all integrations are done
# once we take over LOG_SLOW_STARTUP_INTERVAL (60s) to start up
_LOGGER.warning(
"Waiting on integrations to complete setup: %s",
self._setup_started,
)
_LOGGER.debug("Running timeout Zones: %s", self._hass.timeout.zones)
self._async_schedule_next()
def _async_dispatch(self, remaining_with_setup_started: dict[str, float]) -> None:
"""Dispatch the signal."""
if remaining_with_setup_started or not self._previous_was_empty:
async_dispatcher_send_internal(
self._hass, SIGNAL_BOOTSTRAP_INTEGRATIONS, remaining_with_setup_started
)
self._previous_was_empty = not remaining_with_setup_started
def _async_schedule_next(self) -> None:
"""Schedule the next call."""
self._handle = self._loop.call_later(
SLOW_STARTUP_CHECK_INTERVAL, self._async_watch
)
def async_start(self) -> None:
"""Start watching."""
self._async_schedule_next()
def async_stop(self) -> None:
"""Stop watching."""
self._async_dispatch({})
if self._handle:
self._handle.cancel()
self._handle = None
async def async_setup_multi_components(
hass: core.HomeAssistant,
domains: set[str],
config: dict[str, Any],
) -> None:
"""Set up multiple domains. Log on failure."""
# Avoid creating tasks for domains that were setup in a previous stage
domains_not_yet_setup = domains - hass.config.components
# Create setup tasks for base platforms first since everything will have
# to wait to be imported, and the sooner we can get the base platforms
# loaded the sooner we can start loading the rest of the integrations.
futures = {
domain: hass.async_create_task_internal(
async_setup_component(hass, domain, config),
f"setup component {domain}",
eager_start=True,
)
for domain in sorted(
domains_not_yet_setup, key=SETUP_ORDER_SORT_KEY, reverse=True
)
}
results = await asyncio.gather(*futures.values(), return_exceptions=True)
for idx, domain in enumerate(futures):
result = results[idx]
if isinstance(result, BaseException):
_LOGGER.error(
"Error setting up integration %s - received exception",
domain,
exc_info=(type(result), result, result.__traceback__),
)
async def _async_resolve_domains_to_setup(
hass: core.HomeAssistant, config: dict[str, Any]
) -> tuple[set[str], dict[str, loader.Integration]]:
@@ -895,48 +978,69 @@ async def _async_set_up_integrations(
domains_to_setup, integration_cache = await _async_resolve_domains_to_setup(
hass, config
)
stage_2_domains = domains_to_setup.copy()
# Initialize recorder
if "recorder" in domains_to_setup:
recorder.async_initialize_recorder(hass)
stage_0_and_1_domains: list[tuple[str, set[str], int | None]] = [
*(
(name, domain_group & domains_to_setup, timeout)
for name, domain_group, timeout in STAGE_0_INTEGRATIONS
),
("stage 1", STAGE_1_INTEGRATIONS & domains_to_setup, STAGE_1_TIMEOUT),
pre_stage_domains = [
(name, domains_to_setup & domain_group) for name, domain_group in SETUP_ORDER
]
_LOGGER.info("Setting up stage 0 and 1")
for name, domain_group, timeout in stage_0_and_1_domains:
if not domain_group:
continue
# calculate what components to setup in what stage
stage_1_domains: set[str] = set()
_LOGGER.info("Setting up %s: %s", name, domain_group)
to_be_loaded = domain_group.copy()
to_be_loaded.update(
dep
for domain in domain_group
if (integration := integration_cache.get(domain)) is not None
for dep in integration.all_dependencies
)
async_set_domains_to_be_loaded(hass, to_be_loaded)
stage_2_domains -= to_be_loaded
# Find all dependencies of any dependency of any stage 1 integration that
# 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
while deps_promotion:
old_deps_promotion = deps_promotion
deps_promotion = set()
if timeout is None:
await _async_setup_multi_components(hass, domain_group, config)
else:
try:
async with hass.timeout.async_timeout(timeout, cool_down=COOLDOWN_TIME):
await _async_setup_multi_components(hass, domain_group, config)
except TimeoutError:
_LOGGER.warning(
"Setup timed out for %s waiting on %s - moving forward",
name,
hass._active_tasks, # noqa: SLF001
)
for domain in old_deps_promotion:
if domain not in domains_to_setup or domain in stage_1_domains:
continue
stage_1_domains.add(domain)
if (dep_itg := integration_cache.get(domain)) is None:
continue
deps_promotion.update(dep_itg.all_dependencies)
stage_2_domains = domains_to_setup - stage_1_domains
for name, domain_group in pre_stage_domains:
if domain_group:
stage_2_domains -= domain_group
_LOGGER.info("Setting up %s: %s", name, domain_group)
to_be_loaded = domain_group.copy()
to_be_loaded.update(
dep
for domain in domain_group
if (integration := integration_cache.get(domain)) is not None
for dep in integration.all_dependencies
)
async_set_domains_to_be_loaded(hass, to_be_loaded)
await async_setup_multi_components(hass, domain_group, config)
# Enables after dependencies when setting up stage 1 domains
async_set_domains_to_be_loaded(hass, stage_1_domains)
# Start setup
if stage_1_domains:
_LOGGER.info("Setting up stage 1: %s", stage_1_domains)
try:
async with hass.timeout.async_timeout(
STAGE_1_TIMEOUT, cool_down=COOLDOWN_TIME
):
await async_setup_multi_components(hass, stage_1_domains, config)
except TimeoutError:
_LOGGER.warning(
"Setup timed out for stage 1 waiting on %s - moving forward",
hass._active_tasks, # noqa: SLF001
)
# Add after dependencies when setting up stage 2 domains
async_set_domains_to_be_loaded(hass, stage_2_domains)
@@ -947,7 +1051,7 @@ async def _async_set_up_integrations(
async with hass.timeout.async_timeout(
STAGE_2_TIMEOUT, cool_down=COOLDOWN_TIME
):
await _async_setup_multi_components(hass, stage_2_domains, config)
await async_setup_multi_components(hass, stage_2_domains, config)
except TimeoutError:
_LOGGER.warning(
"Setup timed out for stage 2 waiting on %s - moving forward",
@@ -973,106 +1077,3 @@ async def _async_set_up_integrations(
"Integration setup times: %s",
dict(sorted(setup_time.items(), key=itemgetter(1), reverse=True)),
)
class _WatchPendingSetups:
"""Periodic log and dispatch of setups that are pending."""
def __init__(
self,
hass: core.HomeAssistant,
setup_started: dict[tuple[str, str | None], float],
) -> None:
"""Initialize the WatchPendingSetups class."""
self._hass = hass
self._setup_started = setup_started
self._duration_count = 0
self._handle: asyncio.TimerHandle | None = None
self._previous_was_empty = True
self._loop = hass.loop
def _async_watch(self) -> None:
"""Periodic log of setups that are pending."""
now = monotonic()
self._duration_count += SLOW_STARTUP_CHECK_INTERVAL
remaining_with_setup_started: defaultdict[str, float] = defaultdict(float)
for integration_group, start_time in self._setup_started.items():
domain, _ = integration_group
remaining_with_setup_started[domain] += now - start_time
if remaining_with_setup_started:
_LOGGER.debug("Integration remaining: %s", remaining_with_setup_started)
elif waiting_tasks := self._hass._active_tasks: # noqa: SLF001
_LOGGER.debug("Waiting on tasks: %s", waiting_tasks)
self._async_dispatch(remaining_with_setup_started)
if (
self._setup_started
and self._duration_count % LOG_SLOW_STARTUP_INTERVAL == 0
):
# We log every LOG_SLOW_STARTUP_INTERVAL until all integrations are done
# once we take over LOG_SLOW_STARTUP_INTERVAL (60s) to start up
_LOGGER.warning(
"Waiting on integrations to complete setup: %s",
self._setup_started,
)
_LOGGER.debug("Running timeout Zones: %s", self._hass.timeout.zones)
self._async_schedule_next()
def _async_dispatch(self, remaining_with_setup_started: dict[str, float]) -> None:
"""Dispatch the signal."""
if remaining_with_setup_started or not self._previous_was_empty:
async_dispatcher_send_internal(
self._hass, SIGNAL_BOOTSTRAP_INTEGRATIONS, remaining_with_setup_started
)
self._previous_was_empty = not remaining_with_setup_started
def _async_schedule_next(self) -> None:
"""Schedule the next call."""
self._handle = self._loop.call_later(
SLOW_STARTUP_CHECK_INTERVAL, self._async_watch
)
def async_start(self) -> None:
"""Start watching."""
self._async_schedule_next()
def async_stop(self) -> None:
"""Stop watching."""
self._async_dispatch({})
if self._handle:
self._handle.cancel()
self._handle = None
async def _async_setup_multi_components(
hass: core.HomeAssistant,
domains: set[str],
config: dict[str, Any],
) -> None:
"""Set up multiple domains. Log on failure."""
# Avoid creating tasks for domains that were setup in a previous stage
domains_not_yet_setup = domains - hass.config.components
# Create setup tasks for base platforms first since everything will have
# to wait to be imported, and the sooner we can get the base platforms
# loaded the sooner we can start loading the rest of the integrations.
futures = {
domain: hass.async_create_task_internal(
async_setup_component(hass, domain, config),
f"setup component {domain}",
eager_start=True,
)
for domain in sorted(
domains_not_yet_setup, key=SETUP_ORDER_SORT_KEY, reverse=True
)
}
results = await asyncio.gather(*futures.values(), return_exceptions=True)
for idx, domain in enumerate(futures):
result = results[idx]
if isinstance(result, BaseException):
_LOGGER.error(
"Error setting up integration %s - received exception",
domain,
exc_info=(type(result), result, result.__traceback__),
)

View File

@@ -5,7 +5,6 @@
"google_assistant",
"google_assistant_sdk",
"google_cloud",
"google_drive",
"google_generative_ai_conversation",
"google_mail",
"google_maps",

View File

@@ -11,7 +11,6 @@
"microsoft_face",
"microsoft",
"msteams",
"onedrive",
"xbox"
]
}

View File

@@ -1,5 +0,0 @@
{
"domain": "sensorpush",
"name": "SensorPush",
"integrations": ["sensorpush", "sensorpush_cloud"]
}

View File

@@ -11,7 +11,7 @@ from homeassistant.components.alarm_control_panel import (
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import AbodeSystem
from .const import DOMAIN
@@ -19,9 +19,7 @@ from .entity import AbodeDevice
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up Abode alarm control panel device."""
data: AbodeSystem = hass.data[DOMAIN]

View File

@@ -12,7 +12,7 @@ from homeassistant.components.binary_sensor import (
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util.enum import try_parse_enum
from . import AbodeSystem
@@ -21,9 +21,7 @@ from .entity import AbodeDevice
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up Abode binary sensor devices."""
data: AbodeSystem = hass.data[DOMAIN]

View File

@@ -15,7 +15,7 @@ from homeassistant.components.camera import Camera
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import Event, HomeAssistant
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util import Throttle
from . import AbodeSystem
@@ -26,9 +26,7 @@ MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=90)
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up Abode camera devices."""
data: AbodeSystem = hass.data[DOMAIN]

View File

@@ -7,7 +7,7 @@ from jaraco.abode.devices.cover import Cover
from homeassistant.components.cover import CoverEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import AbodeSystem
from .const import DOMAIN
@@ -15,9 +15,7 @@ from .entity import AbodeDevice
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up Abode cover devices."""
data: AbodeSystem = hass.data[DOMAIN]

View File

@@ -18,7 +18,7 @@ from homeassistant.components.light import (
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import AbodeSystem
from .const import DOMAIN
@@ -26,9 +26,7 @@ from .entity import AbodeDevice
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up Abode light devices."""
data: AbodeSystem = hass.data[DOMAIN]

View File

@@ -7,7 +7,7 @@ from jaraco.abode.devices.lock import Lock
from homeassistant.components.lock import LockEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import AbodeSystem
from .const import DOMAIN
@@ -15,9 +15,7 @@ from .entity import AbodeDevice
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up Abode lock devices."""
data: AbodeSystem = hass.data[DOMAIN]

View File

@@ -16,7 +16,7 @@ from homeassistant.components.sensor import (
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import LIGHT_LUX, PERCENTAGE, UnitOfTemperature
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import AbodeSystem
from .const import DOMAIN
@@ -61,9 +61,7 @@ SENSOR_TYPES: tuple[AbodeSensorDescription, ...] = (
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up Abode sensor devices."""
data: AbodeSystem = hass.data[DOMAIN]

View File

@@ -10,7 +10,7 @@ from homeassistant.components.switch import SwitchEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import AbodeSystem
from .const import DOMAIN
@@ -20,9 +20,7 @@ DEVICE_TYPES = ["switch", "valve"]
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up Abode switch devices."""
data: AbodeSystem = hass.data[DOMAIN]

View File

@@ -11,7 +11,7 @@ from homeassistant.components.binary_sensor import (
BinarySensorEntityDescription,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .coordinator import AcaiaConfigEntry
from .entity import AcaiaEntity
@@ -40,7 +40,7 @@ BINARY_SENSORS: tuple[AcaiaBinarySensorEntityDescription, ...] = (
async def async_setup_entry(
hass: HomeAssistant,
entry: AcaiaConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up binary sensors."""

View File

@@ -8,7 +8,7 @@ from aioacaia.acaiascale import AcaiaScale
from homeassistant.components.button import ButtonEntity, ButtonEntityDescription
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .coordinator import AcaiaConfigEntry
from .entity import AcaiaEntity
@@ -45,7 +45,7 @@ BUTTONS: tuple[AcaiaButtonEntityDescription, ...] = (
async def async_setup_entry(
hass: HomeAssistant,
entry: AcaiaConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up button entities and services."""

View File

@@ -26,5 +26,5 @@
"iot_class": "local_push",
"loggers": ["aioacaia"],
"quality_scale": "platinum",
"requirements": ["aioacaia==0.1.14"]
"requirements": ["aioacaia==0.1.13"]
}

View File

@@ -16,7 +16,7 @@ from homeassistant.components.sensor import (
)
from homeassistant.const import PERCENTAGE, UnitOfMass, UnitOfVolumeFlowRate
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .coordinator import AcaiaConfigEntry
from .entity import AcaiaEntity
@@ -77,7 +77,7 @@ RESTORE_SENSORS: tuple[AcaiaSensorEntityDescription, ...] = (
async def async_setup_entry(
hass: HomeAssistant,
entry: AcaiaConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up sensors."""

View File

@@ -12,8 +12,8 @@ import voluptuous as vol
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
from .const import DOMAIN

View File

@@ -25,7 +25,7 @@ from homeassistant.const import (
UnitOfVolumetricFlux,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import (
@@ -375,7 +375,7 @@ SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
async def async_setup_entry(
hass: HomeAssistant,
entry: AccuWeatherConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Add AccuWeather entities from a config_entry."""
observation_coordinator: AccuWeatherObservationDataUpdateCoordinator = (

View File

@@ -16,7 +16,7 @@
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_api_key": "[%key:common::config_flow::error::invalid_api_key%]",
"requests_exceeded": "The allowed number of requests to the AccuWeather API has been exceeded. You have to wait or change the API key."
"requests_exceeded": "The allowed number of requests to Accuweather API has been exceeded. You have to wait or change API Key."
}
},
"entity": {

View File

@@ -30,7 +30,7 @@ from homeassistant.const import (
UnitOfTemperature,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util.dt import utc_from_timestamp
from .const import (
@@ -54,7 +54,7 @@ PARALLEL_UPDATES = 1
async def async_setup_entry(
hass: HomeAssistant,
entry: AccuWeatherConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Add a AccuWeather weather entity from a config_entry."""
async_add_entities([AccuWeatherEntity(entry.runtime_data)])

View File

@@ -22,7 +22,7 @@ from homeassistant.const import (
STATE_UNKNOWN,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType

View File

@@ -3,7 +3,7 @@
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
import homeassistant.helpers.entity_registry as er
from .hub import PulseHub

View File

@@ -11,7 +11,7 @@ from homeassistant.components.cover import (
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import AcmedaConfigEntry
from .const import ACMEDA_HUB_UPDATE
@@ -22,7 +22,7 @@ from .helpers import async_add_acmeda_entities
async def async_setup_entry(
hass: HomeAssistant,
config_entry: AcmedaConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the Acmeda Rollers from a config entry."""
hub = config_entry.runtime_data

View File

@@ -9,7 +9,7 @@ from aiopulse import Roller
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN, LOGGER
@@ -23,7 +23,7 @@ def async_add_acmeda_entities(
entity_class: type,
config_entry: AcmedaConfigEntry,
current: set[int],
async_add_entities: AddConfigEntryEntitiesCallback,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Add any new entities."""
hub = config_entry.runtime_data

View File

@@ -6,7 +6,7 @@ from homeassistant.components.sensor import SensorDeviceClass, SensorEntity
from homeassistant.const import PERCENTAGE
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import AcmedaConfigEntry
from .const import ACMEDA_HUB_UPDATE
@@ -17,7 +17,7 @@ from .helpers import async_add_acmeda_entities
async def async_setup_entry(
hass: HomeAssistant,
config_entry: AcmedaConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the Acmeda Rollers from a config entry."""
hub = config_entry.runtime_data

View File

@@ -15,7 +15,7 @@ from homeassistant.components.device_tracker import (
)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.typing import ConfigType
from .const import LEASES_REGEX

View File

@@ -25,7 +25,7 @@ from homeassistant.const import (
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import ACCOUNT_ID, CONNECTION_TYPE, DOMAIN, LOCAL
@@ -33,7 +33,7 @@ from .const import ACCOUNT_ID, CONNECTION_TYPE, DOMAIN, LOCAL
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the Adax thermostat with config flow."""
if entry.data.get(CONNECTION_TYPE) == LOCAL:

View File

@@ -7,7 +7,7 @@ from dataclasses import dataclass
from adguardhome import AdGuardHome, AdGuardHomeConnectionError
import voluptuous as vol
from homeassistant.config_entries import ConfigEntry
from homeassistant.config_entries import ConfigEntry, ConfigEntryState
from homeassistant.const import (
CONF_HOST,
CONF_NAME,
@@ -123,7 +123,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: AdGuardConfigEntry) -> b
async def async_unload_entry(hass: HomeAssistant, entry: AdGuardConfigEntry) -> bool:
"""Unload AdGuard Home config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if not hass.config_entries.async_loaded_entries(DOMAIN):
loaded_entries = [
entry
for entry in hass.config_entries.async_entries(DOMAIN)
if entry.state == ConfigEntryState.LOADED
]
if len(loaded_entries) == 1:
# This is the last loaded instance of AdGuard, deregister any services
hass.services.async_remove(DOMAIN, SERVICE_ADD_URL)
hass.services.async_remove(DOMAIN, SERVICE_REMOVE_URL)

View File

@@ -12,7 +12,7 @@ from adguardhome import AdGuardHome
from homeassistant.components.sensor import SensorEntity, SensorEntityDescription
from homeassistant.const import PERCENTAGE, UnitOfTime
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import AdGuardConfigEntry, AdGuardData
from .const import DOMAIN
@@ -85,7 +85,7 @@ SENSORS: tuple[AdGuardHomeEntityDescription, ...] = (
async def async_setup_entry(
hass: HomeAssistant,
entry: AdGuardConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up AdGuard Home sensor based on a config entry."""
data = entry.runtime_data

View File

@@ -79,7 +79,7 @@
"services": {
"add_url": {
"name": "Add URL",
"description": "Adds a new filter subscription to AdGuard Home.",
"description": "Add a new filter subscription to AdGuard Home.",
"fields": {
"name": {
"name": "[%key:common::config_flow::data::name%]",
@@ -123,11 +123,11 @@
},
"refresh": {
"name": "Refresh",
"description": "Refreshes all filter subscriptions in AdGuard Home.",
"description": "Refresh all filter subscriptions in AdGuard Home.",
"fields": {
"force": {
"name": "Force",
"description": "Force update (bypasses AdGuard Home throttling), omit for a regular refresh."
"description": "Force update (bypasses AdGuard Home throttling). \"true\" to force, or \"false\" to omit for a regular refresh."
}
}
}

View File

@@ -11,7 +11,7 @@ from adguardhome import AdGuardHome, AdGuardHomeError
from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import AdGuardConfigEntry, AdGuardData
from .const import DOMAIN, LOGGER
@@ -79,7 +79,7 @@ SWITCHES: tuple[AdGuardHomeSwitchEntityDescription, ...] = (
async def async_setup_entry(
hass: HomeAssistant,
entry: AdGuardConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up AdGuard Home switch based on a config entry."""
data = entry.runtime_data

View File

@@ -12,7 +12,7 @@ from homeassistant.const import (
EVENT_HOMEASSISTANT_STOP,
)
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.helpers import config_validation as cv
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.typing import ConfigType
from .const import CONF_ADS_VAR, DATA_ADS, DOMAIN, AdsType

View File

@@ -13,7 +13,7 @@ from homeassistant.components.binary_sensor import (
)
from homeassistant.const import CONF_DEVICE_CLASS, CONF_NAME
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType

View File

@@ -17,7 +17,7 @@ from homeassistant.components.cover import (
)
from homeassistant.const import CONF_DEVICE_CLASS, CONF_NAME
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType

View File

@@ -15,7 +15,7 @@ from homeassistant.components.light import (
)
from homeassistant.const import CONF_NAME
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType

View File

@@ -11,7 +11,7 @@ from homeassistant.components.select import (
)
from homeassistant.const import CONF_NAME
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType

View File

@@ -15,7 +15,7 @@ from homeassistant.components.sensor import (
)
from homeassistant.const import CONF_DEVICE_CLASS, CONF_NAME, CONF_UNIT_OF_MEASUREMENT
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, StateType

View File

@@ -13,7 +13,7 @@ from homeassistant.components.switch import (
)
from homeassistant.const import CONF_NAME
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType

View File

@@ -14,7 +14,7 @@ from homeassistant.components.valve import (
)
from homeassistant.const import CONF_DEVICE_CLASS, CONF_NAME
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType

View File

@@ -8,7 +8,7 @@ from homeassistant.components.binary_sensor import (
)
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import AdvantageAirDataConfigEntry
from .entity import AdvantageAirAcEntity, AdvantageAirZoneEntity
@@ -20,7 +20,7 @@ PARALLEL_UPDATES = 0
async def async_setup_entry(
hass: HomeAssistant,
config_entry: AdvantageAirDataConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up AdvantageAir Binary Sensor platform."""

View File

@@ -19,7 +19,7 @@ from homeassistant.components.climate import (
from homeassistant.const import ATTR_TEMPERATURE, PRECISION_WHOLE, UnitOfTemperature
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import AdvantageAirDataConfigEntry
from .const import (
@@ -76,7 +76,7 @@ _LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: AdvantageAirDataConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up AdvantageAir climate platform."""

View File

@@ -9,7 +9,7 @@ from homeassistant.components.cover import (
CoverEntityFeature,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import AdvantageAirDataConfigEntry
from .const import ADVANTAGE_AIR_STATE_CLOSE, ADVANTAGE_AIR_STATE_OPEN
@@ -22,7 +22,7 @@ PARALLEL_UPDATES = 0
async def async_setup_entry(
hass: HomeAssistant,
config_entry: AdvantageAirDataConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up AdvantageAir cover platform."""

View File

@@ -5,7 +5,7 @@ from typing import Any
from homeassistant.components.light import ATTR_BRIGHTNESS, ColorMode, LightEntity
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import AdvantageAirDataConfigEntry
from .const import ADVANTAGE_AIR_STATE_ON, DOMAIN as ADVANTAGE_AIR_DOMAIN
@@ -16,7 +16,7 @@ from .models import AdvantageAirData
async def async_setup_entry(
hass: HomeAssistant,
config_entry: AdvantageAirDataConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up AdvantageAir light platform."""

View File

@@ -2,7 +2,7 @@
from homeassistant.components.select import SelectEntity
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import AdvantageAirDataConfigEntry
from .entity import AdvantageAirAcEntity
@@ -14,7 +14,7 @@ ADVANTAGE_AIR_INACTIVE = "Inactive"
async def async_setup_entry(
hass: HomeAssistant,
config_entry: AdvantageAirDataConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up AdvantageAir select platform."""

View File

@@ -15,7 +15,7 @@ from homeassistant.components.sensor import (
from homeassistant.const import PERCENTAGE, EntityCategory, UnitOfTemperature
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv, entity_platform
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import AdvantageAirDataConfigEntry
from .const import ADVANTAGE_AIR_STATE_OPEN
@@ -32,7 +32,7 @@ PARALLEL_UPDATES = 0
async def async_setup_entry(
hass: HomeAssistant,
config_entry: AdvantageAirDataConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up AdvantageAir sensor platform."""

View File

@@ -4,7 +4,7 @@ from typing import Any
from homeassistant.components.switch import SwitchDeviceClass, SwitchEntity
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import AdvantageAirDataConfigEntry
from .const import (
@@ -19,7 +19,7 @@ from .models import AdvantageAirData
async def async_setup_entry(
hass: HomeAssistant,
config_entry: AdvantageAirDataConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up AdvantageAir switch platform."""

View File

@@ -3,7 +3,7 @@
from homeassistant.components.update import UpdateEntity
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import AdvantageAirDataConfigEntry
from .const import DOMAIN as ADVANTAGE_AIR_DOMAIN
@@ -14,7 +14,7 @@ from .models import AdvantageAirData
async def async_setup_entry(
hass: HomeAssistant,
config_entry: AdvantageAirDataConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up AdvantageAir update platform."""

View File

@@ -9,7 +9,7 @@ from aemet_opendata.helpers import dict_nested_value
from homeassistant.components.image import Image, ImageEntity, ImageEntityDescription
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .coordinator import AemetConfigEntry, WeatherUpdateCoordinator
from .entity import AemetEntity
@@ -25,7 +25,7 @@ AEMET_IMAGES: Final[tuple[ImageEntityDescription, ...]] = (
async def async_setup_entry(
hass: HomeAssistant,
config_entry: AemetConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up AEMET OpenData image entities based on a config entry."""
domain_data = config_entry.runtime_data

View File

@@ -52,7 +52,7 @@ from homeassistant.const import (
UnitOfVolumetricFlux,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util import dt as dt_util
from .const import (
@@ -358,7 +358,7 @@ WEATHER_SENSORS: Final[tuple[AemetSensorEntityDescription, ...]] = (
async def async_setup_entry(
hass: HomeAssistant,
config_entry: AemetConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up AEMET OpenData sensor entities based on a config entry."""
domain_data = config_entry.runtime_data

View File

@@ -25,7 +25,7 @@ from homeassistant.const import (
UnitOfTemperature,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import CONDITIONS_MAP
from .coordinator import AemetConfigEntry, WeatherUpdateCoordinator
@@ -35,7 +35,7 @@ from .entity import AemetEntity
async def async_setup_entry(
hass: HomeAssistant,
config_entry: AemetConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up AEMET OpenData weather entity based on a config entry."""
domain_data = config_entry.runtime_data

View File

@@ -7,7 +7,7 @@ from typing import Final
import voluptuous as vol
from homeassistant.helpers import config_validation as cv
import homeassistant.helpers.config_validation as cv
DOMAIN: Final = "aftership"

View File

@@ -9,12 +9,12 @@ from pyaftership import AfterShip, AfterShipException
from homeassistant.components.sensor import SensorEntity
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.helpers import config_validation as cv
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import (
async_dispatcher_connect,
async_dispatcher_send,
)
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util import Throttle
from . import AfterShipConfigEntry
@@ -42,7 +42,7 @@ PLATFORM_SCHEMA: Final = cv.removed(DOMAIN, raise_if_present=False)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: AfterShipConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up AfterShip sensor entities based on a config entry."""
aftership = config_entry.runtime_data

View File

@@ -9,7 +9,7 @@ from homeassistant.components.alarm_control_panel import (
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import AgentDVRConfigEntry
from .const import DOMAIN as AGENT_DOMAIN
@@ -24,7 +24,7 @@ CONST_ALARM_CONTROL_PANEL_NAME = "Alarm Panel"
async def async_setup_entry(
hass: HomeAssistant,
config_entry: AgentDVRConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the Agent DVR Alarm Control Panels."""
async_add_entities([AgentBaseStation(config_entry.runtime_data)])

View File

@@ -10,7 +10,7 @@ from homeassistant.components.mjpeg import MjpegCamera, filter_urllib3_logging
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import (
AddConfigEntryEntitiesCallback,
AddEntitiesCallback,
async_get_current_platform,
)
@@ -39,7 +39,7 @@ CAMERA_SERVICES = {
async def async_setup_entry(
hass: HomeAssistant,
config_entry: AgentDVRConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the Agent cameras."""
filter_urllib3_logging()

View File

@@ -4,11 +4,12 @@ from __future__ import annotations
from airgradient import AirGradientClient
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .coordinator import AirGradientConfigEntry, AirGradientCoordinator
from .coordinator import AirGradientCoordinator
PLATFORMS: list[Platform] = [
Platform.BUTTON,
@@ -20,6 +21,9 @@ PLATFORMS: list[Platform] = [
]
type AirGradientConfigEntry = ConfigEntry[AirGradientCoordinator]
async def async_setup_entry(hass: HomeAssistant, entry: AirGradientConfigEntry) -> bool:
"""Set up Airgradient from a config entry."""
@@ -27,7 +31,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: AirGradientConfigEntry)
entry.data[CONF_HOST], session=async_get_clientsession(hass)
)
coordinator = AirGradientCoordinator(hass, entry, client)
coordinator = AirGradientCoordinator(hass, client)
await coordinator.async_config_entry_first_refresh()

View File

@@ -13,14 +13,12 @@ from homeassistant.components.button import (
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import AirGradientConfigEntry
from .const import DOMAIN
from .coordinator import AirGradientCoordinator
from .entity import AirGradientEntity, exception_handler
PARALLEL_UPDATES = 1
from .entity import AirGradientEntity
@dataclass(frozen=True, kw_only=True)
@@ -47,7 +45,7 @@ LED_BAR_TEST = AirGradientButtonEntityDescription(
async def async_setup_entry(
hass: HomeAssistant,
entry: AirGradientConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up AirGradient button entities based on a config entry."""
coordinator = entry.runtime_data
@@ -102,7 +100,6 @@ class AirGradientButton(AirGradientEntity, ButtonEntity):
self.entity_description = description
self._attr_unique_id = f"{coordinator.serial_number}-{description.key}"
@exception_handler
async def async_press(self) -> None:
"""Press the button."""
await self.entity_description.press_fn(self.coordinator.client)

View File

@@ -1,6 +1,5 @@
"""Config flow for Airgradient."""
from collections.abc import Mapping
from typing import Any
from airgradient import (
@@ -12,12 +11,7 @@ from airgradient import (
from awesomeversion import AwesomeVersion
import voluptuous as vol
from homeassistant.config_entries import (
SOURCE_RECONFIGURE,
SOURCE_USER,
ConfigFlow,
ConfigFlowResult,
)
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_HOST, CONF_MODEL
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.service_info.zeroconf import ZeroconfServiceInfo
@@ -101,18 +95,10 @@ class AirGradientConfigFlow(ConfigFlow, domain=DOMAIN):
await self.async_set_unique_id(
current_measures.serial_number, raise_on_progress=False
)
if self.source == SOURCE_USER:
self._abort_if_unique_id_configured()
if self.source == SOURCE_RECONFIGURE:
self._abort_if_unique_id_mismatch()
self._abort_if_unique_id_configured()
await self.set_configuration_source()
if self.source == SOURCE_USER:
return self.async_create_entry(
title=current_measures.model,
data={CONF_HOST: user_input[CONF_HOST]},
)
return self.async_update_reload_and_abort(
self._get_reconfigure_entry(),
return self.async_create_entry(
title=current_measures.model,
data={CONF_HOST: user_input[CONF_HOST]},
)
return self.async_show_form(
@@ -120,9 +106,3 @@ class AirGradientConfigFlow(ConfigFlow, domain=DOMAIN):
data_schema=vol.Schema({vol.Required(CONF_HOST): str}),
errors=errors,
)
async def async_step_reconfigure(
self, user_input: Mapping[str, Any]
) -> ConfigFlowResult:
"""Handle reconfiguration."""
return await self.async_step_user()

View File

@@ -4,17 +4,18 @@ from __future__ import annotations
from dataclasses import dataclass
from datetime import timedelta
from typing import TYPE_CHECKING
from airgradient import AirGradientClient, AirGradientError, Config, Measures
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import DOMAIN, LOGGER
type AirGradientConfigEntry = ConfigEntry[AirGradientCoordinator]
if TYPE_CHECKING:
from . import AirGradientConfigEntry
@dataclass
@@ -31,17 +32,11 @@ class AirGradientCoordinator(DataUpdateCoordinator[AirGradientData]):
config_entry: AirGradientConfigEntry
_current_version: str
def __init__(
self,
hass: HomeAssistant,
config_entry: AirGradientConfigEntry,
client: AirGradientClient,
) -> None:
def __init__(self, hass: HomeAssistant, client: AirGradientClient) -> None:
"""Initialize coordinator."""
super().__init__(
hass,
logger=LOGGER,
config_entry=config_entry,
name=f"AirGradient {client.host}",
update_interval=timedelta(minutes=1),
)
@@ -60,11 +55,7 @@ class AirGradientCoordinator(DataUpdateCoordinator[AirGradientData]):
measures = await self.client.get_current_measures()
config = await self.client.get_config()
except AirGradientError as error:
raise UpdateFailed(
translation_domain=DOMAIN,
translation_key="update_error",
translation_placeholders={"error": str(error)},
) from error
raise UpdateFailed(error) from error
if measures.firmware_version != self._current_version:
device_registry = dr.async_get(self.hass)
device_entry = device_registry.async_get_device(

View File

@@ -1,11 +1,7 @@
"""Base class for AirGradient entities."""
from collections.abc import Callable, Coroutine
from typing import Any, Concatenate
from airgradient import get_model_name
from airgradient import AirGradientConnectionError, AirGradientError, get_model_name
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity
@@ -30,31 +26,3 @@ class AirGradientEntity(CoordinatorEntity[AirGradientCoordinator]):
serial_number=coordinator.serial_number,
sw_version=measures.firmware_version,
)
def exception_handler[_EntityT: AirGradientEntity, **_P](
func: Callable[Concatenate[_EntityT, _P], Coroutine[Any, Any, Any]],
) -> Callable[Concatenate[_EntityT, _P], Coroutine[Any, Any, None]]:
"""Decorate AirGradient calls to handle exceptions.
A decorator that wraps the passed in function, catches AirGradient errors.
"""
async def handler(self: _EntityT, *args: _P.args, **kwargs: _P.kwargs) -> None:
try:
await func(self, *args, **kwargs)
except AirGradientConnectionError as error:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="communication_error",
translation_placeholders={"error": str(error)},
) from error
except AirGradientError as error:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="unknown_error",
translation_placeholders={"error": str(error)},
) from error
return handler

View File

@@ -6,6 +6,6 @@
"documentation": "https://www.home-assistant.io/integrations/airgradient",
"integration_type": "device",
"iot_class": "local_polling",
"requirements": ["airgradient==0.9.2"],
"requirements": ["airgradient==0.9.1"],
"zeroconf": ["_airgradient._tcp.local."]
}

View File

@@ -14,14 +14,12 @@ from homeassistant.components.number import (
from homeassistant.const import PERCENTAGE, EntityCategory
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import AirGradientConfigEntry
from .const import DOMAIN
from .coordinator import AirGradientCoordinator
from .entity import AirGradientEntity, exception_handler
PARALLEL_UPDATES = 1
from .entity import AirGradientEntity
@dataclass(frozen=True, kw_only=True)
@@ -60,7 +58,7 @@ LED_BAR_BRIGHTNESS = AirGradientNumberEntityDescription(
async def async_setup_entry(
hass: HomeAssistant,
entry: AirGradientConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up AirGradient number entities based on a config entry."""
@@ -123,7 +121,6 @@ class AirGradientNumber(AirGradientEntity, NumberEntity):
"""Return the state of the number."""
return self.entity_description.value_fn(self.coordinator.data.config)
@exception_handler
async def async_set_native_value(self, value: float) -> None:
"""Set the selected value."""
await self.entity_description.set_value_fn(self.coordinator.client, int(value))

View File

@@ -29,7 +29,7 @@ rules:
unique-config-entry: done
# Silver
action-exceptions: done
action-exceptions: todo
config-entry-unloading: done
docs-configuration-parameters:
status: exempt
@@ -38,7 +38,7 @@ rules:
entity-unavailable: done
integration-owner: done
log-when-unavailable: done
parallel-updates: done
parallel-updates: todo
reauthentication-flow:
status: exempt
comment: |
@@ -68,9 +68,9 @@ rules:
entity-device-class: done
entity-disabled-by-default: done
entity-translations: done
exception-translations: done
exception-translations: todo
icon-translations: done
reconfiguration-flow: done
reconfiguration-flow: todo
repair-issues:
status: exempt
comment: |

View File

@@ -14,14 +14,12 @@ from homeassistant.components.select import (
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import AirGradientConfigEntry
from .const import DOMAIN, PM_STANDARD, PM_STANDARD_REVERSE
from .coordinator import AirGradientCoordinator
from .entity import AirGradientEntity, exception_handler
PARALLEL_UPDATES = 1
from .entity import AirGradientEntity
@dataclass(frozen=True, kw_only=True)
@@ -142,7 +140,7 @@ CONTROL_ENTITIES: tuple[AirGradientSelectEntityDescription, ...] = (
async def async_setup_entry(
hass: HomeAssistant,
entry: AirGradientConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up AirGradient select entities based on a config entry."""
@@ -218,7 +216,6 @@ class AirGradientSelect(AirGradientEntity, SelectEntity):
"""Return the state of the select."""
return self.entity_description.value_fn(self.coordinator.data.config)
@exception_handler
async def async_select_option(self, option: str) -> None:
"""Change the selected option."""
await self.entity_description.set_value_fn(self.coordinator.client, option)

View File

@@ -27,7 +27,7 @@ from homeassistant.const import (
UnitOfTime,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
from . import AirGradientConfigEntry
@@ -35,8 +35,6 @@ from .const import PM_STANDARD, PM_STANDARD_REVERSE
from .coordinator import AirGradientCoordinator
from .entity import AirGradientEntity
PARALLEL_UPDATES = 0
@dataclass(frozen=True, kw_only=True)
class AirGradientMeasurementSensorEntityDescription(SensorEntityDescription):
@@ -225,7 +223,7 @@ CONFIG_DISPLAY_SENSOR_TYPES: tuple[AirGradientConfigSensorEntityDescription, ...
async def async_setup_entry(
hass: HomeAssistant,
entry: AirGradientConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up AirGradient sensor entities based on a config entry."""

View File

@@ -17,9 +17,7 @@
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
"invalid_version": "This firmware version is unsupported. Please upgrade the firmware of the device to at least version 3.1.1.",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]",
"unique_id_mismatch": "Please ensure you reconfigure against the same device."
"invalid_version": "This firmware version is unsupported. Please upgrade the firmware of the device to at least version 3.1.1."
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
@@ -167,16 +165,5 @@
"name": "Post data to Airgradient"
}
}
},
"exceptions": {
"communication_error": {
"message": "An error occurred while communicating with the Airgradient device: {error}"
},
"unknown_error": {
"message": "An unknown error occurred while communicating with the Airgradient device: {error}"
},
"update_error": {
"message": "An error occurred while communicating with the Airgradient device: {error}"
}
}
}

View File

@@ -15,14 +15,12 @@ from homeassistant.components.switch import (
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import AirGradientConfigEntry
from .const import DOMAIN
from .coordinator import AirGradientCoordinator
from .entity import AirGradientEntity, exception_handler
PARALLEL_UPDATES = 1
from .entity import AirGradientEntity
@dataclass(frozen=True, kw_only=True)
@@ -45,7 +43,7 @@ POST_DATA_TO_AIRGRADIENT = AirGradientSwitchEntityDescription(
async def async_setup_entry(
hass: HomeAssistant,
entry: AirGradientConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up AirGradient switch entities based on a config entry."""
coordinator = entry.runtime_data
@@ -101,13 +99,11 @@ class AirGradientSwitch(AirGradientEntity, SwitchEntity):
"""Return the state of the switch."""
return self.entity_description.value_fn(self.coordinator.data.config)
@exception_handler
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the switch on."""
await self.entity_description.set_value_fn(self.coordinator.client, True)
await self.coordinator.async_request_refresh()
@exception_handler
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the switch off."""
await self.entity_description.set_value_fn(self.coordinator.client, False)

View File

@@ -2,23 +2,22 @@
from datetime import timedelta
from propcache.api import cached_property
from propcache import cached_property
from homeassistant.components.update import UpdateDeviceClass, UpdateEntity
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import AirGradientConfigEntry, AirGradientCoordinator
from .entity import AirGradientEntity
PARALLEL_UPDATES = 1
SCAN_INTERVAL = timedelta(hours=1)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: AirGradientConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up Airgradient update platform."""

View File

@@ -6,18 +6,21 @@ from datetime import timedelta
import logging
from homeassistant.components.air_quality import DOMAIN as AIR_QUALITY_PLATFORM
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import CONF_USE_NEAREST, DOMAIN, MIN_UPDATE_INTERVAL
from .coordinator import AirlyConfigEntry, AirlyDataUpdateCoordinator
from .coordinator import AirlyDataUpdateCoordinator
PLATFORMS = [Platform.SENSOR]
_LOGGER = logging.getLogger(__name__)
type AirlyConfigEntry = ConfigEntry[AirlyDataUpdateCoordinator]
async def async_setup_entry(hass: HomeAssistant, entry: AirlyConfigEntry) -> bool:
"""Set up Airly as config entry."""
@@ -57,14 +60,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: AirlyConfigEntry) -> boo
update_interval = timedelta(minutes=MIN_UPDATE_INTERVAL)
coordinator = AirlyDataUpdateCoordinator(
hass,
entry,
websession,
api_key,
latitude,
longitude,
update_interval,
use_nearest,
hass, websession, api_key, latitude, longitude, update_interval, use_nearest
)
await coordinator.async_config_entry_first_refresh()

View File

@@ -13,8 +13,8 @@ import voluptuous as vol
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
from .const import CONF_USE_NEAREST, DOMAIN, NO_AIRLY_SENSORS

View File

@@ -10,7 +10,6 @@ from aiohttp.client_exceptions import ClientConnectorError
from airly import Airly
from airly.exceptions import AirlyError
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from homeassistant.util import dt as dt_util
@@ -28,8 +27,6 @@ from .const import (
_LOGGER = logging.getLogger(__name__)
type AirlyConfigEntry = ConfigEntry[AirlyDataUpdateCoordinator]
def set_update_interval(instances_count: int, requests_remaining: int) -> timedelta:
"""Return data update interval.
@@ -61,12 +58,9 @@ def set_update_interval(instances_count: int, requests_remaining: int) -> timede
class AirlyDataUpdateCoordinator(DataUpdateCoordinator[dict[str, str | float | int]]):
"""Define an object to hold Airly data."""
config_entry: AirlyConfigEntry
def __init__(
self,
hass: HomeAssistant,
config_entry: AirlyConfigEntry,
session: ClientSession,
api_key: str,
latitude: float,
@@ -82,13 +76,7 @@ class AirlyDataUpdateCoordinator(DataUpdateCoordinator[dict[str, str | float | i
self.airly = Airly(api_key, session, language=language)
self.use_nearest = use_nearest
super().__init__(
hass,
_LOGGER,
config_entry=config_entry,
name=DOMAIN,
update_interval=update_interval,
)
super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=update_interval)
async def _async_update_data(self) -> dict[str, str | float | int]:
"""Update data via library."""

View File

@@ -13,7 +13,7 @@ from homeassistant.const import (
)
from homeassistant.core import HomeAssistant
from .coordinator import AirlyConfigEntry
from . import AirlyConfigEntry
TO_REDACT = {CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_UNIQUE_ID}

View File

@@ -21,9 +21,10 @@ from homeassistant.const import (
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from . import AirlyConfigEntry, AirlyDataUpdateCoordinator
from .const import (
ATTR_ADVICE,
ATTR_API_ADVICE,
@@ -51,7 +52,6 @@ from .const import (
SUFFIX_PERCENT,
URL,
)
from .coordinator import AirlyConfigEntry, AirlyDataUpdateCoordinator
PARALLEL_UPDATES = 1
@@ -175,7 +175,7 @@ SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = (
async def async_setup_entry(
hass: HomeAssistant,
entry: AirlyConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up Airly sensor entities based on a config entry."""
name = entry.data[CONF_NAME]

View File

@@ -9,8 +9,8 @@ from airly import Airly
from homeassistant.components import system_health
from homeassistant.core import HomeAssistant, callback
from . import AirlyConfigEntry
from .const import DOMAIN
from .coordinator import AirlyConfigEntry
@callback

View File

@@ -15,11 +15,13 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .coordinator import AirNowConfigEntry, AirNowDataUpdateCoordinator
from .coordinator import AirNowDataUpdateCoordinator
_LOGGER = logging.getLogger(__name__)
PLATFORMS = [Platform.SENSOR]
type AirNowConfigEntry = ConfigEntry[AirNowDataUpdateCoordinator]
async def async_setup_entry(hass: HomeAssistant, entry: AirNowConfigEntry) -> bool:
"""Set up AirNow from a config entry."""
@@ -36,7 +38,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: AirNowConfigEntry) -> bo
# Setup the Coordinator
session = async_get_clientsession(hass)
coordinator = AirNowDataUpdateCoordinator(
hass, entry, session, api_key, latitude, longitude, distance, update_interval
hass, session, api_key, latitude, longitude, distance, update_interval
)
# Sync with Coordinator

View File

@@ -18,8 +18,8 @@ from homeassistant.config_entries import (
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_RADIUS
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
from .const import DOMAIN

View File

@@ -10,7 +10,6 @@ from pyairnow import WebServiceAPI
from pyairnow.conv import aqi_to_concentration
from pyairnow.errors import AirNowError
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
@@ -22,6 +21,7 @@ from .const import (
ATTR_API_CAT_DESCRIPTION,
ATTR_API_CAT_LEVEL,
ATTR_API_CATEGORY,
ATTR_API_PM25,
ATTR_API_POLLUTANT,
ATTR_API_REPORT_DATE,
ATTR_API_REPORT_HOUR,
@@ -35,18 +35,13 @@ from .const import (
_LOGGER = logging.getLogger(__name__)
type AirNowConfigEntry = ConfigEntry[AirNowDataUpdateCoordinator]
class AirNowDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
"""The AirNow update coordinator."""
config_entry: AirNowConfigEntry
def __init__(
self,
hass: HomeAssistant,
config_entry: AirNowConfigEntry,
session: ClientSession,
api_key: str,
latitude: float,
@@ -61,13 +56,7 @@ class AirNowDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
self.airnow = WebServiceAPI(api_key, session=session)
super().__init__(
hass,
_LOGGER,
config_entry=config_entry,
name=DOMAIN,
update_interval=update_interval,
)
super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=update_interval)
async def _async_update_data(self) -> dict[str, Any]:
"""Update data via library."""
@@ -102,16 +91,18 @@ class AirNowDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
max_aqi_desc = obv[ATTR_API_CATEGORY][ATTR_API_CAT_DESCRIPTION]
max_aqi_poll = pollutant
# Copy Report Details
data[ATTR_API_REPORT_DATE] = obv[ATTR_API_REPORT_DATE]
data[ATTR_API_REPORT_HOUR] = obv[ATTR_API_REPORT_HOUR]
data[ATTR_API_REPORT_TZ] = obv[ATTR_API_REPORT_TZ]
# Copy other data from PM2.5 Value
if obv[ATTR_API_AQI_PARAM] == ATTR_API_PM25:
# Copy Report Details
data[ATTR_API_REPORT_DATE] = obv[ATTR_API_REPORT_DATE]
data[ATTR_API_REPORT_HOUR] = obv[ATTR_API_REPORT_HOUR]
data[ATTR_API_REPORT_TZ] = obv[ATTR_API_REPORT_TZ]
# Copy Station Details
data[ATTR_API_STATE] = obv[ATTR_API_STATE]
data[ATTR_API_STATION] = obv[ATTR_API_STATION]
data[ATTR_API_STATION_LATITUDE] = obv[ATTR_API_STATION_LATITUDE]
data[ATTR_API_STATION_LONGITUDE] = obv[ATTR_API_STATION_LONGITUDE]
# Copy Station Details
data[ATTR_API_STATE] = obv[ATTR_API_STATE]
data[ATTR_API_STATION] = obv[ATTR_API_STATION]
data[ATTR_API_STATION_LATITUDE] = obv[ATTR_API_STATION_LATITUDE]
data[ATTR_API_STATION_LONGITUDE] = obv[ATTR_API_STATION_LONGITUDE]
# Store Overall AQI
data[ATTR_API_AQI] = max_aqi

View File

@@ -13,7 +13,7 @@ from homeassistant.const import (
)
from homeassistant.core import HomeAssistant
from .coordinator import AirNowConfigEntry
from . import AirNowConfigEntry
ATTR_LATITUDE_CAP = "Latitude"
ATTR_LONGITUDE_CAP = "Longitude"

View File

@@ -21,10 +21,11 @@ from homeassistant.const import (
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from . import AirNowConfigEntry, AirNowDataUpdateCoordinator
from .const import (
ATTR_API_AQI,
ATTR_API_AQI_DESCRIPTION,
@@ -42,7 +43,6 @@ from .const import (
DOMAIN,
US_TZ_OFFSETS,
)
from .coordinator import AirNowConfigEntry, AirNowDataUpdateCoordinator
ATTRIBUTION = "Data provided by AirNow"
@@ -130,7 +130,7 @@ SENSOR_TYPES: tuple[AirNowEntityDescription, ...] = (
async def async_setup_entry(
hass: HomeAssistant,
config_entry: AirNowConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up AirNow sensor entities based on a config entry."""
coordinator = config_entry.runtime_data

View File

@@ -83,7 +83,6 @@ class AirQConfigFlow(ConfigFlow, domain=DOMAIN):
await self.async_set_unique_id(device_info["id"])
self._abort_if_unique_id_configured()
_LOGGER.debug("Creating an entry for %s", device_info["name"])
return self.async_create_entry(title=device_info["name"], data=user_input)
return self.async_show_form(

View File

@@ -5,7 +5,7 @@ from __future__ import annotations
from datetime import timedelta
import logging
from aioairq.core import AirQ, identify_warming_up_sensors
from aioairq import AirQ
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD
@@ -22,8 +22,6 @@ _LOGGER = logging.getLogger(__name__)
class AirQCoordinator(DataUpdateCoordinator):
"""Coordinator is responsible for querying the device at a specified route."""
config_entry: ConfigEntry
def __init__(
self,
hass: HomeAssistant,
@@ -35,7 +33,6 @@ class AirQCoordinator(DataUpdateCoordinator):
super().__init__(
hass,
_LOGGER,
config_entry=entry,
name=DOMAIN,
update_interval=timedelta(seconds=UPDATE_INTERVAL),
)
@@ -55,9 +52,6 @@ class AirQCoordinator(DataUpdateCoordinator):
async def _async_update_data(self) -> dict:
"""Fetch the data from the device."""
if "name" not in self.device_info:
_LOGGER.debug(
"'name' not found in AirQCoordinator.device_info, fetching from the device"
)
info = await self.airq.fetch_device_info()
self.device_info.update(
DeviceInfo(
@@ -67,16 +61,7 @@ class AirQCoordinator(DataUpdateCoordinator):
hw_version=info["hw_version"],
)
)
_LOGGER.debug(
"Updated AirQCoordinator.device_info for 'name' %s",
self.device_info.get("name"),
)
data: dict = await self.airq.get_latest_data(
return await self.airq.get_latest_data( # type: ignore[no-any-return]
return_average=self.return_average,
clip_negative_values=self.clip_negative,
)
if warming_up_sensors := identify_warming_up_sensors(data):
_LOGGER.debug(
"Following sensors are still warming up: %s", warming_up_sensors
)
return data

View File

@@ -7,5 +7,5 @@
"integration_type": "hub",
"iot_class": "local_polling",
"loggers": ["aioairq"],
"requirements": ["aioairq==0.4.4"]
"requirements": ["aioairq==0.4.3"]
}

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