mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 21:27:38 +00:00
Merge branch 'dev' into prepare_protobuf6
This commit is contained in:
commit
469d6d341b
BIN
.github/assets/screenshot-integrations.png
vendored
BIN
.github/assets/screenshot-integrations.png
vendored
Binary file not shown.
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 99 KiB |
100
.github/copilot-instructions.md
vendored
Normal file
100
.github/copilot-instructions.md
vendored
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
# 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
|
48
.github/workflows/builder.yml
vendored
48
.github/workflows/builder.yml
vendored
@ -69,7 +69,7 @@ jobs:
|
|||||||
run: find ./homeassistant/components/*/translations -name "*.json" | tar zcvf translations.tar.gz -T -
|
run: find ./homeassistant/components/*/translations -name "*.json" | tar zcvf translations.tar.gz -T -
|
||||||
|
|
||||||
- name: Upload translations
|
- name: Upload translations
|
||||||
uses: actions/upload-artifact@v4.6.0
|
uses: actions/upload-artifact@v4.6.2
|
||||||
with:
|
with:
|
||||||
name: translations
|
name: translations
|
||||||
path: translations.tar.gz
|
path: translations.tar.gz
|
||||||
@ -94,7 +94,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Download nightly wheels of frontend
|
- name: Download nightly wheels of frontend
|
||||||
if: needs.init.outputs.channel == 'dev'
|
if: needs.init.outputs.channel == 'dev'
|
||||||
uses: dawidd6/action-download-artifact@v8
|
uses: dawidd6/action-download-artifact@v9
|
||||||
with:
|
with:
|
||||||
github_token: ${{secrets.GITHUB_TOKEN}}
|
github_token: ${{secrets.GITHUB_TOKEN}}
|
||||||
repo: home-assistant/frontend
|
repo: home-assistant/frontend
|
||||||
@ -105,7 +105,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Download nightly wheels of intents
|
- name: Download nightly wheels of intents
|
||||||
if: needs.init.outputs.channel == 'dev'
|
if: needs.init.outputs.channel == 'dev'
|
||||||
uses: dawidd6/action-download-artifact@v8
|
uses: dawidd6/action-download-artifact@v9
|
||||||
with:
|
with:
|
||||||
github_token: ${{secrets.GITHUB_TOKEN}}
|
github_token: ${{secrets.GITHUB_TOKEN}}
|
||||||
repo: home-assistant/intents-package
|
repo: home-assistant/intents-package
|
||||||
@ -175,7 +175,7 @@ jobs:
|
|||||||
sed -i "s|pykrakenapi|# pykrakenapi|g" requirements_all.txt
|
sed -i "s|pykrakenapi|# pykrakenapi|g" requirements_all.txt
|
||||||
|
|
||||||
- name: Download translations
|
- name: Download translations
|
||||||
uses: actions/download-artifact@v4.1.8
|
uses: actions/download-artifact@v4.2.1
|
||||||
with:
|
with:
|
||||||
name: translations
|
name: translations
|
||||||
|
|
||||||
@ -190,14 +190,14 @@ jobs:
|
|||||||
echo "${{ github.sha }};${{ github.ref }};${{ github.event_name }};${{ github.actor }}" > rootfs/OFFICIAL_IMAGE
|
echo "${{ github.sha }};${{ github.ref }};${{ github.event_name }};${{ github.actor }}" > rootfs/OFFICIAL_IMAGE
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@v3.3.0
|
uses: docker/login-action@v3.4.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Build base image
|
- name: Build base image
|
||||||
uses: home-assistant/builder@2024.08.2
|
uses: home-assistant/builder@2025.03.0
|
||||||
with:
|
with:
|
||||||
args: |
|
args: |
|
||||||
$BUILD_ARGS \
|
$BUILD_ARGS \
|
||||||
@ -256,14 +256,14 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@v3.3.0
|
uses: docker/login-action@v3.4.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Build base image
|
- name: Build base image
|
||||||
uses: home-assistant/builder@2024.08.2
|
uses: home-assistant/builder@2025.03.0
|
||||||
with:
|
with:
|
||||||
args: |
|
args: |
|
||||||
$BUILD_ARGS \
|
$BUILD_ARGS \
|
||||||
@ -324,20 +324,20 @@ jobs:
|
|||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@v4.2.2
|
||||||
|
|
||||||
- name: Install Cosign
|
- name: Install Cosign
|
||||||
uses: sigstore/cosign-installer@v3.8.0
|
uses: sigstore/cosign-installer@v3.8.1
|
||||||
with:
|
with:
|
||||||
cosign-release: "v2.2.3"
|
cosign-release: "v2.2.3"
|
||||||
|
|
||||||
- name: Login to DockerHub
|
- name: Login to DockerHub
|
||||||
if: matrix.registry == 'docker.io/homeassistant'
|
if: matrix.registry == 'docker.io/homeassistant'
|
||||||
uses: docker/login-action@v3.3.0
|
uses: docker/login-action@v3.4.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
if: matrix.registry == 'ghcr.io/home-assistant'
|
if: matrix.registry == 'ghcr.io/home-assistant'
|
||||||
uses: docker/login-action@v3.3.0
|
uses: docker/login-action@v3.4.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
@ -448,6 +448,9 @@ jobs:
|
|||||||
environment: ${{ needs.init.outputs.channel }}
|
environment: ${{ needs.init.outputs.channel }}
|
||||||
needs: ["init", "build_base"]
|
needs: ["init", "build_base"]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
id-token: write
|
||||||
if: github.repository_owner == 'home-assistant' && needs.init.outputs.publish == 'true'
|
if: github.repository_owner == 'home-assistant' && needs.init.outputs.publish == 'true'
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
@ -459,7 +462,7 @@ jobs:
|
|||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
|
|
||||||
- name: Download translations
|
- name: Download translations
|
||||||
uses: actions/download-artifact@v4.1.8
|
uses: actions/download-artifact@v4.2.1
|
||||||
with:
|
with:
|
||||||
name: translations
|
name: translations
|
||||||
|
|
||||||
@ -473,16 +476,13 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
# Remove dist, build, and homeassistant.egg-info
|
# Remove dist, build, and homeassistant.egg-info
|
||||||
# when build locally for testing!
|
# when build locally for testing!
|
||||||
pip install twine build
|
pip install build
|
||||||
python -m build
|
python -m build
|
||||||
|
|
||||||
- name: Upload package
|
- name: Upload package to PyPI
|
||||||
shell: bash
|
uses: pypa/gh-action-pypi-publish@v1.12.4
|
||||||
run: |
|
with:
|
||||||
export TWINE_USERNAME="__token__"
|
skip-existing: true
|
||||||
export TWINE_PASSWORD="${{ secrets.TWINE_TOKEN }}"
|
|
||||||
|
|
||||||
twine upload dist/* --skip-existing
|
|
||||||
|
|
||||||
hassfest-image:
|
hassfest-image:
|
||||||
name: Build and test hassfest image
|
name: Build and test hassfest image
|
||||||
@ -502,14 +502,14 @@ jobs:
|
|||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
|
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Build Docker image
|
- name: Build Docker image
|
||||||
uses: docker/build-push-action@ca877d9245402d1537745e0e356eab47c3520991 # v6.13.0
|
uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0
|
||||||
with:
|
with:
|
||||||
context: . # So action will not pull the repository again
|
context: . # So action will not pull the repository again
|
||||||
file: ./script/hassfest/docker/Dockerfile
|
file: ./script/hassfest/docker/Dockerfile
|
||||||
@ -522,7 +522,7 @@ jobs:
|
|||||||
- name: Push Docker image
|
- name: Push Docker image
|
||||||
if: needs.init.outputs.channel != 'dev' && needs.init.outputs.publish == 'true'
|
if: needs.init.outputs.channel != 'dev' && needs.init.outputs.publish == 'true'
|
||||||
id: push
|
id: push
|
||||||
uses: docker/build-push-action@ca877d9245402d1537745e0e356eab47c3520991 # v6.13.0
|
uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0
|
||||||
with:
|
with:
|
||||||
context: . # So action will not pull the repository again
|
context: . # So action will not pull the repository again
|
||||||
file: ./script/hassfest/docker/Dockerfile
|
file: ./script/hassfest/docker/Dockerfile
|
||||||
@ -531,7 +531,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Generate artifact attestation
|
- name: Generate artifact attestation
|
||||||
if: needs.init.outputs.channel != 'dev' && needs.init.outputs.publish == 'true'
|
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@c074443f1aee8d4aeeae555aebba3282517141b2 # v2.2.3
|
||||||
with:
|
with:
|
||||||
subject-name: ${{ env.HASSFEST_IMAGE_NAME }}
|
subject-name: ${{ env.HASSFEST_IMAGE_NAME }}
|
||||||
subject-digest: ${{ steps.push.outputs.digest }}
|
subject-digest: ${{ steps.push.outputs.digest }}
|
||||||
|
199
.github/workflows/ci.yaml
vendored
199
.github/workflows/ci.yaml
vendored
@ -37,10 +37,10 @@ on:
|
|||||||
type: boolean
|
type: boolean
|
||||||
|
|
||||||
env:
|
env:
|
||||||
CACHE_VERSION: 11
|
CACHE_VERSION: 12
|
||||||
UV_CACHE_VERSION: 1
|
UV_CACHE_VERSION: 1
|
||||||
MYPY_CACHE_VERSION: 9
|
MYPY_CACHE_VERSION: 9
|
||||||
HA_SHORT_VERSION: "2025.3"
|
HA_SHORT_VERSION: "2025.4"
|
||||||
DEFAULT_PYTHON: "3.13"
|
DEFAULT_PYTHON: "3.13"
|
||||||
ALL_PYTHON_VERSIONS: "['3.13']"
|
ALL_PYTHON_VERSIONS: "['3.13']"
|
||||||
# 10.3 is the oldest supported version
|
# 10.3 is the oldest supported version
|
||||||
@ -89,6 +89,7 @@ jobs:
|
|||||||
test_groups: ${{ steps.info.outputs.test_groups }}
|
test_groups: ${{ steps.info.outputs.test_groups }}
|
||||||
tests_glob: ${{ steps.info.outputs.tests_glob }}
|
tests_glob: ${{ steps.info.outputs.tests_glob }}
|
||||||
tests: ${{ steps.info.outputs.tests }}
|
tests: ${{ steps.info.outputs.tests }}
|
||||||
|
lint_only: ${{ steps.info.outputs.lint_only }}
|
||||||
skip_coverage: ${{ steps.info.outputs.skip_coverage }}
|
skip_coverage: ${{ steps.info.outputs.skip_coverage }}
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
@ -142,6 +143,7 @@ jobs:
|
|||||||
test_group_count=10
|
test_group_count=10
|
||||||
tests="[]"
|
tests="[]"
|
||||||
tests_glob=""
|
tests_glob=""
|
||||||
|
lint_only=""
|
||||||
skip_coverage=""
|
skip_coverage=""
|
||||||
|
|
||||||
if [[ "${{ steps.integrations.outputs.changes }}" != "[]" ]];
|
if [[ "${{ steps.integrations.outputs.changes }}" != "[]" ]];
|
||||||
@ -192,6 +194,17 @@ jobs:
|
|||||||
test_full_suite="true"
|
test_full_suite="true"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [[ "${{ github.event.inputs.lint-only }}" == "true" ]] \
|
||||||
|
|| [[ "${{ github.event.inputs.pylint-only }}" == "true" ]] \
|
||||||
|
|| [[ "${{ github.event.inputs.mypy-only }}" == "true" ]] \
|
||||||
|
|| [[ "${{ github.event.inputs.audit-licenses-only }}" == "true" ]] \
|
||||||
|
|| [[ "${{ github.event_name }}" == "push" \
|
||||||
|
&& "${{ github.event.repository.full_name }}" != "home-assistant/core" ]];
|
||||||
|
then
|
||||||
|
lint_only="true"
|
||||||
|
skip_coverage="true"
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ "${{ github.event.inputs.skip-coverage }}" == "true" ]] \
|
if [[ "${{ github.event.inputs.skip-coverage }}" == "true" ]] \
|
||||||
|| [[ "${{ contains(github.event.pull_request.labels.*.name, 'ci-skip-coverage') }}" == "true" ]];
|
|| [[ "${{ contains(github.event.pull_request.labels.*.name, 'ci-skip-coverage') }}" == "true" ]];
|
||||||
then
|
then
|
||||||
@ -217,6 +230,8 @@ jobs:
|
|||||||
echo "tests=${tests}" >> $GITHUB_OUTPUT
|
echo "tests=${tests}" >> $GITHUB_OUTPUT
|
||||||
echo "tests_glob: ${tests_glob}"
|
echo "tests_glob: ${tests_glob}"
|
||||||
echo "tests_glob=${tests_glob}" >> $GITHUB_OUTPUT
|
echo "tests_glob=${tests_glob}" >> $GITHUB_OUTPUT
|
||||||
|
echo "lint_only": ${lint_only}
|
||||||
|
echo "lint_only=${lint_only}" >> $GITHUB_OUTPUT
|
||||||
echo "skip_coverage: ${skip_coverage}"
|
echo "skip_coverage: ${skip_coverage}"
|
||||||
echo "skip_coverage=${skip_coverage}" >> $GITHUB_OUTPUT
|
echo "skip_coverage=${skip_coverage}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
@ -240,7 +255,7 @@ jobs:
|
|||||||
check-latest: true
|
check-latest: true
|
||||||
- name: Restore base Python virtual environment
|
- name: Restore base Python virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache@v4.2.0
|
uses: actions/cache@v4.2.3
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
key: >-
|
key: >-
|
||||||
@ -256,7 +271,7 @@ jobs:
|
|||||||
uv pip install "$(cat requirements_test.txt | grep pre-commit)"
|
uv pip install "$(cat requirements_test.txt | grep pre-commit)"
|
||||||
- name: Restore pre-commit environment from cache
|
- name: Restore pre-commit environment from cache
|
||||||
id: cache-precommit
|
id: cache-precommit
|
||||||
uses: actions/cache@v4.2.0
|
uses: actions/cache@v4.2.3
|
||||||
with:
|
with:
|
||||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||||
lookup-only: true
|
lookup-only: true
|
||||||
@ -286,7 +301,7 @@ jobs:
|
|||||||
check-latest: true
|
check-latest: true
|
||||||
- name: Restore base Python virtual environment
|
- name: Restore base Python virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache/restore@v4.2.0
|
uses: actions/cache/restore@v4.2.3
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
fail-on-cache-miss: true
|
fail-on-cache-miss: true
|
||||||
@ -295,7 +310,7 @@ jobs:
|
|||||||
needs.info.outputs.pre-commit_cache_key }}
|
needs.info.outputs.pre-commit_cache_key }}
|
||||||
- name: Restore pre-commit environment from cache
|
- name: Restore pre-commit environment from cache
|
||||||
id: cache-precommit
|
id: cache-precommit
|
||||||
uses: actions/cache/restore@v4.2.0
|
uses: actions/cache/restore@v4.2.3
|
||||||
with:
|
with:
|
||||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||||
fail-on-cache-miss: true
|
fail-on-cache-miss: true
|
||||||
@ -326,7 +341,7 @@ jobs:
|
|||||||
check-latest: true
|
check-latest: true
|
||||||
- name: Restore base Python virtual environment
|
- name: Restore base Python virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache/restore@v4.2.0
|
uses: actions/cache/restore@v4.2.3
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
fail-on-cache-miss: true
|
fail-on-cache-miss: true
|
||||||
@ -335,7 +350,7 @@ jobs:
|
|||||||
needs.info.outputs.pre-commit_cache_key }}
|
needs.info.outputs.pre-commit_cache_key }}
|
||||||
- name: Restore pre-commit environment from cache
|
- name: Restore pre-commit environment from cache
|
||||||
id: cache-precommit
|
id: cache-precommit
|
||||||
uses: actions/cache/restore@v4.2.0
|
uses: actions/cache/restore@v4.2.3
|
||||||
with:
|
with:
|
||||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||||
fail-on-cache-miss: true
|
fail-on-cache-miss: true
|
||||||
@ -366,7 +381,7 @@ jobs:
|
|||||||
check-latest: true
|
check-latest: true
|
||||||
- name: Restore base Python virtual environment
|
- name: Restore base Python virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache/restore@v4.2.0
|
uses: actions/cache/restore@v4.2.3
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
fail-on-cache-miss: true
|
fail-on-cache-miss: true
|
||||||
@ -375,7 +390,7 @@ jobs:
|
|||||||
needs.info.outputs.pre-commit_cache_key }}
|
needs.info.outputs.pre-commit_cache_key }}
|
||||||
- name: Restore pre-commit environment from cache
|
- name: Restore pre-commit environment from cache
|
||||||
id: cache-precommit
|
id: cache-precommit
|
||||||
uses: actions/cache/restore@v4.2.0
|
uses: actions/cache/restore@v4.2.3
|
||||||
with:
|
with:
|
||||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||||
fail-on-cache-miss: true
|
fail-on-cache-miss: true
|
||||||
@ -482,7 +497,7 @@ jobs:
|
|||||||
env.HA_SHORT_VERSION }}-$(date -u '+%Y-%m-%dT%H:%M:%s')" >> $GITHUB_OUTPUT
|
env.HA_SHORT_VERSION }}-$(date -u '+%Y-%m-%dT%H:%M:%s')" >> $GITHUB_OUTPUT
|
||||||
- name: Restore base Python virtual environment
|
- name: Restore base Python virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache@v4.2.0
|
uses: actions/cache@v4.2.3
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
key: >-
|
key: >-
|
||||||
@ -490,7 +505,7 @@ jobs:
|
|||||||
needs.info.outputs.python_cache_key }}
|
needs.info.outputs.python_cache_key }}
|
||||||
- name: Restore uv wheel cache
|
- name: Restore uv wheel cache
|
||||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||||
uses: actions/cache@v4.2.0
|
uses: actions/cache@v4.2.3
|
||||||
with:
|
with:
|
||||||
path: ${{ env.UV_CACHE_DIR }}
|
path: ${{ env.UV_CACHE_DIR }}
|
||||||
key: >-
|
key: >-
|
||||||
@ -537,7 +552,7 @@ jobs:
|
|||||||
python --version
|
python --version
|
||||||
uv pip freeze >> pip_freeze.txt
|
uv pip freeze >> pip_freeze.txt
|
||||||
- name: Upload pip_freeze artifact
|
- name: Upload pip_freeze artifact
|
||||||
uses: actions/upload-artifact@v4.6.0
|
uses: actions/upload-artifact@v4.6.2
|
||||||
with:
|
with:
|
||||||
name: pip-freeze-${{ matrix.python-version }}
|
name: pip-freeze-${{ matrix.python-version }}
|
||||||
path: pip_freeze.txt
|
path: pip_freeze.txt
|
||||||
@ -578,7 +593,7 @@ jobs:
|
|||||||
check-latest: true
|
check-latest: true
|
||||||
- name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment
|
- name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache/restore@v4.2.0
|
uses: actions/cache/restore@v4.2.3
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
fail-on-cache-miss: true
|
fail-on-cache-miss: true
|
||||||
@ -611,7 +626,7 @@ jobs:
|
|||||||
check-latest: true
|
check-latest: true
|
||||||
- name: Restore base Python virtual environment
|
- name: Restore base Python virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache/restore@v4.2.0
|
uses: actions/cache/restore@v4.2.3
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
fail-on-cache-miss: true
|
fail-on-cache-miss: true
|
||||||
@ -623,6 +638,25 @@ jobs:
|
|||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
python -m script.gen_requirements_all validate
|
python -m script.gen_requirements_all validate
|
||||||
|
|
||||||
|
dependency-review:
|
||||||
|
name: Dependency review
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
needs:
|
||||||
|
- info
|
||||||
|
- base
|
||||||
|
if: |
|
||||||
|
github.event.inputs.pylint-only != 'true'
|
||||||
|
&& github.event.inputs.mypy-only != 'true'
|
||||||
|
&& needs.info.outputs.requirements == 'true'
|
||||||
|
&& github.event_name == 'pull_request'
|
||||||
|
steps:
|
||||||
|
- name: Check out code from GitHub
|
||||||
|
uses: actions/checkout@v4.2.2
|
||||||
|
- name: Dependency review
|
||||||
|
uses: actions/dependency-review-action@v4.5.0
|
||||||
|
with:
|
||||||
|
license-check: false # We use our own license audit checks
|
||||||
|
|
||||||
audit-licenses:
|
audit-licenses:
|
||||||
name: Audit licenses
|
name: Audit licenses
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
@ -649,7 +683,7 @@ jobs:
|
|||||||
check-latest: true
|
check-latest: true
|
||||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache/restore@v4.2.0
|
uses: actions/cache/restore@v4.2.3
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
fail-on-cache-miss: true
|
fail-on-cache-miss: true
|
||||||
@ -661,7 +695,7 @@ jobs:
|
|||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
python -m script.licenses extract --output-file=licenses-${{ matrix.python-version }}.json
|
python -m script.licenses extract --output-file=licenses-${{ matrix.python-version }}.json
|
||||||
- name: Upload licenses
|
- name: Upload licenses
|
||||||
uses: actions/upload-artifact@v4.6.0
|
uses: actions/upload-artifact@v4.6.2
|
||||||
with:
|
with:
|
||||||
name: licenses-${{ github.run_number }}-${{ matrix.python-version }}
|
name: licenses-${{ github.run_number }}-${{ matrix.python-version }}
|
||||||
path: licenses-${{ matrix.python-version }}.json
|
path: licenses-${{ matrix.python-version }}.json
|
||||||
@ -692,7 +726,7 @@ jobs:
|
|||||||
check-latest: true
|
check-latest: true
|
||||||
- name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment
|
- name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache/restore@v4.2.0
|
uses: actions/cache/restore@v4.2.3
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
fail-on-cache-miss: true
|
fail-on-cache-miss: true
|
||||||
@ -739,7 +773,7 @@ jobs:
|
|||||||
check-latest: true
|
check-latest: true
|
||||||
- name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment
|
- name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache/restore@v4.2.0
|
uses: actions/cache/restore@v4.2.3
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
fail-on-cache-miss: true
|
fail-on-cache-miss: true
|
||||||
@ -791,7 +825,7 @@ jobs:
|
|||||||
env.HA_SHORT_VERSION }}-$(date -u '+%Y-%m-%dT%H:%M:%s')" >> $GITHUB_OUTPUT
|
env.HA_SHORT_VERSION }}-$(date -u '+%Y-%m-%dT%H:%M:%s')" >> $GITHUB_OUTPUT
|
||||||
- name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment
|
- name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache/restore@v4.2.0
|
uses: actions/cache/restore@v4.2.3
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
fail-on-cache-miss: true
|
fail-on-cache-miss: true
|
||||||
@ -799,7 +833,7 @@ jobs:
|
|||||||
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
||||||
needs.info.outputs.python_cache_key }}
|
needs.info.outputs.python_cache_key }}
|
||||||
- name: Restore mypy cache
|
- name: Restore mypy cache
|
||||||
uses: actions/cache@v4.2.0
|
uses: actions/cache@v4.2.3
|
||||||
with:
|
with:
|
||||||
path: .mypy_cache
|
path: .mypy_cache
|
||||||
key: >-
|
key: >-
|
||||||
@ -829,11 +863,7 @@ jobs:
|
|||||||
prepare-pytest-full:
|
prepare-pytest-full:
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
if: |
|
if: |
|
||||||
(github.event_name != 'push' || github.event.repository.full_name == 'home-assistant/core')
|
needs.info.outputs.lint_only != 'true'
|
||||||
&& github.event.inputs.lint-only != 'true'
|
|
||||||
&& github.event.inputs.pylint-only != 'true'
|
|
||||||
&& github.event.inputs.mypy-only != 'true'
|
|
||||||
&& github.event.inputs.audit-licenses-only != 'true'
|
|
||||||
&& needs.info.outputs.test_full_suite == 'true'
|
&& needs.info.outputs.test_full_suite == 'true'
|
||||||
needs:
|
needs:
|
||||||
- info
|
- info
|
||||||
@ -865,7 +895,7 @@ jobs:
|
|||||||
check-latest: true
|
check-latest: true
|
||||||
- name: Restore base Python virtual environment
|
- name: Restore base Python virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache/restore@v4.2.0
|
uses: actions/cache/restore@v4.2.3
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
fail-on-cache-miss: true
|
fail-on-cache-miss: true
|
||||||
@ -877,7 +907,7 @@ jobs:
|
|||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
python -m script.split_tests ${{ needs.info.outputs.test_group_count }} tests
|
python -m script.split_tests ${{ needs.info.outputs.test_group_count }} tests
|
||||||
- name: Upload pytest_buckets
|
- name: Upload pytest_buckets
|
||||||
uses: actions/upload-artifact@v4.6.0
|
uses: actions/upload-artifact@v4.6.2
|
||||||
with:
|
with:
|
||||||
name: pytest_buckets
|
name: pytest_buckets
|
||||||
path: pytest_buckets.txt
|
path: pytest_buckets.txt
|
||||||
@ -886,11 +916,7 @@ jobs:
|
|||||||
pytest-full:
|
pytest-full:
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
if: |
|
if: |
|
||||||
(github.event_name != 'push' || github.event.repository.full_name == 'home-assistant/core')
|
needs.info.outputs.lint_only != 'true'
|
||||||
&& github.event.inputs.lint-only != 'true'
|
|
||||||
&& github.event.inputs.pylint-only != 'true'
|
|
||||||
&& github.event.inputs.mypy-only != 'true'
|
|
||||||
&& github.event.inputs.audit-licenses-only != 'true'
|
|
||||||
&& needs.info.outputs.test_full_suite == 'true'
|
&& needs.info.outputs.test_full_suite == 'true'
|
||||||
needs:
|
needs:
|
||||||
- info
|
- info
|
||||||
@ -929,7 +955,7 @@ jobs:
|
|||||||
check-latest: true
|
check-latest: true
|
||||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache/restore@v4.2.0
|
uses: actions/cache/restore@v4.2.3
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
fail-on-cache-miss: true
|
fail-on-cache-miss: true
|
||||||
@ -942,7 +968,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
echo "::add-matcher::.github/workflows/matchers/pytest-slow.json"
|
echo "::add-matcher::.github/workflows/matchers/pytest-slow.json"
|
||||||
- name: Download pytest_buckets
|
- name: Download pytest_buckets
|
||||||
uses: actions/download-artifact@v4.1.8
|
uses: actions/download-artifact@v4.2.1
|
||||||
with:
|
with:
|
||||||
name: pytest_buckets
|
name: pytest_buckets
|
||||||
- name: Compile English translations
|
- name: Compile English translations
|
||||||
@ -962,6 +988,7 @@ jobs:
|
|||||||
if [[ "${{ needs.info.outputs.skip_coverage }}" != "true" ]]; then
|
if [[ "${{ needs.info.outputs.skip_coverage }}" != "true" ]]; then
|
||||||
cov_params+=(--cov="homeassistant")
|
cov_params+=(--cov="homeassistant")
|
||||||
cov_params+=(--cov-report=xml)
|
cov_params+=(--cov-report=xml)
|
||||||
|
cov_params+=(--junitxml=junit.xml -o junit_family=legacy)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Test group ${{ matrix.group }}: $(sed -n "${{ matrix.group }},1p" pytest_buckets.txt)"
|
echo "Test group ${{ matrix.group }}: $(sed -n "${{ matrix.group }},1p" pytest_buckets.txt)"
|
||||||
@ -980,18 +1007,24 @@ jobs:
|
|||||||
2>&1 | tee pytest-${{ matrix.python-version }}-${{ matrix.group }}.txt
|
2>&1 | tee pytest-${{ matrix.python-version }}-${{ matrix.group }}.txt
|
||||||
- name: Upload pytest output
|
- name: Upload pytest output
|
||||||
if: success() || failure() && steps.pytest-full.conclusion == 'failure'
|
if: success() || failure() && steps.pytest-full.conclusion == 'failure'
|
||||||
uses: actions/upload-artifact@v4.6.0
|
uses: actions/upload-artifact@v4.6.2
|
||||||
with:
|
with:
|
||||||
name: pytest-${{ github.run_number }}-${{ matrix.python-version }}-${{ matrix.group }}
|
name: pytest-${{ github.run_number }}-${{ matrix.python-version }}-${{ matrix.group }}
|
||||||
path: pytest-*.txt
|
path: pytest-*.txt
|
||||||
overwrite: true
|
overwrite: true
|
||||||
- name: Upload coverage artifact
|
- name: Upload coverage artifact
|
||||||
if: needs.info.outputs.skip_coverage != 'true'
|
if: needs.info.outputs.skip_coverage != 'true'
|
||||||
uses: actions/upload-artifact@v4.6.0
|
uses: actions/upload-artifact@v4.6.2
|
||||||
with:
|
with:
|
||||||
name: coverage-${{ matrix.python-version }}-${{ matrix.group }}
|
name: coverage-${{ matrix.python-version }}-${{ matrix.group }}
|
||||||
path: coverage.xml
|
path: coverage.xml
|
||||||
overwrite: true
|
overwrite: true
|
||||||
|
- name: Upload test results artifact
|
||||||
|
if: needs.info.outputs.skip_coverage != 'true' && !cancelled()
|
||||||
|
uses: actions/upload-artifact@v4.6.2
|
||||||
|
with:
|
||||||
|
name: test-results-full-${{ matrix.python-version }}-${{ matrix.group }}
|
||||||
|
path: junit.xml
|
||||||
- name: Remove pytest_buckets
|
- name: Remove pytest_buckets
|
||||||
run: rm pytest_buckets.txt
|
run: rm pytest_buckets.txt
|
||||||
- name: Check dirty
|
- name: Check dirty
|
||||||
@ -1009,11 +1042,7 @@ jobs:
|
|||||||
MYSQL_ROOT_PASSWORD: password
|
MYSQL_ROOT_PASSWORD: password
|
||||||
options: --health-cmd="mysqladmin ping -uroot -ppassword" --health-interval=5s --health-timeout=2s --health-retries=3
|
options: --health-cmd="mysqladmin ping -uroot -ppassword" --health-interval=5s --health-timeout=2s --health-retries=3
|
||||||
if: |
|
if: |
|
||||||
(github.event_name != 'push' || github.event.repository.full_name == 'home-assistant/core')
|
needs.info.outputs.lint_only != 'true'
|
||||||
&& github.event.inputs.lint-only != 'true'
|
|
||||||
&& github.event.inputs.pylint-only != 'true'
|
|
||||||
&& github.event.inputs.mypy-only != 'true'
|
|
||||||
&& github.event.inputs.audit-licenses-only != 'true'
|
|
||||||
&& needs.info.outputs.mariadb_groups != '[]'
|
&& needs.info.outputs.mariadb_groups != '[]'
|
||||||
needs:
|
needs:
|
||||||
- info
|
- info
|
||||||
@ -1051,7 +1080,7 @@ jobs:
|
|||||||
check-latest: true
|
check-latest: true
|
||||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache/restore@v4.2.0
|
uses: actions/cache/restore@v4.2.3
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
fail-on-cache-miss: true
|
fail-on-cache-miss: true
|
||||||
@ -1088,6 +1117,7 @@ jobs:
|
|||||||
cov_params+=(--cov="homeassistant.components.recorder")
|
cov_params+=(--cov="homeassistant.components.recorder")
|
||||||
cov_params+=(--cov-report=xml)
|
cov_params+=(--cov-report=xml)
|
||||||
cov_params+=(--cov-report=term-missing)
|
cov_params+=(--cov-report=term-missing)
|
||||||
|
cov_params+=(--junitxml=junit.xml -o junit_family=legacy)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
python3 -b -X dev -m pytest \
|
python3 -b -X dev -m pytest \
|
||||||
@ -1108,7 +1138,7 @@ jobs:
|
|||||||
2>&1 | tee pytest-${{ matrix.python-version }}-${mariadb}.txt
|
2>&1 | tee pytest-${{ matrix.python-version }}-${mariadb}.txt
|
||||||
- name: Upload pytest output
|
- name: Upload pytest output
|
||||||
if: success() || failure() && steps.pytest-partial.conclusion == 'failure'
|
if: success() || failure() && steps.pytest-partial.conclusion == 'failure'
|
||||||
uses: actions/upload-artifact@v4.6.0
|
uses: actions/upload-artifact@v4.6.2
|
||||||
with:
|
with:
|
||||||
name: pytest-${{ github.run_number }}-${{ matrix.python-version }}-${{
|
name: pytest-${{ github.run_number }}-${{ matrix.python-version }}-${{
|
||||||
steps.pytest-partial.outputs.mariadb }}
|
steps.pytest-partial.outputs.mariadb }}
|
||||||
@ -1116,12 +1146,19 @@ jobs:
|
|||||||
overwrite: true
|
overwrite: true
|
||||||
- name: Upload coverage artifact
|
- name: Upload coverage artifact
|
||||||
if: needs.info.outputs.skip_coverage != 'true'
|
if: needs.info.outputs.skip_coverage != 'true'
|
||||||
uses: actions/upload-artifact@v4.6.0
|
uses: actions/upload-artifact@v4.6.2
|
||||||
with:
|
with:
|
||||||
name: coverage-${{ matrix.python-version }}-${{
|
name: coverage-${{ matrix.python-version }}-${{
|
||||||
steps.pytest-partial.outputs.mariadb }}
|
steps.pytest-partial.outputs.mariadb }}
|
||||||
path: coverage.xml
|
path: coverage.xml
|
||||||
overwrite: true
|
overwrite: true
|
||||||
|
- name: Upload test results artifact
|
||||||
|
if: needs.info.outputs.skip_coverage != 'true' && !cancelled()
|
||||||
|
uses: actions/upload-artifact@v4.6.2
|
||||||
|
with:
|
||||||
|
name: test-results-mariadb-${{ matrix.python-version }}-${{
|
||||||
|
steps.pytest-partial.outputs.mariadb }}
|
||||||
|
path: junit.xml
|
||||||
- name: Check dirty
|
- name: Check dirty
|
||||||
run: |
|
run: |
|
||||||
./script/check_dirty
|
./script/check_dirty
|
||||||
@ -1137,11 +1174,7 @@ jobs:
|
|||||||
POSTGRES_PASSWORD: password
|
POSTGRES_PASSWORD: password
|
||||||
options: --health-cmd="pg_isready -hlocalhost -Upostgres" --health-interval=5s --health-timeout=2s --health-retries=3
|
options: --health-cmd="pg_isready -hlocalhost -Upostgres" --health-interval=5s --health-timeout=2s --health-retries=3
|
||||||
if: |
|
if: |
|
||||||
(github.event_name != 'push' || github.event.repository.full_name == 'home-assistant/core')
|
needs.info.outputs.lint_only != 'true'
|
||||||
&& github.event.inputs.lint-only != 'true'
|
|
||||||
&& github.event.inputs.pylint-only != 'true'
|
|
||||||
&& github.event.inputs.mypy-only != 'true'
|
|
||||||
&& github.event.inputs.audit-licenses-only != 'true'
|
|
||||||
&& needs.info.outputs.postgresql_groups != '[]'
|
&& needs.info.outputs.postgresql_groups != '[]'
|
||||||
needs:
|
needs:
|
||||||
- info
|
- info
|
||||||
@ -1181,7 +1214,7 @@ jobs:
|
|||||||
check-latest: true
|
check-latest: true
|
||||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache/restore@v4.2.0
|
uses: actions/cache/restore@v4.2.3
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
fail-on-cache-miss: true
|
fail-on-cache-miss: true
|
||||||
@ -1218,6 +1251,7 @@ jobs:
|
|||||||
cov_params+=(--cov="homeassistant.components.recorder")
|
cov_params+=(--cov="homeassistant.components.recorder")
|
||||||
cov_params+=(--cov-report=xml)
|
cov_params+=(--cov-report=xml)
|
||||||
cov_params+=(--cov-report=term-missing)
|
cov_params+=(--cov-report=term-missing)
|
||||||
|
cov_params+=(--junitxml=junit.xml -o junit_family=legacy)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
python3 -b -X dev -m pytest \
|
python3 -b -X dev -m pytest \
|
||||||
@ -1239,7 +1273,7 @@ jobs:
|
|||||||
2>&1 | tee pytest-${{ matrix.python-version }}-${postgresql}.txt
|
2>&1 | tee pytest-${{ matrix.python-version }}-${postgresql}.txt
|
||||||
- name: Upload pytest output
|
- name: Upload pytest output
|
||||||
if: success() || failure() && steps.pytest-partial.conclusion == 'failure'
|
if: success() || failure() && steps.pytest-partial.conclusion == 'failure'
|
||||||
uses: actions/upload-artifact@v4.6.0
|
uses: actions/upload-artifact@v4.6.2
|
||||||
with:
|
with:
|
||||||
name: pytest-${{ github.run_number }}-${{ matrix.python-version }}-${{
|
name: pytest-${{ github.run_number }}-${{ matrix.python-version }}-${{
|
||||||
steps.pytest-partial.outputs.postgresql }}
|
steps.pytest-partial.outputs.postgresql }}
|
||||||
@ -1247,12 +1281,19 @@ jobs:
|
|||||||
overwrite: true
|
overwrite: true
|
||||||
- name: Upload coverage artifact
|
- name: Upload coverage artifact
|
||||||
if: needs.info.outputs.skip_coverage != 'true'
|
if: needs.info.outputs.skip_coverage != 'true'
|
||||||
uses: actions/upload-artifact@v4.6.0
|
uses: actions/upload-artifact@v4.6.2
|
||||||
with:
|
with:
|
||||||
name: coverage-${{ matrix.python-version }}-${{
|
name: coverage-${{ matrix.python-version }}-${{
|
||||||
steps.pytest-partial.outputs.postgresql }}
|
steps.pytest-partial.outputs.postgresql }}
|
||||||
path: coverage.xml
|
path: coverage.xml
|
||||||
overwrite: true
|
overwrite: true
|
||||||
|
- name: Upload test results artifact
|
||||||
|
if: needs.info.outputs.skip_coverage != 'true' && !cancelled()
|
||||||
|
uses: actions/upload-artifact@v4.6.2
|
||||||
|
with:
|
||||||
|
name: test-results-postgres-${{ matrix.python-version }}-${{
|
||||||
|
steps.pytest-partial.outputs.postgresql }}
|
||||||
|
path: junit.xml
|
||||||
- name: Check dirty
|
- name: Check dirty
|
||||||
run: |
|
run: |
|
||||||
./script/check_dirty
|
./script/check_dirty
|
||||||
@ -1271,12 +1312,12 @@ jobs:
|
|||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@v4.2.2
|
||||||
- name: Download all coverage artifacts
|
- name: Download all coverage artifacts
|
||||||
uses: actions/download-artifact@v4.1.8
|
uses: actions/download-artifact@v4.2.1
|
||||||
with:
|
with:
|
||||||
pattern: coverage-*
|
pattern: coverage-*
|
||||||
- name: Upload coverage to Codecov
|
- name: Upload coverage to Codecov
|
||||||
if: needs.info.outputs.test_full_suite == 'true'
|
if: needs.info.outputs.test_full_suite == 'true'
|
||||||
uses: codecov/codecov-action@v5.3.1
|
uses: codecov/codecov-action@v5.4.0
|
||||||
with:
|
with:
|
||||||
fail_ci_if_error: true
|
fail_ci_if_error: true
|
||||||
flags: full-suite
|
flags: full-suite
|
||||||
@ -1285,11 +1326,7 @@ jobs:
|
|||||||
pytest-partial:
|
pytest-partial:
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
if: |
|
if: |
|
||||||
(github.event_name != 'push' || github.event.repository.full_name == 'home-assistant/core')
|
needs.info.outputs.lint_only != 'true'
|
||||||
&& github.event.inputs.lint-only != 'true'
|
|
||||||
&& github.event.inputs.pylint-only != 'true'
|
|
||||||
&& github.event.inputs.mypy-only != 'true'
|
|
||||||
&& github.event.inputs.audit-licenses-only != 'true'
|
|
||||||
&& needs.info.outputs.tests_glob
|
&& needs.info.outputs.tests_glob
|
||||||
&& needs.info.outputs.test_full_suite == 'false'
|
&& needs.info.outputs.test_full_suite == 'false'
|
||||||
needs:
|
needs:
|
||||||
@ -1328,7 +1365,7 @@ jobs:
|
|||||||
check-latest: true
|
check-latest: true
|
||||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache/restore@v4.2.0
|
uses: actions/cache/restore@v4.2.3
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
fail-on-cache-miss: true
|
fail-on-cache-miss: true
|
||||||
@ -1365,6 +1402,7 @@ jobs:
|
|||||||
cov_params+=(--cov="homeassistant.components.${{ matrix.group }}")
|
cov_params+=(--cov="homeassistant.components.${{ matrix.group }}")
|
||||||
cov_params+=(--cov-report=xml)
|
cov_params+=(--cov-report=xml)
|
||||||
cov_params+=(--cov-report=term-missing)
|
cov_params+=(--cov-report=term-missing)
|
||||||
|
cov_params+=(--junitxml=junit.xml -o junit_family=legacy)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
python3 -b -X dev -m pytest \
|
python3 -b -X dev -m pytest \
|
||||||
@ -1382,18 +1420,24 @@ jobs:
|
|||||||
2>&1 | tee pytest-${{ matrix.python-version }}-${{ matrix.group }}.txt
|
2>&1 | tee pytest-${{ matrix.python-version }}-${{ matrix.group }}.txt
|
||||||
- name: Upload pytest output
|
- name: Upload pytest output
|
||||||
if: success() || failure() && steps.pytest-partial.conclusion == 'failure'
|
if: success() || failure() && steps.pytest-partial.conclusion == 'failure'
|
||||||
uses: actions/upload-artifact@v4.6.0
|
uses: actions/upload-artifact@v4.6.2
|
||||||
with:
|
with:
|
||||||
name: pytest-${{ github.run_number }}-${{ matrix.python-version }}-${{ matrix.group }}
|
name: pytest-${{ github.run_number }}-${{ matrix.python-version }}-${{ matrix.group }}
|
||||||
path: pytest-*.txt
|
path: pytest-*.txt
|
||||||
overwrite: true
|
overwrite: true
|
||||||
- name: Upload coverage artifact
|
- name: Upload coverage artifact
|
||||||
if: needs.info.outputs.skip_coverage != 'true'
|
if: needs.info.outputs.skip_coverage != 'true'
|
||||||
uses: actions/upload-artifact@v4.6.0
|
uses: actions/upload-artifact@v4.6.2
|
||||||
with:
|
with:
|
||||||
name: coverage-${{ matrix.python-version }}-${{ matrix.group }}
|
name: coverage-${{ matrix.python-version }}-${{ matrix.group }}
|
||||||
path: coverage.xml
|
path: coverage.xml
|
||||||
overwrite: true
|
overwrite: true
|
||||||
|
- name: Upload test results artifact
|
||||||
|
if: needs.info.outputs.skip_coverage != 'true' && !cancelled()
|
||||||
|
uses: actions/upload-artifact@v4.6.2
|
||||||
|
with:
|
||||||
|
name: test-results-partial-${{ matrix.python-version }}-${{ matrix.group }}
|
||||||
|
path: junit.xml
|
||||||
- name: Check dirty
|
- name: Check dirty
|
||||||
run: |
|
run: |
|
||||||
./script/check_dirty
|
./script/check_dirty
|
||||||
@ -1410,12 +1454,37 @@ jobs:
|
|||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@v4.2.2
|
||||||
- name: Download all coverage artifacts
|
- name: Download all coverage artifacts
|
||||||
uses: actions/download-artifact@v4.1.8
|
uses: actions/download-artifact@v4.2.1
|
||||||
with:
|
with:
|
||||||
pattern: coverage-*
|
pattern: coverage-*
|
||||||
- name: Upload coverage to Codecov
|
- name: Upload coverage to Codecov
|
||||||
if: needs.info.outputs.test_full_suite == 'false'
|
if: needs.info.outputs.test_full_suite == 'false'
|
||||||
uses: codecov/codecov-action@v5.3.1
|
uses: codecov/codecov-action@v5.4.0
|
||||||
with:
|
with:
|
||||||
fail_ci_if_error: true
|
fail_ci_if_error: true
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
|
||||||
|
upload-test-results:
|
||||||
|
name: Upload test results to Codecov
|
||||||
|
# codecov/test-results-action currently doesn't support tokenless uploads
|
||||||
|
# therefore we can't run it on forks
|
||||||
|
if: ${{ (github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork) && needs.info.outputs.skip_coverage != 'true' && !cancelled() }}
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
needs:
|
||||||
|
- info
|
||||||
|
- pytest-partial
|
||||||
|
- pytest-full
|
||||||
|
- pytest-postgres
|
||||||
|
- pytest-mariadb
|
||||||
|
timeout-minutes: 10
|
||||||
|
steps:
|
||||||
|
- name: Download all coverage artifacts
|
||||||
|
uses: actions/download-artifact@v4.2.1
|
||||||
|
with:
|
||||||
|
pattern: test-results-*
|
||||||
|
- name: Upload test results to Codecov
|
||||||
|
uses: codecov/test-results-action@v1
|
||||||
|
with:
|
||||||
|
fail_ci_if_error: true
|
||||||
|
verbose: true
|
||||||
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
4
.github/workflows/codeql.yml
vendored
4
.github/workflows/codeql.yml
vendored
@ -24,11 +24,11 @@ jobs:
|
|||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@v4.2.2
|
||||||
|
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v3.28.8
|
uses: github/codeql-action/init@v3.28.12
|
||||||
with:
|
with:
|
||||||
languages: python
|
languages: python
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v3.28.8
|
uses: github/codeql-action/analyze@v3.28.12
|
||||||
with:
|
with:
|
||||||
category: "/language:python"
|
category: "/language:python"
|
||||||
|
66
.github/workflows/wheels.yml
vendored
66
.github/workflows/wheels.yml
vendored
@ -91,7 +91,7 @@ jobs:
|
|||||||
) > build_constraints.txt
|
) > build_constraints.txt
|
||||||
|
|
||||||
- name: Upload env_file
|
- name: Upload env_file
|
||||||
uses: actions/upload-artifact@v4.6.0
|
uses: actions/upload-artifact@v4.6.2
|
||||||
with:
|
with:
|
||||||
name: env_file
|
name: env_file
|
||||||
path: ./.env_file
|
path: ./.env_file
|
||||||
@ -99,14 +99,14 @@ jobs:
|
|||||||
overwrite: true
|
overwrite: true
|
||||||
|
|
||||||
- name: Upload build_constraints
|
- name: Upload build_constraints
|
||||||
uses: actions/upload-artifact@v4.6.0
|
uses: actions/upload-artifact@v4.6.2
|
||||||
with:
|
with:
|
||||||
name: build_constraints
|
name: build_constraints
|
||||||
path: ./build_constraints.txt
|
path: ./build_constraints.txt
|
||||||
overwrite: true
|
overwrite: true
|
||||||
|
|
||||||
- name: Upload requirements_diff
|
- name: Upload requirements_diff
|
||||||
uses: actions/upload-artifact@v4.6.0
|
uses: actions/upload-artifact@v4.6.2
|
||||||
with:
|
with:
|
||||||
name: requirements_diff
|
name: requirements_diff
|
||||||
path: ./requirements_diff.txt
|
path: ./requirements_diff.txt
|
||||||
@ -118,7 +118,7 @@ jobs:
|
|||||||
python -m script.gen_requirements_all ci
|
python -m script.gen_requirements_all ci
|
||||||
|
|
||||||
- name: Upload requirements_all_wheels
|
- name: Upload requirements_all_wheels
|
||||||
uses: actions/upload-artifact@v4.6.0
|
uses: actions/upload-artifact@v4.6.2
|
||||||
with:
|
with:
|
||||||
name: requirements_all_wheels
|
name: requirements_all_wheels
|
||||||
path: ./requirements_all_wheels_*.txt
|
path: ./requirements_all_wheels_*.txt
|
||||||
@ -138,17 +138,17 @@ jobs:
|
|||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@v4.2.2
|
||||||
|
|
||||||
- name: Download env_file
|
- name: Download env_file
|
||||||
uses: actions/download-artifact@v4.1.8
|
uses: actions/download-artifact@v4.2.1
|
||||||
with:
|
with:
|
||||||
name: env_file
|
name: env_file
|
||||||
|
|
||||||
- name: Download build_constraints
|
- name: Download build_constraints
|
||||||
uses: actions/download-artifact@v4.1.8
|
uses: actions/download-artifact@v4.2.1
|
||||||
with:
|
with:
|
||||||
name: build_constraints
|
name: build_constraints
|
||||||
|
|
||||||
- name: Download requirements_diff
|
- name: Download requirements_diff
|
||||||
uses: actions/download-artifact@v4.1.8
|
uses: actions/download-artifact@v4.2.1
|
||||||
with:
|
with:
|
||||||
name: requirements_diff
|
name: requirements_diff
|
||||||
|
|
||||||
@ -159,7 +159,7 @@ jobs:
|
|||||||
sed -i "/uv/d" requirements_diff.txt
|
sed -i "/uv/d" requirements_diff.txt
|
||||||
|
|
||||||
- name: Build wheels
|
- name: Build wheels
|
||||||
uses: home-assistant/wheels@2024.11.0
|
uses: home-assistant/wheels@2025.02.0
|
||||||
with:
|
with:
|
||||||
abi: ${{ matrix.abi }}
|
abi: ${{ matrix.abi }}
|
||||||
tag: musllinux_1_2
|
tag: musllinux_1_2
|
||||||
@ -187,22 +187,22 @@ jobs:
|
|||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@v4.2.2
|
||||||
|
|
||||||
- name: Download env_file
|
- name: Download env_file
|
||||||
uses: actions/download-artifact@v4.1.8
|
uses: actions/download-artifact@v4.2.1
|
||||||
with:
|
with:
|
||||||
name: env_file
|
name: env_file
|
||||||
|
|
||||||
- name: Download build_constraints
|
- name: Download build_constraints
|
||||||
uses: actions/download-artifact@v4.1.8
|
uses: actions/download-artifact@v4.2.1
|
||||||
with:
|
with:
|
||||||
name: build_constraints
|
name: build_constraints
|
||||||
|
|
||||||
- name: Download requirements_diff
|
- name: Download requirements_diff
|
||||||
uses: actions/download-artifact@v4.1.8
|
uses: actions/download-artifact@v4.2.1
|
||||||
with:
|
with:
|
||||||
name: requirements_diff
|
name: requirements_diff
|
||||||
|
|
||||||
- name: Download requirements_all_wheels
|
- name: Download requirements_all_wheels
|
||||||
uses: actions/download-artifact@v4.1.8
|
uses: actions/download-artifact@v4.2.1
|
||||||
with:
|
with:
|
||||||
name: requirements_all_wheels
|
name: requirements_all_wheels
|
||||||
|
|
||||||
@ -218,16 +218,8 @@ jobs:
|
|||||||
sed -i "/uv/d" requirements.txt
|
sed -i "/uv/d" requirements.txt
|
||||||
sed -i "/uv/d" requirements_diff.txt
|
sed -i "/uv/d" requirements_diff.txt
|
||||||
|
|
||||||
- name: Split requirements all
|
- name: Build wheels
|
||||||
run: |
|
uses: home-assistant/wheels@2025.02.0
|
||||||
# We split requirements all into multiple files.
|
|
||||||
# This is to prevent the build from running out of memory when
|
|
||||||
# resolving packages on 32-bits systems (like armhf, armv7).
|
|
||||||
|
|
||||||
split -l $(expr $(expr $(cat requirements_all.txt | wc -l) + 1) / 3) requirements_all_wheels_${{ matrix.arch }}.txt requirements_all.txt
|
|
||||||
|
|
||||||
- name: Build wheels (part 1)
|
|
||||||
uses: home-assistant/wheels@2024.11.0
|
|
||||||
with:
|
with:
|
||||||
abi: ${{ matrix.abi }}
|
abi: ${{ matrix.abi }}
|
||||||
tag: musllinux_1_2
|
tag: musllinux_1_2
|
||||||
@ -238,32 +230,4 @@ jobs:
|
|||||||
skip-binary: aiohttp;charset-normalizer;grpcio;multidict;SQLAlchemy;propcache;protobuf;pymicro-vad;yarl
|
skip-binary: aiohttp;charset-normalizer;grpcio;multidict;SQLAlchemy;propcache;protobuf;pymicro-vad;yarl
|
||||||
constraints: "homeassistant/package_constraints.txt"
|
constraints: "homeassistant/package_constraints.txt"
|
||||||
requirements-diff: "requirements_diff.txt"
|
requirements-diff: "requirements_diff.txt"
|
||||||
requirements: "requirements_all.txtaa"
|
requirements: "requirements_all.txt"
|
||||||
|
|
||||||
- name: Build wheels (part 2)
|
|
||||||
uses: home-assistant/wheels@2024.11.0
|
|
||||||
with:
|
|
||||||
abi: ${{ matrix.abi }}
|
|
||||||
tag: musllinux_1_2
|
|
||||||
arch: ${{ matrix.arch }}
|
|
||||||
wheels-key: ${{ secrets.WHEELS_KEY }}
|
|
||||||
env-file: true
|
|
||||||
apk: "bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;cups-dev;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev;gammu-dev;yaml-dev;openblas-dev;fftw-dev;lapack-dev;gfortran;blas-dev;eigen-dev;freetype-dev;glew-dev;harfbuzz-dev;hdf5-dev;libdc1394-dev;libtbb-dev;mesa-dev;openexr-dev;openjpeg-dev;uchardet-dev;nasm;zlib-ng-dev"
|
|
||||||
skip-binary: aiohttp;charset-normalizer;grpcio;multidict;SQLAlchemy;propcache;protobuf;pymicro-vad;yarl
|
|
||||||
constraints: "homeassistant/package_constraints.txt"
|
|
||||||
requirements-diff: "requirements_diff.txt"
|
|
||||||
requirements: "requirements_all.txtab"
|
|
||||||
|
|
||||||
- name: Build wheels (part 3)
|
|
||||||
uses: home-assistant/wheels@2024.11.0
|
|
||||||
with:
|
|
||||||
abi: ${{ matrix.abi }}
|
|
||||||
tag: musllinux_1_2
|
|
||||||
arch: ${{ matrix.arch }}
|
|
||||||
wheels-key: ${{ secrets.WHEELS_KEY }}
|
|
||||||
env-file: true
|
|
||||||
apk: "bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;cups-dev;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev;gammu-dev;yaml-dev;openblas-dev;fftw-dev;lapack-dev;gfortran;blas-dev;eigen-dev;freetype-dev;glew-dev;harfbuzz-dev;hdf5-dev;libdc1394-dev;libtbb-dev;mesa-dev;openexr-dev;openjpeg-dev;uchardet-dev;nasm;zlib-ng-dev"
|
|
||||||
skip-binary: aiohttp;charset-normalizer;grpcio;multidict;SQLAlchemy;propcache;protobuf;pymicro-vad;yarl
|
|
||||||
constraints: "homeassistant/package_constraints.txt"
|
|
||||||
requirements-diff: "requirements_diff.txt"
|
|
||||||
requirements: "requirements_all.txtac"
|
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -69,6 +69,7 @@ test-reports/
|
|||||||
test-results.xml
|
test-results.xml
|
||||||
test-output.xml
|
test-output.xml
|
||||||
pytest-*.txt
|
pytest-*.txt
|
||||||
|
junit.xml
|
||||||
|
|
||||||
# Translations
|
# Translations
|
||||||
*.mo
|
*.mo
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
rev: v0.9.1
|
rev: v0.11.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff
|
- id: ruff
|
||||||
args:
|
args:
|
||||||
|
@ -103,6 +103,7 @@ homeassistant.components.auth.*
|
|||||||
homeassistant.components.automation.*
|
homeassistant.components.automation.*
|
||||||
homeassistant.components.awair.*
|
homeassistant.components.awair.*
|
||||||
homeassistant.components.axis.*
|
homeassistant.components.axis.*
|
||||||
|
homeassistant.components.azure_storage.*
|
||||||
homeassistant.components.backup.*
|
homeassistant.components.backup.*
|
||||||
homeassistant.components.baf.*
|
homeassistant.components.baf.*
|
||||||
homeassistant.components.bang_olufsen.*
|
homeassistant.components.bang_olufsen.*
|
||||||
@ -135,6 +136,7 @@ homeassistant.components.clicksend.*
|
|||||||
homeassistant.components.climate.*
|
homeassistant.components.climate.*
|
||||||
homeassistant.components.cloud.*
|
homeassistant.components.cloud.*
|
||||||
homeassistant.components.co2signal.*
|
homeassistant.components.co2signal.*
|
||||||
|
homeassistant.components.comelit.*
|
||||||
homeassistant.components.command_line.*
|
homeassistant.components.command_line.*
|
||||||
homeassistant.components.config.*
|
homeassistant.components.config.*
|
||||||
homeassistant.components.configurator.*
|
homeassistant.components.configurator.*
|
||||||
@ -234,6 +236,7 @@ homeassistant.components.here_travel_time.*
|
|||||||
homeassistant.components.history.*
|
homeassistant.components.history.*
|
||||||
homeassistant.components.history_stats.*
|
homeassistant.components.history_stats.*
|
||||||
homeassistant.components.holiday.*
|
homeassistant.components.holiday.*
|
||||||
|
homeassistant.components.home_connect.*
|
||||||
homeassistant.components.homeassistant.*
|
homeassistant.components.homeassistant.*
|
||||||
homeassistant.components.homeassistant_alerts.*
|
homeassistant.components.homeassistant_alerts.*
|
||||||
homeassistant.components.homeassistant_green.*
|
homeassistant.components.homeassistant_green.*
|
||||||
@ -394,6 +397,7 @@ homeassistant.components.pure_energie.*
|
|||||||
homeassistant.components.purpleair.*
|
homeassistant.components.purpleair.*
|
||||||
homeassistant.components.pushbullet.*
|
homeassistant.components.pushbullet.*
|
||||||
homeassistant.components.pvoutput.*
|
homeassistant.components.pvoutput.*
|
||||||
|
homeassistant.components.pyload.*
|
||||||
homeassistant.components.python_script.*
|
homeassistant.components.python_script.*
|
||||||
homeassistant.components.qbus.*
|
homeassistant.components.qbus.*
|
||||||
homeassistant.components.qnap_qsw.*
|
homeassistant.components.qnap_qsw.*
|
||||||
@ -406,7 +410,9 @@ homeassistant.components.raspberry_pi.*
|
|||||||
homeassistant.components.rdw.*
|
homeassistant.components.rdw.*
|
||||||
homeassistant.components.recollect_waste.*
|
homeassistant.components.recollect_waste.*
|
||||||
homeassistant.components.recorder.*
|
homeassistant.components.recorder.*
|
||||||
|
homeassistant.components.remember_the_milk.*
|
||||||
homeassistant.components.remote.*
|
homeassistant.components.remote.*
|
||||||
|
homeassistant.components.remote_calendar.*
|
||||||
homeassistant.components.renault.*
|
homeassistant.components.renault.*
|
||||||
homeassistant.components.reolink.*
|
homeassistant.components.reolink.*
|
||||||
homeassistant.components.repairs.*
|
homeassistant.components.repairs.*
|
||||||
@ -437,6 +443,7 @@ homeassistant.components.select.*
|
|||||||
homeassistant.components.sensibo.*
|
homeassistant.components.sensibo.*
|
||||||
homeassistant.components.sensirion_ble.*
|
homeassistant.components.sensirion_ble.*
|
||||||
homeassistant.components.sensor.*
|
homeassistant.components.sensor.*
|
||||||
|
homeassistant.components.sensorpush_cloud.*
|
||||||
homeassistant.components.sensoterra.*
|
homeassistant.components.sensoterra.*
|
||||||
homeassistant.components.senz.*
|
homeassistant.components.senz.*
|
||||||
homeassistant.components.sfr_box.*
|
homeassistant.components.sfr_box.*
|
||||||
@ -524,6 +531,7 @@ homeassistant.components.vallox.*
|
|||||||
homeassistant.components.valve.*
|
homeassistant.components.valve.*
|
||||||
homeassistant.components.velbus.*
|
homeassistant.components.velbus.*
|
||||||
homeassistant.components.vlc_telnet.*
|
homeassistant.components.vlc_telnet.*
|
||||||
|
homeassistant.components.vodafone_station.*
|
||||||
homeassistant.components.wake_on_lan.*
|
homeassistant.components.wake_on_lan.*
|
||||||
homeassistant.components.wake_word.*
|
homeassistant.components.wake_word.*
|
||||||
homeassistant.components.wallbox.*
|
homeassistant.components.wallbox.*
|
||||||
|
9
.vscode/launch.json
vendored
9
.vscode/launch.json
vendored
@ -38,10 +38,17 @@
|
|||||||
"module": "pytest",
|
"module": "pytest",
|
||||||
"justMyCode": false,
|
"justMyCode": false,
|
||||||
"args": [
|
"args": [
|
||||||
"--timeout=10",
|
|
||||||
"--picked"
|
"--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.
|
// Debug by attaching to local Home Assistant server using Remote Python Debugger.
|
||||||
// See https://www.home-assistant.io/integrations/debugpy/
|
// See https://www.home-assistant.io/integrations/debugpy/
|
||||||
|
4
.vscode/tasks.json
vendored
4
.vscode/tasks.json
vendored
@ -4,7 +4,7 @@
|
|||||||
{
|
{
|
||||||
"label": "Run Home Assistant Core",
|
"label": "Run Home Assistant Core",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "hass -c ./config",
|
"command": "${command:python.interpreterPath} -m homeassistant -c ./config",
|
||||||
"group": "test",
|
"group": "test",
|
||||||
"presentation": {
|
"presentation": {
|
||||||
"reveal": "always",
|
"reveal": "always",
|
||||||
@ -148,7 +148,7 @@
|
|||||||
{
|
{
|
||||||
"label": "Install all Test Requirements",
|
"label": "Install all Test Requirements",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "uv pip install -r requirements_test_all.txt",
|
"command": "uv pip install -r requirements.txt -r requirements_test_all.txt",
|
||||||
"group": {
|
"group": {
|
||||||
"kind": "build",
|
"kind": "build",
|
||||||
"isDefault": true
|
"isDefault": true
|
||||||
|
34
CODEOWNERS
generated
34
CODEOWNERS
generated
@ -180,6 +180,8 @@ build.json @home-assistant/supervisor
|
|||||||
/homeassistant/components/azure_event_hub/ @eavanvalkenburg
|
/homeassistant/components/azure_event_hub/ @eavanvalkenburg
|
||||||
/tests/components/azure_event_hub/ @eavanvalkenburg
|
/tests/components/azure_event_hub/ @eavanvalkenburg
|
||||||
/homeassistant/components/azure_service_bus/ @hfurubotten
|
/homeassistant/components/azure_service_bus/ @hfurubotten
|
||||||
|
/homeassistant/components/azure_storage/ @zweckj
|
||||||
|
/tests/components/azure_storage/ @zweckj
|
||||||
/homeassistant/components/backup/ @home-assistant/core
|
/homeassistant/components/backup/ @home-assistant/core
|
||||||
/tests/components/backup/ @home-assistant/core
|
/tests/components/backup/ @home-assistant/core
|
||||||
/homeassistant/components/baf/ @bdraco @jfroy
|
/homeassistant/components/baf/ @bdraco @jfroy
|
||||||
@ -568,8 +570,8 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/google_cloud/ @lufton @tronikos
|
/tests/components/google_cloud/ @lufton @tronikos
|
||||||
/homeassistant/components/google_drive/ @tronikos
|
/homeassistant/components/google_drive/ @tronikos
|
||||||
/tests/components/google_drive/ @tronikos
|
/tests/components/google_drive/ @tronikos
|
||||||
/homeassistant/components/google_generative_ai_conversation/ @tronikos
|
/homeassistant/components/google_generative_ai_conversation/ @tronikos @ivanlh
|
||||||
/tests/components/google_generative_ai_conversation/ @tronikos
|
/tests/components/google_generative_ai_conversation/ @tronikos @ivanlh
|
||||||
/homeassistant/components/google_mail/ @tkdrob
|
/homeassistant/components/google_mail/ @tkdrob
|
||||||
/tests/components/google_mail/ @tkdrob
|
/tests/components/google_mail/ @tkdrob
|
||||||
/homeassistant/components/google_photos/ @allenporter
|
/homeassistant/components/google_photos/ @allenporter
|
||||||
@ -967,8 +969,8 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/motionblinds_ble/ @LennP @jerrybboy
|
/tests/components/motionblinds_ble/ @LennP @jerrybboy
|
||||||
/homeassistant/components/motioneye/ @dermotduffy
|
/homeassistant/components/motioneye/ @dermotduffy
|
||||||
/tests/components/motioneye/ @dermotduffy
|
/tests/components/motioneye/ @dermotduffy
|
||||||
/homeassistant/components/motionmount/ @RJPoelstra
|
/homeassistant/components/motionmount/ @laiho-vogels
|
||||||
/tests/components/motionmount/ @RJPoelstra
|
/tests/components/motionmount/ @laiho-vogels
|
||||||
/homeassistant/components/mqtt/ @emontnemery @jbouwh @bdraco
|
/homeassistant/components/mqtt/ @emontnemery @jbouwh @bdraco
|
||||||
/tests/components/mqtt/ @emontnemery @jbouwh @bdraco
|
/tests/components/mqtt/ @emontnemery @jbouwh @bdraco
|
||||||
/homeassistant/components/msteams/ @peroyvind
|
/homeassistant/components/msteams/ @peroyvind
|
||||||
@ -1051,8 +1053,8 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/numato/ @clssn
|
/tests/components/numato/ @clssn
|
||||||
/homeassistant/components/number/ @home-assistant/core @Shulyaka
|
/homeassistant/components/number/ @home-assistant/core @Shulyaka
|
||||||
/tests/components/number/ @home-assistant/core @Shulyaka
|
/tests/components/number/ @home-assistant/core @Shulyaka
|
||||||
/homeassistant/components/nut/ @bdraco @ollo69 @pestevez
|
/homeassistant/components/nut/ @bdraco @ollo69 @pestevez @tdfountain
|
||||||
/tests/components/nut/ @bdraco @ollo69 @pestevez
|
/tests/components/nut/ @bdraco @ollo69 @pestevez @tdfountain
|
||||||
/homeassistant/components/nws/ @MatthewFlamm @kamiyo
|
/homeassistant/components/nws/ @MatthewFlamm @kamiyo
|
||||||
/tests/components/nws/ @MatthewFlamm @kamiyo
|
/tests/components/nws/ @MatthewFlamm @kamiyo
|
||||||
/homeassistant/components/nyt_games/ @joostlek
|
/homeassistant/components/nyt_games/ @joostlek
|
||||||
@ -1138,12 +1140,14 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/permobil/ @IsakNyberg
|
/tests/components/permobil/ @IsakNyberg
|
||||||
/homeassistant/components/persistent_notification/ @home-assistant/core
|
/homeassistant/components/persistent_notification/ @home-assistant/core
|
||||||
/tests/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
|
/homeassistant/components/philips_js/ @elupus
|
||||||
/tests/components/philips_js/ @elupus
|
/tests/components/philips_js/ @elupus
|
||||||
/homeassistant/components/pi_hole/ @shenxn
|
/homeassistant/components/pi_hole/ @shenxn
|
||||||
/tests/components/pi_hole/ @shenxn
|
/tests/components/pi_hole/ @shenxn
|
||||||
/homeassistant/components/picnic/ @corneyl
|
/homeassistant/components/picnic/ @corneyl @codesalatdev
|
||||||
/tests/components/picnic/ @corneyl
|
/tests/components/picnic/ @corneyl @codesalatdev
|
||||||
/homeassistant/components/ping/ @jpbede
|
/homeassistant/components/ping/ @jpbede
|
||||||
/tests/components/ping/ @jpbede
|
/tests/components/ping/ @jpbede
|
||||||
/homeassistant/components/plaato/ @JohNan
|
/homeassistant/components/plaato/ @JohNan
|
||||||
@ -1248,6 +1252,8 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/refoss/ @ashionky
|
/tests/components/refoss/ @ashionky
|
||||||
/homeassistant/components/remote/ @home-assistant/core
|
/homeassistant/components/remote/ @home-assistant/core
|
||||||
/tests/components/remote/ @home-assistant/core
|
/tests/components/remote/ @home-assistant/core
|
||||||
|
/homeassistant/components/remote_calendar/ @Thomas55555
|
||||||
|
/tests/components/remote_calendar/ @Thomas55555
|
||||||
/homeassistant/components/renault/ @epenet
|
/homeassistant/components/renault/ @epenet
|
||||||
/tests/components/renault/ @epenet
|
/tests/components/renault/ @epenet
|
||||||
/homeassistant/components/renson/ @jimmyd-be
|
/homeassistant/components/renson/ @jimmyd-be
|
||||||
@ -1340,6 +1346,8 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/sensorpro/ @bdraco
|
/tests/components/sensorpro/ @bdraco
|
||||||
/homeassistant/components/sensorpush/ @bdraco
|
/homeassistant/components/sensorpush/ @bdraco
|
||||||
/tests/components/sensorpush/ @bdraco
|
/tests/components/sensorpush/ @bdraco
|
||||||
|
/homeassistant/components/sensorpush_cloud/ @sstallion
|
||||||
|
/tests/components/sensorpush_cloud/ @sstallion
|
||||||
/homeassistant/components/sensoterra/ @markruys
|
/homeassistant/components/sensoterra/ @markruys
|
||||||
/tests/components/sensoterra/ @markruys
|
/tests/components/sensoterra/ @markruys
|
||||||
/homeassistant/components/sentry/ @dcramer @frenck
|
/homeassistant/components/sentry/ @dcramer @frenck
|
||||||
@ -1395,6 +1403,8 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/smappee/ @bsmappee
|
/tests/components/smappee/ @bsmappee
|
||||||
/homeassistant/components/smart_meter_texas/ @grahamwetzler
|
/homeassistant/components/smart_meter_texas/ @grahamwetzler
|
||||||
/tests/components/smart_meter_texas/ @grahamwetzler
|
/tests/components/smart_meter_texas/ @grahamwetzler
|
||||||
|
/homeassistant/components/smartthings/ @joostlek
|
||||||
|
/tests/components/smartthings/ @joostlek
|
||||||
/homeassistant/components/smarttub/ @mdz
|
/homeassistant/components/smarttub/ @mdz
|
||||||
/tests/components/smarttub/ @mdz
|
/tests/components/smarttub/ @mdz
|
||||||
/homeassistant/components/smarty/ @z0mbieprocess
|
/homeassistant/components/smarty/ @z0mbieprocess
|
||||||
@ -1409,6 +1419,8 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/snapcast/ @luar123
|
/tests/components/snapcast/ @luar123
|
||||||
/homeassistant/components/snmp/ @nmaggioni
|
/homeassistant/components/snmp/ @nmaggioni
|
||||||
/tests/components/snmp/ @nmaggioni
|
/tests/components/snmp/ @nmaggioni
|
||||||
|
/homeassistant/components/snoo/ @Lash-L
|
||||||
|
/tests/components/snoo/ @Lash-L
|
||||||
/homeassistant/components/snooz/ @AustinBrunkhorst
|
/homeassistant/components/snooz/ @AustinBrunkhorst
|
||||||
/tests/components/snooz/ @AustinBrunkhorst
|
/tests/components/snooz/ @AustinBrunkhorst
|
||||||
/homeassistant/components/solaredge/ @frenck @bdraco
|
/homeassistant/components/solaredge/ @frenck @bdraco
|
||||||
@ -1519,8 +1531,8 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/tedee/ @patrickhilker @zweckj
|
/tests/components/tedee/ @patrickhilker @zweckj
|
||||||
/homeassistant/components/tellduslive/ @fredrike
|
/homeassistant/components/tellduslive/ @fredrike
|
||||||
/tests/components/tellduslive/ @fredrike
|
/tests/components/tellduslive/ @fredrike
|
||||||
/homeassistant/components/template/ @PhracturedBlue @home-assistant/core
|
/homeassistant/components/template/ @Petro31 @PhracturedBlue @home-assistant/core
|
||||||
/tests/components/template/ @PhracturedBlue @home-assistant/core
|
/tests/components/template/ @Petro31 @PhracturedBlue @home-assistant/core
|
||||||
/homeassistant/components/tesla_fleet/ @Bre77
|
/homeassistant/components/tesla_fleet/ @Bre77
|
||||||
/tests/components/tesla_fleet/ @Bre77
|
/tests/components/tesla_fleet/ @Bre77
|
||||||
/homeassistant/components/tesla_wall_connector/ @einarhauks
|
/homeassistant/components/tesla_wall_connector/ @einarhauks
|
||||||
@ -1689,6 +1701,8 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/weatherflow_cloud/ @jeeftor
|
/tests/components/weatherflow_cloud/ @jeeftor
|
||||||
/homeassistant/components/weatherkit/ @tjhorner
|
/homeassistant/components/weatherkit/ @tjhorner
|
||||||
/tests/components/weatherkit/ @tjhorner
|
/tests/components/weatherkit/ @tjhorner
|
||||||
|
/homeassistant/components/webdav/ @jpbede
|
||||||
|
/tests/components/webdav/ @jpbede
|
||||||
/homeassistant/components/webhook/ @home-assistant/core
|
/homeassistant/components/webhook/ @home-assistant/core
|
||||||
/tests/components/webhook/ @home-assistant/core
|
/tests/components/webhook/ @home-assistant/core
|
||||||
/homeassistant/components/webmin/ @autinerd
|
/homeassistant/components/webmin/ @autinerd
|
||||||
|
38
Dockerfile
generated
38
Dockerfile
generated
@ -12,8 +12,26 @@ ENV \
|
|||||||
|
|
||||||
ARG QEMU_CPU
|
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.9/go2rtc_linux_${go2rtc_suffix} --output /bin/go2rtc \
|
||||||
|
&& chmod +x /bin/go2rtc \
|
||||||
|
# Verify go2rtc can be executed
|
||||||
|
&& go2rtc --version
|
||||||
|
|
||||||
# Install uv
|
# Install uv
|
||||||
RUN pip3 install uv==0.5.27
|
RUN pip3 install uv==0.6.8
|
||||||
|
|
||||||
WORKDIR /usr/src
|
WORKDIR /usr/src
|
||||||
|
|
||||||
@ -42,22 +60,4 @@ RUN \
|
|||||||
&& python3 -m compileall \
|
&& python3 -m compileall \
|
||||||
homeassistant/homeassistant
|
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
|
WORKDIR /config
|
||||||
|
10
build.yaml
10
build.yaml
@ -1,10 +1,10 @@
|
|||||||
image: ghcr.io/home-assistant/{arch}-homeassistant
|
image: ghcr.io/home-assistant/{arch}-homeassistant
|
||||||
build_from:
|
build_from:
|
||||||
aarch64: ghcr.io/home-assistant/aarch64-homeassistant-base:2024.12.0
|
aarch64: ghcr.io/home-assistant/aarch64-homeassistant-base:2025.02.1
|
||||||
armhf: ghcr.io/home-assistant/armhf-homeassistant-base:2024.12.0
|
armhf: ghcr.io/home-assistant/armhf-homeassistant-base:2025.02.1
|
||||||
armv7: ghcr.io/home-assistant/armv7-homeassistant-base:2024.12.0
|
armv7: ghcr.io/home-assistant/armv7-homeassistant-base:2025.02.1
|
||||||
amd64: ghcr.io/home-assistant/amd64-homeassistant-base:2024.12.0
|
amd64: ghcr.io/home-assistant/amd64-homeassistant-base:2025.02.1
|
||||||
i386: ghcr.io/home-assistant/i386-homeassistant-base:2024.12.0
|
i386: ghcr.io/home-assistant/i386-homeassistant-base:2025.02.1
|
||||||
codenotary:
|
codenotary:
|
||||||
signer: notary@home-assistant.io
|
signer: notary@home-assistant.io
|
||||||
base_image: notary@home-assistant.io
|
base_image: notary@home-assistant.io
|
||||||
|
@ -178,6 +178,15 @@ _BLOCKING_CALLS: tuple[BlockingCall, ...] = (
|
|||||||
strict_core=False,
|
strict_core=False,
|
||||||
skip_for_tests=True,
|
skip_for_tests=True,
|
||||||
),
|
),
|
||||||
|
BlockingCall(
|
||||||
|
original_func=SSLContext.set_default_verify_paths,
|
||||||
|
object=SSLContext,
|
||||||
|
function="set_default_verify_paths",
|
||||||
|
check_allowed=None,
|
||||||
|
strict=False,
|
||||||
|
strict_core=False,
|
||||||
|
skip_for_tests=True,
|
||||||
|
),
|
||||||
BlockingCall(
|
BlockingCall(
|
||||||
original_func=Path.open,
|
original_func=Path.open,
|
||||||
object=Path,
|
object=Path,
|
||||||
|
@ -74,12 +74,14 @@ from .core_config import async_process_ha_core_config
|
|||||||
from .exceptions import HomeAssistantError
|
from .exceptions import HomeAssistantError
|
||||||
from .helpers import (
|
from .helpers import (
|
||||||
area_registry,
|
area_registry,
|
||||||
|
backup,
|
||||||
category_registry,
|
category_registry,
|
||||||
config_validation as cv,
|
config_validation as cv,
|
||||||
device_registry,
|
device_registry,
|
||||||
entity,
|
entity,
|
||||||
entity_registry,
|
entity_registry,
|
||||||
floor_registry,
|
floor_registry,
|
||||||
|
frame,
|
||||||
issue_registry,
|
issue_registry,
|
||||||
label_registry,
|
label_registry,
|
||||||
recorder,
|
recorder,
|
||||||
@ -91,6 +93,7 @@ from .helpers.dispatcher import async_dispatcher_send_internal
|
|||||||
from .helpers.storage import get_internal_store_manager
|
from .helpers.storage import get_internal_store_manager
|
||||||
from .helpers.system_info import async_get_system_info
|
from .helpers.system_info import async_get_system_info
|
||||||
from .helpers.typing import ConfigType
|
from .helpers.typing import ConfigType
|
||||||
|
from .loader import Integration
|
||||||
from .setup import (
|
from .setup import (
|
||||||
# _setup_started is marked as protected to make it clear
|
# _setup_started is marked as protected to make it clear
|
||||||
# that it is not part of the public API and should not be used
|
# that it is not part of the public API and should not be used
|
||||||
@ -134,14 +137,12 @@ DATA_REGISTRIES_LOADED: HassKey[None] = HassKey("bootstrap_registries_loaded")
|
|||||||
LOG_SLOW_STARTUP_INTERVAL = 60
|
LOG_SLOW_STARTUP_INTERVAL = 60
|
||||||
SLOW_STARTUP_CHECK_INTERVAL = 1
|
SLOW_STARTUP_CHECK_INTERVAL = 1
|
||||||
|
|
||||||
|
STAGE_0_SUBSTAGE_TIMEOUT = 60
|
||||||
STAGE_1_TIMEOUT = 120
|
STAGE_1_TIMEOUT = 120
|
||||||
STAGE_2_TIMEOUT = 300
|
STAGE_2_TIMEOUT = 300
|
||||||
WRAP_UP_TIMEOUT = 300
|
WRAP_UP_TIMEOUT = 300
|
||||||
COOLDOWN_TIME = 60
|
COOLDOWN_TIME = 60
|
||||||
|
|
||||||
|
|
||||||
DEBUGGER_INTEGRATIONS = {"debugpy"}
|
|
||||||
|
|
||||||
# Core integrations are unconditionally loaded
|
# Core integrations are unconditionally loaded
|
||||||
CORE_INTEGRATIONS = {"homeassistant", "persistent_notification"}
|
CORE_INTEGRATIONS = {"homeassistant", "persistent_notification"}
|
||||||
|
|
||||||
@ -152,6 +153,10 @@ LOGGING_AND_HTTP_DEPS_INTEGRATIONS = {
|
|||||||
"isal",
|
"isal",
|
||||||
# Set log levels
|
# Set log levels
|
||||||
"logger",
|
"logger",
|
||||||
|
# Ensure network config is available
|
||||||
|
# before hassio or any other integration is
|
||||||
|
# loaded that might create an aiohttp client session
|
||||||
|
"network",
|
||||||
# Error logging
|
# Error logging
|
||||||
"system_log",
|
"system_log",
|
||||||
"sentry",
|
"sentry",
|
||||||
@ -161,23 +166,28 @@ FRONTEND_INTEGRATIONS = {
|
|||||||
# integrations can be removed and database migration status is
|
# integrations can be removed and database migration status is
|
||||||
# visible in frontend
|
# visible in frontend
|
||||||
"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",
|
|
||||||
}
|
}
|
||||||
RECORDER_INTEGRATIONS = {
|
# Stage 0 is divided into substages. Each substage has a name, a set of integrations and a timeout.
|
||||||
# Setup after frontend
|
# The substage containing recorder should have no timeout, as it could cancel a database migration.
|
||||||
# To record data
|
# Recorder freezes "recorder" timeout during a migration, but it does not freeze other timeouts.
|
||||||
"recorder",
|
# 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.
|
||||||
DISCOVERY_INTEGRATIONS = ("bluetooth", "dhcp", "ssdp", "usb", "zeroconf")
|
# 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.
|
||||||
STAGE_1_INTEGRATIONS = {
|
STAGE_1_INTEGRATIONS = {
|
||||||
# We need to make sure discovery integrations
|
# We need to make sure discovery integrations
|
||||||
# update their deps before stage 2 integrations
|
# update their deps before stage 2 integrations
|
||||||
@ -192,6 +202,7 @@ STAGE_1_INTEGRATIONS = {
|
|||||||
# Ensure supervisor is available
|
# Ensure supervisor is available
|
||||||
"hassio",
|
"hassio",
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFAULT_INTEGRATIONS = {
|
DEFAULT_INTEGRATIONS = {
|
||||||
# These integrations are set up unless recovery mode is activated.
|
# These integrations are set up unless recovery mode is activated.
|
||||||
#
|
#
|
||||||
@ -232,22 +243,12 @@ DEFAULT_INTEGRATIONS_SUPERVISOR = {
|
|||||||
# These integrations are set up if using the Supervisor
|
# These integrations are set up if using the Supervisor
|
||||||
"hassio",
|
"hassio",
|
||||||
}
|
}
|
||||||
|
|
||||||
CRITICAL_INTEGRATIONS = {
|
CRITICAL_INTEGRATIONS = {
|
||||||
# Recovery mode is activated if these integrations fail to set up
|
# Recovery mode is activated if these integrations fail to set up
|
||||||
"frontend",
|
"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
|
# Storage keys we are likely to load during startup
|
||||||
# in order of when we expect to load them.
|
# in order of when we expect to load them.
|
||||||
@ -299,14 +300,6 @@ async def async_setup_hass(
|
|||||||
|
|
||||||
return hass
|
return hass
|
||||||
|
|
||||||
async def stop_hass(hass: core.HomeAssistant) -> None:
|
|
||||||
"""Stop hass."""
|
|
||||||
# Ask integrations to shut down. It's messy but we can't
|
|
||||||
# do a clean stop without knowing what is broken
|
|
||||||
with contextlib.suppress(TimeoutError):
|
|
||||||
async with hass.timeout.async_timeout(10):
|
|
||||||
await hass.async_stop()
|
|
||||||
|
|
||||||
hass = await create_hass()
|
hass = await create_hass()
|
||||||
|
|
||||||
if runtime_config.skip_pip or runtime_config.skip_pip_packages:
|
if runtime_config.skip_pip or runtime_config.skip_pip_packages:
|
||||||
@ -322,10 +315,10 @@ async def async_setup_hass(
|
|||||||
|
|
||||||
block_async_io.enable()
|
block_async_io.enable()
|
||||||
|
|
||||||
|
if not (recovery_mode := runtime_config.recovery_mode):
|
||||||
config_dict = None
|
config_dict = None
|
||||||
basic_setup_success = False
|
basic_setup_success = False
|
||||||
|
|
||||||
if not (recovery_mode := runtime_config.recovery_mode):
|
|
||||||
await hass.async_add_executor_job(conf_util.process_ha_config_upgrade, hass)
|
await hass.async_add_executor_job(conf_util.process_ha_config_upgrade, hass)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -345,16 +338,20 @@ async def async_setup_hass(
|
|||||||
|
|
||||||
if config_dict is None:
|
if config_dict is None:
|
||||||
recovery_mode = True
|
recovery_mode = True
|
||||||
await stop_hass(hass)
|
await hass.async_stop(force=True)
|
||||||
hass = await create_hass()
|
hass = await create_hass()
|
||||||
|
|
||||||
elif not basic_setup_success:
|
elif not basic_setup_success:
|
||||||
_LOGGER.warning("Unable to set up core integrations. Activating recovery mode")
|
_LOGGER.warning(
|
||||||
|
"Unable to set up core integrations. Activating recovery mode"
|
||||||
|
)
|
||||||
recovery_mode = True
|
recovery_mode = True
|
||||||
await stop_hass(hass)
|
await hass.async_stop(force=True)
|
||||||
hass = await create_hass()
|
hass = await create_hass()
|
||||||
|
|
||||||
elif any(domain not in hass.config.components for domain in CRITICAL_INTEGRATIONS):
|
elif any(
|
||||||
|
domain not in hass.config.components for domain in CRITICAL_INTEGRATIONS
|
||||||
|
):
|
||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
"Detected that %s did not load. Activating recovery mode",
|
"Detected that %s did not load. Activating recovery mode",
|
||||||
",".join(CRITICAL_INTEGRATIONS),
|
",".join(CRITICAL_INTEGRATIONS),
|
||||||
@ -364,7 +361,7 @@ async def async_setup_hass(
|
|||||||
old_logging = hass.data.get(DATA_LOGGING)
|
old_logging = hass.data.get(DATA_LOGGING)
|
||||||
|
|
||||||
recovery_mode = True
|
recovery_mode = True
|
||||||
await stop_hass(hass)
|
await hass.async_stop(force=True)
|
||||||
hass = await create_hass()
|
hass = await create_hass()
|
||||||
|
|
||||||
if old_logging:
|
if old_logging:
|
||||||
@ -438,9 +435,10 @@ async def async_load_base_functionality(hass: core.HomeAssistant) -> None:
|
|||||||
if DATA_REGISTRIES_LOADED in hass.data:
|
if DATA_REGISTRIES_LOADED in hass.data:
|
||||||
return
|
return
|
||||||
hass.data[DATA_REGISTRIES_LOADED] = None
|
hass.data[DATA_REGISTRIES_LOADED] = None
|
||||||
translation.async_setup(hass)
|
|
||||||
entity.async_setup(hass)
|
entity.async_setup(hass)
|
||||||
|
frame.async_setup(hass)
|
||||||
template.async_setup(hass)
|
template.async_setup(hass)
|
||||||
|
translation.async_setup(hass)
|
||||||
await asyncio.gather(
|
await asyncio.gather(
|
||||||
create_eager_task(get_internal_store_manager(hass).async_initialize()),
|
create_eager_task(get_internal_store_manager(hass).async_initialize()),
|
||||||
create_eager_task(area_registry.async_load(hass)),
|
create_eager_task(area_registry.async_load(hass)),
|
||||||
@ -661,7 +659,6 @@ def _create_log_file(
|
|||||||
err_handler = _RotatingFileHandlerWithoutShouldRollOver(
|
err_handler = _RotatingFileHandlerWithoutShouldRollOver(
|
||||||
err_log_path, backupCount=1
|
err_log_path, backupCount=1
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
err_handler.doRollover()
|
err_handler.doRollover()
|
||||||
except OSError as err:
|
except OSError as err:
|
||||||
@ -694,7 +691,6 @@ async def async_mount_local_lib_path(config_dir: str) -> str:
|
|||||||
return deps_dir
|
return deps_dir
|
||||||
|
|
||||||
|
|
||||||
@core.callback
|
|
||||||
def _get_domains(hass: core.HomeAssistant, config: dict[str, Any]) -> set[str]:
|
def _get_domains(hass: core.HomeAssistant, config: dict[str, Any]) -> set[str]:
|
||||||
"""Get domains of components to set up."""
|
"""Get domains of components to set up."""
|
||||||
# Filter out the repeating and common config section [homeassistant]
|
# Filter out the repeating and common config section [homeassistant]
|
||||||
@ -716,6 +712,258 @@ def _get_domains(hass: core.HomeAssistant, config: dict[str, Any]) -> set[str]:
|
|||||||
return domains
|
return domains
|
||||||
|
|
||||||
|
|
||||||
|
async def _async_resolve_domains_and_preload(
|
||||||
|
hass: core.HomeAssistant, config: dict[str, Any]
|
||||||
|
) -> tuple[dict[str, Integration], dict[str, Integration]]:
|
||||||
|
"""Resolve all dependencies and return integrations to set up.
|
||||||
|
|
||||||
|
The return value is a tuple of two dictionaries:
|
||||||
|
- The first dictionary contains integrations
|
||||||
|
specified by the configuration (including config entries).
|
||||||
|
- The second dictionary contains the same integrations as the first dictionary
|
||||||
|
together with all their dependencies.
|
||||||
|
"""
|
||||||
|
domains_to_setup = _get_domains(hass, config)
|
||||||
|
platform_integrations = conf_util.extract_platform_integrations(
|
||||||
|
config, BASE_PLATFORMS
|
||||||
|
)
|
||||||
|
# Ensure base platforms that have platform integrations are added to `domains`,
|
||||||
|
# so they can be setup first instead of discovering them later when a config
|
||||||
|
# entry setup task notices that it's needed and there is already a long line
|
||||||
|
# to use the import executor.
|
||||||
|
#
|
||||||
|
# For example if we have
|
||||||
|
# sensor:
|
||||||
|
# - platform: template
|
||||||
|
#
|
||||||
|
# `template` has to be loaded to validate the config for sensor
|
||||||
|
# so we want to start loading `sensor` as soon as we know
|
||||||
|
# it will be needed. The more platforms under `sensor:`, the longer
|
||||||
|
# it will take to finish setup for `sensor` because each of these
|
||||||
|
# platforms has to be imported before we can validate the config.
|
||||||
|
#
|
||||||
|
# Thankfully we are migrating away from the platform pattern
|
||||||
|
# so this will be less of a problem in the future.
|
||||||
|
domains_to_setup.update(platform_integrations)
|
||||||
|
|
||||||
|
# Additionally process base platforms since we do not require the manifest
|
||||||
|
# to list them as dependencies.
|
||||||
|
# We want to later avoid lock contention when multiple integrations try to load
|
||||||
|
# their manifests at once.
|
||||||
|
# Also process integrations that are defined under base platforms
|
||||||
|
# to speed things up.
|
||||||
|
additional_domains_to_process = {
|
||||||
|
*BASE_PLATFORMS,
|
||||||
|
*chain.from_iterable(platform_integrations.values()),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Resolve all dependencies so we know all integrations
|
||||||
|
# that will have to be loaded and start right-away
|
||||||
|
integrations_or_excs = await loader.async_get_integrations(
|
||||||
|
hass, {*domains_to_setup, *additional_domains_to_process}
|
||||||
|
)
|
||||||
|
# Eliminate those missing or with invalid manifest
|
||||||
|
integrations_to_process = {
|
||||||
|
domain: itg
|
||||||
|
for domain, itg in integrations_or_excs.items()
|
||||||
|
if isinstance(itg, Integration)
|
||||||
|
}
|
||||||
|
integrations_dependencies = await loader.resolve_integrations_dependencies(
|
||||||
|
hass, integrations_to_process.values()
|
||||||
|
)
|
||||||
|
# Eliminate those without valid dependencies
|
||||||
|
integrations_to_process = {
|
||||||
|
domain: integrations_to_process[domain] for domain in integrations_dependencies
|
||||||
|
}
|
||||||
|
|
||||||
|
integrations_to_setup = {
|
||||||
|
domain: itg
|
||||||
|
for domain, itg in integrations_to_process.items()
|
||||||
|
if domain in domains_to_setup
|
||||||
|
}
|
||||||
|
all_integrations_to_setup = integrations_to_setup.copy()
|
||||||
|
all_integrations_to_setup.update(
|
||||||
|
(dep, loader.async_get_loaded_integration(hass, dep))
|
||||||
|
for domain in integrations_to_setup
|
||||||
|
for dep in integrations_dependencies[domain].difference(
|
||||||
|
all_integrations_to_setup
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Gather requirements for all integrations,
|
||||||
|
# their dependencies and after dependencies.
|
||||||
|
# To gather all the requirements we must ignore exceptions here.
|
||||||
|
# The exceptions will be detected and handled later in the bootstrap process.
|
||||||
|
integrations_after_dependencies = (
|
||||||
|
await loader.resolve_integrations_after_dependencies(
|
||||||
|
hass, integrations_to_process.values(), ignore_exceptions=True
|
||||||
|
)
|
||||||
|
)
|
||||||
|
integrations_requirements = {
|
||||||
|
domain: itg.requirements for domain, itg in integrations_to_process.items()
|
||||||
|
}
|
||||||
|
integrations_requirements.update(
|
||||||
|
(dep, loader.async_get_loaded_integration(hass, dep).requirements)
|
||||||
|
for deps in integrations_after_dependencies.values()
|
||||||
|
for dep in deps.difference(integrations_requirements)
|
||||||
|
)
|
||||||
|
all_requirements = set(chain.from_iterable(integrations_requirements.values()))
|
||||||
|
|
||||||
|
# Optimistically check if requirements are already installed
|
||||||
|
# ahead of setting up the integrations so we can prime the cache
|
||||||
|
# We do not wait for this since it's an optimization only
|
||||||
|
hass.async_create_background_task(
|
||||||
|
requirements.async_load_installed_versions(hass, all_requirements),
|
||||||
|
"check installed requirements",
|
||||||
|
eager_start=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Start loading translations for all integrations we are going to set up
|
||||||
|
# in the background so they are ready when we need them. This avoids a
|
||||||
|
# lot of waiting for the translation load lock and a thundering herd of
|
||||||
|
# tasks trying to load the same translations at the same time as each
|
||||||
|
# integration is loaded.
|
||||||
|
#
|
||||||
|
# We do not wait for this since as soon as the task runs it will
|
||||||
|
# hold the translation load lock and if anything is fast enough to
|
||||||
|
# wait for the translation load lock, loading will be done by the
|
||||||
|
# time it gets to it.
|
||||||
|
translations_to_load = {*all_integrations_to_setup, *additional_domains_to_process}
|
||||||
|
hass.async_create_background_task(
|
||||||
|
translation.async_load_integrations(hass, translations_to_load),
|
||||||
|
"load translations",
|
||||||
|
eager_start=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Preload storage for all integrations we are going to set up
|
||||||
|
# so we do not have to wait for it to be loaded when we need it
|
||||||
|
# in the setup process.
|
||||||
|
hass.async_create_background_task(
|
||||||
|
get_internal_store_manager(hass).async_preload(
|
||||||
|
[*PRELOAD_STORAGE, *all_integrations_to_setup]
|
||||||
|
),
|
||||||
|
"preload storage",
|
||||||
|
eager_start=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
return integrations_to_setup, all_integrations_to_setup
|
||||||
|
|
||||||
|
|
||||||
|
async def _async_set_up_integrations(
|
||||||
|
hass: core.HomeAssistant, config: dict[str, Any]
|
||||||
|
) -> None:
|
||||||
|
"""Set up all the integrations."""
|
||||||
|
watcher = _WatchPendingSetups(hass, _setup_started(hass))
|
||||||
|
watcher.async_start()
|
||||||
|
|
||||||
|
integrations, all_integrations = await _async_resolve_domains_and_preload(
|
||||||
|
hass, config
|
||||||
|
)
|
||||||
|
all_domains = set(all_integrations)
|
||||||
|
domains = set(integrations)
|
||||||
|
|
||||||
|
_LOGGER.info(
|
||||||
|
"Domains to be set up: %s | %s",
|
||||||
|
domains,
|
||||||
|
all_domains - domains,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Initialize recorder
|
||||||
|
if "recorder" in all_domains:
|
||||||
|
recorder.async_initialize_recorder(hass)
|
||||||
|
|
||||||
|
# Initialize backup
|
||||||
|
if "backup" in all_domains:
|
||||||
|
backup.async_initialize_backup(hass)
|
||||||
|
|
||||||
|
stages: list[tuple[str, set[str], int | None]] = [
|
||||||
|
*(
|
||||||
|
(name, domain_group, timeout)
|
||||||
|
for name, domain_group, timeout in STAGE_0_INTEGRATIONS
|
||||||
|
),
|
||||||
|
("1", STAGE_1_INTEGRATIONS, STAGE_1_TIMEOUT),
|
||||||
|
("2", domains, STAGE_2_TIMEOUT),
|
||||||
|
]
|
||||||
|
|
||||||
|
_LOGGER.info("Setting up stage 0")
|
||||||
|
for name, domain_group, timeout in stages:
|
||||||
|
stage_domains_unfiltered = domain_group & all_domains
|
||||||
|
if not stage_domains_unfiltered:
|
||||||
|
_LOGGER.info("Nothing to set up in stage %s: %s", name, domain_group)
|
||||||
|
continue
|
||||||
|
|
||||||
|
stage_domains = stage_domains_unfiltered - hass.config.components
|
||||||
|
if not stage_domains:
|
||||||
|
_LOGGER.info("Already set up stage %s: %s", name, stage_domains_unfiltered)
|
||||||
|
continue
|
||||||
|
|
||||||
|
stage_dep_domains_unfiltered = {
|
||||||
|
dep
|
||||||
|
for domain in stage_domains
|
||||||
|
for dep in all_integrations[domain].all_dependencies
|
||||||
|
if dep not in stage_domains
|
||||||
|
}
|
||||||
|
stage_dep_domains = stage_dep_domains_unfiltered - hass.config.components
|
||||||
|
|
||||||
|
stage_all_domains = stage_domains | stage_dep_domains
|
||||||
|
stage_all_integrations = {
|
||||||
|
domain: all_integrations[domain] for domain in stage_all_domains
|
||||||
|
}
|
||||||
|
# Detect all cycles
|
||||||
|
stage_integrations_after_dependencies = (
|
||||||
|
await loader.resolve_integrations_after_dependencies(
|
||||||
|
hass, stage_all_integrations.values(), stage_all_domains
|
||||||
|
)
|
||||||
|
)
|
||||||
|
stage_all_domains = set(stage_integrations_after_dependencies)
|
||||||
|
stage_domains &= stage_all_domains
|
||||||
|
stage_dep_domains &= stage_all_domains
|
||||||
|
|
||||||
|
_LOGGER.info(
|
||||||
|
"Setting up stage %s: %s | %s\nDependencies: %s | %s",
|
||||||
|
name,
|
||||||
|
stage_domains,
|
||||||
|
stage_domains_unfiltered - stage_domains,
|
||||||
|
stage_dep_domains,
|
||||||
|
stage_dep_domains_unfiltered - stage_dep_domains,
|
||||||
|
)
|
||||||
|
|
||||||
|
async_set_domains_to_be_loaded(hass, stage_all_domains)
|
||||||
|
|
||||||
|
if timeout is None:
|
||||||
|
await _async_setup_multi_components(hass, stage_all_domains, config)
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
async with hass.timeout.async_timeout(timeout, cool_down=COOLDOWN_TIME):
|
||||||
|
await _async_setup_multi_components(hass, stage_all_domains, config)
|
||||||
|
except TimeoutError:
|
||||||
|
_LOGGER.warning(
|
||||||
|
"Setup timed out for stage %s waiting on %s - moving forward",
|
||||||
|
name,
|
||||||
|
hass._active_tasks, # noqa: SLF001
|
||||||
|
)
|
||||||
|
|
||||||
|
# Wrap up startup
|
||||||
|
_LOGGER.debug("Waiting for startup to wrap up")
|
||||||
|
try:
|
||||||
|
async with hass.timeout.async_timeout(WRAP_UP_TIMEOUT, cool_down=COOLDOWN_TIME):
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
except TimeoutError:
|
||||||
|
_LOGGER.warning(
|
||||||
|
"Setup timed out for bootstrap waiting on %s - moving forward",
|
||||||
|
hass._active_tasks, # noqa: SLF001
|
||||||
|
)
|
||||||
|
|
||||||
|
watcher.async_stop()
|
||||||
|
|
||||||
|
if _LOGGER.isEnabledFor(logging.DEBUG):
|
||||||
|
setup_time = async_get_setup_timings(hass)
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Integration setup times: %s",
|
||||||
|
dict(sorted(setup_time.items(), key=itemgetter(1), reverse=True)),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class _WatchPendingSetups:
|
class _WatchPendingSetups:
|
||||||
"""Periodic log and dispatch of setups that are pending."""
|
"""Periodic log and dispatch of setups that are pending."""
|
||||||
|
|
||||||
@ -787,14 +1035,12 @@ class _WatchPendingSetups:
|
|||||||
self._handle = None
|
self._handle = None
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_multi_components(
|
async def _async_setup_multi_components(
|
||||||
hass: core.HomeAssistant,
|
hass: core.HomeAssistant,
|
||||||
domains: set[str],
|
domains: set[str],
|
||||||
config: dict[str, Any],
|
config: dict[str, Any],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up multiple domains. Log on failure."""
|
"""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
|
# 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
|
# 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.
|
# loaded the sooner we can start loading the rest of the integrations.
|
||||||
@ -804,9 +1050,7 @@ async def async_setup_multi_components(
|
|||||||
f"setup component {domain}",
|
f"setup component {domain}",
|
||||||
eager_start=True,
|
eager_start=True,
|
||||||
)
|
)
|
||||||
for domain in sorted(
|
for domain in sorted(domains, key=SETUP_ORDER_SORT_KEY, reverse=True)
|
||||||
domains_not_yet_setup, key=SETUP_ORDER_SORT_KEY, reverse=True
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
results = await asyncio.gather(*futures.values(), return_exceptions=True)
|
results = await asyncio.gather(*futures.values(), return_exceptions=True)
|
||||||
for idx, domain in enumerate(futures):
|
for idx, domain in enumerate(futures):
|
||||||
@ -817,278 +1061,3 @@ async def async_setup_multi_components(
|
|||||||
domain,
|
domain,
|
||||||
exc_info=(type(result), result, result.__traceback__),
|
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]]:
|
|
||||||
"""Resolve all dependencies and return list of domains to set up."""
|
|
||||||
domains_to_setup = _get_domains(hass, config)
|
|
||||||
needed_requirements: set[str] = set()
|
|
||||||
platform_integrations = conf_util.extract_platform_integrations(
|
|
||||||
config, BASE_PLATFORMS
|
|
||||||
)
|
|
||||||
# Ensure base platforms that have platform integrations are added to
|
|
||||||
# to `domains_to_setup so they can be setup first instead of
|
|
||||||
# discovering them when later when a config entry setup task
|
|
||||||
# notices its needed and there is already a long line to use
|
|
||||||
# the import executor.
|
|
||||||
#
|
|
||||||
# For example if we have
|
|
||||||
# sensor:
|
|
||||||
# - platform: template
|
|
||||||
#
|
|
||||||
# `template` has to be loaded to validate the config for sensor
|
|
||||||
# so we want to start loading `sensor` as soon as we know
|
|
||||||
# it will be needed. The more platforms under `sensor:`, the longer
|
|
||||||
# it will take to finish setup for `sensor` because each of these
|
|
||||||
# platforms has to be imported before we can validate the config.
|
|
||||||
#
|
|
||||||
# Thankfully we are migrating away from the platform pattern
|
|
||||||
# so this will be less of a problem in the future.
|
|
||||||
domains_to_setup.update(platform_integrations)
|
|
||||||
|
|
||||||
# Load manifests for base platforms and platform based integrations
|
|
||||||
# that are defined under base platforms right away since we do not require
|
|
||||||
# the manifest to list them as dependencies and we want to avoid the lock
|
|
||||||
# contention when multiple integrations try to load them at once
|
|
||||||
additional_manifests_to_load = {
|
|
||||||
*BASE_PLATFORMS,
|
|
||||||
*chain.from_iterable(platform_integrations.values()),
|
|
||||||
}
|
|
||||||
|
|
||||||
translations_to_load = additional_manifests_to_load.copy()
|
|
||||||
|
|
||||||
# Resolve all dependencies so we know all integrations
|
|
||||||
# that will have to be loaded and start right-away
|
|
||||||
integration_cache: dict[str, loader.Integration] = {}
|
|
||||||
to_resolve: set[str] = domains_to_setup
|
|
||||||
while to_resolve or additional_manifests_to_load:
|
|
||||||
old_to_resolve: set[str] = to_resolve
|
|
||||||
to_resolve = set()
|
|
||||||
|
|
||||||
if additional_manifests_to_load:
|
|
||||||
to_get = {*old_to_resolve, *additional_manifests_to_load}
|
|
||||||
additional_manifests_to_load.clear()
|
|
||||||
else:
|
|
||||||
to_get = old_to_resolve
|
|
||||||
|
|
||||||
manifest_deps: set[str] = set()
|
|
||||||
resolve_dependencies_tasks: list[asyncio.Task[bool]] = []
|
|
||||||
integrations_to_process: list[loader.Integration] = []
|
|
||||||
|
|
||||||
for domain, itg in (await loader.async_get_integrations(hass, to_get)).items():
|
|
||||||
if not isinstance(itg, loader.Integration):
|
|
||||||
continue
|
|
||||||
integration_cache[domain] = itg
|
|
||||||
needed_requirements.update(itg.requirements)
|
|
||||||
|
|
||||||
# Make sure manifests for dependencies are loaded in the next
|
|
||||||
# loop to try to group as many as manifest loads in a single
|
|
||||||
# call to avoid the creating one-off executor jobs later in
|
|
||||||
# the setup process
|
|
||||||
additional_manifests_to_load.update(
|
|
||||||
dep
|
|
||||||
for dep in chain(itg.dependencies, itg.after_dependencies)
|
|
||||||
if dep not in integration_cache
|
|
||||||
)
|
|
||||||
|
|
||||||
if domain not in old_to_resolve:
|
|
||||||
continue
|
|
||||||
|
|
||||||
integrations_to_process.append(itg)
|
|
||||||
manifest_deps.update(itg.dependencies)
|
|
||||||
manifest_deps.update(itg.after_dependencies)
|
|
||||||
if not itg.all_dependencies_resolved:
|
|
||||||
resolve_dependencies_tasks.append(
|
|
||||||
create_eager_task(
|
|
||||||
itg.resolve_dependencies(),
|
|
||||||
name=f"resolve dependencies {domain}",
|
|
||||||
loop=hass.loop,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if unseen_deps := manifest_deps - integration_cache.keys():
|
|
||||||
# If there are dependencies, try to preload all
|
|
||||||
# the integrations manifest at once and add them
|
|
||||||
# to the list of requirements we need to install
|
|
||||||
# so we can try to check if they are already installed
|
|
||||||
# in a single call below which avoids each integration
|
|
||||||
# having to wait for the lock to do it individually
|
|
||||||
deps = await loader.async_get_integrations(hass, unseen_deps)
|
|
||||||
for dependant_domain, dependant_itg in deps.items():
|
|
||||||
if isinstance(dependant_itg, loader.Integration):
|
|
||||||
integration_cache[dependant_domain] = dependant_itg
|
|
||||||
needed_requirements.update(dependant_itg.requirements)
|
|
||||||
|
|
||||||
if resolve_dependencies_tasks:
|
|
||||||
await asyncio.gather(*resolve_dependencies_tasks)
|
|
||||||
|
|
||||||
for itg in integrations_to_process:
|
|
||||||
try:
|
|
||||||
all_deps = itg.all_dependencies
|
|
||||||
except RuntimeError:
|
|
||||||
# Integration.all_dependencies raises RuntimeError if
|
|
||||||
# dependencies could not be resolved
|
|
||||||
continue
|
|
||||||
for dep in all_deps:
|
|
||||||
if dep in domains_to_setup:
|
|
||||||
continue
|
|
||||||
domains_to_setup.add(dep)
|
|
||||||
to_resolve.add(dep)
|
|
||||||
|
|
||||||
_LOGGER.info("Domains to be set up: %s", domains_to_setup)
|
|
||||||
|
|
||||||
# Optimistically check if requirements are already installed
|
|
||||||
# ahead of setting up the integrations so we can prime the cache
|
|
||||||
# We do not wait for this since its an optimization only
|
|
||||||
hass.async_create_background_task(
|
|
||||||
requirements.async_load_installed_versions(hass, needed_requirements),
|
|
||||||
"check installed requirements",
|
|
||||||
eager_start=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
#
|
|
||||||
# Only add the domains_to_setup after we finish resolving
|
|
||||||
# as new domains are likely to added in the process
|
|
||||||
#
|
|
||||||
translations_to_load.update(domains_to_setup)
|
|
||||||
# Start loading translations for all integrations we are going to set up
|
|
||||||
# in the background so they are ready when we need them. This avoids a
|
|
||||||
# lot of waiting for the translation load lock and a thundering herd of
|
|
||||||
# tasks trying to load the same translations at the same time as each
|
|
||||||
# integration is loaded.
|
|
||||||
#
|
|
||||||
# We do not wait for this since as soon as the task runs it will
|
|
||||||
# hold the translation load lock and if anything is fast enough to
|
|
||||||
# wait for the translation load lock, loading will be done by the
|
|
||||||
# time it gets to it.
|
|
||||||
hass.async_create_background_task(
|
|
||||||
translation.async_load_integrations(hass, translations_to_load),
|
|
||||||
"load translations",
|
|
||||||
eager_start=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Preload storage for all integrations we are going to set up
|
|
||||||
# so we do not have to wait for it to be loaded when we need it
|
|
||||||
# in the setup process.
|
|
||||||
hass.async_create_background_task(
|
|
||||||
get_internal_store_manager(hass).async_preload(
|
|
||||||
[*PRELOAD_STORAGE, *domains_to_setup]
|
|
||||||
),
|
|
||||||
"preload storage",
|
|
||||||
eager_start=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
return domains_to_setup, integration_cache
|
|
||||||
|
|
||||||
|
|
||||||
async def _async_set_up_integrations(
|
|
||||||
hass: core.HomeAssistant, config: dict[str, Any]
|
|
||||||
) -> None:
|
|
||||||
"""Set up all the integrations."""
|
|
||||||
watcher = _WatchPendingSetups(hass, _setup_started(hass))
|
|
||||||
watcher.async_start()
|
|
||||||
|
|
||||||
domains_to_setup, integration_cache = await _async_resolve_domains_to_setup(
|
|
||||||
hass, config
|
|
||||||
)
|
|
||||||
|
|
||||||
# Initialize recorder
|
|
||||||
if "recorder" in domains_to_setup:
|
|
||||||
recorder.async_initialize_recorder(hass)
|
|
||||||
|
|
||||||
pre_stage_domains = [
|
|
||||||
(name, domains_to_setup & domain_group) for name, domain_group in SETUP_ORDER
|
|
||||||
]
|
|
||||||
|
|
||||||
# calculate what components to setup in what stage
|
|
||||||
stage_1_domains: set[str] = set()
|
|
||||||
|
|
||||||
# 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()
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
if stage_2_domains:
|
|
||||||
_LOGGER.info("Setting up stage 2: %s", stage_2_domains)
|
|
||||||
try:
|
|
||||||
async with hass.timeout.async_timeout(
|
|
||||||
STAGE_2_TIMEOUT, cool_down=COOLDOWN_TIME
|
|
||||||
):
|
|
||||||
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",
|
|
||||||
hass._active_tasks, # noqa: SLF001
|
|
||||||
)
|
|
||||||
|
|
||||||
# Wrap up startup
|
|
||||||
_LOGGER.debug("Waiting for startup to wrap up")
|
|
||||||
try:
|
|
||||||
async with hass.timeout.async_timeout(WRAP_UP_TIMEOUT, cool_down=COOLDOWN_TIME):
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
except TimeoutError:
|
|
||||||
_LOGGER.warning(
|
|
||||||
"Setup timed out for bootstrap waiting on %s - moving forward",
|
|
||||||
hass._active_tasks, # noqa: SLF001
|
|
||||||
)
|
|
||||||
|
|
||||||
watcher.async_stop()
|
|
||||||
|
|
||||||
if _LOGGER.isEnabledFor(logging.DEBUG):
|
|
||||||
setup_time = async_get_setup_timings(hass)
|
|
||||||
_LOGGER.debug(
|
|
||||||
"Integration setup times: %s",
|
|
||||||
dict(sorted(setup_time.items(), key=itemgetter(1), reverse=True)),
|
|
||||||
)
|
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
"azure_devops",
|
"azure_devops",
|
||||||
"azure_event_hub",
|
"azure_event_hub",
|
||||||
"azure_service_bus",
|
"azure_service_bus",
|
||||||
|
"azure_storage",
|
||||||
"microsoft_face_detect",
|
"microsoft_face_detect",
|
||||||
"microsoft_face_identify",
|
"microsoft_face_identify",
|
||||||
"microsoft_face",
|
"microsoft_face",
|
||||||
|
5
homeassistant/brands/sensorpush.json
Normal file
5
homeassistant/brands/sensorpush.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"domain": "sensorpush",
|
||||||
|
"name": "SensorPush",
|
||||||
|
"integrations": ["sensorpush", "sensorpush_cloud"]
|
||||||
|
}
|
@ -11,7 +11,7 @@ from homeassistant.components.alarm_control_panel import (
|
|||||||
)
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
from . import AbodeSystem
|
from . import AbodeSystem
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
@ -19,7 +19,9 @@ from .entity import AbodeDevice
|
|||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
hass: HomeAssistant,
|
||||||
|
entry: ConfigEntry,
|
||||||
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up Abode alarm control panel device."""
|
"""Set up Abode alarm control panel device."""
|
||||||
data: AbodeSystem = hass.data[DOMAIN]
|
data: AbodeSystem = hass.data[DOMAIN]
|
||||||
|
@ -12,7 +12,7 @@ from homeassistant.components.binary_sensor import (
|
|||||||
)
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
from homeassistant.util.enum import try_parse_enum
|
from homeassistant.util.enum import try_parse_enum
|
||||||
|
|
||||||
from . import AbodeSystem
|
from . import AbodeSystem
|
||||||
@ -21,7 +21,9 @@ from .entity import AbodeDevice
|
|||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
hass: HomeAssistant,
|
||||||
|
entry: ConfigEntry,
|
||||||
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up Abode binary sensor devices."""
|
"""Set up Abode binary sensor devices."""
|
||||||
data: AbodeSystem = hass.data[DOMAIN]
|
data: AbodeSystem = hass.data[DOMAIN]
|
||||||
|
@ -15,7 +15,7 @@ from homeassistant.components.camera import Camera
|
|||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import Event, HomeAssistant
|
from homeassistant.core import Event, HomeAssistant
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
from homeassistant.util import Throttle
|
from homeassistant.util import Throttle
|
||||||
|
|
||||||
from . import AbodeSystem
|
from . import AbodeSystem
|
||||||
@ -26,7 +26,9 @@ MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=90)
|
|||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
hass: HomeAssistant,
|
||||||
|
entry: ConfigEntry,
|
||||||
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up Abode camera devices."""
|
"""Set up Abode camera devices."""
|
||||||
data: AbodeSystem = hass.data[DOMAIN]
|
data: AbodeSystem = hass.data[DOMAIN]
|
||||||
|
@ -7,7 +7,7 @@ from jaraco.abode.devices.cover import Cover
|
|||||||
from homeassistant.components.cover import CoverEntity
|
from homeassistant.components.cover import CoverEntity
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
from . import AbodeSystem
|
from . import AbodeSystem
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
@ -15,7 +15,9 @@ from .entity import AbodeDevice
|
|||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
hass: HomeAssistant,
|
||||||
|
entry: ConfigEntry,
|
||||||
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up Abode cover devices."""
|
"""Set up Abode cover devices."""
|
||||||
data: AbodeSystem = hass.data[DOMAIN]
|
data: AbodeSystem = hass.data[DOMAIN]
|
||||||
|
@ -18,7 +18,7 @@ from homeassistant.components.light import (
|
|||||||
)
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
from . import AbodeSystem
|
from . import AbodeSystem
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
@ -26,7 +26,9 @@ from .entity import AbodeDevice
|
|||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
hass: HomeAssistant,
|
||||||
|
entry: ConfigEntry,
|
||||||
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up Abode light devices."""
|
"""Set up Abode light devices."""
|
||||||
data: AbodeSystem = hass.data[DOMAIN]
|
data: AbodeSystem = hass.data[DOMAIN]
|
||||||
|
@ -7,7 +7,7 @@ from jaraco.abode.devices.lock import Lock
|
|||||||
from homeassistant.components.lock import LockEntity
|
from homeassistant.components.lock import LockEntity
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
from . import AbodeSystem
|
from . import AbodeSystem
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
@ -15,7 +15,9 @@ from .entity import AbodeDevice
|
|||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
hass: HomeAssistant,
|
||||||
|
entry: ConfigEntry,
|
||||||
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up Abode lock devices."""
|
"""Set up Abode lock devices."""
|
||||||
data: AbodeSystem = hass.data[DOMAIN]
|
data: AbodeSystem = hass.data[DOMAIN]
|
||||||
|
@ -16,7 +16,7 @@ from homeassistant.components.sensor import (
|
|||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import LIGHT_LUX, PERCENTAGE, UnitOfTemperature
|
from homeassistant.const import LIGHT_LUX, PERCENTAGE, UnitOfTemperature
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
from . import AbodeSystem
|
from . import AbodeSystem
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
@ -61,7 +61,9 @@ SENSOR_TYPES: tuple[AbodeSensorDescription, ...] = (
|
|||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
hass: HomeAssistant,
|
||||||
|
entry: ConfigEntry,
|
||||||
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up Abode sensor devices."""
|
"""Set up Abode sensor devices."""
|
||||||
data: AbodeSystem = hass.data[DOMAIN]
|
data: AbodeSystem = hass.data[DOMAIN]
|
||||||
|
@ -10,7 +10,7 @@ from homeassistant.components.switch import SwitchEntity
|
|||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
from . import AbodeSystem
|
from . import AbodeSystem
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
@ -20,7 +20,9 @@ DEVICE_TYPES = ["switch", "valve"]
|
|||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
hass: HomeAssistant,
|
||||||
|
entry: ConfigEntry,
|
||||||
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up Abode switch devices."""
|
"""Set up Abode switch devices."""
|
||||||
data: AbodeSystem = hass.data[DOMAIN]
|
data: AbodeSystem = hass.data[DOMAIN]
|
||||||
|
@ -11,7 +11,7 @@ from homeassistant.components.binary_sensor import (
|
|||||||
BinarySensorEntityDescription,
|
BinarySensorEntityDescription,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
from .coordinator import AcaiaConfigEntry
|
from .coordinator import AcaiaConfigEntry
|
||||||
from .entity import AcaiaEntity
|
from .entity import AcaiaEntity
|
||||||
@ -40,7 +40,7 @@ BINARY_SENSORS: tuple[AcaiaBinarySensorEntityDescription, ...] = (
|
|||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry: AcaiaConfigEntry,
|
entry: AcaiaConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up binary sensors."""
|
"""Set up binary sensors."""
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ from aioacaia.acaiascale import AcaiaScale
|
|||||||
|
|
||||||
from homeassistant.components.button import ButtonEntity, ButtonEntityDescription
|
from homeassistant.components.button import ButtonEntity, ButtonEntityDescription
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
from .coordinator import AcaiaConfigEntry
|
from .coordinator import AcaiaConfigEntry
|
||||||
from .entity import AcaiaEntity
|
from .entity import AcaiaEntity
|
||||||
@ -45,7 +45,7 @@ BUTTONS: tuple[AcaiaButtonEntityDescription, ...] = (
|
|||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry: AcaiaConfigEntry,
|
entry: AcaiaConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up button entities and services."""
|
"""Set up button entities and services."""
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ from homeassistant.components.sensor import (
|
|||||||
)
|
)
|
||||||
from homeassistant.const import PERCENTAGE, UnitOfMass, UnitOfVolumeFlowRate
|
from homeassistant.const import PERCENTAGE, UnitOfMass, UnitOfVolumeFlowRate
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
from .coordinator import AcaiaConfigEntry
|
from .coordinator import AcaiaConfigEntry
|
||||||
from .entity import AcaiaEntity
|
from .entity import AcaiaEntity
|
||||||
@ -77,7 +77,7 @@ RESTORE_SENSORS: tuple[AcaiaSensorEntityDescription, ...] = (
|
|||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry: AcaiaConfigEntry,
|
entry: AcaiaConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up sensors."""
|
"""Set up sensors."""
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ from homeassistant.components.weather import (
|
|||||||
|
|
||||||
API_METRIC: Final = "Metric"
|
API_METRIC: Final = "Metric"
|
||||||
ATTRIBUTION: Final = "Data provided by AccuWeather"
|
ATTRIBUTION: Final = "Data provided by AccuWeather"
|
||||||
ATTR_CATEGORY: Final = "Category"
|
ATTR_CATEGORY_VALUE = "CategoryValue"
|
||||||
ATTR_DIRECTION: Final = "Direction"
|
ATTR_DIRECTION: Final = "Direction"
|
||||||
ATTR_ENGLISH: Final = "English"
|
ATTR_ENGLISH: Final = "English"
|
||||||
ATTR_LEVEL: Final = "level"
|
ATTR_LEVEL: Final = "level"
|
||||||
@ -55,5 +55,18 @@ CONDITION_MAP = {
|
|||||||
for cond_ha, cond_codes in CONDITION_CLASSES.items()
|
for cond_ha, cond_codes in CONDITION_CLASSES.items()
|
||||||
for cond_code in cond_codes
|
for cond_code in cond_codes
|
||||||
}
|
}
|
||||||
|
AIR_QUALITY_CATEGORY_MAP = {
|
||||||
|
1: "good",
|
||||||
|
2: "moderate",
|
||||||
|
3: "unhealthy",
|
||||||
|
4: "very_unhealthy",
|
||||||
|
5: "hazardous",
|
||||||
|
}
|
||||||
|
POLLEN_CATEGORY_MAP = {
|
||||||
|
1: "low",
|
||||||
|
2: "moderate",
|
||||||
|
3: "high",
|
||||||
|
4: "very_high",
|
||||||
|
}
|
||||||
UPDATE_INTERVAL_OBSERVATION = timedelta(minutes=40)
|
UPDATE_INTERVAL_OBSERVATION = timedelta(minutes=40)
|
||||||
UPDATE_INTERVAL_DAILY_FORECAST = timedelta(hours=6)
|
UPDATE_INTERVAL_DAILY_FORECAST = timedelta(hours=6)
|
||||||
|
@ -75,7 +75,11 @@ class AccuWeatherObservationDataUpdateCoordinator(
|
|||||||
async with timeout(10):
|
async with timeout(10):
|
||||||
result = await self.accuweather.async_get_current_conditions()
|
result = await self.accuweather.async_get_current_conditions()
|
||||||
except EXCEPTIONS as error:
|
except EXCEPTIONS as error:
|
||||||
raise UpdateFailed(error) from error
|
raise UpdateFailed(
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="current_conditions_update_error",
|
||||||
|
translation_placeholders={"error": repr(error)},
|
||||||
|
) from error
|
||||||
|
|
||||||
_LOGGER.debug("Requests remaining: %d", self.accuweather.requests_remaining)
|
_LOGGER.debug("Requests remaining: %d", self.accuweather.requests_remaining)
|
||||||
|
|
||||||
@ -117,9 +121,15 @@ class AccuWeatherDailyForecastDataUpdateCoordinator(
|
|||||||
"""Update data via library."""
|
"""Update data via library."""
|
||||||
try:
|
try:
|
||||||
async with timeout(10):
|
async with timeout(10):
|
||||||
result = await self.accuweather.async_get_daily_forecast()
|
result = await self.accuweather.async_get_daily_forecast(
|
||||||
|
language=self.hass.config.language
|
||||||
|
)
|
||||||
except EXCEPTIONS as error:
|
except EXCEPTIONS as error:
|
||||||
raise UpdateFailed(error) from error
|
raise UpdateFailed(
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="forecast_update_error",
|
||||||
|
translation_placeholders={"error": repr(error)},
|
||||||
|
) from error
|
||||||
|
|
||||||
_LOGGER.debug("Requests remaining: %d", self.accuweather.requests_remaining)
|
_LOGGER.debug("Requests remaining: %d", self.accuweather.requests_remaining)
|
||||||
|
|
||||||
|
@ -7,6 +7,6 @@
|
|||||||
"integration_type": "service",
|
"integration_type": "service",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["accuweather"],
|
"loggers": ["accuweather"],
|
||||||
"requirements": ["accuweather==4.0.0"],
|
"requirements": ["accuweather==4.2.0"],
|
||||||
"single_config_entry": true
|
"single_config_entry": true
|
||||||
}
|
}
|
||||||
|
@ -25,12 +25,13 @@ from homeassistant.const import (
|
|||||||
UnitOfVolumetricFlux,
|
UnitOfVolumetricFlux,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
|
AIR_QUALITY_CATEGORY_MAP,
|
||||||
API_METRIC,
|
API_METRIC,
|
||||||
ATTR_CATEGORY,
|
ATTR_CATEGORY_VALUE,
|
||||||
ATTR_DIRECTION,
|
ATTR_DIRECTION,
|
||||||
ATTR_ENGLISH,
|
ATTR_ENGLISH,
|
||||||
ATTR_LEVEL,
|
ATTR_LEVEL,
|
||||||
@ -38,6 +39,7 @@ from .const import (
|
|||||||
ATTR_VALUE,
|
ATTR_VALUE,
|
||||||
ATTRIBUTION,
|
ATTRIBUTION,
|
||||||
MAX_FORECAST_DAYS,
|
MAX_FORECAST_DAYS,
|
||||||
|
POLLEN_CATEGORY_MAP,
|
||||||
)
|
)
|
||||||
from .coordinator import (
|
from .coordinator import (
|
||||||
AccuWeatherConfigEntry,
|
AccuWeatherConfigEntry,
|
||||||
@ -59,9 +61,9 @@ class AccuWeatherSensorDescription(SensorEntityDescription):
|
|||||||
FORECAST_SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
|
FORECAST_SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
|
||||||
AccuWeatherSensorDescription(
|
AccuWeatherSensorDescription(
|
||||||
key="AirQuality",
|
key="AirQuality",
|
||||||
value_fn=lambda data: cast(str, data[ATTR_CATEGORY]),
|
value_fn=lambda data: AIR_QUALITY_CATEGORY_MAP[data[ATTR_CATEGORY_VALUE]],
|
||||||
device_class=SensorDeviceClass.ENUM,
|
device_class=SensorDeviceClass.ENUM,
|
||||||
options=["good", "hazardous", "high", "low", "moderate", "unhealthy"],
|
options=list(AIR_QUALITY_CATEGORY_MAP.values()),
|
||||||
translation_key="air_quality",
|
translation_key="air_quality",
|
||||||
),
|
),
|
||||||
AccuWeatherSensorDescription(
|
AccuWeatherSensorDescription(
|
||||||
@ -83,7 +85,9 @@ FORECAST_SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
|
|||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
value_fn=lambda data: cast(int, data[ATTR_VALUE]),
|
value_fn=lambda data: cast(int, data[ATTR_VALUE]),
|
||||||
attr_fn=lambda data: {ATTR_LEVEL: data[ATTR_CATEGORY]},
|
attr_fn=lambda data: {
|
||||||
|
ATTR_LEVEL: POLLEN_CATEGORY_MAP[data[ATTR_CATEGORY_VALUE]]
|
||||||
|
},
|
||||||
translation_key="grass_pollen",
|
translation_key="grass_pollen",
|
||||||
),
|
),
|
||||||
AccuWeatherSensorDescription(
|
AccuWeatherSensorDescription(
|
||||||
@ -107,7 +111,9 @@ FORECAST_SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
|
|||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
value_fn=lambda data: cast(int, data[ATTR_VALUE]),
|
value_fn=lambda data: cast(int, data[ATTR_VALUE]),
|
||||||
attr_fn=lambda data: {ATTR_LEVEL: data[ATTR_CATEGORY]},
|
attr_fn=lambda data: {
|
||||||
|
ATTR_LEVEL: POLLEN_CATEGORY_MAP[data[ATTR_CATEGORY_VALUE]]
|
||||||
|
},
|
||||||
translation_key="mold_pollen",
|
translation_key="mold_pollen",
|
||||||
),
|
),
|
||||||
AccuWeatherSensorDescription(
|
AccuWeatherSensorDescription(
|
||||||
@ -115,7 +121,9 @@ FORECAST_SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
|
|||||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
value_fn=lambda data: cast(int, data[ATTR_VALUE]),
|
value_fn=lambda data: cast(int, data[ATTR_VALUE]),
|
||||||
attr_fn=lambda data: {ATTR_LEVEL: data[ATTR_CATEGORY]},
|
attr_fn=lambda data: {
|
||||||
|
ATTR_LEVEL: POLLEN_CATEGORY_MAP[data[ATTR_CATEGORY_VALUE]]
|
||||||
|
},
|
||||||
translation_key="ragweed_pollen",
|
translation_key="ragweed_pollen",
|
||||||
),
|
),
|
||||||
AccuWeatherSensorDescription(
|
AccuWeatherSensorDescription(
|
||||||
@ -181,14 +189,18 @@ FORECAST_SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
|
|||||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
value_fn=lambda data: cast(int, data[ATTR_VALUE]),
|
value_fn=lambda data: cast(int, data[ATTR_VALUE]),
|
||||||
attr_fn=lambda data: {ATTR_LEVEL: data[ATTR_CATEGORY]},
|
attr_fn=lambda data: {
|
||||||
|
ATTR_LEVEL: POLLEN_CATEGORY_MAP[data[ATTR_CATEGORY_VALUE]]
|
||||||
|
},
|
||||||
translation_key="tree_pollen",
|
translation_key="tree_pollen",
|
||||||
),
|
),
|
||||||
AccuWeatherSensorDescription(
|
AccuWeatherSensorDescription(
|
||||||
key="UVIndex",
|
key="UVIndex",
|
||||||
native_unit_of_measurement=UV_INDEX,
|
native_unit_of_measurement=UV_INDEX,
|
||||||
value_fn=lambda data: cast(int, data[ATTR_VALUE]),
|
value_fn=lambda data: cast(int, data[ATTR_VALUE]),
|
||||||
attr_fn=lambda data: {ATTR_LEVEL: data[ATTR_CATEGORY]},
|
attr_fn=lambda data: {
|
||||||
|
ATTR_LEVEL: POLLEN_CATEGORY_MAP[data[ATTR_CATEGORY_VALUE]]
|
||||||
|
},
|
||||||
translation_key="uv_index_forecast",
|
translation_key="uv_index_forecast",
|
||||||
),
|
),
|
||||||
AccuWeatherSensorDescription(
|
AccuWeatherSensorDescription(
|
||||||
@ -375,7 +387,7 @@ SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
|
|||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry: AccuWeatherConfigEntry,
|
entry: AccuWeatherConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Add AccuWeather entities from a config_entry."""
|
"""Add AccuWeather entities from a config_entry."""
|
||||||
observation_coordinator: AccuWeatherObservationDataUpdateCoordinator = (
|
observation_coordinator: AccuWeatherObservationDataUpdateCoordinator = (
|
||||||
|
@ -26,10 +26,20 @@
|
|||||||
"state": {
|
"state": {
|
||||||
"good": "Good",
|
"good": "Good",
|
||||||
"hazardous": "Hazardous",
|
"hazardous": "Hazardous",
|
||||||
"high": "High",
|
|
||||||
"low": "Low",
|
|
||||||
"moderate": "Moderate",
|
"moderate": "Moderate",
|
||||||
"unhealthy": "Unhealthy"
|
"unhealthy": "Unhealthy",
|
||||||
|
"very_unhealthy": "Very unhealthy"
|
||||||
|
},
|
||||||
|
"state_attributes": {
|
||||||
|
"options": {
|
||||||
|
"state": {
|
||||||
|
"good": "[%key:component::accuweather::entity::sensor::air_quality::state::good%]",
|
||||||
|
"hazardous": "[%key:component::accuweather::entity::sensor::air_quality::state::hazardous%]",
|
||||||
|
"moderate": "[%key:component::accuweather::entity::sensor::air_quality::state::moderate%]",
|
||||||
|
"unhealthy": "[%key:component::accuweather::entity::sensor::air_quality::state::unhealthy%]",
|
||||||
|
"very_unhealthy": "[%key:component::accuweather::entity::sensor::air_quality::state::very_unhealthy%]"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"apparent_temperature": {
|
"apparent_temperature": {
|
||||||
@ -62,12 +72,10 @@
|
|||||||
"level": {
|
"level": {
|
||||||
"name": "Level",
|
"name": "Level",
|
||||||
"state": {
|
"state": {
|
||||||
"good": "[%key:component::accuweather::entity::sensor::air_quality::state::good%]",
|
"high": "High",
|
||||||
"hazardous": "[%key:component::accuweather::entity::sensor::air_quality::state::hazardous%]",
|
"low": "Low",
|
||||||
"high": "[%key:component::accuweather::entity::sensor::air_quality::state::high%]",
|
"moderate": "Moderate",
|
||||||
"low": "[%key:component::accuweather::entity::sensor::air_quality::state::low%]",
|
"very_high": "Very high"
|
||||||
"moderate": "[%key:component::accuweather::entity::sensor::air_quality::state::moderate%]",
|
|
||||||
"unhealthy": "[%key:component::accuweather::entity::sensor::air_quality::state::unhealthy%]"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -81,12 +89,10 @@
|
|||||||
"level": {
|
"level": {
|
||||||
"name": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::name%]",
|
"name": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::name%]",
|
||||||
"state": {
|
"state": {
|
||||||
"good": "[%key:component::accuweather::entity::sensor::air_quality::state::good%]",
|
"high": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::high%]",
|
||||||
"hazardous": "[%key:component::accuweather::entity::sensor::air_quality::state::hazardous%]",
|
"low": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::low%]",
|
||||||
"high": "[%key:component::accuweather::entity::sensor::air_quality::state::high%]",
|
"moderate": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::moderate%]",
|
||||||
"low": "[%key:component::accuweather::entity::sensor::air_quality::state::low%]",
|
"very_high": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::very_high%]"
|
||||||
"moderate": "[%key:component::accuweather::entity::sensor::air_quality::state::moderate%]",
|
|
||||||
"unhealthy": "[%key:component::accuweather::entity::sensor::air_quality::state::unhealthy%]"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -100,6 +106,15 @@
|
|||||||
"steady": "Steady",
|
"steady": "Steady",
|
||||||
"rising": "Rising",
|
"rising": "Rising",
|
||||||
"falling": "Falling"
|
"falling": "Falling"
|
||||||
|
},
|
||||||
|
"state_attributes": {
|
||||||
|
"options": {
|
||||||
|
"state": {
|
||||||
|
"falling": "[%key:component::accuweather::entity::sensor::pressure_tendency::state::falling%]",
|
||||||
|
"rising": "[%key:component::accuweather::entity::sensor::pressure_tendency::state::rising%]",
|
||||||
|
"steady": "[%key:component::accuweather::entity::sensor::pressure_tendency::state::steady%]"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ragweed_pollen": {
|
"ragweed_pollen": {
|
||||||
@ -108,12 +123,10 @@
|
|||||||
"level": {
|
"level": {
|
||||||
"name": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::name%]",
|
"name": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::name%]",
|
||||||
"state": {
|
"state": {
|
||||||
"good": "[%key:component::accuweather::entity::sensor::air_quality::state::good%]",
|
"high": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::high%]",
|
||||||
"hazardous": "[%key:component::accuweather::entity::sensor::air_quality::state::hazardous%]",
|
"low": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::low%]",
|
||||||
"high": "[%key:component::accuweather::entity::sensor::air_quality::state::high%]",
|
"moderate": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::moderate%]",
|
||||||
"low": "[%key:component::accuweather::entity::sensor::air_quality::state::low%]",
|
"very_high": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::very_high%]"
|
||||||
"moderate": "[%key:component::accuweather::entity::sensor::air_quality::state::moderate%]",
|
|
||||||
"unhealthy": "[%key:component::accuweather::entity::sensor::air_quality::state::unhealthy%]"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -154,12 +167,10 @@
|
|||||||
"level": {
|
"level": {
|
||||||
"name": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::name%]",
|
"name": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::name%]",
|
||||||
"state": {
|
"state": {
|
||||||
"good": "[%key:component::accuweather::entity::sensor::air_quality::state::good%]",
|
"high": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::high%]",
|
||||||
"hazardous": "[%key:component::accuweather::entity::sensor::air_quality::state::hazardous%]",
|
"low": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::low%]",
|
||||||
"high": "[%key:component::accuweather::entity::sensor::air_quality::state::high%]",
|
"moderate": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::moderate%]",
|
||||||
"low": "[%key:component::accuweather::entity::sensor::air_quality::state::low%]",
|
"very_high": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::very_high%]"
|
||||||
"moderate": "[%key:component::accuweather::entity::sensor::air_quality::state::moderate%]",
|
|
||||||
"unhealthy": "[%key:component::accuweather::entity::sensor::air_quality::state::unhealthy%]"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -170,12 +181,10 @@
|
|||||||
"level": {
|
"level": {
|
||||||
"name": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::name%]",
|
"name": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::name%]",
|
||||||
"state": {
|
"state": {
|
||||||
"good": "[%key:component::accuweather::entity::sensor::air_quality::state::good%]",
|
"high": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::high%]",
|
||||||
"hazardous": "[%key:component::accuweather::entity::sensor::air_quality::state::hazardous%]",
|
"low": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::low%]",
|
||||||
"high": "[%key:component::accuweather::entity::sensor::air_quality::state::high%]",
|
"moderate": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::moderate%]",
|
||||||
"low": "[%key:component::accuweather::entity::sensor::air_quality::state::low%]",
|
"very_high": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::very_high%]"
|
||||||
"moderate": "[%key:component::accuweather::entity::sensor::air_quality::state::moderate%]",
|
|
||||||
"unhealthy": "[%key:component::accuweather::entity::sensor::air_quality::state::unhealthy%]"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -186,12 +195,10 @@
|
|||||||
"level": {
|
"level": {
|
||||||
"name": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::name%]",
|
"name": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::name%]",
|
||||||
"state": {
|
"state": {
|
||||||
"good": "[%key:component::accuweather::entity::sensor::air_quality::state::good%]",
|
"high": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::high%]",
|
||||||
"hazardous": "[%key:component::accuweather::entity::sensor::air_quality::state::hazardous%]",
|
"low": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::low%]",
|
||||||
"high": "[%key:component::accuweather::entity::sensor::air_quality::state::high%]",
|
"moderate": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::moderate%]",
|
||||||
"low": "[%key:component::accuweather::entity::sensor::air_quality::state::low%]",
|
"very_high": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::very_high%]"
|
||||||
"moderate": "[%key:component::accuweather::entity::sensor::air_quality::state::moderate%]",
|
|
||||||
"unhealthy": "[%key:component::accuweather::entity::sensor::air_quality::state::unhealthy%]"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -222,6 +229,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"exceptions": {
|
||||||
|
"current_conditions_update_error": {
|
||||||
|
"message": "An error occurred while retrieving weather current conditions data from the AccuWeather API: {error}"
|
||||||
|
},
|
||||||
|
"forecast_update_error": {
|
||||||
|
"message": "An error occurred while retrieving weather forecast data from the AccuWeather API: {error}"
|
||||||
|
}
|
||||||
|
},
|
||||||
"system_health": {
|
"system_health": {
|
||||||
"info": {
|
"info": {
|
||||||
"can_reach_server": "Reach AccuWeather server",
|
"can_reach_server": "Reach AccuWeather server",
|
||||||
|
@ -30,7 +30,7 @@ from homeassistant.const import (
|
|||||||
UnitOfTemperature,
|
UnitOfTemperature,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
from homeassistant.util.dt import utc_from_timestamp
|
from homeassistant.util.dt import utc_from_timestamp
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
@ -54,7 +54,7 @@ PARALLEL_UPDATES = 1
|
|||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry: AccuWeatherConfigEntry,
|
entry: AccuWeatherConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Add a AccuWeather weather entity from a config_entry."""
|
"""Add a AccuWeather weather entity from a config_entry."""
|
||||||
async_add_entities([AccuWeatherEntity(entry.runtime_data)])
|
async_add_entities([AccuWeatherEntity(entry.runtime_data)])
|
||||||
|
@ -11,7 +11,7 @@ from homeassistant.components.cover import (
|
|||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
from . import AcmedaConfigEntry
|
from . import AcmedaConfigEntry
|
||||||
from .const import ACMEDA_HUB_UPDATE
|
from .const import ACMEDA_HUB_UPDATE
|
||||||
@ -22,7 +22,7 @@ from .helpers import async_add_acmeda_entities
|
|||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config_entry: AcmedaConfigEntry,
|
config_entry: AcmedaConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up the Acmeda Rollers from a config entry."""
|
"""Set up the Acmeda Rollers from a config entry."""
|
||||||
hub = config_entry.runtime_data
|
hub = config_entry.runtime_data
|
||||||
|
@ -9,7 +9,7 @@ from aiopulse import Roller
|
|||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers import device_registry as dr
|
from homeassistant.helpers import device_registry as dr
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
from .const import DOMAIN, LOGGER
|
from .const import DOMAIN, LOGGER
|
||||||
|
|
||||||
@ -23,7 +23,7 @@ def async_add_acmeda_entities(
|
|||||||
entity_class: type,
|
entity_class: type,
|
||||||
config_entry: AcmedaConfigEntry,
|
config_entry: AcmedaConfigEntry,
|
||||||
current: set[int],
|
current: set[int],
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Add any new entities."""
|
"""Add any new entities."""
|
||||||
hub = config_entry.runtime_data
|
hub = config_entry.runtime_data
|
||||||
|
@ -6,7 +6,7 @@ from homeassistant.components.sensor import SensorDeviceClass, SensorEntity
|
|||||||
from homeassistant.const import PERCENTAGE
|
from homeassistant.const import PERCENTAGE
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
from . import AcmedaConfigEntry
|
from . import AcmedaConfigEntry
|
||||||
from .const import ACMEDA_HUB_UPDATE
|
from .const import ACMEDA_HUB_UPDATE
|
||||||
@ -17,7 +17,7 @@ from .helpers import async_add_acmeda_entities
|
|||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config_entry: AcmedaConfigEntry,
|
config_entry: AcmedaConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up the Acmeda Rollers from a config entry."""
|
"""Set up the Acmeda Rollers from a config entry."""
|
||||||
hub = config_entry.runtime_data
|
hub = config_entry.runtime_data
|
||||||
|
@ -25,7 +25,7 @@ from homeassistant.const import (
|
|||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
from homeassistant.helpers.device_registry import DeviceInfo
|
from homeassistant.helpers.device_registry import DeviceInfo
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
from .const import ACCOUNT_ID, CONNECTION_TYPE, DOMAIN, LOCAL
|
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(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry: ConfigEntry,
|
entry: ConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up the Adax thermostat with config flow."""
|
"""Set up the Adax thermostat with config flow."""
|
||||||
if entry.data.get(CONNECTION_TYPE) == LOCAL:
|
if entry.data.get(CONNECTION_TYPE) == LOCAL:
|
||||||
|
@ -5,14 +5,14 @@
|
|||||||
"data": {
|
"data": {
|
||||||
"connection_type": "Select connection type"
|
"connection_type": "Select connection type"
|
||||||
},
|
},
|
||||||
"description": "Select connection type. Local requires heaters with bluetooth"
|
"description": "Select connection type. Local requires heaters with Bluetooth"
|
||||||
},
|
},
|
||||||
"local": {
|
"local": {
|
||||||
"data": {
|
"data": {
|
||||||
"wifi_ssid": "Wi-Fi SSID",
|
"wifi_ssid": "Wi-Fi SSID",
|
||||||
"wifi_pswd": "Wi-Fi Password"
|
"wifi_pswd": "Wi-Fi password"
|
||||||
},
|
},
|
||||||
"description": "Reset the heater by pressing + and OK until display shows 'Reset'. Then press and hold OK button on the heater until the blue led starts blinking before pressing Submit. Configuring heater might take some minutes."
|
"description": "Reset the heater by pressing + and OK until display shows 'Reset'. Then press and hold OK button on the heater until the blue LED starts blinking before pressing Submit. Configuring heater might take some minutes."
|
||||||
},
|
},
|
||||||
"cloud": {
|
"cloud": {
|
||||||
"data": {
|
"data": {
|
||||||
|
@ -7,7 +7,7 @@ from dataclasses import dataclass
|
|||||||
from adguardhome import AdGuardHome, AdGuardHomeConnectionError
|
from adguardhome import AdGuardHome, AdGuardHomeConnectionError
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry, ConfigEntryState
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_HOST,
|
CONF_HOST,
|
||||||
CONF_NAME,
|
CONF_NAME,
|
||||||
@ -123,12 +123,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: AdGuardConfigEntry) -> b
|
|||||||
async def async_unload_entry(hass: HomeAssistant, entry: AdGuardConfigEntry) -> bool:
|
async def async_unload_entry(hass: HomeAssistant, entry: AdGuardConfigEntry) -> bool:
|
||||||
"""Unload AdGuard Home config entry."""
|
"""Unload AdGuard Home config entry."""
|
||||||
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||||
loaded_entries = [
|
if not hass.config_entries.async_loaded_entries(DOMAIN):
|
||||||
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
|
# 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_ADD_URL)
|
||||||
hass.services.async_remove(DOMAIN, SERVICE_REMOVE_URL)
|
hass.services.async_remove(DOMAIN, SERVICE_REMOVE_URL)
|
||||||
|
@ -12,7 +12,7 @@ from adguardhome import AdGuardHome
|
|||||||
from homeassistant.components.sensor import SensorEntity, SensorEntityDescription
|
from homeassistant.components.sensor import SensorEntity, SensorEntityDescription
|
||||||
from homeassistant.const import PERCENTAGE, UnitOfTime
|
from homeassistant.const import PERCENTAGE, UnitOfTime
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
from . import AdGuardConfigEntry, AdGuardData
|
from . import AdGuardConfigEntry, AdGuardData
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
@ -85,7 +85,7 @@ SENSORS: tuple[AdGuardHomeEntityDescription, ...] = (
|
|||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry: AdGuardConfigEntry,
|
entry: AdGuardConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up AdGuard Home sensor based on a config entry."""
|
"""Set up AdGuard Home sensor based on a config entry."""
|
||||||
data = entry.runtime_data
|
data = entry.runtime_data
|
||||||
|
@ -79,7 +79,7 @@
|
|||||||
"services": {
|
"services": {
|
||||||
"add_url": {
|
"add_url": {
|
||||||
"name": "Add URL",
|
"name": "Add URL",
|
||||||
"description": "Add a new filter subscription to AdGuard Home.",
|
"description": "Adds a new filter subscription to AdGuard Home.",
|
||||||
"fields": {
|
"fields": {
|
||||||
"name": {
|
"name": {
|
||||||
"name": "[%key:common::config_flow::data::name%]",
|
"name": "[%key:common::config_flow::data::name%]",
|
||||||
@ -123,11 +123,11 @@
|
|||||||
},
|
},
|
||||||
"refresh": {
|
"refresh": {
|
||||||
"name": "Refresh",
|
"name": "Refresh",
|
||||||
"description": "Refresh all filter subscriptions in AdGuard Home.",
|
"description": "Refreshes all filter subscriptions in AdGuard Home.",
|
||||||
"fields": {
|
"fields": {
|
||||||
"force": {
|
"force": {
|
||||||
"name": "Force",
|
"name": "Force",
|
||||||
"description": "Force update (bypasses AdGuard Home throttling). \"true\" to force, or \"false\" to omit for a regular refresh."
|
"description": "Force update (bypasses AdGuard Home throttling), omit for a regular refresh."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ from adguardhome import AdGuardHome, AdGuardHomeError
|
|||||||
|
|
||||||
from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
|
from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
from . import AdGuardConfigEntry, AdGuardData
|
from . import AdGuardConfigEntry, AdGuardData
|
||||||
from .const import DOMAIN, LOGGER
|
from .const import DOMAIN, LOGGER
|
||||||
@ -79,7 +79,7 @@ SWITCHES: tuple[AdGuardHomeSwitchEntityDescription, ...] = (
|
|||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry: AdGuardConfigEntry,
|
entry: AdGuardConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up AdGuard Home switch based on a config entry."""
|
"""Set up AdGuard Home switch based on a config entry."""
|
||||||
data = entry.runtime_data
|
data = entry.runtime_data
|
||||||
|
@ -8,7 +8,7 @@ from homeassistant.components.binary_sensor import (
|
|||||||
)
|
)
|
||||||
from homeassistant.const import EntityCategory
|
from homeassistant.const import EntityCategory
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
from . import AdvantageAirDataConfigEntry
|
from . import AdvantageAirDataConfigEntry
|
||||||
from .entity import AdvantageAirAcEntity, AdvantageAirZoneEntity
|
from .entity import AdvantageAirAcEntity, AdvantageAirZoneEntity
|
||||||
@ -20,7 +20,7 @@ PARALLEL_UPDATES = 0
|
|||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config_entry: AdvantageAirDataConfigEntry,
|
config_entry: AdvantageAirDataConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up AdvantageAir Binary Sensor platform."""
|
"""Set up AdvantageAir Binary Sensor platform."""
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from decimal import Decimal
|
||||||
import logging
|
import logging
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
@ -14,12 +15,13 @@ from homeassistant.components.climate import (
|
|||||||
FAN_MEDIUM,
|
FAN_MEDIUM,
|
||||||
ClimateEntity,
|
ClimateEntity,
|
||||||
ClimateEntityFeature,
|
ClimateEntityFeature,
|
||||||
|
HVACAction,
|
||||||
HVACMode,
|
HVACMode,
|
||||||
)
|
)
|
||||||
from homeassistant.const import ATTR_TEMPERATURE, PRECISION_WHOLE, UnitOfTemperature
|
from homeassistant.const import ATTR_TEMPERATURE, PRECISION_WHOLE, UnitOfTemperature
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.exceptions import ServiceValidationError
|
from homeassistant.exceptions import ServiceValidationError
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
from . import AdvantageAirDataConfigEntry
|
from . import AdvantageAirDataConfigEntry
|
||||||
from .const import (
|
from .const import (
|
||||||
@ -49,6 +51,14 @@ ADVANTAGE_AIR_MYTEMP_ENABLED = "climateControlModeEnabled"
|
|||||||
ADVANTAGE_AIR_HEAT_TARGET = "myAutoHeatTargetTemp"
|
ADVANTAGE_AIR_HEAT_TARGET = "myAutoHeatTargetTemp"
|
||||||
ADVANTAGE_AIR_COOL_TARGET = "myAutoCoolTargetTemp"
|
ADVANTAGE_AIR_COOL_TARGET = "myAutoCoolTargetTemp"
|
||||||
ADVANTAGE_AIR_MYFAN = "autoAA"
|
ADVANTAGE_AIR_MYFAN = "autoAA"
|
||||||
|
ADVANTAGE_AIR_MYAUTO_MODE_SET = "myAutoModeCurrentSetMode"
|
||||||
|
|
||||||
|
HVAC_ACTIONS = {
|
||||||
|
"cool": HVACAction.COOLING,
|
||||||
|
"heat": HVACAction.HEATING,
|
||||||
|
"vent": HVACAction.FAN,
|
||||||
|
"dry": HVACAction.DRYING,
|
||||||
|
}
|
||||||
|
|
||||||
HVAC_MODES = [
|
HVAC_MODES = [
|
||||||
HVACMode.OFF,
|
HVACMode.OFF,
|
||||||
@ -76,7 +86,7 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config_entry: AdvantageAirDataConfigEntry,
|
config_entry: AdvantageAirDataConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up AdvantageAir climate platform."""
|
"""Set up AdvantageAir climate platform."""
|
||||||
|
|
||||||
@ -175,6 +185,17 @@ class AdvantageAirAC(AdvantageAirAcEntity, ClimateEntity):
|
|||||||
return ADVANTAGE_AIR_HVAC_MODES.get(self._ac["mode"])
|
return ADVANTAGE_AIR_HVAC_MODES.get(self._ac["mode"])
|
||||||
return HVACMode.OFF
|
return HVACMode.OFF
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hvac_action(self) -> HVACAction | None:
|
||||||
|
"""Return the current running HVAC action."""
|
||||||
|
if self._ac["state"] == ADVANTAGE_AIR_STATE_OFF:
|
||||||
|
return HVACAction.OFF
|
||||||
|
if self._ac["mode"] == "myauto":
|
||||||
|
return HVAC_ACTIONS.get(
|
||||||
|
self._ac.get(ADVANTAGE_AIR_MYAUTO_MODE_SET, HVACAction.OFF)
|
||||||
|
)
|
||||||
|
return HVAC_ACTIONS.get(self._ac["mode"])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def fan_mode(self) -> str | None:
|
def fan_mode(self) -> str | None:
|
||||||
"""Return the current fan modes."""
|
"""Return the current fan modes."""
|
||||||
@ -273,6 +294,22 @@ class AdvantageAirZone(AdvantageAirZoneEntity, ClimateEntity):
|
|||||||
return HVACMode.HEAT_COOL
|
return HVACMode.HEAT_COOL
|
||||||
return HVACMode.OFF
|
return HVACMode.OFF
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hvac_action(self) -> HVACAction | None:
|
||||||
|
"""Return the HVAC action, inheriting from master AC if zone is open but idle if air is <= 5%."""
|
||||||
|
if self._ac["state"] == ADVANTAGE_AIR_STATE_OFF:
|
||||||
|
return HVACAction.OFF
|
||||||
|
master_action = HVAC_ACTIONS.get(self._ac["mode"], HVACAction.OFF)
|
||||||
|
if self._ac["mode"] == "myauto":
|
||||||
|
master_action = HVAC_ACTIONS.get(
|
||||||
|
str(self._ac.get(ADVANTAGE_AIR_MYAUTO_MODE_SET)), HVACAction.OFF
|
||||||
|
)
|
||||||
|
if self._zone["state"] == ADVANTAGE_AIR_STATE_OPEN:
|
||||||
|
if self._zone["value"] <= Decimal(5):
|
||||||
|
return HVACAction.IDLE
|
||||||
|
return master_action
|
||||||
|
return HVACAction.OFF
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_temperature(self) -> float | None:
|
def current_temperature(self) -> float | None:
|
||||||
"""Return the current temperature."""
|
"""Return the current temperature."""
|
||||||
|
@ -7,3 +7,4 @@ ADVANTAGE_AIR_STATE_CLOSE = "close"
|
|||||||
ADVANTAGE_AIR_STATE_ON = "on"
|
ADVANTAGE_AIR_STATE_ON = "on"
|
||||||
ADVANTAGE_AIR_STATE_OFF = "off"
|
ADVANTAGE_AIR_STATE_OFF = "off"
|
||||||
ADVANTAGE_AIR_AUTOFAN_ENABLED = "aaAutoFanModeEnabled"
|
ADVANTAGE_AIR_AUTOFAN_ENABLED = "aaAutoFanModeEnabled"
|
||||||
|
ADVANTAGE_AIR_NIGHT_MODE_ENABLED = "quietNightModeEnabled"
|
||||||
|
@ -9,7 +9,7 @@ from homeassistant.components.cover import (
|
|||||||
CoverEntityFeature,
|
CoverEntityFeature,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
from . import AdvantageAirDataConfigEntry
|
from . import AdvantageAirDataConfigEntry
|
||||||
from .const import ADVANTAGE_AIR_STATE_CLOSE, ADVANTAGE_AIR_STATE_OPEN
|
from .const import ADVANTAGE_AIR_STATE_CLOSE, ADVANTAGE_AIR_STATE_OPEN
|
||||||
@ -22,7 +22,7 @@ PARALLEL_UPDATES = 0
|
|||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config_entry: AdvantageAirDataConfigEntry,
|
config_entry: AdvantageAirDataConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up AdvantageAir cover platform."""
|
"""Set up AdvantageAir cover platform."""
|
||||||
|
|
||||||
@ -41,7 +41,7 @@ async def async_setup_entry(
|
|||||||
entities.append(
|
entities.append(
|
||||||
AdvantageAirThingCover(instance, thing, CoverDeviceClass.BLIND)
|
AdvantageAirThingCover(instance, thing, CoverDeviceClass.BLIND)
|
||||||
)
|
)
|
||||||
elif thing["channelDipState"] == 3: # 3 = "Garage door"
|
elif thing["channelDipState"] in [3, 10]: # 3 & 10 = "Garage door"
|
||||||
entities.append(
|
entities.append(
|
||||||
AdvantageAirThingCover(instance, thing, CoverDeviceClass.GARAGE)
|
AdvantageAirThingCover(instance, thing, CoverDeviceClass.GARAGE)
|
||||||
)
|
)
|
||||||
|
@ -5,7 +5,7 @@ from typing import Any
|
|||||||
from homeassistant.components.light import ATTR_BRIGHTNESS, ColorMode, LightEntity
|
from homeassistant.components.light import ATTR_BRIGHTNESS, ColorMode, LightEntity
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.device_registry import DeviceInfo
|
from homeassistant.helpers.device_registry import DeviceInfo
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
from . import AdvantageAirDataConfigEntry
|
from . import AdvantageAirDataConfigEntry
|
||||||
from .const import ADVANTAGE_AIR_STATE_ON, DOMAIN as ADVANTAGE_AIR_DOMAIN
|
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(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config_entry: AdvantageAirDataConfigEntry,
|
config_entry: AdvantageAirDataConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up AdvantageAir light platform."""
|
"""Set up AdvantageAir light platform."""
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
from homeassistant.components.select import SelectEntity
|
from homeassistant.components.select import SelectEntity
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
from . import AdvantageAirDataConfigEntry
|
from . import AdvantageAirDataConfigEntry
|
||||||
from .entity import AdvantageAirAcEntity
|
from .entity import AdvantageAirAcEntity
|
||||||
@ -14,7 +14,7 @@ ADVANTAGE_AIR_INACTIVE = "Inactive"
|
|||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config_entry: AdvantageAirDataConfigEntry,
|
config_entry: AdvantageAirDataConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up AdvantageAir select platform."""
|
"""Set up AdvantageAir select platform."""
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ from homeassistant.components.sensor import (
|
|||||||
from homeassistant.const import PERCENTAGE, EntityCategory, UnitOfTemperature
|
from homeassistant.const import PERCENTAGE, EntityCategory, UnitOfTemperature
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import config_validation as cv, entity_platform
|
from homeassistant.helpers import config_validation as cv, entity_platform
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
from . import AdvantageAirDataConfigEntry
|
from . import AdvantageAirDataConfigEntry
|
||||||
from .const import ADVANTAGE_AIR_STATE_OPEN
|
from .const import ADVANTAGE_AIR_STATE_OPEN
|
||||||
@ -32,7 +32,7 @@ PARALLEL_UPDATES = 0
|
|||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config_entry: AdvantageAirDataConfigEntry,
|
config_entry: AdvantageAirDataConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up AdvantageAir sensor platform."""
|
"""Set up AdvantageAir sensor platform."""
|
||||||
|
|
||||||
|
@ -4,11 +4,12 @@ from typing import Any
|
|||||||
|
|
||||||
from homeassistant.components.switch import SwitchDeviceClass, SwitchEntity
|
from homeassistant.components.switch import SwitchDeviceClass, SwitchEntity
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
from . import AdvantageAirDataConfigEntry
|
from . import AdvantageAirDataConfigEntry
|
||||||
from .const import (
|
from .const import (
|
||||||
ADVANTAGE_AIR_AUTOFAN_ENABLED,
|
ADVANTAGE_AIR_AUTOFAN_ENABLED,
|
||||||
|
ADVANTAGE_AIR_NIGHT_MODE_ENABLED,
|
||||||
ADVANTAGE_AIR_STATE_OFF,
|
ADVANTAGE_AIR_STATE_OFF,
|
||||||
ADVANTAGE_AIR_STATE_ON,
|
ADVANTAGE_AIR_STATE_ON,
|
||||||
)
|
)
|
||||||
@ -19,7 +20,7 @@ from .models import AdvantageAirData
|
|||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config_entry: AdvantageAirDataConfigEntry,
|
config_entry: AdvantageAirDataConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up AdvantageAir switch platform."""
|
"""Set up AdvantageAir switch platform."""
|
||||||
|
|
||||||
@ -32,6 +33,8 @@ async def async_setup_entry(
|
|||||||
entities.append(AdvantageAirFreshAir(instance, ac_key))
|
entities.append(AdvantageAirFreshAir(instance, ac_key))
|
||||||
if ADVANTAGE_AIR_AUTOFAN_ENABLED in ac_device["info"]:
|
if ADVANTAGE_AIR_AUTOFAN_ENABLED in ac_device["info"]:
|
||||||
entities.append(AdvantageAirMyFan(instance, ac_key))
|
entities.append(AdvantageAirMyFan(instance, ac_key))
|
||||||
|
if ADVANTAGE_AIR_NIGHT_MODE_ENABLED in ac_device["info"]:
|
||||||
|
entities.append(AdvantageAirNightMode(instance, ac_key))
|
||||||
if things := instance.coordinator.data.get("myThings"):
|
if things := instance.coordinator.data.get("myThings"):
|
||||||
entities.extend(
|
entities.extend(
|
||||||
AdvantageAirRelay(instance, thing)
|
AdvantageAirRelay(instance, thing)
|
||||||
@ -93,6 +96,32 @@ class AdvantageAirMyFan(AdvantageAirAcEntity, SwitchEntity):
|
|||||||
await self.async_update_ac({ADVANTAGE_AIR_AUTOFAN_ENABLED: False})
|
await self.async_update_ac({ADVANTAGE_AIR_AUTOFAN_ENABLED: False})
|
||||||
|
|
||||||
|
|
||||||
|
class AdvantageAirNightMode(AdvantageAirAcEntity, SwitchEntity):
|
||||||
|
"""Representation of Advantage 'MySleep$aver' Mode control."""
|
||||||
|
|
||||||
|
_attr_icon = "mdi:weather-night"
|
||||||
|
_attr_name = "MySleep$aver"
|
||||||
|
_attr_device_class = SwitchDeviceClass.SWITCH
|
||||||
|
|
||||||
|
def __init__(self, instance: AdvantageAirData, ac_key: str) -> None:
|
||||||
|
"""Initialize an Advantage Air Night Mode control."""
|
||||||
|
super().__init__(instance, ac_key)
|
||||||
|
self._attr_unique_id += "-nightmode"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_on(self) -> bool:
|
||||||
|
"""Return the Night Mode status."""
|
||||||
|
return self._ac[ADVANTAGE_AIR_NIGHT_MODE_ENABLED]
|
||||||
|
|
||||||
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||||
|
"""Turn Night Mode on."""
|
||||||
|
await self.async_update_ac({ADVANTAGE_AIR_NIGHT_MODE_ENABLED: True})
|
||||||
|
|
||||||
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||||
|
"""Turn Night Mode off."""
|
||||||
|
await self.async_update_ac({ADVANTAGE_AIR_NIGHT_MODE_ENABLED: False})
|
||||||
|
|
||||||
|
|
||||||
class AdvantageAirRelay(AdvantageAirThingEntity, SwitchEntity):
|
class AdvantageAirRelay(AdvantageAirThingEntity, SwitchEntity):
|
||||||
"""Representation of Advantage Air Thing."""
|
"""Representation of Advantage Air Thing."""
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
from homeassistant.components.update import UpdateEntity
|
from homeassistant.components.update import UpdateEntity
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.device_registry import DeviceInfo
|
from homeassistant.helpers.device_registry import DeviceInfo
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
from . import AdvantageAirDataConfigEntry
|
from . import AdvantageAirDataConfigEntry
|
||||||
from .const import DOMAIN as ADVANTAGE_AIR_DOMAIN
|
from .const import DOMAIN as ADVANTAGE_AIR_DOMAIN
|
||||||
@ -14,7 +14,7 @@ from .models import AdvantageAirData
|
|||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config_entry: AdvantageAirDataConfigEntry,
|
config_entry: AdvantageAirDataConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up AdvantageAir update platform."""
|
"""Set up AdvantageAir update platform."""
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ from aemet_opendata.helpers import dict_nested_value
|
|||||||
|
|
||||||
from homeassistant.components.image import Image, ImageEntity, ImageEntityDescription
|
from homeassistant.components.image import Image, ImageEntity, ImageEntityDescription
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
from .coordinator import AemetConfigEntry, WeatherUpdateCoordinator
|
from .coordinator import AemetConfigEntry, WeatherUpdateCoordinator
|
||||||
from .entity import AemetEntity
|
from .entity import AemetEntity
|
||||||
@ -25,7 +25,7 @@ AEMET_IMAGES: Final[tuple[ImageEntityDescription, ...]] = (
|
|||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config_entry: AemetConfigEntry,
|
config_entry: AemetConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up AEMET OpenData image entities based on a config entry."""
|
"""Set up AEMET OpenData image entities based on a config entry."""
|
||||||
domain_data = config_entry.runtime_data
|
domain_data = config_entry.runtime_data
|
||||||
|
@ -52,7 +52,7 @@ from homeassistant.const import (
|
|||||||
UnitOfVolumetricFlux,
|
UnitOfVolumetricFlux,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt as dt_util
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
@ -358,7 +358,7 @@ WEATHER_SENSORS: Final[tuple[AemetSensorEntityDescription, ...]] = (
|
|||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config_entry: AemetConfigEntry,
|
config_entry: AemetConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up AEMET OpenData sensor entities based on a config entry."""
|
"""Set up AEMET OpenData sensor entities based on a config entry."""
|
||||||
domain_data = config_entry.runtime_data
|
domain_data = config_entry.runtime_data
|
||||||
|
@ -25,7 +25,7 @@ from homeassistant.const import (
|
|||||||
UnitOfTemperature,
|
UnitOfTemperature,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
from .const import CONDITIONS_MAP
|
from .const import CONDITIONS_MAP
|
||||||
from .coordinator import AemetConfigEntry, WeatherUpdateCoordinator
|
from .coordinator import AemetConfigEntry, WeatherUpdateCoordinator
|
||||||
@ -35,7 +35,7 @@ from .entity import AemetEntity
|
|||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config_entry: AemetConfigEntry,
|
config_entry: AemetConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up AEMET OpenData weather entity based on a config entry."""
|
"""Set up AEMET OpenData weather entity based on a config entry."""
|
||||||
domain_data = config_entry.runtime_data
|
domain_data = config_entry.runtime_data
|
||||||
|
@ -14,7 +14,7 @@ from homeassistant.helpers.dispatcher import (
|
|||||||
async_dispatcher_connect,
|
async_dispatcher_connect,
|
||||||
async_dispatcher_send,
|
async_dispatcher_send,
|
||||||
)
|
)
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
from homeassistant.util import Throttle
|
from homeassistant.util import Throttle
|
||||||
|
|
||||||
from . import AfterShipConfigEntry
|
from . import AfterShipConfigEntry
|
||||||
@ -42,7 +42,7 @@ PLATFORM_SCHEMA: Final = cv.removed(DOMAIN, raise_if_present=False)
|
|||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config_entry: AfterShipConfigEntry,
|
config_entry: AfterShipConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up AfterShip sensor entities based on a config entry."""
|
"""Set up AfterShip sensor entities based on a config entry."""
|
||||||
aftership = config_entry.runtime_data
|
aftership = config_entry.runtime_data
|
||||||
|
@ -51,7 +51,7 @@
|
|||||||
"issues": {
|
"issues": {
|
||||||
"deprecated_yaml_import_issue_cannot_connect": {
|
"deprecated_yaml_import_issue_cannot_connect": {
|
||||||
"title": "The {integration_title} YAML configuration import failed",
|
"title": "The {integration_title} YAML configuration import failed",
|
||||||
"description": "Configuring {integration_title} using YAML is being removed but there was an connection error importing your YAML configuration.\n\nEnsure connection to {integration_title} works and restart Home Assistant to try again or remove the {integration_title} YAML configuration from your configuration.yaml file and continue to [set up the integration]({url}) manually."
|
"description": "Configuring {integration_title} using YAML is being removed but there was a connection error importing your YAML configuration.\n\nEnsure connection to {integration_title} works and restart Home Assistant to try again or remove the {integration_title} YAML configuration from your configuration.yaml file and continue to [set up the integration]({url}) manually."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ from homeassistant.components.alarm_control_panel import (
|
|||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.device_registry import DeviceInfo
|
from homeassistant.helpers.device_registry import DeviceInfo
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
from . import AgentDVRConfigEntry
|
from . import AgentDVRConfigEntry
|
||||||
from .const import DOMAIN as AGENT_DOMAIN
|
from .const import DOMAIN as AGENT_DOMAIN
|
||||||
@ -24,7 +24,7 @@ CONST_ALARM_CONTROL_PANEL_NAME = "Alarm Panel"
|
|||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config_entry: AgentDVRConfigEntry,
|
config_entry: AgentDVRConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up the Agent DVR Alarm Control Panels."""
|
"""Set up the Agent DVR Alarm Control Panels."""
|
||||||
async_add_entities([AgentBaseStation(config_entry.runtime_data)])
|
async_add_entities([AgentBaseStation(config_entry.runtime_data)])
|
||||||
|
@ -10,7 +10,7 @@ from homeassistant.components.mjpeg import MjpegCamera, filter_urllib3_logging
|
|||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.device_registry import DeviceInfo
|
from homeassistant.helpers.device_registry import DeviceInfo
|
||||||
from homeassistant.helpers.entity_platform import (
|
from homeassistant.helpers.entity_platform import (
|
||||||
AddEntitiesCallback,
|
AddConfigEntryEntitiesCallback,
|
||||||
async_get_current_platform,
|
async_get_current_platform,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -39,7 +39,7 @@ CAMERA_SERVICES = {
|
|||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config_entry: AgentDVRConfigEntry,
|
config_entry: AgentDVRConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up the Agent cameras."""
|
"""Set up the Agent cameras."""
|
||||||
filter_urllib3_logging()
|
filter_urllib3_logging()
|
||||||
|
@ -13,7 +13,7 @@ from homeassistant.components.button import (
|
|||||||
from homeassistant.const import EntityCategory
|
from homeassistant.const import EntityCategory
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers import entity_registry as er
|
from homeassistant.helpers import entity_registry as er
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
from . import AirGradientConfigEntry
|
from . import AirGradientConfigEntry
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
@ -47,7 +47,7 @@ LED_BAR_TEST = AirGradientButtonEntityDescription(
|
|||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry: AirGradientConfigEntry,
|
entry: AirGradientConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up AirGradient button entities based on a config entry."""
|
"""Set up AirGradient button entities based on a config entry."""
|
||||||
coordinator = entry.runtime_data
|
coordinator = entry.runtime_data
|
||||||
|
@ -6,6 +6,6 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/airgradient",
|
"documentation": "https://www.home-assistant.io/integrations/airgradient",
|
||||||
"integration_type": "device",
|
"integration_type": "device",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"requirements": ["airgradient==0.9.1"],
|
"requirements": ["airgradient==0.9.2"],
|
||||||
"zeroconf": ["_airgradient._tcp.local."]
|
"zeroconf": ["_airgradient._tcp.local."]
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ from homeassistant.components.number import (
|
|||||||
from homeassistant.const import PERCENTAGE, EntityCategory
|
from homeassistant.const import PERCENTAGE, EntityCategory
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers import entity_registry as er
|
from homeassistant.helpers import entity_registry as er
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
from . import AirGradientConfigEntry
|
from . import AirGradientConfigEntry
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
@ -60,7 +60,7 @@ LED_BAR_BRIGHTNESS = AirGradientNumberEntityDescription(
|
|||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry: AirGradientConfigEntry,
|
entry: AirGradientConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up AirGradient number entities based on a config entry."""
|
"""Set up AirGradient number entities based on a config entry."""
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ from homeassistant.components.select import (
|
|||||||
from homeassistant.const import EntityCategory
|
from homeassistant.const import EntityCategory
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers import entity_registry as er
|
from homeassistant.helpers import entity_registry as er
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
from . import AirGradientConfigEntry
|
from . import AirGradientConfigEntry
|
||||||
from .const import DOMAIN, PM_STANDARD, PM_STANDARD_REVERSE
|
from .const import DOMAIN, PM_STANDARD, PM_STANDARD_REVERSE
|
||||||
@ -142,7 +142,7 @@ CONTROL_ENTITIES: tuple[AirGradientSelectEntityDescription, ...] = (
|
|||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry: AirGradientConfigEntry,
|
entry: AirGradientConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up AirGradient select entities based on a config entry."""
|
"""Set up AirGradient select entities based on a config entry."""
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ from homeassistant.const import (
|
|||||||
UnitOfTime,
|
UnitOfTime,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
from homeassistant.helpers.typing import StateType
|
from homeassistant.helpers.typing import StateType
|
||||||
|
|
||||||
from . import AirGradientConfigEntry
|
from . import AirGradientConfigEntry
|
||||||
@ -225,7 +225,7 @@ CONFIG_DISPLAY_SENSOR_TYPES: tuple[AirGradientConfigSensorEntityDescription, ...
|
|||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry: AirGradientConfigEntry,
|
entry: AirGradientConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up AirGradient sensor entities based on a config entry."""
|
"""Set up AirGradient sensor entities based on a config entry."""
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ from homeassistant.components.switch import (
|
|||||||
from homeassistant.const import EntityCategory
|
from homeassistant.const import EntityCategory
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers import entity_registry as er
|
from homeassistant.helpers import entity_registry as er
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
from . import AirGradientConfigEntry
|
from . import AirGradientConfigEntry
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
@ -45,7 +45,7 @@ POST_DATA_TO_AIRGRADIENT = AirGradientSwitchEntityDescription(
|
|||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry: AirGradientConfigEntry,
|
entry: AirGradientConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up AirGradient switch entities based on a config entry."""
|
"""Set up AirGradient switch entities based on a config entry."""
|
||||||
coordinator = entry.runtime_data
|
coordinator = entry.runtime_data
|
||||||
|
@ -6,7 +6,7 @@ from propcache.api import cached_property
|
|||||||
|
|
||||||
from homeassistant.components.update import UpdateDeviceClass, UpdateEntity
|
from homeassistant.components.update import UpdateDeviceClass, UpdateEntity
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
from . import AirGradientConfigEntry, AirGradientCoordinator
|
from . import AirGradientConfigEntry, AirGradientCoordinator
|
||||||
from .entity import AirGradientEntity
|
from .entity import AirGradientEntity
|
||||||
@ -18,7 +18,7 @@ SCAN_INTERVAL = timedelta(hours=1)
|
|||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config_entry: AirGradientConfigEntry,
|
config_entry: AirGradientConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up Airgradient update platform."""
|
"""Set up Airgradient update platform."""
|
||||||
|
|
||||||
|
@ -105,7 +105,14 @@ class AirlyDataUpdateCoordinator(DataUpdateCoordinator[dict[str, str | float | i
|
|||||||
try:
|
try:
|
||||||
await measurements.update()
|
await measurements.update()
|
||||||
except (AirlyError, ClientConnectorError) as error:
|
except (AirlyError, ClientConnectorError) as error:
|
||||||
raise UpdateFailed(error) from error
|
raise UpdateFailed(
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="update_error",
|
||||||
|
translation_placeholders={
|
||||||
|
"entry": self.config_entry.title,
|
||||||
|
"error": repr(error),
|
||||||
|
},
|
||||||
|
) from error
|
||||||
|
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"Requests remaining: %s/%s",
|
"Requests remaining: %s/%s",
|
||||||
@ -126,7 +133,11 @@ class AirlyDataUpdateCoordinator(DataUpdateCoordinator[dict[str, str | float | i
|
|||||||
standards = measurements.current["standards"]
|
standards = measurements.current["standards"]
|
||||||
|
|
||||||
if index["description"] == NO_AIRLY_SENSORS:
|
if index["description"] == NO_AIRLY_SENSORS:
|
||||||
raise UpdateFailed("Can't retrieve data: no Airly sensors in this area")
|
raise UpdateFailed(
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="no_station",
|
||||||
|
translation_placeholders={"entry": self.config_entry.title},
|
||||||
|
)
|
||||||
for value in values:
|
for value in values:
|
||||||
data[value["name"]] = value["value"]
|
data[value["name"]] = value["value"]
|
||||||
for standard in standards:
|
for standard in standards:
|
||||||
|
@ -21,7 +21,7 @@ from homeassistant.const import (
|
|||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
@ -175,7 +175,7 @@ SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = (
|
|||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry: AirlyConfigEntry,
|
entry: AirlyConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up Airly sensor entities based on a config entry."""
|
"""Set up Airly sensor entities based on a config entry."""
|
||||||
name = entry.data[CONF_NAME]
|
name = entry.data[CONF_NAME]
|
||||||
|
@ -36,5 +36,13 @@
|
|||||||
"name": "[%key:component::sensor::entity_component::carbon_monoxide::name%]"
|
"name": "[%key:component::sensor::entity_component::carbon_monoxide::name%]"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"exceptions": {
|
||||||
|
"update_error": {
|
||||||
|
"message": "An error occurred while retrieving data from the Airly API for {entry}: {error}"
|
||||||
|
},
|
||||||
|
"no_station": {
|
||||||
|
"message": "An error occurred while retrieving data from the Airly API for {entry}: no measuring stations in this area"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ from homeassistant.const import (
|
|||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
from homeassistant.helpers.typing import StateType
|
from homeassistant.helpers.typing import StateType
|
||||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
|
|
||||||
@ -130,7 +130,7 @@ SENSOR_TYPES: tuple[AirNowEntityDescription, ...] = (
|
|||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config_entry: AirNowConfigEntry,
|
config_entry: AirNowConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up AirNow sensor entities based on a config entry."""
|
"""Set up AirNow sensor entities based on a config entry."""
|
||||||
coordinator = config_entry.runtime_data
|
coordinator = config_entry.runtime_data
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
"api_key": "[%key:common::config_flow::data::api_key%]",
|
"api_key": "[%key:common::config_flow::data::api_key%]",
|
||||||
"latitude": "[%key:common::config_flow::data::latitude%]",
|
"latitude": "[%key:common::config_flow::data::latitude%]",
|
||||||
"longitude": "[%key:common::config_flow::data::longitude%]",
|
"longitude": "[%key:common::config_flow::data::longitude%]",
|
||||||
"radius": "Station Radius (miles; optional)"
|
"radius": "Station radius (miles; optional)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -25,7 +25,7 @@
|
|||||||
"step": {
|
"step": {
|
||||||
"init": {
|
"init": {
|
||||||
"data": {
|
"data": {
|
||||||
"radius": "Station Radius (miles)"
|
"radius": "Station radius (miles)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -83,6 +83,7 @@ class AirQConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
await self.async_set_unique_id(device_info["id"])
|
await self.async_set_unique_id(device_info["id"])
|
||||||
self._abort_if_unique_id_configured()
|
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_create_entry(title=device_info["name"], data=user_input)
|
||||||
|
|
||||||
return self.async_show_form(
|
return self.async_show_form(
|
||||||
|
@ -5,7 +5,7 @@ from __future__ import annotations
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from aioairq import AirQ
|
from aioairq.core import AirQ, identify_warming_up_sensors
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD
|
from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD
|
||||||
@ -22,6 +22,8 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
class AirQCoordinator(DataUpdateCoordinator):
|
class AirQCoordinator(DataUpdateCoordinator):
|
||||||
"""Coordinator is responsible for querying the device at a specified route."""
|
"""Coordinator is responsible for querying the device at a specified route."""
|
||||||
|
|
||||||
|
config_entry: ConfigEntry
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
@ -33,6 +35,7 @@ class AirQCoordinator(DataUpdateCoordinator):
|
|||||||
super().__init__(
|
super().__init__(
|
||||||
hass,
|
hass,
|
||||||
_LOGGER,
|
_LOGGER,
|
||||||
|
config_entry=entry,
|
||||||
name=DOMAIN,
|
name=DOMAIN,
|
||||||
update_interval=timedelta(seconds=UPDATE_INTERVAL),
|
update_interval=timedelta(seconds=UPDATE_INTERVAL),
|
||||||
)
|
)
|
||||||
@ -52,6 +55,9 @@ class AirQCoordinator(DataUpdateCoordinator):
|
|||||||
async def _async_update_data(self) -> dict:
|
async def _async_update_data(self) -> dict:
|
||||||
"""Fetch the data from the device."""
|
"""Fetch the data from the device."""
|
||||||
if "name" not in self.device_info:
|
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()
|
info = await self.airq.fetch_device_info()
|
||||||
self.device_info.update(
|
self.device_info.update(
|
||||||
DeviceInfo(
|
DeviceInfo(
|
||||||
@ -61,7 +67,16 @@ class AirQCoordinator(DataUpdateCoordinator):
|
|||||||
hw_version=info["hw_version"],
|
hw_version=info["hw_version"],
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return await self.airq.get_latest_data( # type: ignore[no-any-return]
|
_LOGGER.debug(
|
||||||
|
"Updated AirQCoordinator.device_info for 'name' %s",
|
||||||
|
self.device_info.get("name"),
|
||||||
|
)
|
||||||
|
data: dict = await self.airq.get_latest_data(
|
||||||
return_average=self.return_average,
|
return_average=self.return_average,
|
||||||
clip_negative_values=self.clip_negative,
|
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
|
||||||
|
@ -24,7 +24,7 @@ from homeassistant.const import (
|
|||||||
UnitOfTemperature,
|
UnitOfTemperature,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
|
|
||||||
from . import AirQConfigEntry, AirQCoordinator
|
from . import AirQConfigEntry, AirQCoordinator
|
||||||
@ -399,7 +399,7 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
|
|||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry: AirQConfigEntry,
|
entry: AirQConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up sensor entities based on a config entry."""
|
"""Set up sensor entities based on a config entry."""
|
||||||
|
|
||||||
|
@ -91,7 +91,7 @@
|
|||||||
"name": "Hydrogen fluoride"
|
"name": "Hydrogen fluoride"
|
||||||
},
|
},
|
||||||
"health_index": {
|
"health_index": {
|
||||||
"name": "Health Index"
|
"name": "Health index"
|
||||||
},
|
},
|
||||||
"absolute_humidity": {
|
"absolute_humidity": {
|
||||||
"name": "Absolute humidity"
|
"name": "Absolute humidity"
|
||||||
@ -112,10 +112,10 @@
|
|||||||
"name": "Oxygen"
|
"name": "Oxygen"
|
||||||
},
|
},
|
||||||
"performance_index": {
|
"performance_index": {
|
||||||
"name": "Performance Index"
|
"name": "Performance index"
|
||||||
},
|
},
|
||||||
"hydrogen_phosphide": {
|
"hydrogen_phosphide": {
|
||||||
"name": "Hydrogen Phosphide"
|
"name": "Hydrogen phosphide"
|
||||||
},
|
},
|
||||||
"relative_pressure": {
|
"relative_pressure": {
|
||||||
"name": "Relative pressure"
|
"name": "Relative pressure"
|
||||||
@ -127,22 +127,22 @@
|
|||||||
"name": "Refrigerant"
|
"name": "Refrigerant"
|
||||||
},
|
},
|
||||||
"silicon_hydride": {
|
"silicon_hydride": {
|
||||||
"name": "Silicon Hydride"
|
"name": "Silicon hydride"
|
||||||
},
|
},
|
||||||
"noise": {
|
"noise": {
|
||||||
"name": "Noise"
|
"name": "Noise"
|
||||||
},
|
},
|
||||||
"maximum_noise": {
|
"maximum_noise": {
|
||||||
"name": "Noise (Maximum)"
|
"name": "Noise (maximum)"
|
||||||
},
|
},
|
||||||
"radon": {
|
"radon": {
|
||||||
"name": "Radon"
|
"name": "Radon"
|
||||||
},
|
},
|
||||||
"industrial_volatile_organic_compounds": {
|
"industrial_volatile_organic_compounds": {
|
||||||
"name": "VOCs (Industrial)"
|
"name": "VOCs (industrial)"
|
||||||
},
|
},
|
||||||
"virus_index": {
|
"virus_index": {
|
||||||
"name": "Virus Index"
|
"name": "Virus index"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ from homeassistant.const import (
|
|||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.device_registry import DeviceInfo
|
from homeassistant.helpers.device_registry import DeviceInfo
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
from homeassistant.helpers.typing import StateType
|
from homeassistant.helpers.typing import StateType
|
||||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
|
|
||||||
@ -114,7 +114,7 @@ SENSORS: dict[str, SensorEntityDescription] = {
|
|||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry: AirthingsConfigEntry,
|
entry: AirthingsConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up the Airthings sensor."""
|
"""Set up the Airthings sensor."""
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ from homeassistant.const import (
|
|||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||||
from homeassistant.helpers.device_registry import CONNECTION_BLUETOOTH, DeviceInfo
|
from homeassistant.helpers.device_registry import CONNECTION_BLUETOOTH, DeviceInfo
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
from homeassistant.helpers.entity_registry import (
|
from homeassistant.helpers.entity_registry import (
|
||||||
RegistryEntry,
|
RegistryEntry,
|
||||||
async_entries_for_device,
|
async_entries_for_device,
|
||||||
@ -153,7 +153,7 @@ def async_migrate(hass: HomeAssistant, address: str, sensor_name: str) -> None:
|
|||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry: AirthingsBLEConfigEntry,
|
entry: AirthingsBLEConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up the Airthings BLE sensors."""
|
"""Set up the Airthings BLE sensors."""
|
||||||
is_metric = hass.config.units is METRIC_SYSTEM
|
is_metric = hass.config.units is METRIC_SYSTEM
|
||||||
|
@ -19,7 +19,7 @@ from homeassistant.components.climate import (
|
|||||||
from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature
|
from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.device_registry import DeviceInfo
|
from homeassistant.helpers.device_registry import DeviceInfo
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
|
|
||||||
from . import AirTouch4ConfigEntry
|
from . import AirTouch4ConfigEntry
|
||||||
@ -64,7 +64,7 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config_entry: AirTouch4ConfigEntry,
|
config_entry: AirTouch4ConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up the Airtouch 4."""
|
"""Set up the Airtouch 4."""
|
||||||
coordinator = config_entry.runtime_data
|
coordinator = config_entry.runtime_data
|
||||||
|
@ -37,7 +37,7 @@ from homeassistant.components.climate import (
|
|||||||
from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature
|
from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.device_registry import DeviceInfo
|
from homeassistant.helpers.device_registry import DeviceInfo
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
from . import Airtouch5ConfigEntry
|
from . import Airtouch5ConfigEntry
|
||||||
from .const import DOMAIN, FAN_INTELLIGENT_AUTO, FAN_TURBO
|
from .const import DOMAIN, FAN_INTELLIGENT_AUTO, FAN_TURBO
|
||||||
@ -93,7 +93,7 @@ FAN_MODE_TO_SET_AC_FAN_SPEED = {
|
|||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config_entry: Airtouch5ConfigEntry,
|
config_entry: Airtouch5ConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up the Airtouch 5 Climate entities."""
|
"""Set up the Airtouch 5 Climate entities."""
|
||||||
client = config_entry.runtime_data
|
client = config_entry.runtime_data
|
||||||
|
@ -20,7 +20,7 @@ from homeassistant.components.cover import (
|
|||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.device_registry import DeviceInfo
|
from homeassistant.helpers.device_registry import DeviceInfo
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
from . import Airtouch5ConfigEntry
|
from . import Airtouch5ConfigEntry
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
@ -32,7 +32,7 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config_entry: Airtouch5ConfigEntry,
|
config_entry: Airtouch5ConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up the Airtouch 5 Cover entities."""
|
"""Set up the Airtouch 5 Cover entities."""
|
||||||
client = config_entry.runtime_data
|
client = config_entry.runtime_data
|
||||||
|
@ -23,7 +23,7 @@ from homeassistant.const import (
|
|||||||
CONF_STATE,
|
CONF_STATE,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||||
|
|
||||||
from . import AirVisualConfigEntry
|
from . import AirVisualConfigEntry
|
||||||
@ -108,7 +108,7 @@ POLLUTANT_UNITS = {
|
|||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry: AirVisualConfigEntry,
|
entry: AirVisualConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up AirVisual sensors based on a config entry."""
|
"""Set up AirVisual sensors based on a config entry."""
|
||||||
coordinator = entry.runtime_data
|
coordinator = entry.runtime_data
|
||||||
|
@ -20,7 +20,7 @@ from homeassistant.const import (
|
|||||||
UnitOfTemperature,
|
UnitOfTemperature,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
from . import AirVisualProConfigEntry
|
from . import AirVisualProConfigEntry
|
||||||
from .entity import AirVisualProEntity
|
from .entity import AirVisualProEntity
|
||||||
@ -130,7 +130,7 @@ def async_get_aqi_locale(settings: dict[str, Any]) -> str:
|
|||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry: AirVisualProConfigEntry,
|
entry: AirVisualProConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up AirVisual sensors based on a config entry."""
|
"""Set up AirVisual sensors based on a config entry."""
|
||||||
async_add_entities(
|
async_add_entities(
|
||||||
|
@ -23,7 +23,7 @@ from homeassistant.components.binary_sensor import (
|
|||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import EntityCategory
|
from homeassistant.const import EntityCategory
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
from .coordinator import AirzoneConfigEntry, AirzoneUpdateCoordinator
|
from .coordinator import AirzoneConfigEntry, AirzoneUpdateCoordinator
|
||||||
from .entity import AirzoneEntity, AirzoneSystemEntity, AirzoneZoneEntity
|
from .entity import AirzoneEntity, AirzoneSystemEntity, AirzoneZoneEntity
|
||||||
@ -76,7 +76,7 @@ ZONE_BINARY_SENSOR_TYPES: Final[tuple[AirzoneBinarySensorEntityDescription, ...]
|
|||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry: AirzoneConfigEntry,
|
entry: AirzoneConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Add Airzone binary sensors from a config_entry."""
|
"""Add Airzone binary sensors from a config_entry."""
|
||||||
coordinator = entry.runtime_data
|
coordinator = entry.runtime_data
|
||||||
|
@ -48,7 +48,7 @@ from homeassistant.config_entries import ConfigEntry
|
|||||||
from homeassistant.const import ATTR_TEMPERATURE
|
from homeassistant.const import ATTR_TEMPERATURE
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
from .const import API_TEMPERATURE_STEP, TEMP_UNIT_LIB_TO_HASS
|
from .const import API_TEMPERATURE_STEP, TEMP_UNIT_LIB_TO_HASS
|
||||||
from .coordinator import AirzoneConfigEntry, AirzoneUpdateCoordinator
|
from .coordinator import AirzoneConfigEntry, AirzoneUpdateCoordinator
|
||||||
@ -100,7 +100,7 @@ HVAC_MODE_HASS_TO_LIB: Final[dict[HVACMode, OperationMode]] = {
|
|||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry: AirzoneConfigEntry,
|
entry: AirzoneConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Add Airzone climate from a config_entry."""
|
"""Add Airzone climate from a config_entry."""
|
||||||
coordinator = entry.runtime_data
|
coordinator = entry.runtime_data
|
||||||
|
@ -25,7 +25,7 @@ from homeassistant.components.select import SelectEntity, SelectEntityDescriptio
|
|||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import EntityCategory
|
from homeassistant.const import EntityCategory
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
from .coordinator import AirzoneConfigEntry, AirzoneUpdateCoordinator
|
from .coordinator import AirzoneConfigEntry, AirzoneUpdateCoordinator
|
||||||
from .entity import AirzoneEntity, AirzoneZoneEntity
|
from .entity import AirzoneEntity, AirzoneZoneEntity
|
||||||
@ -117,7 +117,7 @@ ZONE_SELECT_TYPES: Final[tuple[AirzoneSelectDescription, ...]] = (
|
|||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry: AirzoneConfigEntry,
|
entry: AirzoneConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Add Airzone select from a config_entry."""
|
"""Add Airzone select from a config_entry."""
|
||||||
coordinator = entry.runtime_data
|
coordinator = entry.runtime_data
|
||||||
|
@ -28,7 +28,7 @@ from homeassistant.const import (
|
|||||||
UnitOfTemperature,
|
UnitOfTemperature,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
from .const import TEMP_UNIT_LIB_TO_HASS
|
from .const import TEMP_UNIT_LIB_TO_HASS
|
||||||
from .coordinator import AirzoneConfigEntry, AirzoneUpdateCoordinator
|
from .coordinator import AirzoneConfigEntry, AirzoneUpdateCoordinator
|
||||||
@ -79,7 +79,7 @@ ZONE_SENSOR_TYPES: Final[tuple[SensorEntityDescription, ...]] = (
|
|||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry: AirzoneConfigEntry,
|
entry: AirzoneConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Add Airzone sensors from a config_entry."""
|
"""Add Airzone sensors from a config_entry."""
|
||||||
coordinator = entry.runtime_data
|
coordinator = entry.runtime_data
|
||||||
|
@ -14,7 +14,7 @@ from homeassistant.components.switch import (
|
|||||||
)
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
from .coordinator import AirzoneConfigEntry, AirzoneUpdateCoordinator
|
from .coordinator import AirzoneConfigEntry, AirzoneUpdateCoordinator
|
||||||
from .entity import AirzoneEntity, AirzoneZoneEntity
|
from .entity import AirzoneEntity, AirzoneZoneEntity
|
||||||
@ -39,7 +39,7 @@ ZONE_SWITCH_TYPES: Final[tuple[AirzoneSwitchDescription, ...]] = (
|
|||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry: AirzoneConfigEntry,
|
entry: AirzoneConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Add Airzone switch from a config_entry."""
|
"""Add Airzone switch from a config_entry."""
|
||||||
coordinator = entry.runtime_data
|
coordinator = entry.runtime_data
|
||||||
|
@ -28,7 +28,7 @@ from homeassistant.components.water_heater import (
|
|||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import ATTR_TEMPERATURE, STATE_OFF
|
from homeassistant.const import ATTR_TEMPERATURE, STATE_OFF
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
from .const import TEMP_UNIT_LIB_TO_HASS
|
from .const import TEMP_UNIT_LIB_TO_HASS
|
||||||
from .coordinator import AirzoneConfigEntry, AirzoneUpdateCoordinator
|
from .coordinator import AirzoneConfigEntry, AirzoneUpdateCoordinator
|
||||||
@ -58,7 +58,7 @@ OPERATION_MODE_TO_DHW_PARAMS: Final[dict[str, dict[str, Any]]] = {
|
|||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry: AirzoneConfigEntry,
|
entry: AirzoneConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Add Airzone Water Heater from a config_entry."""
|
"""Add Airzone Water Heater from a config_entry."""
|
||||||
coordinator = entry.runtime_data
|
coordinator = entry.runtime_data
|
||||||
|
@ -5,12 +5,11 @@ from __future__ import annotations
|
|||||||
from aioairzone_cloud.cloudapi import AirzoneCloudApi
|
from aioairzone_cloud.cloudapi import AirzoneCloudApi
|
||||||
from aioairzone_cloud.common import ConnectionOptions
|
from aioairzone_cloud.common import ConnectionOptions
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
|
||||||
from homeassistant.const import CONF_ID, CONF_PASSWORD, CONF_USERNAME, Platform
|
from homeassistant.const import CONF_ID, CONF_PASSWORD, CONF_USERNAME, Platform
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import aiohttp_client
|
from homeassistant.helpers import aiohttp_client
|
||||||
|
|
||||||
from .coordinator import AirzoneUpdateCoordinator
|
from .coordinator import AirzoneCloudConfigEntry, AirzoneUpdateCoordinator
|
||||||
|
|
||||||
PLATFORMS: list[Platform] = [
|
PLATFORMS: list[Platform] = [
|
||||||
Platform.BINARY_SENSOR,
|
Platform.BINARY_SENSOR,
|
||||||
@ -21,8 +20,6 @@ PLATFORMS: list[Platform] = [
|
|||||||
Platform.WATER_HEATER,
|
Platform.WATER_HEATER,
|
||||||
]
|
]
|
||||||
|
|
||||||
type AirzoneCloudConfigEntry = ConfigEntry[AirzoneUpdateCoordinator]
|
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant, entry: AirzoneCloudConfigEntry
|
hass: HomeAssistant, entry: AirzoneCloudConfigEntry
|
||||||
@ -42,7 +39,7 @@ async def async_setup_entry(
|
|||||||
airzone.select_installation(inst)
|
airzone.select_installation(inst)
|
||||||
await airzone.update_installation(inst)
|
await airzone.update_installation(inst)
|
||||||
|
|
||||||
coordinator = AirzoneUpdateCoordinator(hass, airzone)
|
coordinator = AirzoneUpdateCoordinator(hass, entry, airzone)
|
||||||
await coordinator.async_config_entry_first_refresh()
|
await coordinator.async_config_entry_first_refresh()
|
||||||
|
|
||||||
entry.runtime_data = coordinator
|
entry.runtime_data = coordinator
|
||||||
|
@ -26,10 +26,9 @@ from homeassistant.components.binary_sensor import (
|
|||||||
)
|
)
|
||||||
from homeassistant.const import EntityCategory
|
from homeassistant.const import EntityCategory
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
from . import AirzoneCloudConfigEntry
|
from .coordinator import AirzoneCloudConfigEntry, AirzoneUpdateCoordinator
|
||||||
from .coordinator import AirzoneUpdateCoordinator
|
|
||||||
from .entity import (
|
from .entity import (
|
||||||
AirzoneAidooEntity,
|
AirzoneAidooEntity,
|
||||||
AirzoneEntity,
|
AirzoneEntity,
|
||||||
@ -112,7 +111,7 @@ ZONE_BINARY_SENSOR_TYPES: Final[tuple[AirzoneBinarySensorEntityDescription, ...]
|
|||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry: AirzoneCloudConfigEntry,
|
entry: AirzoneCloudConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Add Airzone Cloud binary sensors from a config_entry."""
|
"""Add Airzone Cloud binary sensors from a config_entry."""
|
||||||
coordinator = entry.runtime_data
|
coordinator = entry.runtime_data
|
||||||
|
@ -56,10 +56,9 @@ from homeassistant.components.climate import (
|
|||||||
from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature
|
from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
from . import AirzoneCloudConfigEntry
|
from .coordinator import AirzoneCloudConfigEntry, AirzoneUpdateCoordinator
|
||||||
from .coordinator import AirzoneUpdateCoordinator
|
|
||||||
from .entity import (
|
from .entity import (
|
||||||
AirzoneAidooEntity,
|
AirzoneAidooEntity,
|
||||||
AirzoneEntity,
|
AirzoneEntity,
|
||||||
@ -120,7 +119,7 @@ HVAC_MODE_HASS_TO_LIB: Final[dict[HVACMode, OperationMode]] = {
|
|||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry: AirzoneCloudConfigEntry,
|
entry: AirzoneCloudConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Add Airzone climate from a config_entry."""
|
"""Add Airzone climate from a config_entry."""
|
||||||
coordinator = entry.runtime_data
|
coordinator = entry.runtime_data
|
||||||
|
@ -10,6 +10,7 @@ from typing import Any
|
|||||||
from aioairzone_cloud.cloudapi import AirzoneCloudApi
|
from aioairzone_cloud.cloudapi import AirzoneCloudApi
|
||||||
from aioairzone_cloud.exceptions import AirzoneCloudError
|
from aioairzone_cloud.exceptions import AirzoneCloudError
|
||||||
|
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||||
|
|
||||||
@ -19,11 +20,20 @@ SCAN_INTERVAL = timedelta(seconds=60)
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
type AirzoneCloudConfigEntry = ConfigEntry[AirzoneUpdateCoordinator]
|
||||||
|
|
||||||
|
|
||||||
class AirzoneUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
class AirzoneUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||||
"""Class to manage fetching data from the Airzone Cloud device."""
|
"""Class to manage fetching data from the Airzone Cloud device."""
|
||||||
|
|
||||||
def __init__(self, hass: HomeAssistant, airzone: AirzoneCloudApi) -> None:
|
config_entry: AirzoneCloudConfigEntry
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config_entry: AirzoneCloudConfigEntry,
|
||||||
|
airzone: AirzoneCloudApi,
|
||||||
|
) -> None:
|
||||||
"""Initialize."""
|
"""Initialize."""
|
||||||
self.airzone = airzone
|
self.airzone = airzone
|
||||||
self.airzone.set_update_callback(self.async_set_updated_data)
|
self.airzone.set_update_callback(self.async_set_updated_data)
|
||||||
@ -31,6 +41,7 @@ class AirzoneUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
|||||||
super().__init__(
|
super().__init__(
|
||||||
hass,
|
hass,
|
||||||
_LOGGER,
|
_LOGGER,
|
||||||
|
config_entry=config_entry,
|
||||||
name=DOMAIN,
|
name=DOMAIN,
|
||||||
update_interval=SCAN_INTERVAL,
|
update_interval=SCAN_INTERVAL,
|
||||||
)
|
)
|
||||||
|
@ -25,7 +25,7 @@ from homeassistant.components.diagnostics import async_redact_data
|
|||||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
from . import AirzoneCloudConfigEntry
|
from .coordinator import AirzoneCloudConfigEntry
|
||||||
|
|
||||||
TO_REDACT_API = [
|
TO_REDACT_API = [
|
||||||
API_CITY,
|
API_CITY,
|
||||||
|
@ -21,10 +21,9 @@ from aioairzone_cloud.const import (
|
|||||||
from homeassistant.components.select import SelectEntity, SelectEntityDescription
|
from homeassistant.components.select import SelectEntity, SelectEntityDescription
|
||||||
from homeassistant.const import EntityCategory
|
from homeassistant.const import EntityCategory
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
from . import AirzoneCloudConfigEntry
|
from .coordinator import AirzoneCloudConfigEntry, AirzoneUpdateCoordinator
|
||||||
from .coordinator import AirzoneUpdateCoordinator
|
|
||||||
from .entity import AirzoneEntity, AirzoneZoneEntity
|
from .entity import AirzoneEntity, AirzoneZoneEntity
|
||||||
|
|
||||||
|
|
||||||
@ -90,7 +89,7 @@ ZONE_SELECT_TYPES: Final[tuple[AirzoneSelectDescription, ...]] = (
|
|||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry: AirzoneCloudConfigEntry,
|
entry: AirzoneCloudConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Add Airzone Cloud select from a config_entry."""
|
"""Add Airzone Cloud select from a config_entry."""
|
||||||
coordinator = entry.runtime_data
|
coordinator = entry.runtime_data
|
||||||
|
@ -47,10 +47,9 @@ from homeassistant.const import (
|
|||||||
UnitOfTemperature,
|
UnitOfTemperature,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
from . import AirzoneCloudConfigEntry
|
from .coordinator import AirzoneCloudConfigEntry, AirzoneUpdateCoordinator
|
||||||
from .coordinator import AirzoneUpdateCoordinator
|
|
||||||
from .entity import (
|
from .entity import (
|
||||||
AirzoneAidooEntity,
|
AirzoneAidooEntity,
|
||||||
AirzoneEntity,
|
AirzoneEntity,
|
||||||
@ -222,7 +221,7 @@ ZONE_SENSOR_TYPES: Final[tuple[SensorEntityDescription, ...]] = (
|
|||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry: AirzoneCloudConfigEntry,
|
entry: AirzoneCloudConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Add Airzone Cloud sensors from a config_entry."""
|
"""Add Airzone Cloud sensors from a config_entry."""
|
||||||
coordinator = entry.runtime_data
|
coordinator = entry.runtime_data
|
||||||
|
@ -13,10 +13,9 @@ from homeassistant.components.switch import (
|
|||||||
SwitchEntityDescription,
|
SwitchEntityDescription,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
from . import AirzoneCloudConfigEntry
|
from .coordinator import AirzoneCloudConfigEntry, AirzoneUpdateCoordinator
|
||||||
from .coordinator import AirzoneUpdateCoordinator
|
|
||||||
from .entity import AirzoneEntity, AirzoneZoneEntity
|
from .entity import AirzoneEntity, AirzoneZoneEntity
|
||||||
|
|
||||||
|
|
||||||
@ -39,7 +38,7 @@ ZONE_SWITCH_TYPES: Final[tuple[AirzoneSwitchDescription, ...]] = (
|
|||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry: AirzoneCloudConfigEntry,
|
entry: AirzoneCloudConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Add Airzone Cloud switch from a config_entry."""
|
"""Add Airzone Cloud switch from a config_entry."""
|
||||||
coordinator = entry.runtime_data
|
coordinator = entry.runtime_data
|
||||||
|
@ -29,10 +29,9 @@ from homeassistant.components.water_heater import (
|
|||||||
)
|
)
|
||||||
from homeassistant.const import ATTR_TEMPERATURE, STATE_OFF, UnitOfTemperature
|
from homeassistant.const import ATTR_TEMPERATURE, STATE_OFF, UnitOfTemperature
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
from . import AirzoneCloudConfigEntry
|
from .coordinator import AirzoneCloudConfigEntry, AirzoneUpdateCoordinator
|
||||||
from .coordinator import AirzoneUpdateCoordinator
|
|
||||||
from .entity import AirzoneHotWaterEntity
|
from .entity import AirzoneHotWaterEntity
|
||||||
|
|
||||||
OPERATION_LIB_TO_HASS: Final[dict[HotWaterOperation, str]] = {
|
OPERATION_LIB_TO_HASS: Final[dict[HotWaterOperation, str]] = {
|
||||||
@ -69,7 +68,7 @@ OPERATION_MODE_TO_DHW_PARAMS: Final[dict[str, dict[str, Any]]] = {
|
|||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry: AirzoneCloudConfigEntry,
|
entry: AirzoneCloudConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Add Airzone Cloud Water Heater from a config_entry."""
|
"""Add Airzone Cloud Water Heater from a config_entry."""
|
||||||
coordinator = entry.runtime_data
|
coordinator = entry.runtime_data
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry, ConfigEntryState
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import issue_registry as ir
|
from homeassistant.helpers import issue_registry as ir
|
||||||
|
|
||||||
@ -28,11 +28,13 @@ async def async_setup_entry(hass: HomeAssistant, _: ConfigEntry) -> bool:
|
|||||||
|
|
||||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
"""Unload a config entry."""
|
"""Unload a config entry."""
|
||||||
if all(
|
|
||||||
config_entry.state is ConfigEntryState.NOT_LOADED
|
|
||||||
for config_entry in hass.config_entries.async_entries(DOMAIN)
|
|
||||||
if config_entry.entry_id != entry.entry_id
|
|
||||||
):
|
|
||||||
ir.async_delete_issue(hass, DOMAIN, DOMAIN)
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||||
|
"""Remove a config entry."""
|
||||||
|
if not hass.config_entries.async_loaded_entries(DOMAIN):
|
||||||
|
ir.async_delete_issue(hass, DOMAIN, DOMAIN)
|
||||||
|
# Remove any remaining disabled or ignored entries
|
||||||
|
for _entry in hass.config_entries.async_entries(DOMAIN):
|
||||||
|
hass.async_create_task(hass.config_entries.async_remove(_entry.entry_id))
|
||||||
|
@ -90,7 +90,7 @@
|
|||||||
},
|
},
|
||||||
"alarm_arm_home": {
|
"alarm_arm_home": {
|
||||||
"name": "Arm home",
|
"name": "Arm home",
|
||||||
"description": "Sets the alarm to: _armed, but someone is home_.",
|
"description": "Arms the alarm in the home mode.",
|
||||||
"fields": {
|
"fields": {
|
||||||
"code": {
|
"code": {
|
||||||
"name": "[%key:component::alarm_control_panel::services::alarm_disarm::fields::code::name%]",
|
"name": "[%key:component::alarm_control_panel::services::alarm_disarm::fields::code::name%]",
|
||||||
@ -100,7 +100,7 @@
|
|||||||
},
|
},
|
||||||
"alarm_arm_away": {
|
"alarm_arm_away": {
|
||||||
"name": "Arm away",
|
"name": "Arm away",
|
||||||
"description": "Sets the alarm to: _armed, no one home_.",
|
"description": "Arms the alarm in the away mode.",
|
||||||
"fields": {
|
"fields": {
|
||||||
"code": {
|
"code": {
|
||||||
"name": "[%key:component::alarm_control_panel::services::alarm_disarm::fields::code::name%]",
|
"name": "[%key:component::alarm_control_panel::services::alarm_disarm::fields::code::name%]",
|
||||||
@ -110,7 +110,7 @@
|
|||||||
},
|
},
|
||||||
"alarm_arm_night": {
|
"alarm_arm_night": {
|
||||||
"name": "Arm night",
|
"name": "Arm night",
|
||||||
"description": "Sets the alarm to: _armed for the night_.",
|
"description": "Arms the alarm in the night mode.",
|
||||||
"fields": {
|
"fields": {
|
||||||
"code": {
|
"code": {
|
||||||
"name": "[%key:component::alarm_control_panel::services::alarm_disarm::fields::code::name%]",
|
"name": "[%key:component::alarm_control_panel::services::alarm_disarm::fields::code::name%]",
|
||||||
@ -120,7 +120,7 @@
|
|||||||
},
|
},
|
||||||
"alarm_arm_vacation": {
|
"alarm_arm_vacation": {
|
||||||
"name": "Arm vacation",
|
"name": "Arm vacation",
|
||||||
"description": "Sets the alarm to: _armed for vacation_.",
|
"description": "Arms the alarm in the vacation mode.",
|
||||||
"fields": {
|
"fields": {
|
||||||
"code": {
|
"code": {
|
||||||
"name": "[%key:component::alarm_control_panel::services::alarm_disarm::fields::code::name%]",
|
"name": "[%key:component::alarm_control_panel::services::alarm_disarm::fields::code::name%]",
|
||||||
@ -130,7 +130,7 @@
|
|||||||
},
|
},
|
||||||
"alarm_trigger": {
|
"alarm_trigger": {
|
||||||
"name": "Trigger",
|
"name": "Trigger",
|
||||||
"description": "Trigger the alarm manually.",
|
"description": "Triggers the alarm manually.",
|
||||||
"fields": {
|
"fields": {
|
||||||
"code": {
|
"code": {
|
||||||
"name": "[%key:component::alarm_control_panel::services::alarm_disarm::fields::code::name%]",
|
"name": "[%key:component::alarm_control_panel::services::alarm_disarm::fields::code::name%]",
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user