mirror of
https://github.com/home-assistant/core.git
synced 2025-07-27 07:07:28 +00:00
Merge branch 'dev' into lutron_caseta_event_rework
This commit is contained in:
commit
72efcf0e94
@ -14,6 +14,7 @@ core: &core
|
|||||||
base_platforms: &base_platforms
|
base_platforms: &base_platforms
|
||||||
- homeassistant/components/air_quality/**
|
- homeassistant/components/air_quality/**
|
||||||
- homeassistant/components/alarm_control_panel/**
|
- homeassistant/components/alarm_control_panel/**
|
||||||
|
- homeassistant/components/assist_satellite/**
|
||||||
- homeassistant/components/binary_sensor/**
|
- homeassistant/components/binary_sensor/**
|
||||||
- homeassistant/components/button/**
|
- homeassistant/components/button/**
|
||||||
- homeassistant/components/calendar/**
|
- homeassistant/components/calendar/**
|
||||||
@ -61,6 +62,7 @@ components: &components
|
|||||||
- homeassistant/components/auth/**
|
- homeassistant/components/auth/**
|
||||||
- homeassistant/components/automation/**
|
- homeassistant/components/automation/**
|
||||||
- homeassistant/components/backup/**
|
- homeassistant/components/backup/**
|
||||||
|
- homeassistant/components/blueprint/**
|
||||||
- homeassistant/components/bluetooth/**
|
- homeassistant/components/bluetooth/**
|
||||||
- homeassistant/components/cloud/**
|
- homeassistant/components/cloud/**
|
||||||
- homeassistant/components/config/**
|
- homeassistant/components/config/**
|
||||||
@ -146,6 +148,7 @@ requirements: &requirements
|
|||||||
- homeassistant/package_constraints.txt
|
- homeassistant/package_constraints.txt
|
||||||
- requirements*.txt
|
- requirements*.txt
|
||||||
- pyproject.toml
|
- pyproject.toml
|
||||||
|
- script/licenses.py
|
||||||
|
|
||||||
any:
|
any:
|
||||||
- *base_platforms
|
- *base_platforms
|
||||||
|
67
.github/workflows/builder.yml
vendored
67
.github/workflows/builder.yml
vendored
@ -32,7 +32,7 @@ jobs:
|
|||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
uses: actions/setup-python@v5.1.1
|
uses: actions/setup-python@v5.2.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
|
|
||||||
@ -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.3.4
|
uses: actions/upload-artifact@v4.4.0
|
||||||
with:
|
with:
|
||||||
name: translations
|
name: translations
|
||||||
path: translations.tar.gz
|
path: translations.tar.gz
|
||||||
@ -116,7 +116,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
if: needs.init.outputs.channel == 'dev'
|
if: needs.init.outputs.channel == 'dev'
|
||||||
uses: actions/setup-python@v5.1.1
|
uses: actions/setup-python@v5.2.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
|
|
||||||
@ -197,7 +197,7 @@ jobs:
|
|||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Build base image
|
- name: Build base image
|
||||||
uses: home-assistant/builder@2024.03.5
|
uses: home-assistant/builder@2024.08.2
|
||||||
with:
|
with:
|
||||||
args: |
|
args: |
|
||||||
$BUILD_ARGS \
|
$BUILD_ARGS \
|
||||||
@ -263,7 +263,7 @@ jobs:
|
|||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Build base image
|
- name: Build base image
|
||||||
uses: home-assistant/builder@2024.03.5
|
uses: home-assistant/builder@2024.08.2
|
||||||
with:
|
with:
|
||||||
args: |
|
args: |
|
||||||
$BUILD_ARGS \
|
$BUILD_ARGS \
|
||||||
@ -323,7 +323,7 @@ jobs:
|
|||||||
uses: actions/checkout@v4.1.7
|
uses: actions/checkout@v4.1.7
|
||||||
|
|
||||||
- name: Install Cosign
|
- name: Install Cosign
|
||||||
uses: sigstore/cosign-installer@v3.5.0
|
uses: sigstore/cosign-installer@v3.6.0
|
||||||
with:
|
with:
|
||||||
cosign-release: "v2.2.3"
|
cosign-release: "v2.2.3"
|
||||||
|
|
||||||
@ -453,7 +453,7 @@ jobs:
|
|||||||
uses: actions/checkout@v4.1.7
|
uses: actions/checkout@v4.1.7
|
||||||
|
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
uses: actions/setup-python@v5.1.1
|
uses: actions/setup-python@v5.2.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
|
|
||||||
@ -482,3 +482,56 @@ jobs:
|
|||||||
export TWINE_PASSWORD="${{ secrets.TWINE_TOKEN }}"
|
export TWINE_PASSWORD="${{ secrets.TWINE_TOKEN }}"
|
||||||
|
|
||||||
twine upload dist/* --skip-existing
|
twine upload dist/* --skip-existing
|
||||||
|
|
||||||
|
hassfest-image:
|
||||||
|
name: Build and test hassfest image
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
attestations: write
|
||||||
|
id-token: write
|
||||||
|
needs: ["init"]
|
||||||
|
if: github.repository_owner == 'home-assistant'
|
||||||
|
env:
|
||||||
|
HASSFEST_IMAGE_NAME: ghcr.io/home-assistant/hassfest
|
||||||
|
HASSFEST_IMAGE_TAG: ghcr.io/home-assistant/hassfest:${{ needs.init.outputs.version }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||||
|
|
||||||
|
- name: Login to GitHub Container Registry
|
||||||
|
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build Docker image
|
||||||
|
uses: docker/build-push-action@5cd11c3a4ced054e52742c5fd54dca954e0edd85 # v6.7.0
|
||||||
|
with:
|
||||||
|
context: . # So action will not pull the repository again
|
||||||
|
file: ./script/hassfest/docker/Dockerfile
|
||||||
|
load: true
|
||||||
|
tags: ${{ env.HASSFEST_IMAGE_TAG }}
|
||||||
|
|
||||||
|
- name: Run hassfest against core
|
||||||
|
run: docker run --rm -v ${{ github.workspace }}/homeassistant:/github/workspace/homeassistant ${{ env.HASSFEST_IMAGE_TAG }} --core-integrations-path=/github/workspace/homeassistant/components
|
||||||
|
|
||||||
|
- name: Push Docker image
|
||||||
|
if: needs.init.outputs.channel != 'dev' && needs.init.outputs.publish == 'true'
|
||||||
|
id: push
|
||||||
|
uses: docker/build-push-action@5cd11c3a4ced054e52742c5fd54dca954e0edd85 # v6.7.0
|
||||||
|
with:
|
||||||
|
context: . # So action will not pull the repository again
|
||||||
|
file: ./script/hassfest/docker/Dockerfile
|
||||||
|
push: true
|
||||||
|
tags: ${{ env.HASSFEST_IMAGE_TAG }},${{ env.HASSFEST_IMAGE_NAME }}:latest
|
||||||
|
|
||||||
|
- name: Generate artifact attestation
|
||||||
|
if: needs.init.outputs.channel != 'dev' && needs.init.outputs.publish == 'true'
|
||||||
|
uses: actions/attest-build-provenance@6149ea5740be74af77f260b9db67e633f6b0a9a1 # v1.4.2
|
||||||
|
with:
|
||||||
|
subject-name: ${{ env.HASSFEST_IMAGE_NAME }}
|
||||||
|
subject-digest: ${{ steps.push.outputs.digest }}
|
||||||
|
push-to-registry: true
|
||||||
|
117
.github/workflows/ci.yaml
vendored
117
.github/workflows/ci.yaml
vendored
@ -31,12 +31,16 @@ on:
|
|||||||
description: "Only run mypy"
|
description: "Only run mypy"
|
||||||
default: false
|
default: false
|
||||||
type: boolean
|
type: boolean
|
||||||
|
audit-licenses-only:
|
||||||
|
description: "Only run audit licenses"
|
||||||
|
default: false
|
||||||
|
type: boolean
|
||||||
|
|
||||||
env:
|
env:
|
||||||
CACHE_VERSION: 9
|
CACHE_VERSION: 10
|
||||||
UV_CACHE_VERSION: 1
|
UV_CACHE_VERSION: 1
|
||||||
MYPY_CACHE_VERSION: 8
|
MYPY_CACHE_VERSION: 8
|
||||||
HA_SHORT_VERSION: "2024.8"
|
HA_SHORT_VERSION: "2024.10"
|
||||||
DEFAULT_PYTHON: "3.12"
|
DEFAULT_PYTHON: "3.12"
|
||||||
ALL_PYTHON_VERSIONS: "['3.12']"
|
ALL_PYTHON_VERSIONS: "['3.12']"
|
||||||
# 10.3 is the oldest supported version
|
# 10.3 is the oldest supported version
|
||||||
@ -222,6 +226,7 @@ jobs:
|
|||||||
if: |
|
if: |
|
||||||
github.event.inputs.pylint-only != 'true'
|
github.event.inputs.pylint-only != 'true'
|
||||||
&& github.event.inputs.mypy-only != 'true'
|
&& github.event.inputs.mypy-only != 'true'
|
||||||
|
&& github.event.inputs.audit-licenses-only != 'true'
|
||||||
needs:
|
needs:
|
||||||
- info
|
- info
|
||||||
steps:
|
steps:
|
||||||
@ -229,7 +234,7 @@ jobs:
|
|||||||
uses: actions/checkout@v4.1.7
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
id: python
|
id: python
|
||||||
uses: actions/setup-python@v5.1.1
|
uses: actions/setup-python@v5.2.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
@ -274,7 +279,7 @@ jobs:
|
|||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.1.7
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
uses: actions/setup-python@v5.1.1
|
uses: actions/setup-python@v5.2.0
|
||||||
id: python
|
id: python
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
@ -314,7 +319,7 @@ jobs:
|
|||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.1.7
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
uses: actions/setup-python@v5.1.1
|
uses: actions/setup-python@v5.2.0
|
||||||
id: python
|
id: python
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
@ -343,6 +348,7 @@ jobs:
|
|||||||
pre-commit run --hook-stage manual ruff --all-files --show-diff-on-failure
|
pre-commit run --hook-stage manual ruff --all-files --show-diff-on-failure
|
||||||
env:
|
env:
|
||||||
RUFF_OUTPUT_FORMAT: github
|
RUFF_OUTPUT_FORMAT: github
|
||||||
|
|
||||||
lint-other:
|
lint-other:
|
||||||
name: Check other linters
|
name: Check other linters
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
@ -353,7 +359,7 @@ jobs:
|
|||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.1.7
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
uses: actions/setup-python@v5.1.1
|
uses: actions/setup-python@v5.2.0
|
||||||
id: python
|
id: python
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
@ -423,17 +429,32 @@ jobs:
|
|||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
pre-commit run --show-diff-on-failure --hook-stage manual codespell --all-files
|
pre-commit run --show-diff-on-failure --hook-stage manual codespell --all-files
|
||||||
|
|
||||||
|
lint-hadolint:
|
||||||
|
name: Check ${{ matrix.file }}
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
needs:
|
||||||
|
- info
|
||||||
|
if: |
|
||||||
|
github.event.inputs.pylint-only != 'true'
|
||||||
|
&& github.event.inputs.mypy-only != 'true'
|
||||||
|
&& github.event.inputs.audit-licenses-only != 'true'
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
file:
|
||||||
|
- Dockerfile
|
||||||
|
- Dockerfile.dev
|
||||||
|
- script/hassfest/docker/Dockerfile
|
||||||
|
steps:
|
||||||
|
- name: Check out code from GitHub
|
||||||
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Register hadolint problem matcher
|
- name: Register hadolint problem matcher
|
||||||
run: |
|
run: |
|
||||||
echo "::add-matcher::.github/workflows/matchers/hadolint.json"
|
echo "::add-matcher::.github/workflows/matchers/hadolint.json"
|
||||||
- name: Check Dockerfile
|
- name: Check ${{ matrix.file }}
|
||||||
uses: docker://hadolint/hadolint:v1.18.2
|
uses: docker://hadolint/hadolint:v2.12.0
|
||||||
with:
|
with:
|
||||||
args: hadolint Dockerfile
|
args: hadolint ${{ matrix.file }}
|
||||||
- name: Check Dockerfile.dev
|
|
||||||
uses: docker://hadolint/hadolint:v1.18.2
|
|
||||||
with:
|
|
||||||
args: hadolint Dockerfile.dev
|
|
||||||
|
|
||||||
base:
|
base:
|
||||||
name: Prepare dependencies
|
name: Prepare dependencies
|
||||||
@ -448,7 +469,7 @@ jobs:
|
|||||||
uses: actions/checkout@v4.1.7
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
id: python
|
id: python
|
||||||
uses: actions/setup-python@v5.1.1
|
uses: actions/setup-python@v5.2.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
@ -508,8 +529,7 @@ jobs:
|
|||||||
uv pip install -U "pip>=21.3.1" setuptools wheel
|
uv pip install -U "pip>=21.3.1" setuptools wheel
|
||||||
uv pip install -r requirements.txt
|
uv pip install -r requirements.txt
|
||||||
python -m script.gen_requirements_all ci
|
python -m script.gen_requirements_all ci
|
||||||
uv pip install -r requirements_all_pytest.txt
|
uv pip install -r requirements_all_pytest.txt -r requirements_test.txt
|
||||||
uv pip install -r requirements_test.txt
|
|
||||||
uv pip install -e . --config-settings editable_mode=compat
|
uv pip install -e . --config-settings editable_mode=compat
|
||||||
|
|
||||||
hassfest:
|
hassfest:
|
||||||
@ -518,6 +538,7 @@ jobs:
|
|||||||
if: |
|
if: |
|
||||||
github.event.inputs.pylint-only != 'true'
|
github.event.inputs.pylint-only != 'true'
|
||||||
&& github.event.inputs.mypy-only != 'true'
|
&& github.event.inputs.mypy-only != 'true'
|
||||||
|
&& github.event.inputs.audit-licenses-only != 'true'
|
||||||
needs:
|
needs:
|
||||||
- info
|
- info
|
||||||
- base
|
- base
|
||||||
@ -532,7 +553,7 @@ jobs:
|
|||||||
uses: actions/checkout@v4.1.7
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
id: python
|
id: python
|
||||||
uses: actions/setup-python@v5.1.1
|
uses: actions/setup-python@v5.2.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
@ -556,6 +577,7 @@ jobs:
|
|||||||
if: |
|
if: |
|
||||||
github.event.inputs.pylint-only != 'true'
|
github.event.inputs.pylint-only != 'true'
|
||||||
&& github.event.inputs.mypy-only != 'true'
|
&& github.event.inputs.mypy-only != 'true'
|
||||||
|
&& github.event.inputs.audit-licenses-only != 'true'
|
||||||
needs:
|
needs:
|
||||||
- info
|
- info
|
||||||
- base
|
- base
|
||||||
@ -564,7 +586,7 @@ jobs:
|
|||||||
uses: actions/checkout@v4.1.7
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
id: python
|
id: python
|
||||||
uses: actions/setup-python@v5.1.1
|
uses: actions/setup-python@v5.2.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
@ -589,13 +611,16 @@ jobs:
|
|||||||
- info
|
- info
|
||||||
- base
|
- base
|
||||||
if: |
|
if: |
|
||||||
needs.info.outputs.requirements == 'true'
|
(github.event.inputs.pylint-only != 'true'
|
||||||
|
&& github.event.inputs.mypy-only != 'true'
|
||||||
|
|| github.event.inputs.audit-licenses-only == 'true')
|
||||||
|
&& needs.info.outputs.requirements == 'true'
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.1.7
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
id: python
|
id: python
|
||||||
uses: actions/setup-python@v5.1.1
|
uses: actions/setup-python@v5.2.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
@ -613,7 +638,7 @@ jobs:
|
|||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
pip-licenses --format=json --output-file=licenses.json
|
pip-licenses --format=json --output-file=licenses.json
|
||||||
- name: Upload licenses
|
- name: Upload licenses
|
||||||
uses: actions/upload-artifact@v4.3.4
|
uses: actions/upload-artifact@v4.4.0
|
||||||
with:
|
with:
|
||||||
name: licenses
|
name: licenses
|
||||||
path: licenses.json
|
path: licenses.json
|
||||||
@ -628,6 +653,7 @@ jobs:
|
|||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
if: |
|
if: |
|
||||||
github.event.inputs.mypy-only != 'true'
|
github.event.inputs.mypy-only != 'true'
|
||||||
|
&& github.event.inputs.audit-licenses-only != 'true'
|
||||||
|| github.event.inputs.pylint-only == 'true'
|
|| github.event.inputs.pylint-only == 'true'
|
||||||
needs:
|
needs:
|
||||||
- info
|
- info
|
||||||
@ -637,7 +663,7 @@ jobs:
|
|||||||
uses: actions/checkout@v4.1.7
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
id: python
|
id: python
|
||||||
uses: actions/setup-python@v5.1.1
|
uses: actions/setup-python@v5.2.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
@ -672,7 +698,9 @@ jobs:
|
|||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
if: |
|
if: |
|
||||||
(github.event.inputs.mypy-only != 'true' || github.event.inputs.pylint-only == 'true')
|
(github.event.inputs.mypy-only != 'true'
|
||||||
|
&& github.event.inputs.audit-licenses-only != 'true'
|
||||||
|
|| github.event.inputs.pylint-only == 'true')
|
||||||
&& (needs.info.outputs.tests_glob || needs.info.outputs.test_full_suite == 'true')
|
&& (needs.info.outputs.tests_glob || needs.info.outputs.test_full_suite == 'true')
|
||||||
needs:
|
needs:
|
||||||
- info
|
- info
|
||||||
@ -682,7 +710,7 @@ jobs:
|
|||||||
uses: actions/checkout@v4.1.7
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
id: python
|
id: python
|
||||||
uses: actions/setup-python@v5.1.1
|
uses: actions/setup-python@v5.2.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
@ -703,20 +731,21 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
python --version
|
python --version
|
||||||
pylint --ignore-missing-annotations=y tests
|
pylint tests
|
||||||
- name: Run pylint (partially)
|
- name: Run pylint (partially)
|
||||||
if: needs.info.outputs.test_full_suite == 'false'
|
if: needs.info.outputs.test_full_suite == 'false'
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
python --version
|
python --version
|
||||||
pylint --ignore-missing-annotations=y tests/components/${{ needs.info.outputs.tests_glob }}
|
pylint tests/components/${{ needs.info.outputs.tests_glob }}
|
||||||
|
|
||||||
mypy:
|
mypy:
|
||||||
name: Check mypy
|
name: Check mypy
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
if: |
|
if: |
|
||||||
github.event.inputs.pylint-only != 'true'
|
github.event.inputs.pylint-only != 'true'
|
||||||
|
&& github.event.inputs.audit-licenses-only != 'true'
|
||||||
|| github.event.inputs.mypy-only == 'true'
|
|| github.event.inputs.mypy-only == 'true'
|
||||||
needs:
|
needs:
|
||||||
- info
|
- info
|
||||||
@ -726,7 +755,7 @@ jobs:
|
|||||||
uses: actions/checkout@v4.1.7
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
id: python
|
id: python
|
||||||
uses: actions/setup-python@v5.1.1
|
uses: actions/setup-python@v5.2.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
@ -781,6 +810,7 @@ jobs:
|
|||||||
&& github.event.inputs.lint-only != 'true'
|
&& github.event.inputs.lint-only != 'true'
|
||||||
&& github.event.inputs.pylint-only != 'true'
|
&& github.event.inputs.pylint-only != 'true'
|
||||||
&& github.event.inputs.mypy-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
|
||||||
@ -800,7 +830,7 @@ jobs:
|
|||||||
uses: actions/checkout@v4.1.7
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
id: python
|
id: python
|
||||||
uses: actions/setup-python@v5.1.1
|
uses: actions/setup-python@v5.2.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
@ -818,7 +848,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.3.4
|
uses: actions/upload-artifact@v4.4.0
|
||||||
with:
|
with:
|
||||||
name: pytest_buckets
|
name: pytest_buckets
|
||||||
path: pytest_buckets.txt
|
path: pytest_buckets.txt
|
||||||
@ -831,6 +861,7 @@ jobs:
|
|||||||
&& github.event.inputs.lint-only != 'true'
|
&& github.event.inputs.lint-only != 'true'
|
||||||
&& github.event.inputs.pylint-only != 'true'
|
&& github.event.inputs.pylint-only != 'true'
|
||||||
&& github.event.inputs.mypy-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
|
||||||
@ -863,7 +894,7 @@ jobs:
|
|||||||
uses: actions/checkout@v4.1.7
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
id: python
|
id: python
|
||||||
uses: actions/setup-python@v5.1.1
|
uses: actions/setup-python@v5.2.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
@ -904,6 +935,7 @@ jobs:
|
|||||||
cov_params+=(--cov-report=xml)
|
cov_params+=(--cov-report=xml)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
echo "Test group ${{ matrix.group }}: $(sed -n "${{ matrix.group }},1p" pytest_buckets.txt)"
|
||||||
python3 -b -X dev -m pytest \
|
python3 -b -X dev -m pytest \
|
||||||
-qq \
|
-qq \
|
||||||
--timeout=9 \
|
--timeout=9 \
|
||||||
@ -917,14 +949,14 @@ 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.3.4
|
uses: actions/upload-artifact@v4.4.0
|
||||||
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.3.4
|
uses: actions/upload-artifact@v4.4.0
|
||||||
with:
|
with:
|
||||||
name: coverage-${{ matrix.python-version }}-${{ matrix.group }}
|
name: coverage-${{ matrix.python-version }}-${{ matrix.group }}
|
||||||
path: coverage.xml
|
path: coverage.xml
|
||||||
@ -950,6 +982,7 @@ jobs:
|
|||||||
&& github.event.inputs.lint-only != 'true'
|
&& github.event.inputs.lint-only != 'true'
|
||||||
&& github.event.inputs.pylint-only != 'true'
|
&& github.event.inputs.pylint-only != 'true'
|
||||||
&& github.event.inputs.mypy-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
|
||||||
@ -981,7 +1014,7 @@ jobs:
|
|||||||
uses: actions/checkout@v4.1.7
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
id: python
|
id: python
|
||||||
uses: actions/setup-python@v5.1.1
|
uses: actions/setup-python@v5.2.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
@ -1042,7 +1075,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.3.4
|
uses: actions/upload-artifact@v4.4.0
|
||||||
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 }}
|
||||||
@ -1050,7 +1083,7 @@ 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.3.4
|
uses: actions/upload-artifact@v4.4.0
|
||||||
with:
|
with:
|
||||||
name: coverage-${{ matrix.python-version }}-${{
|
name: coverage-${{ matrix.python-version }}-${{
|
||||||
steps.pytest-partial.outputs.mariadb }}
|
steps.pytest-partial.outputs.mariadb }}
|
||||||
@ -1075,6 +1108,7 @@ jobs:
|
|||||||
&& github.event.inputs.lint-only != 'true'
|
&& github.event.inputs.lint-only != 'true'
|
||||||
&& github.event.inputs.pylint-only != 'true'
|
&& github.event.inputs.pylint-only != 'true'
|
||||||
&& github.event.inputs.mypy-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
|
||||||
@ -1106,7 +1140,7 @@ jobs:
|
|||||||
uses: actions/checkout@v4.1.7
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
id: python
|
id: python
|
||||||
uses: actions/setup-python@v5.1.1
|
uses: actions/setup-python@v5.2.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
@ -1168,7 +1202,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.3.4
|
uses: actions/upload-artifact@v4.4.0
|
||||||
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 }}
|
||||||
@ -1176,7 +1210,7 @@ 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.3.4
|
uses: actions/upload-artifact@v4.4.0
|
||||||
with:
|
with:
|
||||||
name: coverage-${{ matrix.python-version }}-${{
|
name: coverage-${{ matrix.python-version }}-${{
|
||||||
steps.pytest-partial.outputs.postgresql }}
|
steps.pytest-partial.outputs.postgresql }}
|
||||||
@ -1219,6 +1253,7 @@ jobs:
|
|||||||
&& github.event.inputs.lint-only != 'true'
|
&& github.event.inputs.lint-only != 'true'
|
||||||
&& github.event.inputs.pylint-only != 'true'
|
&& github.event.inputs.pylint-only != 'true'
|
||||||
&& github.event.inputs.mypy-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:
|
||||||
@ -1251,7 +1286,7 @@ jobs:
|
|||||||
uses: actions/checkout@v4.1.7
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
id: python
|
id: python
|
||||||
uses: actions/setup-python@v5.1.1
|
uses: actions/setup-python@v5.2.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
@ -1309,14 +1344,14 @@ 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.3.4
|
uses: actions/upload-artifact@v4.4.0
|
||||||
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.3.4
|
uses: actions/upload-artifact@v4.4.0
|
||||||
with:
|
with:
|
||||||
name: coverage-${{ matrix.python-version }}-${{ matrix.group }}
|
name: coverage-${{ matrix.python-version }}-${{ matrix.group }}
|
||||||
path: coverage.xml
|
path: coverage.xml
|
||||||
|
4
.github/workflows/codeql.yml
vendored
4
.github/workflows/codeql.yml
vendored
@ -24,11 +24,11 @@ jobs:
|
|||||||
uses: actions/checkout@v4.1.7
|
uses: actions/checkout@v4.1.7
|
||||||
|
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v3.25.15
|
uses: github/codeql-action/init@v3.26.6
|
||||||
with:
|
with:
|
||||||
languages: python
|
languages: python
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v3.25.15
|
uses: github/codeql-action/analyze@v3.26.6
|
||||||
with:
|
with:
|
||||||
category: "/language:python"
|
category: "/language:python"
|
||||||
|
2
.github/workflows/translations.yml
vendored
2
.github/workflows/translations.yml
vendored
@ -22,7 +22,7 @@ jobs:
|
|||||||
uses: actions/checkout@v4.1.7
|
uses: actions/checkout@v4.1.7
|
||||||
|
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
uses: actions/setup-python@v5.1.1
|
uses: actions/setup-python@v5.2.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
|
|
||||||
|
19
.github/workflows/wheels.yml
vendored
19
.github/workflows/wheels.yml
vendored
@ -36,7 +36,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
id: python
|
id: python
|
||||||
uses: actions/setup-python@v5.1.1
|
uses: actions/setup-python@v5.2.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
@ -82,14 +82,15 @@ jobs:
|
|||||||
) > .env_file
|
) > .env_file
|
||||||
|
|
||||||
- name: Upload env_file
|
- name: Upload env_file
|
||||||
uses: actions/upload-artifact@v4.3.4
|
uses: actions/upload-artifact@v4.4.0
|
||||||
with:
|
with:
|
||||||
name: env_file
|
name: env_file
|
||||||
path: ./.env_file
|
path: ./.env_file
|
||||||
|
include-hidden-files: true
|
||||||
overwrite: true
|
overwrite: true
|
||||||
|
|
||||||
- name: Upload requirements_diff
|
- name: Upload requirements_diff
|
||||||
uses: actions/upload-artifact@v4.3.4
|
uses: actions/upload-artifact@v4.4.0
|
||||||
with:
|
with:
|
||||||
name: requirements_diff
|
name: requirements_diff
|
||||||
path: ./requirements_diff.txt
|
path: ./requirements_diff.txt
|
||||||
@ -101,7 +102,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.3.4
|
uses: actions/upload-artifact@v4.4.0
|
||||||
with:
|
with:
|
||||||
name: requirements_all_wheels
|
name: requirements_all_wheels
|
||||||
path: ./requirements_all_wheels_*.txt
|
path: ./requirements_all_wheels_*.txt
|
||||||
@ -139,7 +140,7 @@ jobs:
|
|||||||
wheels-key: ${{ secrets.WHEELS_KEY }}
|
wheels-key: ${{ secrets.WHEELS_KEY }}
|
||||||
env-file: true
|
env-file: true
|
||||||
apk: "libffi-dev;openssl-dev;yaml-dev;nasm"
|
apk: "libffi-dev;openssl-dev;yaml-dev;nasm"
|
||||||
skip-binary: aiohttp
|
skip-binary: aiohttp;multidict;yarl
|
||||||
constraints: "homeassistant/package_constraints.txt"
|
constraints: "homeassistant/package_constraints.txt"
|
||||||
requirements-diff: "requirements_diff.txt"
|
requirements-diff: "requirements_diff.txt"
|
||||||
requirements: "requirements.txt"
|
requirements: "requirements.txt"
|
||||||
@ -211,7 +212,7 @@ jobs:
|
|||||||
wheels-key: ${{ secrets.WHEELS_KEY }}
|
wheels-key: ${{ secrets.WHEELS_KEY }}
|
||||||
env-file: true
|
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"
|
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"
|
||||||
skip-binary: aiohttp;charset-normalizer;grpcio;SQLAlchemy;protobuf;pydantic
|
skip-binary: aiohttp;charset-normalizer;grpcio;multidict;SQLAlchemy;protobuf;pydantic;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_old-cython.txt"
|
requirements: "requirements_old-cython.txt"
|
||||||
@ -226,7 +227,7 @@ jobs:
|
|||||||
wheels-key: ${{ secrets.WHEELS_KEY }}
|
wheels-key: ${{ secrets.WHEELS_KEY }}
|
||||||
env-file: true
|
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"
|
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"
|
||||||
skip-binary: aiohttp;charset-normalizer;grpcio;SQLAlchemy;protobuf;pydantic
|
skip-binary: aiohttp;charset-normalizer;grpcio;multidict;SQLAlchemy;protobuf;pydantic;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.txtaa"
|
||||||
@ -240,7 +241,7 @@ jobs:
|
|||||||
wheels-key: ${{ secrets.WHEELS_KEY }}
|
wheels-key: ${{ secrets.WHEELS_KEY }}
|
||||||
env-file: true
|
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"
|
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"
|
||||||
skip-binary: aiohttp;charset-normalizer;grpcio;SQLAlchemy;protobuf;pydantic
|
skip-binary: aiohttp;charset-normalizer;grpcio;multidict;SQLAlchemy;protobuf;pydantic;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.txtab"
|
requirements: "requirements_all.txtab"
|
||||||
@ -254,7 +255,7 @@ jobs:
|
|||||||
wheels-key: ${{ secrets.WHEELS_KEY }}
|
wheels-key: ${{ secrets.WHEELS_KEY }}
|
||||||
env-file: true
|
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"
|
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"
|
||||||
skip-binary: aiohttp;charset-normalizer;grpcio;SQLAlchemy;protobuf;pydantic
|
skip-binary: aiohttp;charset-normalizer;grpcio;multidict;SQLAlchemy;protobuf;pydantic;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.txtac"
|
requirements: "requirements_all.txtac"
|
||||||
|
@ -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.5.5
|
rev: v0.6.4
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff
|
- id: ruff
|
||||||
args:
|
args:
|
||||||
@ -12,7 +12,7 @@ repos:
|
|||||||
hooks:
|
hooks:
|
||||||
- id: codespell
|
- id: codespell
|
||||||
args:
|
args:
|
||||||
- --ignore-words-list=astroid,checkin,currenty,hass,iif,incomfort,lookin,nam,NotIn,pres,ser,ue
|
- --ignore-words-list=astroid,checkin,currenty,hass,iif,incomfort,lookin,nam,NotIn
|
||||||
- --skip="./.*,*.csv,*.json,*.ambr"
|
- --skip="./.*,*.csv,*.json,*.ambr"
|
||||||
- --quiet-level=2
|
- --quiet-level=2
|
||||||
exclude_types: [csv, json, html]
|
exclude_types: [csv, json, html]
|
||||||
|
@ -95,8 +95,7 @@ homeassistant.components.aruba.*
|
|||||||
homeassistant.components.arwn.*
|
homeassistant.components.arwn.*
|
||||||
homeassistant.components.aseko_pool_live.*
|
homeassistant.components.aseko_pool_live.*
|
||||||
homeassistant.components.assist_pipeline.*
|
homeassistant.components.assist_pipeline.*
|
||||||
homeassistant.components.asterisk_cdr.*
|
homeassistant.components.assist_satellite.*
|
||||||
homeassistant.components.asterisk_mbox.*
|
|
||||||
homeassistant.components.asuswrt.*
|
homeassistant.components.asuswrt.*
|
||||||
homeassistant.components.autarco.*
|
homeassistant.components.autarco.*
|
||||||
homeassistant.components.auth.*
|
homeassistant.components.auth.*
|
||||||
@ -112,6 +111,7 @@ homeassistant.components.bitcoin.*
|
|||||||
homeassistant.components.blockchain.*
|
homeassistant.components.blockchain.*
|
||||||
homeassistant.components.blue_current.*
|
homeassistant.components.blue_current.*
|
||||||
homeassistant.components.blueprint.*
|
homeassistant.components.blueprint.*
|
||||||
|
homeassistant.components.bluesound.*
|
||||||
homeassistant.components.bluetooth.*
|
homeassistant.components.bluetooth.*
|
||||||
homeassistant.components.bluetooth_adapters.*
|
homeassistant.components.bluetooth_adapters.*
|
||||||
homeassistant.components.bluetooth_tracker.*
|
homeassistant.components.bluetooth_tracker.*
|
||||||
@ -141,6 +141,7 @@ homeassistant.components.cpuspeed.*
|
|||||||
homeassistant.components.crownstone.*
|
homeassistant.components.crownstone.*
|
||||||
homeassistant.components.date.*
|
homeassistant.components.date.*
|
||||||
homeassistant.components.datetime.*
|
homeassistant.components.datetime.*
|
||||||
|
homeassistant.components.deako.*
|
||||||
homeassistant.components.deconz.*
|
homeassistant.components.deconz.*
|
||||||
homeassistant.components.default_config.*
|
homeassistant.components.default_config.*
|
||||||
homeassistant.components.demo.*
|
homeassistant.components.demo.*
|
||||||
@ -168,6 +169,7 @@ homeassistant.components.ecowitt.*
|
|||||||
homeassistant.components.efergy.*
|
homeassistant.components.efergy.*
|
||||||
homeassistant.components.electrasmart.*
|
homeassistant.components.electrasmart.*
|
||||||
homeassistant.components.electric_kiwi.*
|
homeassistant.components.electric_kiwi.*
|
||||||
|
homeassistant.components.elevenlabs.*
|
||||||
homeassistant.components.elgato.*
|
homeassistant.components.elgato.*
|
||||||
homeassistant.components.elkm1.*
|
homeassistant.components.elkm1.*
|
||||||
homeassistant.components.emulated_hue.*
|
homeassistant.components.emulated_hue.*
|
||||||
@ -197,7 +199,9 @@ homeassistant.components.fritzbox.*
|
|||||||
homeassistant.components.fritzbox_callmonitor.*
|
homeassistant.components.fritzbox_callmonitor.*
|
||||||
homeassistant.components.fronius.*
|
homeassistant.components.fronius.*
|
||||||
homeassistant.components.frontend.*
|
homeassistant.components.frontend.*
|
||||||
|
homeassistant.components.fujitsu_fglair.*
|
||||||
homeassistant.components.fully_kiosk.*
|
homeassistant.components.fully_kiosk.*
|
||||||
|
homeassistant.components.fyta.*
|
||||||
homeassistant.components.generic_hygrostat.*
|
homeassistant.components.generic_hygrostat.*
|
||||||
homeassistant.components.generic_thermostat.*
|
homeassistant.components.generic_thermostat.*
|
||||||
homeassistant.components.geo_location.*
|
homeassistant.components.geo_location.*
|
||||||
@ -207,6 +211,8 @@ homeassistant.components.glances.*
|
|||||||
homeassistant.components.goalzero.*
|
homeassistant.components.goalzero.*
|
||||||
homeassistant.components.google.*
|
homeassistant.components.google.*
|
||||||
homeassistant.components.google_assistant_sdk.*
|
homeassistant.components.google_assistant_sdk.*
|
||||||
|
homeassistant.components.google_cloud.*
|
||||||
|
homeassistant.components.google_photos.*
|
||||||
homeassistant.components.google_sheets.*
|
homeassistant.components.google_sheets.*
|
||||||
homeassistant.components.gpsd.*
|
homeassistant.components.gpsd.*
|
||||||
homeassistant.components.greeneye_monitor.*
|
homeassistant.components.greeneye_monitor.*
|
||||||
@ -277,6 +283,7 @@ homeassistant.components.lawn_mower.*
|
|||||||
homeassistant.components.lcn.*
|
homeassistant.components.lcn.*
|
||||||
homeassistant.components.ld2410_ble.*
|
homeassistant.components.ld2410_ble.*
|
||||||
homeassistant.components.led_ble.*
|
homeassistant.components.led_ble.*
|
||||||
|
homeassistant.components.lektrico.*
|
||||||
homeassistant.components.lidarr.*
|
homeassistant.components.lidarr.*
|
||||||
homeassistant.components.lifx.*
|
homeassistant.components.lifx.*
|
||||||
homeassistant.components.light.*
|
homeassistant.components.light.*
|
||||||
@ -293,7 +300,7 @@ homeassistant.components.london_underground.*
|
|||||||
homeassistant.components.lookin.*
|
homeassistant.components.lookin.*
|
||||||
homeassistant.components.luftdaten.*
|
homeassistant.components.luftdaten.*
|
||||||
homeassistant.components.madvr.*
|
homeassistant.components.madvr.*
|
||||||
homeassistant.components.mailbox.*
|
homeassistant.components.manual.*
|
||||||
homeassistant.components.map.*
|
homeassistant.components.map.*
|
||||||
homeassistant.components.mastodon.*
|
homeassistant.components.mastodon.*
|
||||||
homeassistant.components.matrix.*
|
homeassistant.components.matrix.*
|
||||||
@ -309,6 +316,7 @@ homeassistant.components.minecraft_server.*
|
|||||||
homeassistant.components.mjpeg.*
|
homeassistant.components.mjpeg.*
|
||||||
homeassistant.components.modbus.*
|
homeassistant.components.modbus.*
|
||||||
homeassistant.components.modem_callerid.*
|
homeassistant.components.modem_callerid.*
|
||||||
|
homeassistant.components.mold_indicator.*
|
||||||
homeassistant.components.monzo.*
|
homeassistant.components.monzo.*
|
||||||
homeassistant.components.moon.*
|
homeassistant.components.moon.*
|
||||||
homeassistant.components.mopeka.*
|
homeassistant.components.mopeka.*
|
||||||
@ -335,6 +343,7 @@ homeassistant.components.nut.*
|
|||||||
homeassistant.components.onboarding.*
|
homeassistant.components.onboarding.*
|
||||||
homeassistant.components.oncue.*
|
homeassistant.components.oncue.*
|
||||||
homeassistant.components.onewire.*
|
homeassistant.components.onewire.*
|
||||||
|
homeassistant.components.onkyo.*
|
||||||
homeassistant.components.open_meteo.*
|
homeassistant.components.open_meteo.*
|
||||||
homeassistant.components.openexchangerates.*
|
homeassistant.components.openexchangerates.*
|
||||||
homeassistant.components.opensky.*
|
homeassistant.components.opensky.*
|
||||||
@ -394,6 +403,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.sensoterra.*
|
||||||
homeassistant.components.senz.*
|
homeassistant.components.senz.*
|
||||||
homeassistant.components.sfr_box.*
|
homeassistant.components.sfr_box.*
|
||||||
homeassistant.components.shelly.*
|
homeassistant.components.shelly.*
|
||||||
@ -406,9 +416,11 @@ homeassistant.components.slack.*
|
|||||||
homeassistant.components.sleepiq.*
|
homeassistant.components.sleepiq.*
|
||||||
homeassistant.components.smhi.*
|
homeassistant.components.smhi.*
|
||||||
homeassistant.components.snooz.*
|
homeassistant.components.snooz.*
|
||||||
|
homeassistant.components.solarlog.*
|
||||||
homeassistant.components.sonarr.*
|
homeassistant.components.sonarr.*
|
||||||
homeassistant.components.speedtestdotnet.*
|
homeassistant.components.speedtestdotnet.*
|
||||||
homeassistant.components.sql.*
|
homeassistant.components.sql.*
|
||||||
|
homeassistant.components.squeezebox.*
|
||||||
homeassistant.components.ssdp.*
|
homeassistant.components.ssdp.*
|
||||||
homeassistant.components.starlink.*
|
homeassistant.components.starlink.*
|
||||||
homeassistant.components.statistics.*
|
homeassistant.components.statistics.*
|
||||||
|
54
CODEOWNERS
54
CODEOWNERS
@ -108,6 +108,8 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/anova/ @Lash-L
|
/tests/components/anova/ @Lash-L
|
||||||
/homeassistant/components/anthemav/ @hyralex
|
/homeassistant/components/anthemav/ @hyralex
|
||||||
/tests/components/anthemav/ @hyralex
|
/tests/components/anthemav/ @hyralex
|
||||||
|
/homeassistant/components/anthropic/ @Shulyaka
|
||||||
|
/tests/components/anthropic/ @Shulyaka
|
||||||
/homeassistant/components/aosmith/ @bdr99
|
/homeassistant/components/aosmith/ @bdr99
|
||||||
/tests/components/aosmith/ @bdr99
|
/tests/components/aosmith/ @bdr99
|
||||||
/homeassistant/components/apache_kafka/ @bachya
|
/homeassistant/components/apache_kafka/ @bachya
|
||||||
@ -141,6 +143,8 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/aseko_pool_live/ @milanmeu
|
/tests/components/aseko_pool_live/ @milanmeu
|
||||||
/homeassistant/components/assist_pipeline/ @balloob @synesthesiam
|
/homeassistant/components/assist_pipeline/ @balloob @synesthesiam
|
||||||
/tests/components/assist_pipeline/ @balloob @synesthesiam
|
/tests/components/assist_pipeline/ @balloob @synesthesiam
|
||||||
|
/homeassistant/components/assist_satellite/ @home-assistant/core @synesthesiam
|
||||||
|
/tests/components/assist_satellite/ @home-assistant/core @synesthesiam
|
||||||
/homeassistant/components/asuswrt/ @kennedyshead @ollo69
|
/homeassistant/components/asuswrt/ @kennedyshead @ollo69
|
||||||
/tests/components/asuswrt/ @kennedyshead @ollo69
|
/tests/components/asuswrt/ @kennedyshead @ollo69
|
||||||
/homeassistant/components/atag/ @MatsNL
|
/homeassistant/components/atag/ @MatsNL
|
||||||
@ -226,8 +230,8 @@ build.json @home-assistant/supervisor
|
|||||||
/homeassistant/components/bsblan/ @liudger
|
/homeassistant/components/bsblan/ @liudger
|
||||||
/tests/components/bsblan/ @liudger
|
/tests/components/bsblan/ @liudger
|
||||||
/homeassistant/components/bt_smarthub/ @typhoon2099
|
/homeassistant/components/bt_smarthub/ @typhoon2099
|
||||||
/homeassistant/components/bthome/ @Ernst79
|
/homeassistant/components/bthome/ @Ernst79 @thecode
|
||||||
/tests/components/bthome/ @Ernst79
|
/tests/components/bthome/ @Ernst79 @thecode
|
||||||
/homeassistant/components/buienradar/ @mjj4791 @ties @Robbie1221
|
/homeassistant/components/buienradar/ @mjj4791 @ties @Robbie1221
|
||||||
/tests/components/buienradar/ @mjj4791 @ties @Robbie1221
|
/tests/components/buienradar/ @mjj4791 @ties @Robbie1221
|
||||||
/homeassistant/components/button/ @home-assistant/core
|
/homeassistant/components/button/ @home-assistant/core
|
||||||
@ -292,6 +296,8 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/date/ @home-assistant/core
|
/tests/components/date/ @home-assistant/core
|
||||||
/homeassistant/components/datetime/ @home-assistant/core
|
/homeassistant/components/datetime/ @home-assistant/core
|
||||||
/tests/components/datetime/ @home-assistant/core
|
/tests/components/datetime/ @home-assistant/core
|
||||||
|
/homeassistant/components/deako/ @sebirdman @balake @deakolights
|
||||||
|
/tests/components/deako/ @sebirdman @balake @deakolights
|
||||||
/homeassistant/components/debugpy/ @frenck
|
/homeassistant/components/debugpy/ @frenck
|
||||||
/tests/components/debugpy/ @frenck
|
/tests/components/debugpy/ @frenck
|
||||||
/homeassistant/components/deconz/ @Kane610
|
/homeassistant/components/deconz/ @Kane610
|
||||||
@ -347,8 +353,8 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/dremel_3d_printer/ @tkdrob
|
/tests/components/dremel_3d_printer/ @tkdrob
|
||||||
/homeassistant/components/drop_connect/ @ChandlerSystems @pfrazer
|
/homeassistant/components/drop_connect/ @ChandlerSystems @pfrazer
|
||||||
/tests/components/drop_connect/ @ChandlerSystems @pfrazer
|
/tests/components/drop_connect/ @ChandlerSystems @pfrazer
|
||||||
/homeassistant/components/dsmr/ @Robbie1221 @frenck
|
/homeassistant/components/dsmr/ @Robbie1221
|
||||||
/tests/components/dsmr/ @Robbie1221 @frenck
|
/tests/components/dsmr/ @Robbie1221
|
||||||
/homeassistant/components/dsmr_reader/ @sorted-bits @glodenox @erwindouna
|
/homeassistant/components/dsmr_reader/ @sorted-bits @glodenox @erwindouna
|
||||||
/tests/components/dsmr_reader/ @sorted-bits @glodenox @erwindouna
|
/tests/components/dsmr_reader/ @sorted-bits @glodenox @erwindouna
|
||||||
/homeassistant/components/duotecno/ @cereal2nd
|
/homeassistant/components/duotecno/ @cereal2nd
|
||||||
@ -376,6 +382,8 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/electrasmart/ @jafar-atili
|
/tests/components/electrasmart/ @jafar-atili
|
||||||
/homeassistant/components/electric_kiwi/ @mikey0000
|
/homeassistant/components/electric_kiwi/ @mikey0000
|
||||||
/tests/components/electric_kiwi/ @mikey0000
|
/tests/components/electric_kiwi/ @mikey0000
|
||||||
|
/homeassistant/components/elevenlabs/ @sorgfresser
|
||||||
|
/tests/components/elevenlabs/ @sorgfresser
|
||||||
/homeassistant/components/elgato/ @frenck
|
/homeassistant/components/elgato/ @frenck
|
||||||
/tests/components/elgato/ @frenck
|
/tests/components/elgato/ @frenck
|
||||||
/homeassistant/components/elkm1/ @gwww @bdraco
|
/homeassistant/components/elkm1/ @gwww @bdraco
|
||||||
@ -429,6 +437,7 @@ build.json @home-assistant/supervisor
|
|||||||
/homeassistant/components/evil_genius_labs/ @balloob
|
/homeassistant/components/evil_genius_labs/ @balloob
|
||||||
/tests/components/evil_genius_labs/ @balloob
|
/tests/components/evil_genius_labs/ @balloob
|
||||||
/homeassistant/components/evohome/ @zxdavb
|
/homeassistant/components/evohome/ @zxdavb
|
||||||
|
/tests/components/evohome/ @zxdavb
|
||||||
/homeassistant/components/ezviz/ @RenierM26 @baqs
|
/homeassistant/components/ezviz/ @RenierM26 @baqs
|
||||||
/tests/components/ezviz/ @RenierM26 @baqs
|
/tests/components/ezviz/ @RenierM26 @baqs
|
||||||
/homeassistant/components/faa_delays/ @ntilley905
|
/homeassistant/components/faa_delays/ @ntilley905
|
||||||
@ -494,6 +503,8 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/frontend/ @home-assistant/frontend
|
/tests/components/frontend/ @home-assistant/frontend
|
||||||
/homeassistant/components/frontier_silicon/ @wlcrs
|
/homeassistant/components/frontier_silicon/ @wlcrs
|
||||||
/tests/components/frontier_silicon/ @wlcrs
|
/tests/components/frontier_silicon/ @wlcrs
|
||||||
|
/homeassistant/components/fujitsu_fglair/ @crevetor
|
||||||
|
/tests/components/fujitsu_fglair/ @crevetor
|
||||||
/homeassistant/components/fully_kiosk/ @cgarwood
|
/homeassistant/components/fully_kiosk/ @cgarwood
|
||||||
/tests/components/fully_kiosk/ @cgarwood
|
/tests/components/fully_kiosk/ @cgarwood
|
||||||
/homeassistant/components/fyta/ @dontinelli
|
/homeassistant/components/fyta/ @dontinelli
|
||||||
@ -540,11 +551,14 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/google_assistant/ @home-assistant/cloud
|
/tests/components/google_assistant/ @home-assistant/cloud
|
||||||
/homeassistant/components/google_assistant_sdk/ @tronikos
|
/homeassistant/components/google_assistant_sdk/ @tronikos
|
||||||
/tests/components/google_assistant_sdk/ @tronikos
|
/tests/components/google_assistant_sdk/ @tronikos
|
||||||
/homeassistant/components/google_cloud/ @lufton
|
/homeassistant/components/google_cloud/ @lufton @tronikos
|
||||||
|
/tests/components/google_cloud/ @lufton @tronikos
|
||||||
/homeassistant/components/google_generative_ai_conversation/ @tronikos
|
/homeassistant/components/google_generative_ai_conversation/ @tronikos
|
||||||
/tests/components/google_generative_ai_conversation/ @tronikos
|
/tests/components/google_generative_ai_conversation/ @tronikos
|
||||||
/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
|
||||||
|
/tests/components/google_photos/ @allenporter
|
||||||
/homeassistant/components/google_sheets/ @tkdrob
|
/homeassistant/components/google_sheets/ @tkdrob
|
||||||
/tests/components/google_sheets/ @tkdrob
|
/tests/components/google_sheets/ @tkdrob
|
||||||
/homeassistant/components/google_tasks/ @allenporter
|
/homeassistant/components/google_tasks/ @allenporter
|
||||||
@ -622,6 +636,8 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/homewizard/ @DCSBL
|
/tests/components/homewizard/ @DCSBL
|
||||||
/homeassistant/components/honeywell/ @rdfurman @mkmer
|
/homeassistant/components/honeywell/ @rdfurman @mkmer
|
||||||
/tests/components/honeywell/ @rdfurman @mkmer
|
/tests/components/honeywell/ @rdfurman @mkmer
|
||||||
|
/homeassistant/components/html5/ @alexyao2015
|
||||||
|
/tests/components/html5/ @alexyao2015
|
||||||
/homeassistant/components/http/ @home-assistant/core
|
/homeassistant/components/http/ @home-assistant/core
|
||||||
/tests/components/http/ @home-assistant/core
|
/tests/components/http/ @home-assistant/core
|
||||||
/homeassistant/components/huawei_lte/ @scop @fphammerle
|
/homeassistant/components/huawei_lte/ @scop @fphammerle
|
||||||
@ -700,8 +716,8 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/ios/ @robbiet480
|
/tests/components/ios/ @robbiet480
|
||||||
/homeassistant/components/iotawatt/ @gtdiehl @jyavenard
|
/homeassistant/components/iotawatt/ @gtdiehl @jyavenard
|
||||||
/tests/components/iotawatt/ @gtdiehl @jyavenard
|
/tests/components/iotawatt/ @gtdiehl @jyavenard
|
||||||
/homeassistant/components/iotty/ @pburgio
|
/homeassistant/components/iotty/ @pburgio @shapournemati-iotty
|
||||||
/tests/components/iotty/ @pburgio
|
/tests/components/iotty/ @pburgio @shapournemati-iotty
|
||||||
/homeassistant/components/iperf3/ @rohankapoorcom
|
/homeassistant/components/iperf3/ @rohankapoorcom
|
||||||
/homeassistant/components/ipma/ @dgomes
|
/homeassistant/components/ipma/ @dgomes
|
||||||
/tests/components/ipma/ @dgomes
|
/tests/components/ipma/ @dgomes
|
||||||
@ -714,6 +730,8 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/iron_os/ @tr4nt0r
|
/tests/components/iron_os/ @tr4nt0r
|
||||||
/homeassistant/components/isal/ @bdraco
|
/homeassistant/components/isal/ @bdraco
|
||||||
/tests/components/isal/ @bdraco
|
/tests/components/isal/ @bdraco
|
||||||
|
/homeassistant/components/iskra/ @iskramis
|
||||||
|
/tests/components/iskra/ @iskramis
|
||||||
/homeassistant/components/islamic_prayer_times/ @engrbm87 @cpfair
|
/homeassistant/components/islamic_prayer_times/ @engrbm87 @cpfair
|
||||||
/tests/components/islamic_prayer_times/ @engrbm87 @cpfair
|
/tests/components/islamic_prayer_times/ @engrbm87 @cpfair
|
||||||
/homeassistant/components/israel_rail/ @shaiu
|
/homeassistant/components/israel_rail/ @shaiu
|
||||||
@ -790,8 +808,12 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/leaone/ @bdraco
|
/tests/components/leaone/ @bdraco
|
||||||
/homeassistant/components/led_ble/ @bdraco
|
/homeassistant/components/led_ble/ @bdraco
|
||||||
/tests/components/led_ble/ @bdraco
|
/tests/components/led_ble/ @bdraco
|
||||||
|
/homeassistant/components/lektrico/ @lektrico
|
||||||
|
/tests/components/lektrico/ @lektrico
|
||||||
/homeassistant/components/lg_netcast/ @Drafteed @splinter98
|
/homeassistant/components/lg_netcast/ @Drafteed @splinter98
|
||||||
/tests/components/lg_netcast/ @Drafteed @splinter98
|
/tests/components/lg_netcast/ @Drafteed @splinter98
|
||||||
|
/homeassistant/components/lg_thinq/ @LG-ThinQ-Integration
|
||||||
|
/tests/components/lg_thinq/ @LG-ThinQ-Integration
|
||||||
/homeassistant/components/lidarr/ @tkdrob
|
/homeassistant/components/lidarr/ @tkdrob
|
||||||
/tests/components/lidarr/ @tkdrob
|
/tests/components/lidarr/ @tkdrob
|
||||||
/homeassistant/components/lifx/ @Djelibeybi
|
/homeassistant/components/lifx/ @Djelibeybi
|
||||||
@ -821,8 +843,6 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/logbook/ @home-assistant/core
|
/tests/components/logbook/ @home-assistant/core
|
||||||
/homeassistant/components/logger/ @home-assistant/core
|
/homeassistant/components/logger/ @home-assistant/core
|
||||||
/tests/components/logger/ @home-assistant/core
|
/tests/components/logger/ @home-assistant/core
|
||||||
/homeassistant/components/logi_circle/ @evanjd
|
|
||||||
/tests/components/logi_circle/ @evanjd
|
|
||||||
/homeassistant/components/london_underground/ @jpbede
|
/homeassistant/components/london_underground/ @jpbede
|
||||||
/tests/components/london_underground/ @jpbede
|
/tests/components/london_underground/ @jpbede
|
||||||
/homeassistant/components/lookin/ @ANMalko @bdraco
|
/homeassistant/components/lookin/ @ANMalko @bdraco
|
||||||
@ -965,6 +985,8 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/nfandroidtv/ @tkdrob
|
/tests/components/nfandroidtv/ @tkdrob
|
||||||
/homeassistant/components/nibe_heatpump/ @elupus
|
/homeassistant/components/nibe_heatpump/ @elupus
|
||||||
/tests/components/nibe_heatpump/ @elupus
|
/tests/components/nibe_heatpump/ @elupus
|
||||||
|
/homeassistant/components/nice_go/ @IceBotYT
|
||||||
|
/tests/components/nice_go/ @IceBotYT
|
||||||
/homeassistant/components/nightscout/ @marciogranzotto
|
/homeassistant/components/nightscout/ @marciogranzotto
|
||||||
/tests/components/nightscout/ @marciogranzotto
|
/tests/components/nightscout/ @marciogranzotto
|
||||||
/homeassistant/components/nilu/ @hfurubotten
|
/homeassistant/components/nilu/ @hfurubotten
|
||||||
@ -1051,8 +1073,8 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/otbr/ @home-assistant/core
|
/tests/components/otbr/ @home-assistant/core
|
||||||
/homeassistant/components/ourgroceries/ @OnFreund
|
/homeassistant/components/ourgroceries/ @OnFreund
|
||||||
/tests/components/ourgroceries/ @OnFreund
|
/tests/components/ourgroceries/ @OnFreund
|
||||||
/homeassistant/components/overkiz/ @imicknl @vlebourl @tetienne @nyroDev @tronix117
|
/homeassistant/components/overkiz/ @imicknl @vlebourl @tetienne @nyroDev @tronix117 @alexfp14
|
||||||
/tests/components/overkiz/ @imicknl @vlebourl @tetienne @nyroDev @tronix117
|
/tests/components/overkiz/ @imicknl @vlebourl @tetienne @nyroDev @tronix117 @alexfp14
|
||||||
/homeassistant/components/ovo_energy/ @timmo001
|
/homeassistant/components/ovo_energy/ @timmo001
|
||||||
/tests/components/ovo_energy/ @timmo001
|
/tests/components/ovo_energy/ @timmo001
|
||||||
/homeassistant/components/p1_monitor/ @klaasnicolaas
|
/homeassistant/components/p1_monitor/ @klaasnicolaas
|
||||||
@ -1268,6 +1290,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/sensoterra/ @markruys
|
||||||
|
/tests/components/sensoterra/ @markruys
|
||||||
/homeassistant/components/sentry/ @dcramer @frenck
|
/homeassistant/components/sentry/ @dcramer @frenck
|
||||||
/tests/components/sentry/ @dcramer @frenck
|
/tests/components/sentry/ @dcramer @frenck
|
||||||
/homeassistant/components/senz/ @milanmeu
|
/homeassistant/components/senz/ @milanmeu
|
||||||
@ -1322,6 +1346,8 @@ build.json @home-assistant/supervisor
|
|||||||
/homeassistant/components/smarty/ @z0mbieprocess
|
/homeassistant/components/smarty/ @z0mbieprocess
|
||||||
/homeassistant/components/smhi/ @gjohansson-ST
|
/homeassistant/components/smhi/ @gjohansson-ST
|
||||||
/tests/components/smhi/ @gjohansson-ST
|
/tests/components/smhi/ @gjohansson-ST
|
||||||
|
/homeassistant/components/smlight/ @tl-sl
|
||||||
|
/tests/components/smlight/ @tl-sl
|
||||||
/homeassistant/components/sms/ @ocalvo
|
/homeassistant/components/sms/ @ocalvo
|
||||||
/tests/components/sms/ @ocalvo
|
/tests/components/sms/ @ocalvo
|
||||||
/homeassistant/components/snapcast/ @luar123
|
/homeassistant/components/snapcast/ @luar123
|
||||||
@ -1484,6 +1510,8 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/tomorrowio/ @raman325 @lymanepp
|
/tests/components/tomorrowio/ @raman325 @lymanepp
|
||||||
/homeassistant/components/totalconnect/ @austinmroczek
|
/homeassistant/components/totalconnect/ @austinmroczek
|
||||||
/tests/components/totalconnect/ @austinmroczek
|
/tests/components/totalconnect/ @austinmroczek
|
||||||
|
/homeassistant/components/touchline_sl/ @jnsgruk
|
||||||
|
/tests/components/touchline_sl/ @jnsgruk
|
||||||
/homeassistant/components/tplink/ @rytilahti @bdraco @sdb9696
|
/homeassistant/components/tplink/ @rytilahti @bdraco @sdb9696
|
||||||
/tests/components/tplink/ @rytilahti @bdraco @sdb9696
|
/tests/components/tplink/ @rytilahti @bdraco @sdb9696
|
||||||
/homeassistant/components/tplink_omada/ @MarkGodwin
|
/homeassistant/components/tplink_omada/ @MarkGodwin
|
||||||
@ -1612,6 +1640,8 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/webostv/ @thecode
|
/tests/components/webostv/ @thecode
|
||||||
/homeassistant/components/websocket_api/ @home-assistant/core
|
/homeassistant/components/websocket_api/ @home-assistant/core
|
||||||
/tests/components/websocket_api/ @home-assistant/core
|
/tests/components/websocket_api/ @home-assistant/core
|
||||||
|
/homeassistant/components/weheat/ @jesperraemaekers
|
||||||
|
/tests/components/weheat/ @jesperraemaekers
|
||||||
/homeassistant/components/wemo/ @esev
|
/homeassistant/components/wemo/ @esev
|
||||||
/tests/components/wemo/ @esev
|
/tests/components/wemo/ @esev
|
||||||
/homeassistant/components/whirlpool/ @abmantis @mkmer
|
/homeassistant/components/whirlpool/ @abmantis @mkmer
|
||||||
@ -1649,6 +1679,8 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/xiaomi_miio/ @rytilahti @syssi @starkillerOG
|
/tests/components/xiaomi_miio/ @rytilahti @syssi @starkillerOG
|
||||||
/homeassistant/components/xiaomi_tv/ @simse
|
/homeassistant/components/xiaomi_tv/ @simse
|
||||||
/homeassistant/components/xmpp/ @fabaff @flowolf
|
/homeassistant/components/xmpp/ @fabaff @flowolf
|
||||||
|
/homeassistant/components/yale/ @bdraco
|
||||||
|
/tests/components/yale/ @bdraco
|
||||||
/homeassistant/components/yale_smart_alarm/ @gjohansson-ST
|
/homeassistant/components/yale_smart_alarm/ @gjohansson-ST
|
||||||
/tests/components/yale_smart_alarm/ @gjohansson-ST
|
/tests/components/yale_smart_alarm/ @gjohansson-ST
|
||||||
/homeassistant/components/yalexs_ble/ @bdraco
|
/homeassistant/components/yalexs_ble/ @bdraco
|
||||||
|
@ -42,7 +42,8 @@ WORKDIR /usr/src
|
|||||||
|
|
||||||
# Setup hass-release
|
# Setup hass-release
|
||||||
RUN git clone --depth 1 https://github.com/home-assistant/hass-release \
|
RUN git clone --depth 1 https://github.com/home-assistant/hass-release \
|
||||||
&& uv pip install --system -e hass-release/
|
&& uv pip install --system -e hass-release/ \
|
||||||
|
&& chown -R vscode /usr/src/hass-release/data
|
||||||
|
|
||||||
USER vscode
|
USER vscode
|
||||||
ENV VIRTUAL_ENV="/home/vscode/.local/ha-venv"
|
ENV VIRTUAL_ENV="/home/vscode/.local/ha-venv"
|
||||||
|
@ -18,9 +18,12 @@ from homeassistant.const import (
|
|||||||
EVENT_THEMES_UPDATED,
|
EVENT_THEMES_UPDATED,
|
||||||
)
|
)
|
||||||
from homeassistant.helpers.area_registry import EVENT_AREA_REGISTRY_UPDATED
|
from homeassistant.helpers.area_registry import EVENT_AREA_REGISTRY_UPDATED
|
||||||
|
from homeassistant.helpers.category_registry import EVENT_CATEGORY_REGISTRY_UPDATED
|
||||||
from homeassistant.helpers.device_registry import EVENT_DEVICE_REGISTRY_UPDATED
|
from homeassistant.helpers.device_registry import EVENT_DEVICE_REGISTRY_UPDATED
|
||||||
from homeassistant.helpers.entity_registry import EVENT_ENTITY_REGISTRY_UPDATED
|
from homeassistant.helpers.entity_registry import EVENT_ENTITY_REGISTRY_UPDATED
|
||||||
|
from homeassistant.helpers.floor_registry import EVENT_FLOOR_REGISTRY_UPDATED
|
||||||
from homeassistant.helpers.issue_registry import EVENT_REPAIRS_ISSUE_REGISTRY_UPDATED
|
from homeassistant.helpers.issue_registry import EVENT_REPAIRS_ISSUE_REGISTRY_UPDATED
|
||||||
|
from homeassistant.helpers.label_registry import EVENT_LABEL_REGISTRY_UPDATED
|
||||||
from homeassistant.util.event_type import EventType
|
from homeassistant.util.event_type import EventType
|
||||||
|
|
||||||
# These are events that do not contain any sensitive data
|
# These are events that do not contain any sensitive data
|
||||||
@ -41,4 +44,7 @@ SUBSCRIBE_ALLOWLIST: Final[set[EventType[Any] | str]] = {
|
|||||||
EVENT_SHOPPING_LIST_UPDATED,
|
EVENT_SHOPPING_LIST_UPDATED,
|
||||||
EVENT_STATE_CHANGED,
|
EVENT_STATE_CHANGED,
|
||||||
EVENT_THEMES_UPDATED,
|
EVENT_THEMES_UPDATED,
|
||||||
|
EVENT_LABEL_REGISTRY_UPDATED,
|
||||||
|
EVENT_CATEGORY_REGISTRY_UPDATED,
|
||||||
|
EVENT_FLOOR_REGISTRY_UPDATED,
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,8 @@ import glob
|
|||||||
from http.client import HTTPConnection
|
from http.client import HTTPConnection
|
||||||
import importlib
|
import importlib
|
||||||
import os
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
from ssl import SSLContext
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
@ -143,6 +145,78 @@ _BLOCKING_CALLS: tuple[BlockingCall, ...] = (
|
|||||||
strict_core=False,
|
strict_core=False,
|
||||||
skip_for_tests=True,
|
skip_for_tests=True,
|
||||||
),
|
),
|
||||||
|
BlockingCall(
|
||||||
|
original_func=SSLContext.load_default_certs,
|
||||||
|
object=SSLContext,
|
||||||
|
function="load_default_certs",
|
||||||
|
check_allowed=None,
|
||||||
|
strict=False,
|
||||||
|
strict_core=False,
|
||||||
|
skip_for_tests=True,
|
||||||
|
),
|
||||||
|
BlockingCall(
|
||||||
|
original_func=SSLContext.load_verify_locations,
|
||||||
|
object=SSLContext,
|
||||||
|
function="load_verify_locations",
|
||||||
|
check_allowed=None,
|
||||||
|
strict=False,
|
||||||
|
strict_core=False,
|
||||||
|
skip_for_tests=True,
|
||||||
|
),
|
||||||
|
BlockingCall(
|
||||||
|
original_func=SSLContext.load_cert_chain,
|
||||||
|
object=SSLContext,
|
||||||
|
function="load_cert_chain",
|
||||||
|
check_allowed=None,
|
||||||
|
strict=False,
|
||||||
|
strict_core=False,
|
||||||
|
skip_for_tests=True,
|
||||||
|
),
|
||||||
|
BlockingCall(
|
||||||
|
original_func=Path.open,
|
||||||
|
object=Path,
|
||||||
|
function="open",
|
||||||
|
check_allowed=_check_file_allowed,
|
||||||
|
strict=False,
|
||||||
|
strict_core=False,
|
||||||
|
skip_for_tests=True,
|
||||||
|
),
|
||||||
|
BlockingCall(
|
||||||
|
original_func=Path.read_text,
|
||||||
|
object=Path,
|
||||||
|
function="read_text",
|
||||||
|
check_allowed=_check_file_allowed,
|
||||||
|
strict=False,
|
||||||
|
strict_core=False,
|
||||||
|
skip_for_tests=True,
|
||||||
|
),
|
||||||
|
BlockingCall(
|
||||||
|
original_func=Path.read_bytes,
|
||||||
|
object=Path,
|
||||||
|
function="read_bytes",
|
||||||
|
check_allowed=_check_file_allowed,
|
||||||
|
strict=False,
|
||||||
|
strict_core=False,
|
||||||
|
skip_for_tests=True,
|
||||||
|
),
|
||||||
|
BlockingCall(
|
||||||
|
original_func=Path.write_text,
|
||||||
|
object=Path,
|
||||||
|
function="write_text",
|
||||||
|
check_allowed=_check_file_allowed,
|
||||||
|
strict=False,
|
||||||
|
strict_core=False,
|
||||||
|
skip_for_tests=True,
|
||||||
|
),
|
||||||
|
BlockingCall(
|
||||||
|
original_func=Path.write_bytes,
|
||||||
|
object=Path,
|
||||||
|
function="write_bytes",
|
||||||
|
check_allowed=_check_file_allowed,
|
||||||
|
strict=False,
|
||||||
|
strict_core=False,
|
||||||
|
skip_for_tests=True,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -586,10 +586,10 @@ async def async_enable_logging(
|
|||||||
logging.getLogger("aiohttp.access").setLevel(logging.WARNING)
|
logging.getLogger("aiohttp.access").setLevel(logging.WARNING)
|
||||||
logging.getLogger("httpx").setLevel(logging.WARNING)
|
logging.getLogger("httpx").setLevel(logging.WARNING)
|
||||||
|
|
||||||
sys.excepthook = lambda *args: logging.getLogger(None).exception(
|
sys.excepthook = lambda *args: logging.getLogger().exception(
|
||||||
"Uncaught exception", exc_info=args
|
"Uncaught exception", exc_info=args
|
||||||
)
|
)
|
||||||
threading.excepthook = lambda args: logging.getLogger(None).exception(
|
threading.excepthook = lambda args: logging.getLogger().exception(
|
||||||
"Uncaught thread exception",
|
"Uncaught thread exception",
|
||||||
exc_info=( # type: ignore[arg-type]
|
exc_info=( # type: ignore[arg-type]
|
||||||
args.exc_type,
|
args.exc_type,
|
||||||
@ -616,10 +616,9 @@ async def async_enable_logging(
|
|||||||
_create_log_file, err_log_path, log_rotate_days
|
_create_log_file, err_log_path, log_rotate_days
|
||||||
)
|
)
|
||||||
|
|
||||||
err_handler.setLevel(logging.INFO if verbose else logging.WARNING)
|
|
||||||
err_handler.setFormatter(logging.Formatter(fmt, datefmt=FORMAT_DATETIME))
|
err_handler.setFormatter(logging.Formatter(fmt, datefmt=FORMAT_DATETIME))
|
||||||
|
|
||||||
logger = logging.getLogger("")
|
logger = logging.getLogger()
|
||||||
logger.addHandler(err_handler)
|
logger.addHandler(err_handler)
|
||||||
logger.setLevel(logging.INFO if verbose else logging.WARNING)
|
logger.setLevel(logging.INFO if verbose else logging.WARNING)
|
||||||
|
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"domain": "asterisk",
|
|
||||||
"name": "Asterisk",
|
|
||||||
"integrations": ["asterisk_cdr", "asterisk_mbox"]
|
|
||||||
}
|
|
5
homeassistant/brands/fujitsu.json
Normal file
5
homeassistant/brands/fujitsu.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"domain": "fujitsu",
|
||||||
|
"name": "Fujitsu",
|
||||||
|
"integrations": ["fujitsu_anywair", "fujitsu_fglair"]
|
||||||
|
}
|
@ -9,6 +9,7 @@
|
|||||||
"google_generative_ai_conversation",
|
"google_generative_ai_conversation",
|
||||||
"google_mail",
|
"google_mail",
|
||||||
"google_maps",
|
"google_maps",
|
||||||
|
"google_photos",
|
||||||
"google_pubsub",
|
"google_pubsub",
|
||||||
"google_sheets",
|
"google_sheets",
|
||||||
"google_tasks",
|
"google_tasks",
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"domain": "lg",
|
"domain": "lg",
|
||||||
"name": "LG",
|
"name": "LG",
|
||||||
"integrations": ["lg_netcast", "lg_soundbar", "webostv"]
|
"integrations": ["lg_netcast", "lg_thinq", "lg_soundbar", "webostv"]
|
||||||
}
|
}
|
||||||
|
5
homeassistant/brands/roth.json
Normal file
5
homeassistant/brands/roth.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"domain": "roth",
|
||||||
|
"name": "Roth",
|
||||||
|
"integrations": ["touchline", "touchline_sl"]
|
||||||
|
}
|
@ -1,5 +1,11 @@
|
|||||||
{
|
{
|
||||||
"domain": "yale",
|
"domain": "yale",
|
||||||
"name": "Yale",
|
"name": "Yale",
|
||||||
"integrations": ["august", "yale_smart_alarm", "yalexs_ble", "yale_home"]
|
"integrations": [
|
||||||
|
"august",
|
||||||
|
"yale_smart_alarm",
|
||||||
|
"yalexs_ble",
|
||||||
|
"yale_home",
|
||||||
|
"yale"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
@ -6,52 +6,3 @@ Component design guidelines:
|
|||||||
format "<DOMAIN>.<OBJECT_ID>".
|
format "<DOMAIN>.<OBJECT_ID>".
|
||||||
- Each component should publish services only under its own domain.
|
- Each component should publish services only under its own domain.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from homeassistant.core import HomeAssistant, split_entity_id
|
|
||||||
from homeassistant.helpers.frame import report
|
|
||||||
from homeassistant.helpers.group import expand_entity_ids
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def is_on(hass: HomeAssistant, entity_id: str | None = None) -> bool:
|
|
||||||
"""Load up the module to call the is_on method.
|
|
||||||
|
|
||||||
If there is no entity id given we will check all.
|
|
||||||
"""
|
|
||||||
report(
|
|
||||||
(
|
|
||||||
"uses homeassistant.components.is_on."
|
|
||||||
" This is deprecated and will stop working in Home Assistant 2024.9, it"
|
|
||||||
" should be updated to use the function of the platform directly."
|
|
||||||
),
|
|
||||||
error_if_core=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
if entity_id:
|
|
||||||
entity_ids = expand_entity_ids(hass, [entity_id])
|
|
||||||
else:
|
|
||||||
entity_ids = hass.states.entity_ids()
|
|
||||||
|
|
||||||
for ent_id in entity_ids:
|
|
||||||
domain = split_entity_id(ent_id)[0]
|
|
||||||
|
|
||||||
try:
|
|
||||||
component = getattr(hass.components, domain)
|
|
||||||
|
|
||||||
except ImportError:
|
|
||||||
_LOGGER.error("Failed to call %s.is_on: component not found", domain)
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not hasattr(component, "is_on"):
|
|
||||||
_LOGGER.warning("Integration %s has no is_on method", domain)
|
|
||||||
continue
|
|
||||||
|
|
||||||
if component.is_on(ent_id):
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
@ -7,8 +7,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"services": {
|
"services": {
|
||||||
"capture_image": "mdi:camera",
|
"capture_image": {
|
||||||
"change_setting": "mdi:cog",
|
"service": "mdi:camera"
|
||||||
"trigger_automation": "mdi:play"
|
},
|
||||||
|
"change_setting": {
|
||||||
|
"service": "mdi:cog"
|
||||||
|
},
|
||||||
|
"trigger_automation": {
|
||||||
|
"service": "mdi:play"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ from homeassistant.const import (
|
|||||||
UV_INDEX,
|
UV_INDEX,
|
||||||
UnitOfIrradiance,
|
UnitOfIrradiance,
|
||||||
UnitOfLength,
|
UnitOfLength,
|
||||||
|
UnitOfPressure,
|
||||||
UnitOfSpeed,
|
UnitOfSpeed,
|
||||||
UnitOfTemperature,
|
UnitOfTemperature,
|
||||||
UnitOfTime,
|
UnitOfTime,
|
||||||
@ -279,6 +280,15 @@ SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
|
|||||||
value_fn=lambda data: cast(float, data[API_METRIC][ATTR_VALUE]),
|
value_fn=lambda data: cast(float, data[API_METRIC][ATTR_VALUE]),
|
||||||
translation_key="realfeel_temperature_shade",
|
translation_key="realfeel_temperature_shade",
|
||||||
),
|
),
|
||||||
|
AccuWeatherSensorDescription(
|
||||||
|
key="RelativeHumidity",
|
||||||
|
device_class=SensorDeviceClass.HUMIDITY,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
|
value_fn=lambda data: cast(int, data),
|
||||||
|
translation_key="humidity",
|
||||||
|
),
|
||||||
AccuWeatherSensorDescription(
|
AccuWeatherSensorDescription(
|
||||||
key="Precipitation",
|
key="Precipitation",
|
||||||
device_class=SensorDeviceClass.PRECIPITATION_INTENSITY,
|
device_class=SensorDeviceClass.PRECIPITATION_INTENSITY,
|
||||||
@ -288,6 +298,16 @@ SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
|
|||||||
attr_fn=lambda data: {"type": data["PrecipitationType"]},
|
attr_fn=lambda data: {"type": data["PrecipitationType"]},
|
||||||
translation_key="precipitation",
|
translation_key="precipitation",
|
||||||
),
|
),
|
||||||
|
AccuWeatherSensorDescription(
|
||||||
|
key="Pressure",
|
||||||
|
device_class=SensorDeviceClass.PRESSURE,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
suggested_display_precision=0,
|
||||||
|
native_unit_of_measurement=UnitOfPressure.HPA,
|
||||||
|
value_fn=lambda data: cast(float, data[API_METRIC][ATTR_VALUE]),
|
||||||
|
translation_key="pressure",
|
||||||
|
),
|
||||||
AccuWeatherSensorDescription(
|
AccuWeatherSensorDescription(
|
||||||
key="PressureTendency",
|
key="PressureTendency",
|
||||||
device_class=SensorDeviceClass.ENUM,
|
device_class=SensorDeviceClass.ENUM,
|
||||||
@ -295,9 +315,19 @@ SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
|
|||||||
value_fn=lambda data: cast(str, data["LocalizedText"]).lower(),
|
value_fn=lambda data: cast(str, data["LocalizedText"]).lower(),
|
||||||
translation_key="pressure_tendency",
|
translation_key="pressure_tendency",
|
||||||
),
|
),
|
||||||
|
AccuWeatherSensorDescription(
|
||||||
|
key="Temperature",
|
||||||
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
|
value_fn=lambda data: cast(float, data[API_METRIC][ATTR_VALUE]),
|
||||||
|
translation_key="temperature",
|
||||||
|
),
|
||||||
AccuWeatherSensorDescription(
|
AccuWeatherSensorDescription(
|
||||||
key="UVIndex",
|
key="UVIndex",
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
native_unit_of_measurement=UV_INDEX,
|
native_unit_of_measurement=UV_INDEX,
|
||||||
value_fn=lambda data: cast(int, data),
|
value_fn=lambda data: cast(int, data),
|
||||||
attr_fn=lambda data: {ATTR_LEVEL: data["UVIndexText"]},
|
attr_fn=lambda data: {ATTR_LEVEL: data["UVIndexText"]},
|
||||||
@ -324,6 +354,7 @@ SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
|
|||||||
AccuWeatherSensorDescription(
|
AccuWeatherSensorDescription(
|
||||||
key="Wind",
|
key="Wind",
|
||||||
device_class=SensorDeviceClass.WIND_SPEED,
|
device_class=SensorDeviceClass.WIND_SPEED,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
native_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR,
|
native_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR,
|
||||||
value_fn=lambda data: cast(float, data[ATTR_SPEED][API_METRIC][ATTR_VALUE]),
|
value_fn=lambda data: cast(float, data[ATTR_SPEED][API_METRIC][ATTR_VALUE]),
|
||||||
|
@ -81,7 +81,7 @@ class AcerSwitch(SwitchEntity):
|
|||||||
write_timeout: int,
|
write_timeout: int,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Init of the Acer projector."""
|
"""Init of the Acer projector."""
|
||||||
self.ser = serial.Serial(
|
self.serial = serial.Serial(
|
||||||
port=serial_port, timeout=timeout, write_timeout=write_timeout
|
port=serial_port, timeout=timeout, write_timeout=write_timeout
|
||||||
)
|
)
|
||||||
self._serial_port = serial_port
|
self._serial_port = serial_port
|
||||||
@ -99,16 +99,16 @@ class AcerSwitch(SwitchEntity):
|
|||||||
# was disconnected during runtime.
|
# was disconnected during runtime.
|
||||||
# This way the projector can be reconnected and will still work
|
# This way the projector can be reconnected and will still work
|
||||||
try:
|
try:
|
||||||
if not self.ser.is_open:
|
if not self.serial.is_open:
|
||||||
self.ser.open()
|
self.serial.open()
|
||||||
self.ser.write(msg.encode("utf-8"))
|
self.serial.write(msg.encode("utf-8"))
|
||||||
# Size is an experience value there is no real limit.
|
# Size is an experience value there is no real limit.
|
||||||
# AFAIK there is no limit and no end character so we will usually
|
# AFAIK there is no limit and no end character so we will usually
|
||||||
# need to wait for timeout
|
# need to wait for timeout
|
||||||
ret = self.ser.read_until(size=20).decode("utf-8")
|
ret = self.serial.read_until(size=20).decode("utf-8")
|
||||||
except serial.SerialException:
|
except serial.SerialException:
|
||||||
_LOGGER.error("Problem communicating with %s", self._serial_port)
|
_LOGGER.error("Problem communicating with %s", self._serial_port)
|
||||||
self.ser.close()
|
self.serial.close()
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def _write_read_format(self, msg: str) -> str:
|
def _write_read_format(self, msg: str) -> str:
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import Platform
|
from homeassistant.const import Platform
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
import homeassistant.helpers.entity_registry as er
|
||||||
|
|
||||||
from .hub import PulseHub
|
from .hub import PulseHub
|
||||||
|
|
||||||
@ -17,6 +18,9 @@ async def async_setup_entry(
|
|||||||
hass: HomeAssistant, config_entry: AcmedaConfigEntry
|
hass: HomeAssistant, config_entry: AcmedaConfigEntry
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Set up Rollease Acmeda Automate hub from a config entry."""
|
"""Set up Rollease Acmeda Automate hub from a config entry."""
|
||||||
|
|
||||||
|
await _migrate_unique_ids(hass, config_entry)
|
||||||
|
|
||||||
hub = PulseHub(hass, config_entry)
|
hub = PulseHub(hass, config_entry)
|
||||||
|
|
||||||
if not await hub.async_setup():
|
if not await hub.async_setup():
|
||||||
@ -28,6 +32,19 @@ async def async_setup_entry(
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def _migrate_unique_ids(hass: HomeAssistant, entry: AcmedaConfigEntry) -> None:
|
||||||
|
"""Migrate pre-config flow unique ids."""
|
||||||
|
entity_registry = er.async_get(hass)
|
||||||
|
registry_entries = er.async_entries_for_config_entry(
|
||||||
|
entity_registry, entry.entry_id
|
||||||
|
)
|
||||||
|
for reg_entry in registry_entries:
|
||||||
|
if isinstance(reg_entry.unique_id, int): # type: ignore[unreachable]
|
||||||
|
entity_registry.async_update_entity( # type: ignore[unreachable]
|
||||||
|
reg_entry.entity_id, new_unique_id=str(reg_entry.unique_id)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def async_unload_entry(
|
async def async_unload_entry(
|
||||||
hass: HomeAssistant, config_entry: AcmedaConfigEntry
|
hass: HomeAssistant, config_entry: AcmedaConfigEntry
|
||||||
) -> bool:
|
) -> bool:
|
||||||
|
@ -67,7 +67,7 @@ class AcmedaBase(entity.Entity):
|
|||||||
@property
|
@property
|
||||||
def unique_id(self) -> str:
|
def unique_id(self) -> str:
|
||||||
"""Return the unique ID of this roller."""
|
"""Return the unique ID of this roller."""
|
||||||
return self.roller.id # type: ignore[no-any-return]
|
return str(self.roller.id)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_id(self) -> str:
|
def device_id(self) -> str:
|
||||||
|
@ -6,5 +6,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/acmeda",
|
"documentation": "https://www.home-assistant.io/integrations/acmeda",
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["aiopulse"],
|
"loggers": ["aiopulse"],
|
||||||
"requirements": ["aiopulse==0.4.4"]
|
"requirements": ["aiopulse==0.4.6"]
|
||||||
}
|
}
|
||||||
|
@ -66,10 +66,20 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"services": {
|
"services": {
|
||||||
"add_url": "mdi:link-plus",
|
"add_url": {
|
||||||
"remove_url": "mdi:link-off",
|
"service": "mdi:link-plus"
|
||||||
"enable_url": "mdi:link-variant",
|
},
|
||||||
"disable_url": "mdi:link-variant-off",
|
"remove_url": {
|
||||||
"refresh": "mdi:refresh"
|
"service": "mdi:link-off"
|
||||||
|
},
|
||||||
|
"enable_url": {
|
||||||
|
"service": "mdi:link-variant"
|
||||||
|
},
|
||||||
|
"disable_url": {
|
||||||
|
"service": "mdi:link-variant-off"
|
||||||
|
},
|
||||||
|
"refresh": {
|
||||||
|
"service": "mdi:refresh"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,18 +29,40 @@ DATA_ADS = "data_ads"
|
|||||||
# Supported Types
|
# Supported Types
|
||||||
ADSTYPE_BOOL = "bool"
|
ADSTYPE_BOOL = "bool"
|
||||||
ADSTYPE_BYTE = "byte"
|
ADSTYPE_BYTE = "byte"
|
||||||
ADSTYPE_DINT = "dint"
|
|
||||||
ADSTYPE_INT = "int"
|
ADSTYPE_INT = "int"
|
||||||
ADSTYPE_UDINT = "udint"
|
|
||||||
ADSTYPE_UINT = "uint"
|
ADSTYPE_UINT = "uint"
|
||||||
|
ADSTYPE_SINT = "sint"
|
||||||
|
ADSTYPE_USINT = "usint"
|
||||||
|
ADSTYPE_DINT = "dint"
|
||||||
|
ADSTYPE_UDINT = "udint"
|
||||||
|
ADSTYPE_WORD = "word"
|
||||||
|
ADSTYPE_DWORD = "dword"
|
||||||
|
ADSTYPE_LREAL = "lreal"
|
||||||
|
ADSTYPE_REAL = "real"
|
||||||
|
ADSTYPE_STRING = "string"
|
||||||
|
ADSTYPE_TIME = "time"
|
||||||
|
ADSTYPE_DATE = "date"
|
||||||
|
ADSTYPE_DATE_AND_TIME = "dt"
|
||||||
|
ADSTYPE_TOD = "tod"
|
||||||
|
|
||||||
ADS_TYPEMAP = {
|
ADS_TYPEMAP = {
|
||||||
ADSTYPE_BOOL: pyads.PLCTYPE_BOOL,
|
ADSTYPE_BOOL: pyads.PLCTYPE_BOOL,
|
||||||
ADSTYPE_BYTE: pyads.PLCTYPE_BYTE,
|
ADSTYPE_BYTE: pyads.PLCTYPE_BYTE,
|
||||||
ADSTYPE_DINT: pyads.PLCTYPE_DINT,
|
|
||||||
ADSTYPE_INT: pyads.PLCTYPE_INT,
|
ADSTYPE_INT: pyads.PLCTYPE_INT,
|
||||||
ADSTYPE_UDINT: pyads.PLCTYPE_UDINT,
|
|
||||||
ADSTYPE_UINT: pyads.PLCTYPE_UINT,
|
ADSTYPE_UINT: pyads.PLCTYPE_UINT,
|
||||||
|
ADSTYPE_SINT: pyads.PLCTYPE_SINT,
|
||||||
|
ADSTYPE_USINT: pyads.PLCTYPE_USINT,
|
||||||
|
ADSTYPE_DINT: pyads.PLCTYPE_DINT,
|
||||||
|
ADSTYPE_UDINT: pyads.PLCTYPE_UDINT,
|
||||||
|
ADSTYPE_WORD: pyads.PLCTYPE_WORD,
|
||||||
|
ADSTYPE_DWORD: pyads.PLCTYPE_DWORD,
|
||||||
|
ADSTYPE_REAL: pyads.PLCTYPE_REAL,
|
||||||
|
ADSTYPE_LREAL: pyads.PLCTYPE_LREAL,
|
||||||
|
ADSTYPE_STRING: pyads.PLCTYPE_STRING,
|
||||||
|
ADSTYPE_TIME: pyads.PLCTYPE_TIME,
|
||||||
|
ADSTYPE_DATE: pyads.PLCTYPE_DATE,
|
||||||
|
ADSTYPE_DATE_AND_TIME: pyads.PLCTYPE_DT,
|
||||||
|
ADSTYPE_TOD: pyads.PLCTYPE_TOD,
|
||||||
}
|
}
|
||||||
|
|
||||||
CONF_ADS_FACTOR = "factor"
|
CONF_ADS_FACTOR = "factor"
|
||||||
@ -75,12 +97,23 @@ SCHEMA_SERVICE_WRITE_DATA_BY_NAME = vol.Schema(
|
|||||||
{
|
{
|
||||||
vol.Required(CONF_ADS_TYPE): vol.In(
|
vol.Required(CONF_ADS_TYPE): vol.In(
|
||||||
[
|
[
|
||||||
|
ADSTYPE_BOOL,
|
||||||
|
ADSTYPE_BYTE,
|
||||||
ADSTYPE_INT,
|
ADSTYPE_INT,
|
||||||
ADSTYPE_UINT,
|
ADSTYPE_UINT,
|
||||||
ADSTYPE_BYTE,
|
ADSTYPE_SINT,
|
||||||
ADSTYPE_BOOL,
|
ADSTYPE_USINT,
|
||||||
ADSTYPE_DINT,
|
ADSTYPE_DINT,
|
||||||
ADSTYPE_UDINT,
|
ADSTYPE_UDINT,
|
||||||
|
ADSTYPE_WORD,
|
||||||
|
ADSTYPE_DWORD,
|
||||||
|
ADSTYPE_REAL,
|
||||||
|
ADSTYPE_LREAL,
|
||||||
|
ADSTYPE_STRING,
|
||||||
|
ADSTYPE_TIME,
|
||||||
|
ADSTYPE_DATE,
|
||||||
|
ADSTYPE_DATE_AND_TIME,
|
||||||
|
ADSTYPE_TOD,
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
vol.Required(CONF_ADS_VALUE): vol.Coerce(int),
|
vol.Required(CONF_ADS_VALUE): vol.Coerce(int),
|
||||||
@ -136,7 +169,7 @@ def setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|||||||
|
|
||||||
|
|
||||||
# Tuple to hold data needed for notification
|
# Tuple to hold data needed for notification
|
||||||
NotificationItem = namedtuple(
|
NotificationItem = namedtuple( # noqa: PYI024
|
||||||
"NotificationItem", "hnotify huser name plc_datatype callback"
|
"NotificationItem", "hnotify huser name plc_datatype callback"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -222,37 +255,53 @@ class AdsHub:
|
|||||||
def _device_notification_callback(self, notification, name):
|
def _device_notification_callback(self, notification, name):
|
||||||
"""Handle device notifications."""
|
"""Handle device notifications."""
|
||||||
contents = notification.contents
|
contents = notification.contents
|
||||||
|
|
||||||
hnotify = int(contents.hNotification)
|
hnotify = int(contents.hNotification)
|
||||||
_LOGGER.debug("Received notification %d", hnotify)
|
_LOGGER.debug("Received notification %d", hnotify)
|
||||||
|
|
||||||
# get dynamically sized data array
|
# Get dynamically sized data array
|
||||||
data_size = contents.cbSampleSize
|
data_size = contents.cbSampleSize
|
||||||
data = (ctypes.c_ubyte * data_size).from_address(
|
data_address = (
|
||||||
ctypes.addressof(contents)
|
ctypes.addressof(contents)
|
||||||
+ pyads.structs.SAdsNotificationHeader.data.offset
|
+ pyads.structs.SAdsNotificationHeader.data.offset
|
||||||
)
|
)
|
||||||
|
data = (ctypes.c_ubyte * data_size).from_address(data_address)
|
||||||
|
|
||||||
try:
|
# Acquire notification item
|
||||||
with self._lock:
|
with self._lock:
|
||||||
notification_item = self._notification_items[hnotify]
|
notification_item = self._notification_items.get(hnotify)
|
||||||
except KeyError:
|
|
||||||
|
if not notification_item:
|
||||||
_LOGGER.error("Unknown device notification handle: %d", hnotify)
|
_LOGGER.error("Unknown device notification handle: %d", hnotify)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Parse data to desired datatype
|
# Data parsing based on PLC data type
|
||||||
if notification_item.plc_datatype == pyads.PLCTYPE_BOOL:
|
plc_datatype = notification_item.plc_datatype
|
||||||
|
unpack_formats = {
|
||||||
|
pyads.PLCTYPE_BYTE: "<b",
|
||||||
|
pyads.PLCTYPE_INT: "<h",
|
||||||
|
pyads.PLCTYPE_UINT: "<H",
|
||||||
|
pyads.PLCTYPE_SINT: "<b",
|
||||||
|
pyads.PLCTYPE_USINT: "<B",
|
||||||
|
pyads.PLCTYPE_DINT: "<i",
|
||||||
|
pyads.PLCTYPE_UDINT: "<I",
|
||||||
|
pyads.PLCTYPE_WORD: "<H",
|
||||||
|
pyads.PLCTYPE_DWORD: "<I",
|
||||||
|
pyads.PLCTYPE_LREAL: "<d",
|
||||||
|
pyads.PLCTYPE_REAL: "<f",
|
||||||
|
pyads.PLCTYPE_TOD: "<i", # Treat as DINT
|
||||||
|
pyads.PLCTYPE_DATE: "<i", # Treat as DINT
|
||||||
|
pyads.PLCTYPE_DT: "<i", # Treat as DINT
|
||||||
|
pyads.PLCTYPE_TIME: "<i", # Treat as DINT
|
||||||
|
}
|
||||||
|
|
||||||
|
if plc_datatype == pyads.PLCTYPE_BOOL:
|
||||||
value = bool(struct.unpack("<?", bytearray(data))[0])
|
value = bool(struct.unpack("<?", bytearray(data))[0])
|
||||||
elif notification_item.plc_datatype == pyads.PLCTYPE_INT:
|
elif plc_datatype == pyads.PLCTYPE_STRING:
|
||||||
value = struct.unpack("<h", bytearray(data))[0]
|
value = (
|
||||||
elif notification_item.plc_datatype == pyads.PLCTYPE_BYTE:
|
bytearray(data).split(b"\x00", 1)[0].decode("utf-8", errors="ignore")
|
||||||
value = struct.unpack("<B", bytearray(data))[0]
|
)
|
||||||
elif notification_item.plc_datatype == pyads.PLCTYPE_UINT:
|
elif plc_datatype in unpack_formats:
|
||||||
value = struct.unpack("<H", bytearray(data))[0]
|
value = struct.unpack(unpack_formats[plc_datatype], bytearray(data))[0]
|
||||||
elif notification_item.plc_datatype == pyads.PLCTYPE_DINT:
|
|
||||||
value = struct.unpack("<i", bytearray(data))[0]
|
|
||||||
elif notification_item.plc_datatype == pyads.PLCTYPE_UDINT:
|
|
||||||
value = struct.unpack("<I", bytearray(data))[0]
|
|
||||||
else:
|
else:
|
||||||
value = bytearray(data)
|
value = bytearray(data)
|
||||||
_LOGGER.warning("No callback available for this datatype")
|
_LOGGER.warning("No callback available for this datatype")
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
{
|
{
|
||||||
"services": {
|
"services": {
|
||||||
"write_data_by_name": "mdi:pencil"
|
"write_data_by_name": {
|
||||||
|
"service": "mdi:pencil"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -206,7 +206,8 @@ class AdvantageAirAC(AdvantageAirAcEntity, ClimateEntity):
|
|||||||
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
|
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
|
||||||
"""Set the HVAC Mode and State."""
|
"""Set the HVAC Mode and State."""
|
||||||
if hvac_mode == HVACMode.OFF:
|
if hvac_mode == HVACMode.OFF:
|
||||||
return await self.async_turn_off()
|
await self.async_turn_off()
|
||||||
|
return
|
||||||
if hvac_mode == HVACMode.HEAT_COOL and self.preset_mode != ADVANTAGE_AIR_MYAUTO:
|
if hvac_mode == HVACMode.HEAT_COOL and self.preset_mode != ADVANTAGE_AIR_MYAUTO:
|
||||||
raise ServiceValidationError("Heat/Cool is not supported in this mode")
|
raise ServiceValidationError("Heat/Cool is not supported in this mode")
|
||||||
await self.async_update_ac(
|
await self.async_update_ac(
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
{
|
{
|
||||||
"services": {
|
"services": {
|
||||||
"set_time_to": "mdi:timer-cog"
|
"set_time_to": {
|
||||||
|
"service": "mdi:timer-cog"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,5 +6,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/aemet",
|
"documentation": "https://www.home-assistant.io/integrations/aemet",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["aemet_opendata"],
|
"loggers": ["aemet_opendata"],
|
||||||
"requirements": ["AEMET-OpenData==0.5.3"]
|
"requirements": ["AEMET-OpenData==0.5.4"]
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"services": {
|
"services": {
|
||||||
"add_tracking": "mdi:package-variant-plus",
|
"add_tracking": {
|
||||||
"remove_tracking": "mdi:package-variant-minus"
|
"service": "mdi:package-variant-plus"
|
||||||
|
},
|
||||||
|
"remove_tracking": {
|
||||||
|
"service": "mdi:package-variant-minus"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,7 +59,7 @@ async def async_setup_entry(
|
|||||||
|
|
||||||
platform = async_get_current_platform()
|
platform = async_get_current_platform()
|
||||||
for service, method in CAMERA_SERVICES.items():
|
for service, method in CAMERA_SERVICES.items():
|
||||||
platform.async_register_entity_service(service, {}, method)
|
platform.async_register_entity_service(service, None, method)
|
||||||
|
|
||||||
|
|
||||||
class AgentCamera(MjpegCamera):
|
class AgentCamera(MjpegCamera):
|
||||||
|
@ -1,9 +1,19 @@
|
|||||||
{
|
{
|
||||||
"services": {
|
"services": {
|
||||||
"start_recording": "mdi:record-rec",
|
"start_recording": {
|
||||||
"stop_recording": "mdi:stop",
|
"service": "mdi:record-rec"
|
||||||
"enable_alerts": "mdi:bell-alert",
|
},
|
||||||
"disable_alerts": "mdi:bell-off",
|
"stop_recording": {
|
||||||
"snapshot": "mdi:camera"
|
"service": "mdi:stop"
|
||||||
|
},
|
||||||
|
"enable_alerts": {
|
||||||
|
"service": "mdi:bell-alert"
|
||||||
|
},
|
||||||
|
"disable_alerts": {
|
||||||
|
"service": "mdi:bell-off"
|
||||||
|
},
|
||||||
|
"snapshot": {
|
||||||
|
"service": "mdi:camera"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,18 +2,14 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from airgradient import AirGradientClient
|
||||||
|
|
||||||
from airgradient import AirGradientClient, get_model_name
|
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import CONF_HOST, Platform
|
from homeassistant.const import CONF_HOST, Platform
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import device_registry as dr
|
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .coordinator import AirGradientCoordinator
|
||||||
from .coordinator import AirGradientConfigCoordinator, AirGradientMeasurementCoordinator
|
|
||||||
|
|
||||||
PLATFORMS: list[Platform] = [
|
PLATFORMS: list[Platform] = [
|
||||||
Platform.BUTTON,
|
Platform.BUTTON,
|
||||||
@ -21,18 +17,11 @@ PLATFORMS: list[Platform] = [
|
|||||||
Platform.SELECT,
|
Platform.SELECT,
|
||||||
Platform.SENSOR,
|
Platform.SENSOR,
|
||||||
Platform.SWITCH,
|
Platform.SWITCH,
|
||||||
|
Platform.UPDATE,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
type AirGradientConfigEntry = ConfigEntry[AirGradientCoordinator]
|
||||||
class AirGradientData:
|
|
||||||
"""AirGradient data class."""
|
|
||||||
|
|
||||||
measurement: AirGradientMeasurementCoordinator
|
|
||||||
config: AirGradientConfigCoordinator
|
|
||||||
|
|
||||||
|
|
||||||
type AirGradientConfigEntry = ConfigEntry[AirGradientData]
|
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistant, entry: AirGradientConfigEntry) -> bool:
|
async def async_setup_entry(hass: HomeAssistant, entry: AirGradientConfigEntry) -> bool:
|
||||||
@ -42,27 +31,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: AirGradientConfigEntry)
|
|||||||
entry.data[CONF_HOST], session=async_get_clientsession(hass)
|
entry.data[CONF_HOST], session=async_get_clientsession(hass)
|
||||||
)
|
)
|
||||||
|
|
||||||
measurement_coordinator = AirGradientMeasurementCoordinator(hass, client)
|
coordinator = AirGradientCoordinator(hass, client)
|
||||||
config_coordinator = AirGradientConfigCoordinator(hass, client)
|
|
||||||
|
|
||||||
await measurement_coordinator.async_config_entry_first_refresh()
|
await coordinator.async_config_entry_first_refresh()
|
||||||
await config_coordinator.async_config_entry_first_refresh()
|
|
||||||
|
|
||||||
device_registry = dr.async_get(hass)
|
entry.runtime_data = coordinator
|
||||||
device_registry.async_get_or_create(
|
|
||||||
config_entry_id=entry.entry_id,
|
|
||||||
identifiers={(DOMAIN, measurement_coordinator.serial_number)},
|
|
||||||
manufacturer="AirGradient",
|
|
||||||
model=get_model_name(measurement_coordinator.data.model),
|
|
||||||
model_id=measurement_coordinator.data.model,
|
|
||||||
serial_number=measurement_coordinator.data.serial_number,
|
|
||||||
sw_version=measurement_coordinator.data.firmware_version,
|
|
||||||
)
|
|
||||||
|
|
||||||
entry.runtime_data = AirGradientData(
|
|
||||||
measurement=measurement_coordinator,
|
|
||||||
config=config_coordinator,
|
|
||||||
)
|
|
||||||
|
|
||||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
|
|
||||||
|
@ -15,8 +15,9 @@ 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 AddEntitiesCallback
|
||||||
|
|
||||||
from . import DOMAIN, AirGradientConfigEntry
|
from . import AirGradientConfigEntry
|
||||||
from .coordinator import AirGradientConfigCoordinator
|
from .const import DOMAIN
|
||||||
|
from .coordinator import AirGradientCoordinator
|
||||||
from .entity import AirGradientEntity
|
from .entity import AirGradientEntity
|
||||||
|
|
||||||
|
|
||||||
@ -47,8 +48,8 @@ async def async_setup_entry(
|
|||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up AirGradient button entities based on a config entry."""
|
"""Set up AirGradient button entities based on a config entry."""
|
||||||
model = entry.runtime_data.measurement.data.model
|
coordinator = entry.runtime_data
|
||||||
coordinator = entry.runtime_data.config
|
model = coordinator.data.measures.model
|
||||||
|
|
||||||
added_entities = False
|
added_entities = False
|
||||||
|
|
||||||
@ -57,7 +58,7 @@ async def async_setup_entry(
|
|||||||
nonlocal added_entities
|
nonlocal added_entities
|
||||||
|
|
||||||
if (
|
if (
|
||||||
coordinator.data.configuration_control is ConfigurationControl.LOCAL
|
coordinator.data.config.configuration_control is ConfigurationControl.LOCAL
|
||||||
and not added_entities
|
and not added_entities
|
||||||
):
|
):
|
||||||
entities = [AirGradientButton(coordinator, CO2_CALIBRATION)]
|
entities = [AirGradientButton(coordinator, CO2_CALIBRATION)]
|
||||||
@ -67,7 +68,8 @@ async def async_setup_entry(
|
|||||||
async_add_entities(entities)
|
async_add_entities(entities)
|
||||||
added_entities = True
|
added_entities = True
|
||||||
elif (
|
elif (
|
||||||
coordinator.data.configuration_control is not ConfigurationControl.LOCAL
|
coordinator.data.config.configuration_control
|
||||||
|
is not ConfigurationControl.LOCAL
|
||||||
and added_entities
|
and added_entities
|
||||||
):
|
):
|
||||||
entity_registry = er.async_get(hass)
|
entity_registry = er.async_get(hass)
|
||||||
@ -87,11 +89,10 @@ class AirGradientButton(AirGradientEntity, ButtonEntity):
|
|||||||
"""Defines an AirGradient button."""
|
"""Defines an AirGradient button."""
|
||||||
|
|
||||||
entity_description: AirGradientButtonEntityDescription
|
entity_description: AirGradientButtonEntityDescription
|
||||||
coordinator: AirGradientConfigCoordinator
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
coordinator: AirGradientConfigCoordinator,
|
coordinator: AirGradientCoordinator,
|
||||||
description: AirGradientButtonEntityDescription,
|
description: AirGradientButtonEntityDescription,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize airgradient button."""
|
"""Initialize airgradient button."""
|
||||||
|
@ -92,7 +92,9 @@ class AirGradientConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
except AirGradientError:
|
except AirGradientError:
|
||||||
errors["base"] = "cannot_connect"
|
errors["base"] = "cannot_connect"
|
||||||
else:
|
else:
|
||||||
await self.async_set_unique_id(current_measures.serial_number)
|
await self.async_set_unique_id(
|
||||||
|
current_measures.serial_number, raise_on_progress=False
|
||||||
|
)
|
||||||
self._abort_if_unique_id_configured()
|
self._abort_if_unique_id_configured()
|
||||||
await self.set_configuration_source()
|
await self.set_configuration_source()
|
||||||
return self.async_create_entry(
|
return self.async_create_entry(
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
@ -16,7 +17,15 @@ if TYPE_CHECKING:
|
|||||||
from . import AirGradientConfigEntry
|
from . import AirGradientConfigEntry
|
||||||
|
|
||||||
|
|
||||||
class AirGradientCoordinator[_DataT](DataUpdateCoordinator[_DataT]):
|
@dataclass
|
||||||
|
class AirGradientData:
|
||||||
|
"""Class for AirGradient data."""
|
||||||
|
|
||||||
|
measures: Measures
|
||||||
|
config: Config
|
||||||
|
|
||||||
|
|
||||||
|
class AirGradientCoordinator(DataUpdateCoordinator[AirGradientData]):
|
||||||
"""Class to manage fetching AirGradient data."""
|
"""Class to manage fetching AirGradient data."""
|
||||||
|
|
||||||
config_entry: AirGradientConfigEntry
|
config_entry: AirGradientConfigEntry
|
||||||
@ -33,25 +42,11 @@ class AirGradientCoordinator[_DataT](DataUpdateCoordinator[_DataT]):
|
|||||||
assert self.config_entry.unique_id
|
assert self.config_entry.unique_id
|
||||||
self.serial_number = self.config_entry.unique_id
|
self.serial_number = self.config_entry.unique_id
|
||||||
|
|
||||||
async def _async_update_data(self) -> _DataT:
|
async def _async_update_data(self) -> AirGradientData:
|
||||||
try:
|
try:
|
||||||
return await self._update_data()
|
measures = await self.client.get_current_measures()
|
||||||
|
config = await self.client.get_config()
|
||||||
except AirGradientError as error:
|
except AirGradientError as error:
|
||||||
raise UpdateFailed(error) from error
|
raise UpdateFailed(error) from error
|
||||||
|
else:
|
||||||
async def _update_data(self) -> _DataT:
|
return AirGradientData(measures, config)
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
|
|
||||||
class AirGradientMeasurementCoordinator(AirGradientCoordinator[Measures]):
|
|
||||||
"""Class to manage fetching AirGradient data."""
|
|
||||||
|
|
||||||
async def _update_data(self) -> Measures:
|
|
||||||
return await self.client.get_current_measures()
|
|
||||||
|
|
||||||
|
|
||||||
class AirGradientConfigCoordinator(AirGradientCoordinator[Config]):
|
|
||||||
"""Class to manage fetching AirGradient data."""
|
|
||||||
|
|
||||||
async def _update_data(self) -> Config:
|
|
||||||
return await self.client.get_config()
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
"""Base class for AirGradient entities."""
|
"""Base class for AirGradient entities."""
|
||||||
|
|
||||||
|
from airgradient import get_model_name
|
||||||
|
|
||||||
from homeassistant.helpers.device_registry import DeviceInfo
|
from homeassistant.helpers.device_registry import DeviceInfo
|
||||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
|
|
||||||
@ -15,6 +17,12 @@ class AirGradientEntity(CoordinatorEntity[AirGradientCoordinator]):
|
|||||||
def __init__(self, coordinator: AirGradientCoordinator) -> None:
|
def __init__(self, coordinator: AirGradientCoordinator) -> None:
|
||||||
"""Initialize airgradient entity."""
|
"""Initialize airgradient entity."""
|
||||||
super().__init__(coordinator)
|
super().__init__(coordinator)
|
||||||
|
measures = coordinator.data.measures
|
||||||
self._attr_device_info = DeviceInfo(
|
self._attr_device_info = DeviceInfo(
|
||||||
identifiers={(DOMAIN, coordinator.serial_number)},
|
identifiers={(DOMAIN, coordinator.serial_number)},
|
||||||
|
manufacturer="AirGradient",
|
||||||
|
model=get_model_name(measures.model),
|
||||||
|
model_id=measures.model,
|
||||||
|
serial_number=coordinator.serial_number,
|
||||||
|
sw_version=measures.firmware_version,
|
||||||
)
|
)
|
||||||
|
@ -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.7.1"],
|
"requirements": ["airgradient==0.8.0"],
|
||||||
"zeroconf": ["_airgradient._tcp.local."]
|
"zeroconf": ["_airgradient._tcp.local."]
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|||||||
|
|
||||||
from . import AirGradientConfigEntry
|
from . import AirGradientConfigEntry
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
from .coordinator import AirGradientConfigCoordinator
|
from .coordinator import AirGradientCoordinator
|
||||||
from .entity import AirGradientEntity
|
from .entity import AirGradientEntity
|
||||||
|
|
||||||
|
|
||||||
@ -62,8 +62,8 @@ async def async_setup_entry(
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Set up AirGradient number entities based on a config entry."""
|
"""Set up AirGradient number entities based on a config entry."""
|
||||||
|
|
||||||
model = entry.runtime_data.measurement.data.model
|
coordinator = entry.runtime_data
|
||||||
coordinator = entry.runtime_data.config
|
model = coordinator.data.measures.model
|
||||||
|
|
||||||
added_entities = False
|
added_entities = False
|
||||||
|
|
||||||
@ -72,7 +72,7 @@ async def async_setup_entry(
|
|||||||
nonlocal added_entities
|
nonlocal added_entities
|
||||||
|
|
||||||
if (
|
if (
|
||||||
coordinator.data.configuration_control is ConfigurationControl.LOCAL
|
coordinator.data.config.configuration_control is ConfigurationControl.LOCAL
|
||||||
and not added_entities
|
and not added_entities
|
||||||
):
|
):
|
||||||
entities = []
|
entities = []
|
||||||
@ -84,7 +84,8 @@ async def async_setup_entry(
|
|||||||
async_add_entities(entities)
|
async_add_entities(entities)
|
||||||
added_entities = True
|
added_entities = True
|
||||||
elif (
|
elif (
|
||||||
coordinator.data.configuration_control is not ConfigurationControl.LOCAL
|
coordinator.data.config.configuration_control
|
||||||
|
is not ConfigurationControl.LOCAL
|
||||||
and added_entities
|
and added_entities
|
||||||
):
|
):
|
||||||
entity_registry = er.async_get(hass)
|
entity_registry = er.async_get(hass)
|
||||||
@ -104,11 +105,10 @@ class AirGradientNumber(AirGradientEntity, NumberEntity):
|
|||||||
"""Defines an AirGradient number entity."""
|
"""Defines an AirGradient number entity."""
|
||||||
|
|
||||||
entity_description: AirGradientNumberEntityDescription
|
entity_description: AirGradientNumberEntityDescription
|
||||||
coordinator: AirGradientConfigCoordinator
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
coordinator: AirGradientConfigCoordinator,
|
coordinator: AirGradientCoordinator,
|
||||||
description: AirGradientNumberEntityDescription,
|
description: AirGradientNumberEntityDescription,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize AirGradient number."""
|
"""Initialize AirGradient number."""
|
||||||
@ -119,7 +119,7 @@ class AirGradientNumber(AirGradientEntity, NumberEntity):
|
|||||||
@property
|
@property
|
||||||
def native_value(self) -> int | None:
|
def native_value(self) -> int | None:
|
||||||
"""Return the state of the number."""
|
"""Return the state of the number."""
|
||||||
return self.entity_description.value_fn(self.coordinator.data)
|
return self.entity_description.value_fn(self.coordinator.data.config)
|
||||||
|
|
||||||
async def async_set_native_value(self, value: float) -> None:
|
async def async_set_native_value(self, value: float) -> None:
|
||||||
"""Set the selected value."""
|
"""Set the selected value."""
|
||||||
|
@ -18,7 +18,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|||||||
|
|
||||||
from . import AirGradientConfigEntry
|
from . import AirGradientConfigEntry
|
||||||
from .const import DOMAIN, PM_STANDARD, PM_STANDARD_REVERSE
|
from .const import DOMAIN, PM_STANDARD, PM_STANDARD_REVERSE
|
||||||
from .coordinator import AirGradientConfigCoordinator
|
from .coordinator import AirGradientCoordinator
|
||||||
from .entity import AirGradientEntity
|
from .entity import AirGradientEntity
|
||||||
|
|
||||||
|
|
||||||
@ -144,13 +144,11 @@ async def async_setup_entry(
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Set up AirGradient select entities based on a config entry."""
|
"""Set up AirGradient select entities based on a config entry."""
|
||||||
|
|
||||||
coordinator = entry.runtime_data.config
|
coordinator = entry.runtime_data
|
||||||
measurement_coordinator = entry.runtime_data.measurement
|
model = coordinator.data.measures.model
|
||||||
|
|
||||||
async_add_entities([AirGradientSelect(coordinator, CONFIG_CONTROL_ENTITY)])
|
async_add_entities([AirGradientSelect(coordinator, CONFIG_CONTROL_ENTITY)])
|
||||||
|
|
||||||
model = measurement_coordinator.data.model
|
|
||||||
|
|
||||||
added_entities = False
|
added_entities = False
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
@ -158,7 +156,7 @@ async def async_setup_entry(
|
|||||||
nonlocal added_entities
|
nonlocal added_entities
|
||||||
|
|
||||||
if (
|
if (
|
||||||
coordinator.data.configuration_control is ConfigurationControl.LOCAL
|
coordinator.data.config.configuration_control is ConfigurationControl.LOCAL
|
||||||
and not added_entities
|
and not added_entities
|
||||||
):
|
):
|
||||||
entities: list[AirGradientSelect] = [
|
entities: list[AirGradientSelect] = [
|
||||||
@ -179,7 +177,8 @@ async def async_setup_entry(
|
|||||||
async_add_entities(entities)
|
async_add_entities(entities)
|
||||||
added_entities = True
|
added_entities = True
|
||||||
elif (
|
elif (
|
||||||
coordinator.data.configuration_control is not ConfigurationControl.LOCAL
|
coordinator.data.config.configuration_control
|
||||||
|
is not ConfigurationControl.LOCAL
|
||||||
and added_entities
|
and added_entities
|
||||||
):
|
):
|
||||||
entity_registry = er.async_get(hass)
|
entity_registry = er.async_get(hass)
|
||||||
@ -201,11 +200,10 @@ class AirGradientSelect(AirGradientEntity, SelectEntity):
|
|||||||
"""Defines an AirGradient select entity."""
|
"""Defines an AirGradient select entity."""
|
||||||
|
|
||||||
entity_description: AirGradientSelectEntityDescription
|
entity_description: AirGradientSelectEntityDescription
|
||||||
coordinator: AirGradientConfigCoordinator
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
coordinator: AirGradientConfigCoordinator,
|
coordinator: AirGradientCoordinator,
|
||||||
description: AirGradientSelectEntityDescription,
|
description: AirGradientSelectEntityDescription,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize AirGradient select."""
|
"""Initialize AirGradient select."""
|
||||||
@ -216,7 +214,7 @@ class AirGradientSelect(AirGradientEntity, SelectEntity):
|
|||||||
@property
|
@property
|
||||||
def current_option(self) -> str | None:
|
def current_option(self) -> str | None:
|
||||||
"""Return the state of the select."""
|
"""Return the state of the select."""
|
||||||
return self.entity_description.value_fn(self.coordinator.data)
|
return self.entity_description.value_fn(self.coordinator.data.config)
|
||||||
|
|
||||||
async def async_select_option(self, option: str) -> None:
|
async def async_select_option(self, option: str) -> None:
|
||||||
"""Change the selected option."""
|
"""Change the selected option."""
|
||||||
|
@ -32,7 +32,7 @@ from homeassistant.helpers.typing import StateType
|
|||||||
|
|
||||||
from . import AirGradientConfigEntry
|
from . import AirGradientConfigEntry
|
||||||
from .const import PM_STANDARD, PM_STANDARD_REVERSE
|
from .const import PM_STANDARD, PM_STANDARD_REVERSE
|
||||||
from .coordinator import AirGradientConfigCoordinator, AirGradientMeasurementCoordinator
|
from .coordinator import AirGradientCoordinator
|
||||||
from .entity import AirGradientEntity
|
from .entity import AirGradientEntity
|
||||||
|
|
||||||
|
|
||||||
@ -218,7 +218,7 @@ async def async_setup_entry(
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Set up AirGradient sensor entities based on a config entry."""
|
"""Set up AirGradient sensor entities based on a config entry."""
|
||||||
|
|
||||||
coordinator = entry.runtime_data.measurement
|
coordinator = entry.runtime_data
|
||||||
listener: Callable[[], None] | None = None
|
listener: Callable[[], None] | None = None
|
||||||
not_setup: set[AirGradientMeasurementSensorEntityDescription] = set(
|
not_setup: set[AirGradientMeasurementSensorEntityDescription] = set(
|
||||||
MEASUREMENT_SENSOR_TYPES
|
MEASUREMENT_SENSOR_TYPES
|
||||||
@ -232,7 +232,7 @@ async def async_setup_entry(
|
|||||||
not_setup = set()
|
not_setup = set()
|
||||||
sensors = []
|
sensors = []
|
||||||
for description in sensor_descriptions:
|
for description in sensor_descriptions:
|
||||||
if description.value_fn(coordinator.data) is None:
|
if description.value_fn(coordinator.data.measures) is None:
|
||||||
not_setup.add(description)
|
not_setup.add(description)
|
||||||
else:
|
else:
|
||||||
sensors.append(AirGradientMeasurementSensor(coordinator, description))
|
sensors.append(AirGradientMeasurementSensor(coordinator, description))
|
||||||
@ -248,64 +248,65 @@ async def async_setup_entry(
|
|||||||
add_entities()
|
add_entities()
|
||||||
|
|
||||||
entities = [
|
entities = [
|
||||||
AirGradientConfigSensor(entry.runtime_data.config, description)
|
AirGradientConfigSensor(coordinator, description)
|
||||||
for description in CONFIG_SENSOR_TYPES
|
for description in CONFIG_SENSOR_TYPES
|
||||||
]
|
]
|
||||||
if "L" in coordinator.data.model:
|
if "L" in coordinator.data.measures.model:
|
||||||
entities.extend(
|
entities.extend(
|
||||||
AirGradientConfigSensor(entry.runtime_data.config, description)
|
AirGradientConfigSensor(coordinator, description)
|
||||||
for description in CONFIG_LED_BAR_SENSOR_TYPES
|
for description in CONFIG_LED_BAR_SENSOR_TYPES
|
||||||
)
|
)
|
||||||
if "I" in coordinator.data.model:
|
if "I" in coordinator.data.measures.model:
|
||||||
entities.extend(
|
entities.extend(
|
||||||
AirGradientConfigSensor(entry.runtime_data.config, description)
|
AirGradientConfigSensor(coordinator, description)
|
||||||
for description in CONFIG_DISPLAY_SENSOR_TYPES
|
for description in CONFIG_DISPLAY_SENSOR_TYPES
|
||||||
)
|
)
|
||||||
async_add_entities(entities)
|
async_add_entities(entities)
|
||||||
|
|
||||||
|
|
||||||
class AirGradientMeasurementSensor(AirGradientEntity, SensorEntity):
|
class AirGradientSensor(AirGradientEntity, SensorEntity):
|
||||||
"""Defines an AirGradient sensor."""
|
"""Defines an AirGradient sensor."""
|
||||||
|
|
||||||
entity_description: AirGradientMeasurementSensorEntityDescription
|
|
||||||
coordinator: AirGradientMeasurementCoordinator
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
coordinator: AirGradientMeasurementCoordinator,
|
coordinator: AirGradientCoordinator,
|
||||||
description: AirGradientMeasurementSensorEntityDescription,
|
description: SensorEntityDescription,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize airgradient sensor."""
|
"""Initialize airgradient sensor."""
|
||||||
super().__init__(coordinator)
|
super().__init__(coordinator)
|
||||||
self.entity_description = description
|
self.entity_description = description
|
||||||
self._attr_unique_id = f"{coordinator.serial_number}-{description.key}"
|
self._attr_unique_id = f"{coordinator.serial_number}-{description.key}"
|
||||||
|
|
||||||
|
|
||||||
|
class AirGradientMeasurementSensor(AirGradientSensor):
|
||||||
|
"""Defines an AirGradient sensor."""
|
||||||
|
|
||||||
|
entity_description: AirGradientMeasurementSensorEntityDescription
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def native_value(self) -> StateType:
|
def native_value(self) -> StateType:
|
||||||
"""Return the state of the sensor."""
|
"""Return the state of the sensor."""
|
||||||
return self.entity_description.value_fn(self.coordinator.data)
|
return self.entity_description.value_fn(self.coordinator.data.measures)
|
||||||
|
|
||||||
|
|
||||||
class AirGradientConfigSensor(AirGradientEntity, SensorEntity):
|
class AirGradientConfigSensor(AirGradientSensor):
|
||||||
"""Defines an AirGradient sensor."""
|
"""Defines an AirGradient sensor."""
|
||||||
|
|
||||||
entity_description: AirGradientConfigSensorEntityDescription
|
entity_description: AirGradientConfigSensorEntityDescription
|
||||||
coordinator: AirGradientConfigCoordinator
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
coordinator: AirGradientConfigCoordinator,
|
coordinator: AirGradientCoordinator,
|
||||||
description: AirGradientConfigSensorEntityDescription,
|
description: AirGradientConfigSensorEntityDescription,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize airgradient sensor."""
|
"""Initialize airgradient sensor."""
|
||||||
super().__init__(coordinator)
|
super().__init__(coordinator, description)
|
||||||
self.entity_description = description
|
|
||||||
self._attr_unique_id = f"{coordinator.serial_number}-{description.key}"
|
|
||||||
self._attr_entity_registry_enabled_default = (
|
self._attr_entity_registry_enabled_default = (
|
||||||
coordinator.data.configuration_control is not ConfigurationControl.LOCAL
|
coordinator.data.config.configuration_control
|
||||||
|
is not ConfigurationControl.LOCAL
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def native_value(self) -> StateType:
|
def native_value(self) -> StateType:
|
||||||
"""Return the state of the sensor."""
|
"""Return the state of the sensor."""
|
||||||
return self.entity_description.value_fn(self.coordinator.data)
|
return self.entity_description.value_fn(self.coordinator.data.config)
|
||||||
|
@ -19,7 +19,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|||||||
|
|
||||||
from . import AirGradientConfigEntry
|
from . import AirGradientConfigEntry
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
from .coordinator import AirGradientConfigCoordinator
|
from .coordinator import AirGradientCoordinator
|
||||||
from .entity import AirGradientEntity
|
from .entity import AirGradientEntity
|
||||||
|
|
||||||
|
|
||||||
@ -46,7 +46,7 @@ async def async_setup_entry(
|
|||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddEntitiesCallback,
|
||||||
) -> 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.config
|
coordinator = entry.runtime_data
|
||||||
|
|
||||||
added_entities = False
|
added_entities = False
|
||||||
|
|
||||||
@ -55,7 +55,7 @@ async def async_setup_entry(
|
|||||||
nonlocal added_entities
|
nonlocal added_entities
|
||||||
|
|
||||||
if (
|
if (
|
||||||
coordinator.data.configuration_control is ConfigurationControl.LOCAL
|
coordinator.data.config.configuration_control is ConfigurationControl.LOCAL
|
||||||
and not added_entities
|
and not added_entities
|
||||||
):
|
):
|
||||||
async_add_entities(
|
async_add_entities(
|
||||||
@ -63,7 +63,8 @@ async def async_setup_entry(
|
|||||||
)
|
)
|
||||||
added_entities = True
|
added_entities = True
|
||||||
elif (
|
elif (
|
||||||
coordinator.data.configuration_control is not ConfigurationControl.LOCAL
|
coordinator.data.config.configuration_control
|
||||||
|
is not ConfigurationControl.LOCAL
|
||||||
and added_entities
|
and added_entities
|
||||||
):
|
):
|
||||||
entity_registry = er.async_get(hass)
|
entity_registry = er.async_get(hass)
|
||||||
@ -82,11 +83,10 @@ class AirGradientSwitch(AirGradientEntity, SwitchEntity):
|
|||||||
"""Defines an AirGradient switch entity."""
|
"""Defines an AirGradient switch entity."""
|
||||||
|
|
||||||
entity_description: AirGradientSwitchEntityDescription
|
entity_description: AirGradientSwitchEntityDescription
|
||||||
coordinator: AirGradientConfigCoordinator
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
coordinator: AirGradientConfigCoordinator,
|
coordinator: AirGradientCoordinator,
|
||||||
description: AirGradientSwitchEntityDescription,
|
description: AirGradientSwitchEntityDescription,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize AirGradient switch."""
|
"""Initialize AirGradient switch."""
|
||||||
@ -97,7 +97,7 @@ class AirGradientSwitch(AirGradientEntity, SwitchEntity):
|
|||||||
@property
|
@property
|
||||||
def is_on(self) -> bool:
|
def is_on(self) -> bool:
|
||||||
"""Return the state of the switch."""
|
"""Return the state of the switch."""
|
||||||
return self.entity_description.value_fn(self.coordinator.data)
|
return self.entity_description.value_fn(self.coordinator.data.config)
|
||||||
|
|
||||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||||
"""Turn the switch on."""
|
"""Turn the switch on."""
|
||||||
|
54
homeassistant/components/airgradient/update.py
Normal file
54
homeassistant/components/airgradient/update.py
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
"""Airgradient Update platform."""
|
||||||
|
|
||||||
|
from datetime import timedelta
|
||||||
|
from functools import cached_property
|
||||||
|
|
||||||
|
from homeassistant.components.update import UpdateDeviceClass, UpdateEntity
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
|
from . import AirGradientConfigEntry, AirGradientCoordinator
|
||||||
|
from .entity import AirGradientEntity
|
||||||
|
|
||||||
|
SCAN_INTERVAL = timedelta(hours=1)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config_entry: AirGradientConfigEntry,
|
||||||
|
async_add_entities: AddEntitiesCallback,
|
||||||
|
) -> None:
|
||||||
|
"""Set up Airgradient update platform."""
|
||||||
|
|
||||||
|
coordinator = config_entry.runtime_data
|
||||||
|
|
||||||
|
async_add_entities([AirGradientUpdate(coordinator)], True)
|
||||||
|
|
||||||
|
|
||||||
|
class AirGradientUpdate(AirGradientEntity, UpdateEntity):
|
||||||
|
"""Representation of Airgradient Update."""
|
||||||
|
|
||||||
|
_attr_device_class = UpdateDeviceClass.FIRMWARE
|
||||||
|
|
||||||
|
def __init__(self, coordinator: AirGradientCoordinator) -> None:
|
||||||
|
"""Initialize the entity."""
|
||||||
|
super().__init__(coordinator)
|
||||||
|
self._attr_unique_id = f"{coordinator.serial_number}-update"
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def should_poll(self) -> bool:
|
||||||
|
"""Return True because we need to poll the latest version."""
|
||||||
|
return True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def installed_version(self) -> str:
|
||||||
|
"""Return the installed version of the entity."""
|
||||||
|
return self.coordinator.data.measures.firmware_version
|
||||||
|
|
||||||
|
async def async_update(self) -> None:
|
||||||
|
"""Update the entity."""
|
||||||
|
self._attr_latest_version = (
|
||||||
|
await self.coordinator.client.get_latest_firmware_version(
|
||||||
|
self.coordinator.serial_number
|
||||||
|
)
|
||||||
|
)
|
@ -24,5 +24,5 @@
|
|||||||
"dependencies": ["bluetooth_adapters"],
|
"dependencies": ["bluetooth_adapters"],
|
||||||
"documentation": "https://www.home-assistant.io/integrations/airthings_ble",
|
"documentation": "https://www.home-assistant.io/integrations/airthings_ble",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"requirements": ["airthings-ble==0.9.0"]
|
"requirements": ["airthings-ble==0.9.1"]
|
||||||
}
|
}
|
||||||
|
@ -156,7 +156,8 @@ class AirtouchAC(CoordinatorEntity, ClimateEntity):
|
|||||||
raise ValueError(f"Unsupported HVAC mode: {hvac_mode}")
|
raise ValueError(f"Unsupported HVAC mode: {hvac_mode}")
|
||||||
|
|
||||||
if hvac_mode == HVACMode.OFF:
|
if hvac_mode == HVACMode.OFF:
|
||||||
return await self.async_turn_off()
|
await self.async_turn_off()
|
||||||
|
return
|
||||||
await self._airtouch.SetCoolingModeForAc(
|
await self._airtouch.SetCoolingModeForAc(
|
||||||
self._ac_number, HA_STATE_TO_AT[hvac_mode]
|
self._ac_number, HA_STATE_TO_AT[hvac_mode]
|
||||||
)
|
)
|
||||||
@ -262,7 +263,8 @@ class AirtouchGroup(CoordinatorEntity, ClimateEntity):
|
|||||||
raise ValueError(f"Unsupported HVAC mode: {hvac_mode}")
|
raise ValueError(f"Unsupported HVAC mode: {hvac_mode}")
|
||||||
|
|
||||||
if hvac_mode == HVACMode.OFF:
|
if hvac_mode == HVACMode.OFF:
|
||||||
return await self.async_turn_off()
|
await self.async_turn_off()
|
||||||
|
return
|
||||||
if self.hvac_mode == HVACMode.OFF:
|
if self.hvac_mode == HVACMode.OFF:
|
||||||
await self.async_turn_on()
|
await self.async_turn_on()
|
||||||
self._unit = self._airtouch.GetGroups()[self._group_number]
|
self._unit = self._airtouch.GetGroups()[self._group_number]
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
"""Config flow for AirTouch4."""
|
"""Config flow for AirTouch4."""
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from airtouch4pyapi import AirTouch, AirTouchStatus
|
from airtouch4pyapi import AirTouch, AirTouchStatus
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigFlow
|
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
|
||||||
from homeassistant.const import CONF_HOST
|
from homeassistant.const import CONF_HOST
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
@ -16,7 +18,9 @@ class AirtouchConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
|
|
||||||
VERSION = 1
|
VERSION = 1
|
||||||
|
|
||||||
async def async_step_user(self, user_input=None):
|
async def async_step_user(
|
||||||
|
self, user_input: dict[str, Any] | None = None
|
||||||
|
) -> ConfigFlowResult:
|
||||||
"""Handle a flow initialized by the user."""
|
"""Handle a flow initialized by the user."""
|
||||||
if user_input is None:
|
if user_input is None:
|
||||||
return self.async_show_form(step_id="user", data_schema=DATA_SCHEMA)
|
return self.async_show_form(step_id="user", data_schema=DATA_SCHEMA)
|
||||||
|
@ -262,7 +262,7 @@ class Airtouch5AC(Airtouch5ClimateEntity):
|
|||||||
_LOGGER.debug("Argument `temperature` is missing in set_temperature")
|
_LOGGER.debug("Argument `temperature` is missing in set_temperature")
|
||||||
return
|
return
|
||||||
|
|
||||||
await self._control(temp=temp)
|
await self._control(setpoint=SetpointControl.CHANGE_SETPOINT, temp=temp)
|
||||||
|
|
||||||
|
|
||||||
class Airtouch5Zone(Airtouch5ClimateEntity):
|
class Airtouch5Zone(Airtouch5ClimateEntity):
|
||||||
|
@ -31,7 +31,6 @@ from homeassistant.core import HomeAssistant, callback
|
|||||||
from homeassistant.exceptions import ConfigEntryAuthFailed
|
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||||
from homeassistant.helpers import (
|
from homeassistant.helpers import (
|
||||||
aiohttp_client,
|
aiohttp_client,
|
||||||
config_validation as cv,
|
|
||||||
device_registry as dr,
|
device_registry as dr,
|
||||||
entity_registry as er,
|
entity_registry as er,
|
||||||
)
|
)
|
||||||
@ -54,6 +53,8 @@ from .const import (
|
|||||||
LOGGER,
|
LOGGER,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type AirVisualConfigEntry = ConfigEntry[DataUpdateCoordinator]
|
||||||
|
|
||||||
# We use a raw string for the airvisual_pro domain (instead of importing the actual
|
# We use a raw string for the airvisual_pro domain (instead of importing the actual
|
||||||
# constant) so that we can avoid listing it as a dependency:
|
# constant) so that we can avoid listing it as a dependency:
|
||||||
DOMAIN_AIRVISUAL_PRO = "airvisual_pro"
|
DOMAIN_AIRVISUAL_PRO = "airvisual_pro"
|
||||||
@ -62,8 +63,6 @@ PLATFORMS = [Platform.SENSOR]
|
|||||||
|
|
||||||
DEFAULT_ATTRIBUTION = "Data provided by AirVisual"
|
DEFAULT_ATTRIBUTION = "Data provided by AirVisual"
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False)
|
|
||||||
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_get_cloud_api_update_interval(
|
def async_get_cloud_api_update_interval(
|
||||||
@ -94,10 +93,9 @@ def async_get_cloud_coordinators_by_api_key(
|
|||||||
) -> list[DataUpdateCoordinator]:
|
) -> list[DataUpdateCoordinator]:
|
||||||
"""Get all DataUpdateCoordinator objects related to a particular API key."""
|
"""Get all DataUpdateCoordinator objects related to a particular API key."""
|
||||||
return [
|
return [
|
||||||
coordinator
|
entry.runtime_data
|
||||||
for entry_id, coordinator in hass.data[DOMAIN].items()
|
for entry in hass.config_entries.async_entries(DOMAIN)
|
||||||
if (entry := hass.config_entries.async_get_entry(entry_id))
|
if entry.data.get(CONF_API_KEY) == api_key and hasattr(entry, "runtime_data")
|
||||||
and entry.data.get(CONF_API_KEY) == api_key
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -175,7 +173,7 @@ def _standardize_geography_config_entry(
|
|||||||
hass.config_entries.async_update_entry(entry, **entry_updates)
|
hass.config_entries.async_update_entry(entry, **entry_updates)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_setup_entry(hass: HomeAssistant, entry: AirVisualConfigEntry) -> bool:
|
||||||
"""Set up AirVisual as config entry."""
|
"""Set up AirVisual as config entry."""
|
||||||
if CONF_API_KEY not in entry.data:
|
if CONF_API_KEY not in entry.data:
|
||||||
# If this is a migrated AirVisual Pro entry, there's no actual setup to do;
|
# If this is a migrated AirVisual Pro entry, there's no actual setup to do;
|
||||||
@ -223,8 +221,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
entry.async_on_unload(entry.add_update_listener(async_reload_entry))
|
entry.async_on_unload(entry.add_update_listener(async_reload_entry))
|
||||||
|
|
||||||
await coordinator.async_config_entry_first_refresh()
|
await coordinator.async_config_entry_first_refresh()
|
||||||
hass.data.setdefault(DOMAIN, {})
|
entry.runtime_data = coordinator
|
||||||
hass.data[DOMAIN][entry.entry_id] = coordinator
|
|
||||||
|
|
||||||
# Reassess the interval between 2 server requests
|
# Reassess the interval between 2 server requests
|
||||||
async_sync_geo_coordinator_update_intervals(hass, entry.data[CONF_API_KEY])
|
async_sync_geo_coordinator_update_intervals(hass, entry.data[CONF_API_KEY])
|
||||||
@ -234,7 +231,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_migrate_entry(hass: HomeAssistant, entry: AirVisualConfigEntry) -> bool:
|
||||||
"""Migrate an old config entry."""
|
"""Migrate an old config entry."""
|
||||||
version = entry.version
|
version = entry.version
|
||||||
|
|
||||||
@ -391,13 +388,11 @@ async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_unload_entry(hass: HomeAssistant, entry: AirVisualConfigEntry) -> bool:
|
||||||
"""Unload an AirVisual config entry."""
|
"""Unload an AirVisual config entry."""
|
||||||
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||||
|
|
||||||
if unload_ok:
|
if unload_ok and CONF_API_KEY in entry.data:
|
||||||
hass.data[DOMAIN].pop(entry.entry_id)
|
|
||||||
if CONF_API_KEY in entry.data:
|
|
||||||
# Re-calculate the update interval period for any remaining consumers of
|
# Re-calculate the update interval period for any remaining consumers of
|
||||||
# this API key:
|
# this API key:
|
||||||
async_sync_geo_coordinator_update_intervals(hass, entry.data[CONF_API_KEY])
|
async_sync_geo_coordinator_update_intervals(hass, entry.data[CONF_API_KEY])
|
||||||
@ -405,7 +400,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
return unload_ok
|
return unload_ok
|
||||||
|
|
||||||
|
|
||||||
async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
async def async_reload_entry(hass: HomeAssistant, entry: AirVisualConfigEntry) -> None:
|
||||||
"""Handle an options update."""
|
"""Handle an options update."""
|
||||||
await hass.config_entries.async_reload(entry.entry_id)
|
await hass.config_entries.async_reload(entry.entry_id)
|
||||||
|
|
||||||
|
@ -5,7 +5,6 @@ from __future__ import annotations
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from homeassistant.components.diagnostics import async_redact_data
|
from homeassistant.components.diagnostics import async_redact_data
|
||||||
from homeassistant.config_entries import ConfigEntry
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_API_KEY,
|
CONF_API_KEY,
|
||||||
CONF_COUNTRY,
|
CONF_COUNTRY,
|
||||||
@ -15,9 +14,9 @@ from homeassistant.const import (
|
|||||||
CONF_UNIQUE_ID,
|
CONF_UNIQUE_ID,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
|
||||||
|
|
||||||
from .const import CONF_CITY, DOMAIN
|
from . import AirVisualConfigEntry
|
||||||
|
from .const import CONF_CITY
|
||||||
|
|
||||||
CONF_COORDINATES = "coordinates"
|
CONF_COORDINATES = "coordinates"
|
||||||
CONF_TITLE = "title"
|
CONF_TITLE = "title"
|
||||||
@ -37,10 +36,10 @@ TO_REDACT = {
|
|||||||
|
|
||||||
|
|
||||||
async def async_get_config_entry_diagnostics(
|
async def async_get_config_entry_diagnostics(
|
||||||
hass: HomeAssistant, entry: ConfigEntry
|
hass: HomeAssistant, entry: AirVisualConfigEntry
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""Return diagnostics for a config entry."""
|
"""Return diagnostics for a config entry."""
|
||||||
coordinator: DataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
coordinator = entry.runtime_data
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"entry": async_redact_data(entry.as_dict(), TO_REDACT),
|
"entry": async_redact_data(entry.as_dict(), TO_REDACT),
|
||||||
|
@ -26,8 +26,8 @@ from homeassistant.core import HomeAssistant, callback
|
|||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||||
|
|
||||||
from . import AirVisualEntity
|
from . import AirVisualConfigEntry, AirVisualEntity
|
||||||
from .const import CONF_CITY, DOMAIN
|
from .const import CONF_CITY
|
||||||
|
|
||||||
ATTR_CITY = "city"
|
ATTR_CITY = "city"
|
||||||
ATTR_COUNTRY = "country"
|
ATTR_COUNTRY = "country"
|
||||||
@ -105,10 +105,12 @@ POLLUTANT_UNITS = {
|
|||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
hass: HomeAssistant,
|
||||||
|
entry: AirVisualConfigEntry,
|
||||||
|
async_add_entities: AddEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up AirVisual sensors based on a config entry."""
|
"""Set up AirVisual sensors based on a config entry."""
|
||||||
coordinator = hass.data[DOMAIN][entry.entry_id]
|
coordinator = entry.runtime_data
|
||||||
async_add_entities(
|
async_add_entities(
|
||||||
AirVisualGeographySensor(coordinator, entry, description, locale)
|
AirVisualGeographySensor(coordinator, entry, description, locale)
|
||||||
for locale in GEOGRAPHY_SENSOR_LOCALES
|
for locale in GEOGRAPHY_SENSOR_LOCALES
|
||||||
|
@ -80,11 +80,9 @@ class AirVisualProFlowHandler(ConfigFlow, domain=DOMAIN):
|
|||||||
"""Initialize."""
|
"""Initialize."""
|
||||||
self._reauth_entry: ConfigEntry | None = None
|
self._reauth_entry: ConfigEntry | None = None
|
||||||
|
|
||||||
async def async_step_import(
|
async def async_step_import(self, import_data: dict[str, Any]) -> ConfigFlowResult:
|
||||||
self, import_config: dict[str, Any]
|
"""Import a config entry from `airvisual` integration (see #83882)."""
|
||||||
) -> ConfigFlowResult:
|
return await self.async_step_user(import_data)
|
||||||
"""Import a config entry from configuration.yaml."""
|
|
||||||
return await self.async_step_user(import_config)
|
|
||||||
|
|
||||||
async def async_step_reauth(
|
async def async_step_reauth(
|
||||||
self, entry_data: Mapping[str, Any]
|
self, entry_data: Mapping[str, Any]
|
||||||
|
@ -114,7 +114,7 @@ class AirZoneConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
await airzone.get_version()
|
await airzone.get_version()
|
||||||
except AirzoneError as err:
|
except (AirzoneError, TimeoutError) as err:
|
||||||
raise AbortFlow("cannot_connect") from err
|
raise AbortFlow("cannot_connect") from err
|
||||||
|
|
||||||
return await self.async_step_discovered_connection()
|
return await self.async_step_discovered_connection()
|
||||||
|
@ -11,5 +11,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/airzone",
|
"documentation": "https://www.home-assistant.io/integrations/airzone",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["aioairzone"],
|
"loggers": ["aioairzone"],
|
||||||
"requirements": ["aioairzone==0.8.1"]
|
"requirements": ["aioairzone==0.9.1"]
|
||||||
}
|
}
|
||||||
|
@ -2,16 +2,21 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Callable
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any, Final
|
from typing import Any, Final
|
||||||
|
|
||||||
from aioairzone.common import GrilleAngle, SleepTimeout
|
from aioairzone.common import GrilleAngle, OperationMode, SleepTimeout
|
||||||
from aioairzone.const import (
|
from aioairzone.const import (
|
||||||
API_COLD_ANGLE,
|
API_COLD_ANGLE,
|
||||||
API_HEAT_ANGLE,
|
API_HEAT_ANGLE,
|
||||||
|
API_MODE,
|
||||||
API_SLEEP,
|
API_SLEEP,
|
||||||
AZD_COLD_ANGLE,
|
AZD_COLD_ANGLE,
|
||||||
AZD_HEAT_ANGLE,
|
AZD_HEAT_ANGLE,
|
||||||
|
AZD_MASTER,
|
||||||
|
AZD_MODE,
|
||||||
|
AZD_MODES,
|
||||||
AZD_SLEEP,
|
AZD_SLEEP,
|
||||||
AZD_ZONES,
|
AZD_ZONES,
|
||||||
)
|
)
|
||||||
@ -33,6 +38,9 @@ class AirzoneSelectDescription(SelectEntityDescription):
|
|||||||
|
|
||||||
api_param: str
|
api_param: str
|
||||||
options_dict: dict[str, int]
|
options_dict: dict[str, int]
|
||||||
|
options_fn: Callable[[dict[str, Any], dict[str, int]], list[str]] = (
|
||||||
|
lambda zone_data, value: list(value)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
GRILLE_ANGLE_DICT: Final[dict[str, int]] = {
|
GRILLE_ANGLE_DICT: Final[dict[str, int]] = {
|
||||||
@ -42,6 +50,15 @@ GRILLE_ANGLE_DICT: Final[dict[str, int]] = {
|
|||||||
"40deg": GrilleAngle.DEG_40,
|
"40deg": GrilleAngle.DEG_40,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MODE_DICT: Final[dict[str, int]] = {
|
||||||
|
"cool": OperationMode.COOLING,
|
||||||
|
"dry": OperationMode.DRY,
|
||||||
|
"fan": OperationMode.FAN,
|
||||||
|
"heat": OperationMode.HEATING,
|
||||||
|
"heat_cool": OperationMode.AUTO,
|
||||||
|
"stop": OperationMode.STOP,
|
||||||
|
}
|
||||||
|
|
||||||
SLEEP_DICT: Final[dict[str, int]] = {
|
SLEEP_DICT: Final[dict[str, int]] = {
|
||||||
"off": SleepTimeout.SLEEP_OFF,
|
"off": SleepTimeout.SLEEP_OFF,
|
||||||
"30m": SleepTimeout.SLEEP_30,
|
"30m": SleepTimeout.SLEEP_30,
|
||||||
@ -50,6 +67,26 @@ SLEEP_DICT: Final[dict[str, int]] = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def main_zone_options(
|
||||||
|
zone_data: dict[str, Any],
|
||||||
|
options: dict[str, int],
|
||||||
|
) -> list[str]:
|
||||||
|
"""Filter available modes."""
|
||||||
|
modes = zone_data.get(AZD_MODES, [])
|
||||||
|
return [k for k, v in options.items() if v in modes]
|
||||||
|
|
||||||
|
|
||||||
|
MAIN_ZONE_SELECT_TYPES: Final[tuple[AirzoneSelectDescription, ...]] = (
|
||||||
|
AirzoneSelectDescription(
|
||||||
|
api_param=API_MODE,
|
||||||
|
key=AZD_MODE,
|
||||||
|
options_dict=MODE_DICT,
|
||||||
|
options_fn=main_zone_options,
|
||||||
|
translation_key="modes",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
ZONE_SELECT_TYPES: Final[tuple[AirzoneSelectDescription, ...]] = (
|
ZONE_SELECT_TYPES: Final[tuple[AirzoneSelectDescription, ...]] = (
|
||||||
AirzoneSelectDescription(
|
AirzoneSelectDescription(
|
||||||
api_param=API_COLD_ANGLE,
|
api_param=API_COLD_ANGLE,
|
||||||
@ -95,7 +132,20 @@ async def async_setup_entry(
|
|||||||
received_zones = set(zones_data)
|
received_zones = set(zones_data)
|
||||||
new_zones = received_zones - added_zones
|
new_zones = received_zones - added_zones
|
||||||
if new_zones:
|
if new_zones:
|
||||||
async_add_entities(
|
entities: list[AirzoneZoneSelect] = [
|
||||||
|
AirzoneZoneSelect(
|
||||||
|
coordinator,
|
||||||
|
description,
|
||||||
|
entry,
|
||||||
|
system_zone_id,
|
||||||
|
zones_data.get(system_zone_id),
|
||||||
|
)
|
||||||
|
for system_zone_id in new_zones
|
||||||
|
for description in MAIN_ZONE_SELECT_TYPES
|
||||||
|
if description.key in zones_data.get(system_zone_id)
|
||||||
|
and zones_data.get(system_zone_id).get(AZD_MASTER) is True
|
||||||
|
]
|
||||||
|
entities += [
|
||||||
AirzoneZoneSelect(
|
AirzoneZoneSelect(
|
||||||
coordinator,
|
coordinator,
|
||||||
description,
|
description,
|
||||||
@ -106,7 +156,8 @@ async def async_setup_entry(
|
|||||||
for system_zone_id in new_zones
|
for system_zone_id in new_zones
|
||||||
for description in ZONE_SELECT_TYPES
|
for description in ZONE_SELECT_TYPES
|
||||||
if description.key in zones_data.get(system_zone_id)
|
if description.key in zones_data.get(system_zone_id)
|
||||||
)
|
]
|
||||||
|
async_add_entities(entities)
|
||||||
added_zones.update(new_zones)
|
added_zones.update(new_zones)
|
||||||
|
|
||||||
entry.async_on_unload(coordinator.async_add_listener(_async_entity_listener))
|
entry.async_on_unload(coordinator.async_add_listener(_async_entity_listener))
|
||||||
@ -153,6 +204,11 @@ class AirzoneZoneSelect(AirzoneZoneEntity, AirzoneBaseSelect):
|
|||||||
f"{self._attr_unique_id}_{system_zone_id}_{description.key}"
|
f"{self._attr_unique_id}_{system_zone_id}_{description.key}"
|
||||||
)
|
)
|
||||||
self.entity_description = description
|
self.entity_description = description
|
||||||
|
|
||||||
|
self._attr_options = self.entity_description.options_fn(
|
||||||
|
zone_data, description.options_dict
|
||||||
|
)
|
||||||
|
|
||||||
self.values_dict = {v: k for k, v in description.options_dict.items()}
|
self.values_dict = {v: k for k, v in description.options_dict.items()}
|
||||||
|
|
||||||
self._async_update_attrs()
|
self._async_update_attrs()
|
||||||
|
@ -52,6 +52,17 @@
|
|||||||
"40deg": "[%key:component::airzone::entity::select::grille_angles::state::40deg%]"
|
"40deg": "[%key:component::airzone::entity::select::grille_angles::state::40deg%]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"modes": {
|
||||||
|
"name": "Mode",
|
||||||
|
"state": {
|
||||||
|
"cool": "[%key:component::climate::entity_component::_::state::cool%]",
|
||||||
|
"dry": "[%key:component::climate::entity_component::_::state::dry%]",
|
||||||
|
"fan": "[%key:component::climate::entity_component::_::state::fan_only%]",
|
||||||
|
"heat": "[%key:component::climate::entity_component::_::state::heat%]",
|
||||||
|
"heat_cool": "[%key:component::climate::entity_component::_::state::heat_cool%]",
|
||||||
|
"stop": "Stop"
|
||||||
|
}
|
||||||
|
},
|
||||||
"sleep_times": {
|
"sleep_times": {
|
||||||
"name": "Sleep",
|
"name": "Sleep",
|
||||||
"state": {
|
"state": {
|
||||||
|
@ -161,6 +161,11 @@ class AirzoneBinarySensor(AirzoneEntity, BinarySensorEntity):
|
|||||||
|
|
||||||
entity_description: AirzoneBinarySensorEntityDescription
|
entity_description: AirzoneBinarySensorEntityDescription
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available(self) -> bool:
|
||||||
|
"""Return Airzone Cloud binary sensor availability."""
|
||||||
|
return super().available and self.is_on is not None
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _handle_coordinator_update(self) -> None:
|
def _handle_coordinator_update(self) -> None:
|
||||||
"""Update attributes when the coordinator updates."""
|
"""Update attributes when the coordinator updates."""
|
||||||
|
@ -6,5 +6,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/airzone_cloud",
|
"documentation": "https://www.home-assistant.io/integrations/airzone_cloud",
|
||||||
"iot_class": "cloud_push",
|
"iot_class": "cloud_push",
|
||||||
"loggers": ["aioairzone_cloud"],
|
"loggers": ["aioairzone_cloud"],
|
||||||
"requirements": ["aioairzone-cloud==0.6.1"]
|
"requirements": ["aioairzone-cloud==0.6.5"]
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,16 @@ from aioairzone_cloud.const import (
|
|||||||
AZD_AQ_PM_10,
|
AZD_AQ_PM_10,
|
||||||
AZD_CPU_USAGE,
|
AZD_CPU_USAGE,
|
||||||
AZD_HUMIDITY,
|
AZD_HUMIDITY,
|
||||||
|
AZD_INDOOR_EXCHANGER_TEMP,
|
||||||
|
AZD_INDOOR_RETURN_TEMP,
|
||||||
|
AZD_INDOOR_WORK_TEMP,
|
||||||
AZD_MEMORY_FREE,
|
AZD_MEMORY_FREE,
|
||||||
|
AZD_OUTDOOR_CONDENSER_PRESS,
|
||||||
|
AZD_OUTDOOR_DISCHARGE_TEMP,
|
||||||
|
AZD_OUTDOOR_ELECTRIC_CURRENT,
|
||||||
|
AZD_OUTDOOR_EVAPORATOR_PRESS,
|
||||||
|
AZD_OUTDOOR_EXCHANGER_TEMP,
|
||||||
|
AZD_OUTDOOR_TEMP,
|
||||||
AZD_TEMP,
|
AZD_TEMP,
|
||||||
AZD_THERMOSTAT_BATTERY,
|
AZD_THERMOSTAT_BATTERY,
|
||||||
AZD_THERMOSTAT_COVERAGE,
|
AZD_THERMOSTAT_COVERAGE,
|
||||||
@ -32,7 +41,9 @@ from homeassistant.const import (
|
|||||||
PERCENTAGE,
|
PERCENTAGE,
|
||||||
SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
|
SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
|
||||||
EntityCategory,
|
EntityCategory,
|
||||||
|
UnitOfElectricCurrent,
|
||||||
UnitOfInformation,
|
UnitOfInformation,
|
||||||
|
UnitOfPressure,
|
||||||
UnitOfTemperature,
|
UnitOfTemperature,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
@ -48,6 +59,78 @@ from .entity import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
AIDOO_SENSOR_TYPES: Final[tuple[SensorEntityDescription, ...]] = (
|
AIDOO_SENSOR_TYPES: Final[tuple[SensorEntityDescription, ...]] = (
|
||||||
|
SensorEntityDescription(
|
||||||
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
key=AZD_INDOOR_EXCHANGER_TEMP,
|
||||||
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
translation_key="indoor_exchanger_temp",
|
||||||
|
),
|
||||||
|
SensorEntityDescription(
|
||||||
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
key=AZD_INDOOR_RETURN_TEMP,
|
||||||
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
translation_key="indoor_return_temp",
|
||||||
|
),
|
||||||
|
SensorEntityDescription(
|
||||||
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
key=AZD_INDOOR_WORK_TEMP,
|
||||||
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
translation_key="indoor_work_temp",
|
||||||
|
),
|
||||||
|
SensorEntityDescription(
|
||||||
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
key=AZD_OUTDOOR_CONDENSER_PRESS,
|
||||||
|
native_unit_of_measurement=UnitOfPressure.KPA,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
translation_key="outdoor_condenser_press",
|
||||||
|
),
|
||||||
|
SensorEntityDescription(
|
||||||
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
key=AZD_OUTDOOR_DISCHARGE_TEMP,
|
||||||
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
translation_key="outdoor_discharge_temp",
|
||||||
|
),
|
||||||
|
SensorEntityDescription(
|
||||||
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
key=AZD_OUTDOOR_ELECTRIC_CURRENT,
|
||||||
|
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
translation_key="outdoor_electric_current",
|
||||||
|
),
|
||||||
|
SensorEntityDescription(
|
||||||
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
key=AZD_OUTDOOR_EVAPORATOR_PRESS,
|
||||||
|
native_unit_of_measurement=UnitOfPressure.KPA,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
translation_key="outdoor_evaporator_press",
|
||||||
|
),
|
||||||
|
SensorEntityDescription(
|
||||||
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
key=AZD_OUTDOOR_EXCHANGER_TEMP,
|
||||||
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
translation_key="outdoor_exchanger_temp",
|
||||||
|
),
|
||||||
|
SensorEntityDescription(
|
||||||
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
key=AZD_OUTDOOR_TEMP,
|
||||||
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
translation_key="outdoor_temp",
|
||||||
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
key=AZD_TEMP,
|
key=AZD_TEMP,
|
||||||
@ -189,6 +272,11 @@ async def async_setup_entry(
|
|||||||
class AirzoneSensor(AirzoneEntity, SensorEntity):
|
class AirzoneSensor(AirzoneEntity, SensorEntity):
|
||||||
"""Define an Airzone Cloud sensor."""
|
"""Define an Airzone Cloud sensor."""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available(self) -> bool:
|
||||||
|
"""Return Airzone Cloud sensor availability."""
|
||||||
|
return super().available and self.native_value is not None
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _handle_coordinator_update(self) -> None:
|
def _handle_coordinator_update(self) -> None:
|
||||||
"""Update attributes when the coordinator updates."""
|
"""Update attributes when the coordinator updates."""
|
||||||
|
@ -45,6 +45,33 @@
|
|||||||
"free_memory": {
|
"free_memory": {
|
||||||
"name": "Free memory"
|
"name": "Free memory"
|
||||||
},
|
},
|
||||||
|
"indoor_exchanger_temp": {
|
||||||
|
"name": "Indoor exchanger temperature"
|
||||||
|
},
|
||||||
|
"indoor_return_temp": {
|
||||||
|
"name": "Indoor return temperature"
|
||||||
|
},
|
||||||
|
"indoor_work_temp": {
|
||||||
|
"name": "Indoor working temperature"
|
||||||
|
},
|
||||||
|
"outdoor_condenser_press": {
|
||||||
|
"name": "Outdoor condenser pressure"
|
||||||
|
},
|
||||||
|
"outdoor_discharge_temp": {
|
||||||
|
"name": "Outdoor discharge temperature"
|
||||||
|
},
|
||||||
|
"outdoor_electric_current": {
|
||||||
|
"name": "Outdoor electric current"
|
||||||
|
},
|
||||||
|
"outdoor_evaporator_press": {
|
||||||
|
"name": "Outdoor evaporator pressure"
|
||||||
|
},
|
||||||
|
"outdoor_exchanger_temp": {
|
||||||
|
"name": "Outdoor exchanger temperature"
|
||||||
|
},
|
||||||
|
"outdoor_temp": {
|
||||||
|
"name": "Outdoor temperature"
|
||||||
|
},
|
||||||
"thermostat_coverage": {
|
"thermostat_coverage": {
|
||||||
"name": "Signal percentage"
|
"name": "Signal percentage"
|
||||||
}
|
}
|
||||||
|
@ -15,12 +15,26 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"services": {
|
"services": {
|
||||||
"alarm_arm_away": "mdi:shield-lock",
|
"alarm_arm_away": {
|
||||||
"alarm_arm_home": "mdi:shield-home",
|
"service": "mdi:shield-lock"
|
||||||
"alarm_arm_night": "mdi:shield-moon",
|
},
|
||||||
"alarm_arm_custom_bypass": "mdi:security",
|
"alarm_arm_home": {
|
||||||
"alarm_disarm": "mdi:shield-off",
|
"service": "mdi:shield-home"
|
||||||
"alarm_trigger": "mdi:bell-ring",
|
},
|
||||||
"alarm_arm_vacation": "mdi:shield-airplane"
|
"alarm_arm_night": {
|
||||||
|
"service": "mdi:shield-moon"
|
||||||
|
},
|
||||||
|
"alarm_arm_custom_bypass": {
|
||||||
|
"service": "mdi:security"
|
||||||
|
},
|
||||||
|
"alarm_disarm": {
|
||||||
|
"service": "mdi:shield-off"
|
||||||
|
},
|
||||||
|
"alarm_trigger": {
|
||||||
|
"service": "mdi:bell-ring"
|
||||||
|
},
|
||||||
|
"alarm_arm_vacation": {
|
||||||
|
"service": "mdi:shield-airplane"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"services": {
|
"services": {
|
||||||
"alarm_keypress": "mdi:dialpad",
|
"alarm_keypress": {
|
||||||
"alarm_toggle_chime": "mdi:abc"
|
"service": "mdi:dialpad"
|
||||||
|
},
|
||||||
|
"alarm_toggle_chime": {
|
||||||
|
"service": "mdi:abc"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -124,9 +124,9 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|||||||
if not entities:
|
if not entities:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
component.async_register_entity_service(SERVICE_TURN_OFF, {}, "async_turn_off")
|
component.async_register_entity_service(SERVICE_TURN_OFF, None, "async_turn_off")
|
||||||
component.async_register_entity_service(SERVICE_TURN_ON, {}, "async_turn_on")
|
component.async_register_entity_service(SERVICE_TURN_ON, None, "async_turn_on")
|
||||||
component.async_register_entity_service(SERVICE_TOGGLE, {}, "async_toggle")
|
component.async_register_entity_service(SERVICE_TOGGLE, None, "async_toggle")
|
||||||
|
|
||||||
await component.async_add_entities(entities)
|
await component.async_add_entities(entities)
|
||||||
|
|
||||||
@ -162,16 +162,8 @@ class Alert(Entity):
|
|||||||
self._data = data
|
self._data = data
|
||||||
|
|
||||||
self._message_template = message_template
|
self._message_template = message_template
|
||||||
if self._message_template is not None:
|
|
||||||
self._message_template.hass = hass
|
|
||||||
|
|
||||||
self._done_message_template = done_message_template
|
self._done_message_template = done_message_template
|
||||||
if self._done_message_template is not None:
|
|
||||||
self._done_message_template.hass = hass
|
|
||||||
|
|
||||||
self._title_template = title_template
|
self._title_template = title_template
|
||||||
if self._title_template is not None:
|
|
||||||
self._title_template.hass = hass
|
|
||||||
|
|
||||||
self._notifiers = notifiers
|
self._notifiers = notifiers
|
||||||
self._can_ack = can_ack
|
self._can_ack = can_ack
|
||||||
|
@ -1,7 +1,13 @@
|
|||||||
{
|
{
|
||||||
"services": {
|
"services": {
|
||||||
"toggle": "mdi:bell-ring",
|
"toggle": {
|
||||||
"turn_off": "mdi:bell-off",
|
"service": "mdi:bell-ring"
|
||||||
"turn_on": "mdi:bell-alert"
|
},
|
||||||
|
"turn_off": {
|
||||||
|
"service": "mdi:bell-off"
|
||||||
|
},
|
||||||
|
"turn_on": {
|
||||||
|
"service": "mdi:bell-alert"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -661,6 +661,9 @@ class RemoteCapabilities(AlexaEntity):
|
|||||||
def interfaces(self) -> Generator[AlexaCapability]:
|
def interfaces(self) -> Generator[AlexaCapability]:
|
||||||
"""Yield the supported interfaces."""
|
"""Yield the supported interfaces."""
|
||||||
yield AlexaPowerController(self.entity)
|
yield AlexaPowerController(self.entity)
|
||||||
|
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||||
|
activities = self.entity.attributes.get(remote.ATTR_ACTIVITY_LIST) or []
|
||||||
|
if activities and supported & remote.RemoteEntityFeature.ACTIVITY:
|
||||||
yield AlexaModeController(
|
yield AlexaModeController(
|
||||||
self.entity, instance=f"{remote.DOMAIN}.{remote.ATTR_ACTIVITY}"
|
self.entity, instance=f"{remote.DOMAIN}.{remote.ATTR_ACTIVITY}"
|
||||||
)
|
)
|
||||||
|
@ -52,7 +52,6 @@ class AlexaFlashBriefingView(http.HomeAssistantView):
|
|||||||
"""Initialize Alexa view."""
|
"""Initialize Alexa view."""
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.flash_briefings = flash_briefings
|
self.flash_briefings = flash_briefings
|
||||||
template.attach(hass, self.flash_briefings)
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def get(
|
def get(
|
||||||
|
@ -1206,7 +1206,7 @@ async def async_api_set_mode(
|
|||||||
raise AlexaInvalidValueError(msg)
|
raise AlexaInvalidValueError(msg)
|
||||||
|
|
||||||
# Remote Activity
|
# Remote Activity
|
||||||
if instance == f"{remote.DOMAIN}.{remote.ATTR_ACTIVITY}":
|
elif instance == f"{remote.DOMAIN}.{remote.ATTR_ACTIVITY}":
|
||||||
activity = mode.split(".")[1]
|
activity = mode.split(".")[1]
|
||||||
activities: list[str] | None = entity.attributes.get(remote.ATTR_ACTIVITY_LIST)
|
activities: list[str] | None = entity.attributes.get(remote.ATTR_ACTIVITY_LIST)
|
||||||
if activity != PRESET_MODE_NA and activities and activity in activities:
|
if activity != PRESET_MODE_NA and activities and activity in activities:
|
||||||
|
@ -5,5 +5,6 @@
|
|||||||
"codeowners": ["@home-assistant/cloud", "@ochlocracy", "@jbouwh"],
|
"codeowners": ["@home-assistant/cloud", "@ochlocracy", "@jbouwh"],
|
||||||
"dependencies": ["http"],
|
"dependencies": ["http"],
|
||||||
"documentation": "https://www.home-assistant.io/integrations/alexa",
|
"documentation": "https://www.home-assistant.io/integrations/alexa",
|
||||||
|
"integration_type": "system",
|
||||||
"iot_class": "cloud_push"
|
"iot_class": "cloud_push"
|
||||||
}
|
}
|
||||||
|
@ -283,7 +283,7 @@ class AlexaPresetResource(AlexaCapabilityResource):
|
|||||||
"""Implements Alexa PresetResources.
|
"""Implements Alexa PresetResources.
|
||||||
|
|
||||||
Use presetResources with RangeController to provide a set of
|
Use presetResources with RangeController to provide a set of
|
||||||
friendlyNamesfor each RangeController preset.
|
friendlyNames for each RangeController preset.
|
||||||
|
|
||||||
https://developer.amazon.com/docs/device-apis/resources-and-assets.html#presetresources
|
https://developer.amazon.com/docs/device-apis/resources-and-assets.html#presetresources
|
||||||
"""
|
"""
|
||||||
|
@ -194,7 +194,7 @@ async def async_handle_message(
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
if not enabled:
|
if not enabled:
|
||||||
raise AlexaBridgeUnreachableError(
|
raise AlexaBridgeUnreachableError( # noqa: TRY301
|
||||||
"Alexa API not enabled in Home Assistant configuration"
|
"Alexa API not enabled in Home Assistant configuration"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -8,128 +8,23 @@ CONF_REGION: Final = "region_name"
|
|||||||
CONF_ACCESS_KEY_ID: Final = "aws_access_key_id"
|
CONF_ACCESS_KEY_ID: Final = "aws_access_key_id"
|
||||||
CONF_SECRET_ACCESS_KEY: Final = "aws_secret_access_key"
|
CONF_SECRET_ACCESS_KEY: Final = "aws_secret_access_key"
|
||||||
|
|
||||||
DEFAULT_REGION: Final = "us-east-1"
|
|
||||||
SUPPORTED_REGIONS: Final[list[str]] = [
|
|
||||||
"us-east-1",
|
|
||||||
"us-east-2",
|
|
||||||
"us-west-1",
|
|
||||||
"us-west-2",
|
|
||||||
"ca-central-1",
|
|
||||||
"eu-west-1",
|
|
||||||
"eu-central-1",
|
|
||||||
"eu-west-2",
|
|
||||||
"eu-west-3",
|
|
||||||
"ap-southeast-1",
|
|
||||||
"ap-southeast-2",
|
|
||||||
"ap-northeast-2",
|
|
||||||
"ap-northeast-1",
|
|
||||||
"ap-south-1",
|
|
||||||
"sa-east-1",
|
|
||||||
]
|
|
||||||
|
|
||||||
CONF_ENGINE: Final = "engine"
|
CONF_ENGINE: Final = "engine"
|
||||||
CONF_VOICE: Final = "voice"
|
CONF_VOICE: Final = "voice"
|
||||||
CONF_OUTPUT_FORMAT: Final = "output_format"
|
CONF_OUTPUT_FORMAT: Final = "output_format"
|
||||||
CONF_SAMPLE_RATE: Final = "sample_rate"
|
CONF_SAMPLE_RATE: Final = "sample_rate"
|
||||||
CONF_TEXT_TYPE: Final = "text_type"
|
CONF_TEXT_TYPE: Final = "text_type"
|
||||||
|
|
||||||
SUPPORTED_VOICES: Final[list[str]] = [
|
SUPPORTED_OUTPUT_FORMATS: Final[set[str]] = {"mp3", "ogg_vorbis", "pcm"}
|
||||||
"Aditi", # Hindi
|
|
||||||
"Amy", # English (British)
|
|
||||||
"Aria", # English (New Zealand), Neural
|
|
||||||
"Arlet", # Catalan, Neural
|
|
||||||
"Arthur", # English, Neural
|
|
||||||
"Astrid", # Swedish
|
|
||||||
"Ayanda", # English (South African), Neural
|
|
||||||
"Bianca", # Italian
|
|
||||||
"Brian", # English (British)
|
|
||||||
"Camila", # Portuguese, Brazilian
|
|
||||||
"Carla", # Italian
|
|
||||||
"Carmen", # Romanian
|
|
||||||
"Celine", # French
|
|
||||||
"Chantal", # French Canadian
|
|
||||||
"Conchita", # Spanish (European)
|
|
||||||
"Cristiano", # Portuguese (European)
|
|
||||||
"Daniel", # German, Neural
|
|
||||||
"Dora", # Icelandic
|
|
||||||
"Elin", # Swedish, Neural
|
|
||||||
"Emma", # English
|
|
||||||
"Enrique", # Spanish (European)
|
|
||||||
"Ewa", # Polish
|
|
||||||
"Filiz", # Turkish
|
|
||||||
"Gabrielle", # French (Canadian)
|
|
||||||
"Geraint", # English Welsh
|
|
||||||
"Giorgio", # Italian
|
|
||||||
"Gwyneth", # Welsh
|
|
||||||
"Hala", # Arabic (Gulf), Neural
|
|
||||||
"Hannah", # German (Austrian), Neural
|
|
||||||
"Hans", # German
|
|
||||||
"Hiujin", # Chinese (Cantonese), Neural
|
|
||||||
"Ida", # Norwegian, Neural
|
|
||||||
"Ines", # Portuguese, European # codespell:ignore ines
|
|
||||||
"Ivy", # English
|
|
||||||
"Jacek", # Polish
|
|
||||||
"Jan", # Polish
|
|
||||||
"Joanna", # English
|
|
||||||
"Joey", # English
|
|
||||||
"Justin", # English
|
|
||||||
"Kajal", # English (Indian)/Hindi (Bilingual ), Neural
|
|
||||||
"Karl", # Icelandic
|
|
||||||
"Kendra", # English
|
|
||||||
"Kevin", # English, Neural
|
|
||||||
"Kimberly", # English
|
|
||||||
"Laura", # Dutch, Neural
|
|
||||||
"Lea", # French
|
|
||||||
"Liam", # Canadian French, Neural
|
|
||||||
"Liv", # Norwegian
|
|
||||||
"Lotte", # Dutch
|
|
||||||
"Lucia", # Spanish European
|
|
||||||
"Lupe", # Spanish US
|
|
||||||
"Mads", # Danish
|
|
||||||
"Maja", # Polish
|
|
||||||
"Marlene", # German
|
|
||||||
"Mathieu", # French
|
|
||||||
"Matthew", # English
|
|
||||||
"Maxim", # Russian
|
|
||||||
"Mia", # Spanish Mexican
|
|
||||||
"Miguel", # Spanish US
|
|
||||||
"Mizuki", # Japanese
|
|
||||||
"Naja", # Danish
|
|
||||||
"Nicole", # English Australian
|
|
||||||
"Ola", # Polish, Neural
|
|
||||||
"Olivia", # Female, Australian, Neural
|
|
||||||
"Penelope", # Spanish US
|
|
||||||
"Pedro", # Spanish US, Neural
|
|
||||||
"Raveena", # English, Indian
|
|
||||||
"Ricardo", # Portuguese (Brazilian)
|
|
||||||
"Ruben", # Dutch
|
|
||||||
"Russell", # English (Australian)
|
|
||||||
"Ruth", # English, Neural
|
|
||||||
"Salli", # English
|
|
||||||
"Seoyeon", # Korean
|
|
||||||
"Stephen", # English, Neural
|
|
||||||
"Suvi", # Finnish
|
|
||||||
"Takumi", # Japanese
|
|
||||||
"Tatyana", # Russian
|
|
||||||
"Vicki", # German
|
|
||||||
"Vitoria", # Portuguese, Brazilian
|
|
||||||
"Zeina", # Arabic
|
|
||||||
"Zhiyu", # Chinese
|
|
||||||
]
|
|
||||||
|
|
||||||
SUPPORTED_OUTPUT_FORMATS: Final[list[str]] = ["mp3", "ogg_vorbis", "pcm"]
|
SUPPORTED_SAMPLE_RATES: Final[set[str]] = {"8000", "16000", "22050", "24000"}
|
||||||
|
|
||||||
SUPPORTED_ENGINES: Final[list[str]] = ["neural", "standard"]
|
SUPPORTED_SAMPLE_RATES_MAP: Final[dict[str, set[str]]] = {
|
||||||
|
"mp3": {"8000", "16000", "22050", "24000"},
|
||||||
SUPPORTED_SAMPLE_RATES: Final[list[str]] = ["8000", "16000", "22050", "24000"]
|
"ogg_vorbis": {"8000", "16000", "22050"},
|
||||||
|
"pcm": {"8000", "16000"},
|
||||||
SUPPORTED_SAMPLE_RATES_MAP: Final[dict[str, list[str]]] = {
|
|
||||||
"mp3": ["8000", "16000", "22050", "24000"],
|
|
||||||
"ogg_vorbis": ["8000", "16000", "22050"],
|
|
||||||
"pcm": ["8000", "16000"],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SUPPORTED_TEXT_TYPES: Final[list[str]] = ["text", "ssml"]
|
SUPPORTED_TEXT_TYPES: Final[set[str]] = {"text", "ssml"}
|
||||||
|
|
||||||
CONTENT_TYPE_EXTENSIONS: Final[dict[str, str]] = {
|
CONTENT_TYPE_EXTENSIONS: Final[dict[str, str]] = {
|
||||||
"audio/mpeg": "mp3",
|
"audio/mpeg": "mp3",
|
||||||
@ -137,6 +32,8 @@ CONTENT_TYPE_EXTENSIONS: Final[dict[str, str]] = {
|
|||||||
"audio/pcm": "pcm",
|
"audio/pcm": "pcm",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DEFAULT_REGION: Final = "us-east-1"
|
||||||
|
|
||||||
DEFAULT_ENGINE: Final = "standard"
|
DEFAULT_ENGINE: Final = "standard"
|
||||||
DEFAULT_VOICE: Final = "Joanna"
|
DEFAULT_VOICE: Final = "Joanna"
|
||||||
DEFAULT_OUTPUT_FORMAT: Final = "mp3"
|
DEFAULT_OUTPUT_FORMAT: Final = "mp3"
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections import defaultdict
|
||||||
import logging
|
import logging
|
||||||
from typing import Any, Final
|
from typing import Any, Final
|
||||||
|
|
||||||
@ -16,6 +17,11 @@ from homeassistant.components.tts import (
|
|||||||
)
|
)
|
||||||
from homeassistant.const import ATTR_CREDENTIALS, CONF_PROFILE_NAME
|
from homeassistant.const import ATTR_CREDENTIALS, CONF_PROFILE_NAME
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.generated.amazon_polly import (
|
||||||
|
SUPPORTED_ENGINES,
|
||||||
|
SUPPORTED_REGIONS,
|
||||||
|
SUPPORTED_VOICES,
|
||||||
|
)
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||||
|
|
||||||
@ -38,13 +44,10 @@ from .const import (
|
|||||||
DEFAULT_SAMPLE_RATES,
|
DEFAULT_SAMPLE_RATES,
|
||||||
DEFAULT_TEXT_TYPE,
|
DEFAULT_TEXT_TYPE,
|
||||||
DEFAULT_VOICE,
|
DEFAULT_VOICE,
|
||||||
SUPPORTED_ENGINES,
|
|
||||||
SUPPORTED_OUTPUT_FORMATS,
|
SUPPORTED_OUTPUT_FORMATS,
|
||||||
SUPPORTED_REGIONS,
|
|
||||||
SUPPORTED_SAMPLE_RATES,
|
SUPPORTED_SAMPLE_RATES,
|
||||||
SUPPORTED_SAMPLE_RATES_MAP,
|
SUPPORTED_SAMPLE_RATES_MAP,
|
||||||
SUPPORTED_TEXT_TYPES,
|
SUPPORTED_TEXT_TYPES,
|
||||||
SUPPORTED_VOICES,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
_LOGGER: Final = logging.getLogger(__name__)
|
_LOGGER: Final = logging.getLogger(__name__)
|
||||||
@ -112,6 +115,8 @@ def get_engine(
|
|||||||
|
|
||||||
all_voices: dict[str, dict[str, str]] = {}
|
all_voices: dict[str, dict[str, str]] = {}
|
||||||
|
|
||||||
|
all_engines: dict[str, set[str]] = defaultdict(set)
|
||||||
|
|
||||||
all_voices_req = polly_client.describe_voices()
|
all_voices_req = polly_client.describe_voices()
|
||||||
|
|
||||||
for voice in all_voices_req.get("Voices", []):
|
for voice in all_voices_req.get("Voices", []):
|
||||||
@ -122,8 +127,12 @@ def get_engine(
|
|||||||
language_code: str | None = voice.get("LanguageCode")
|
language_code: str | None = voice.get("LanguageCode")
|
||||||
if language_code is not None and language_code not in supported_languages:
|
if language_code is not None and language_code not in supported_languages:
|
||||||
supported_languages.append(language_code)
|
supported_languages.append(language_code)
|
||||||
|
for engine in voice.get("SupportedEngines"):
|
||||||
|
all_engines[engine].add(voice_id)
|
||||||
|
|
||||||
return AmazonPollyProvider(polly_client, config, supported_languages, all_voices)
|
return AmazonPollyProvider(
|
||||||
|
polly_client, config, supported_languages, all_voices, all_engines
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class AmazonPollyProvider(Provider):
|
class AmazonPollyProvider(Provider):
|
||||||
@ -135,13 +144,16 @@ class AmazonPollyProvider(Provider):
|
|||||||
config: ConfigType,
|
config: ConfigType,
|
||||||
supported_languages: list[str],
|
supported_languages: list[str],
|
||||||
all_voices: dict[str, dict[str, str]],
|
all_voices: dict[str, dict[str, str]],
|
||||||
|
all_engines: dict[str, set[str]],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize Amazon Polly provider for TTS."""
|
"""Initialize Amazon Polly provider for TTS."""
|
||||||
self.client = polly_client
|
self.client = polly_client
|
||||||
self.config = config
|
self.config = config
|
||||||
self.supported_langs = supported_languages
|
self.supported_langs = supported_languages
|
||||||
self.all_voices = all_voices
|
self.all_voices = all_voices
|
||||||
|
self.all_engines = all_engines
|
||||||
self.default_voice: str = self.config[CONF_VOICE]
|
self.default_voice: str = self.config[CONF_VOICE]
|
||||||
|
self.default_engine: str = self.config[CONF_ENGINE]
|
||||||
self.name = "Amazon Polly"
|
self.name = "Amazon Polly"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -157,12 +169,12 @@ class AmazonPollyProvider(Provider):
|
|||||||
@property
|
@property
|
||||||
def default_options(self) -> dict[str, str]:
|
def default_options(self) -> dict[str, str]:
|
||||||
"""Return dict include default options."""
|
"""Return dict include default options."""
|
||||||
return {CONF_VOICE: self.default_voice}
|
return {CONF_VOICE: self.default_voice, CONF_ENGINE: self.default_engine}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_options(self) -> list[str]:
|
def supported_options(self) -> list[str]:
|
||||||
"""Return a list of supported options."""
|
"""Return a list of supported options."""
|
||||||
return [CONF_VOICE]
|
return [CONF_VOICE, CONF_ENGINE]
|
||||||
|
|
||||||
def get_tts_audio(
|
def get_tts_audio(
|
||||||
self,
|
self,
|
||||||
@ -177,9 +189,14 @@ class AmazonPollyProvider(Provider):
|
|||||||
_LOGGER.error("%s does not support the %s language", voice_id, language)
|
_LOGGER.error("%s does not support the %s language", voice_id, language)
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
|
engine = options.get(CONF_ENGINE, self.default_engine)
|
||||||
|
if voice_id not in self.all_engines[engine]:
|
||||||
|
_LOGGER.error("%s does not support the %s engine", voice_id, engine)
|
||||||
|
return None, None
|
||||||
|
|
||||||
_LOGGER.debug("Requesting TTS file for text: %s", message)
|
_LOGGER.debug("Requesting TTS file for text: %s", message)
|
||||||
resp = self.client.synthesize_speech(
|
resp = self.client.synthesize_speech(
|
||||||
Engine=self.config[CONF_ENGINE],
|
Engine=engine,
|
||||||
OutputFormat=self.config[CONF_OUTPUT_FORMAT],
|
OutputFormat=self.config[CONF_OUTPUT_FORMAT],
|
||||||
SampleRate=self.config[CONF_SAMPLE_RATE],
|
SampleRate=self.config[CONF_SAMPLE_RATE],
|
||||||
Text=message,
|
Text=message,
|
||||||
|
@ -7,11 +7,13 @@ from homeassistant.config_entries import ConfigEntry
|
|||||||
from homeassistant.const import CONF_API_TOKEN
|
from homeassistant.const import CONF_API_TOKEN
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
from .const import CONF_SITE_ID, DOMAIN, PLATFORMS
|
from .const import CONF_SITE_ID, PLATFORMS
|
||||||
from .coordinator import AmberUpdateCoordinator
|
from .coordinator import AmberUpdateCoordinator
|
||||||
|
|
||||||
|
type AmberConfigEntry = ConfigEntry[AmberUpdateCoordinator]
|
||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|
||||||
|
async def async_setup_entry(hass: HomeAssistant, entry: AmberConfigEntry) -> bool:
|
||||||
"""Set up Amber Electric from a config entry."""
|
"""Set up Amber Electric from a config entry."""
|
||||||
configuration = Configuration(access_token=entry.data[CONF_API_TOKEN])
|
configuration = Configuration(access_token=entry.data[CONF_API_TOKEN])
|
||||||
api_instance = amber_api.AmberApi.create(configuration)
|
api_instance = amber_api.AmberApi.create(configuration)
|
||||||
@ -19,15 +21,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
|
|
||||||
coordinator = AmberUpdateCoordinator(hass, api_instance, site_id)
|
coordinator = AmberUpdateCoordinator(hass, api_instance, site_id)
|
||||||
await coordinator.async_config_entry_first_refresh()
|
await coordinator.async_config_entry_first_refresh()
|
||||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
|
entry.runtime_data = coordinator
|
||||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_unload_entry(hass: HomeAssistant, entry: AmberConfigEntry) -> bool:
|
||||||
"""Unload a config entry."""
|
"""Unload a config entry."""
|
||||||
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||||
if unload_ok:
|
|
||||||
hass.data[DOMAIN].pop(entry.entry_id)
|
|
||||||
|
|
||||||
return unload_ok
|
|
||||||
|
@ -8,12 +8,12 @@ from homeassistant.components.binary_sensor import (
|
|||||||
BinarySensorEntity,
|
BinarySensorEntity,
|
||||||
BinarySensorEntityDescription,
|
BinarySensorEntityDescription,
|
||||||
)
|
)
|
||||||
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 AddEntitiesCallback
|
||||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
|
|
||||||
from .const import ATTRIBUTION, DOMAIN
|
from . import AmberConfigEntry
|
||||||
|
from .const import ATTRIBUTION
|
||||||
from .coordinator import AmberUpdateCoordinator
|
from .coordinator import AmberUpdateCoordinator
|
||||||
|
|
||||||
PRICE_SPIKE_ICONS = {
|
PRICE_SPIKE_ICONS = {
|
||||||
@ -85,11 +85,11 @@ class AmberDemandWindowBinarySensor(AmberPriceGridSensor):
|
|||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry: ConfigEntry,
|
entry: AmberConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up a config entry."""
|
"""Set up a config entry."""
|
||||||
coordinator: AmberUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
coordinator = entry.runtime_data
|
||||||
|
|
||||||
price_spike_description = BinarySensorEntityDescription(
|
price_spike_description = BinarySensorEntityDescription(
|
||||||
key="price_spike",
|
key="price_spike",
|
||||||
|
@ -17,13 +17,13 @@ from homeassistant.components.sensor import (
|
|||||||
SensorEntityDescription,
|
SensorEntityDescription,
|
||||||
SensorStateClass,
|
SensorStateClass,
|
||||||
)
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
|
||||||
from homeassistant.const import CURRENCY_DOLLAR, PERCENTAGE, UnitOfEnergy
|
from homeassistant.const import CURRENCY_DOLLAR, PERCENTAGE, UnitOfEnergy
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
|
|
||||||
from .const import ATTRIBUTION, DOMAIN
|
from . import AmberConfigEntry
|
||||||
|
from .const import ATTRIBUTION
|
||||||
from .coordinator import AmberUpdateCoordinator, normalize_descriptor
|
from .coordinator import AmberUpdateCoordinator, normalize_descriptor
|
||||||
|
|
||||||
UNIT = f"{CURRENCY_DOLLAR}/{UnitOfEnergy.KILO_WATT_HOUR}"
|
UNIT = f"{CURRENCY_DOLLAR}/{UnitOfEnergy.KILO_WATT_HOUR}"
|
||||||
@ -196,11 +196,11 @@ class AmberGridSensor(CoordinatorEntity[AmberUpdateCoordinator], SensorEntity):
|
|||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry: ConfigEntry,
|
entry: AmberConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up a config entry."""
|
"""Set up a config entry."""
|
||||||
coordinator: AmberUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
coordinator = entry.runtime_data
|
||||||
|
|
||||||
current: dict[str, CurrentInterval] = coordinator.data["current"]
|
current: dict[str, CurrentInterval] = coordinator.data["current"]
|
||||||
forecasts: dict[str, list[ForecastInterval]] = coordinator.data["forecasts"]
|
forecasts: dict[str, list[ForecastInterval]] = coordinator.data["forecasts"]
|
||||||
|
@ -8,28 +8,30 @@ from homeassistant.config_entries import ConfigEntry
|
|||||||
from homeassistant.const import Platform
|
from homeassistant.const import Platform
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
from .const import DOMAIN
|
|
||||||
from .coordinator import AmbientNetworkDataUpdateCoordinator
|
from .coordinator import AmbientNetworkDataUpdateCoordinator
|
||||||
|
|
||||||
|
type AmbientNetworkConfigEntry = ConfigEntry[AmbientNetworkDataUpdateCoordinator]
|
||||||
|
|
||||||
PLATFORMS: list[Platform] = [Platform.SENSOR]
|
PLATFORMS: list[Platform] = [Platform.SENSOR]
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_setup_entry(
|
||||||
|
hass: HomeAssistant, entry: AmbientNetworkConfigEntry
|
||||||
|
) -> bool:
|
||||||
"""Set up the Ambient Weather Network from a config entry."""
|
"""Set up the Ambient Weather Network from a config entry."""
|
||||||
|
|
||||||
api = OpenAPI()
|
api = OpenAPI()
|
||||||
coordinator = AmbientNetworkDataUpdateCoordinator(hass, api)
|
coordinator = AmbientNetworkDataUpdateCoordinator(hass, api)
|
||||||
await coordinator.async_config_entry_first_refresh()
|
await coordinator.async_config_entry_first_refresh()
|
||||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
|
entry.runtime_data = coordinator
|
||||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_unload_entry(
|
||||||
|
hass: HomeAssistant, entry: AmbientNetworkConfigEntry
|
||||||
|
) -> bool:
|
||||||
"""Unload a config entry."""
|
"""Unload a config entry."""
|
||||||
|
|
||||||
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
|
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||||
hass.data[DOMAIN].pop(entry.entry_id)
|
|
||||||
|
|
||||||
return unload_ok
|
|
||||||
|
@ -7,5 +7,5 @@
|
|||||||
"integration_type": "service",
|
"integration_type": "service",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["aioambient"],
|
"loggers": ["aioambient"],
|
||||||
"requirements": ["aioambient==2024.01.0"]
|
"requirements": ["aioambient==2024.08.0"]
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,6 @@ from homeassistant.components.sensor import (
|
|||||||
SensorEntityDescription,
|
SensorEntityDescription,
|
||||||
SensorStateClass,
|
SensorStateClass,
|
||||||
)
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
CONCENTRATION_PARTS_PER_MILLION,
|
CONCENTRATION_PARTS_PER_MILLION,
|
||||||
@ -29,7 +28,7 @@ from homeassistant.core import HomeAssistant
|
|||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt as dt_util
|
||||||
|
|
||||||
from .const import DOMAIN
|
from . import AmbientNetworkConfigEntry
|
||||||
from .coordinator import AmbientNetworkDataUpdateCoordinator
|
from .coordinator import AmbientNetworkDataUpdateCoordinator
|
||||||
from .entity import AmbientNetworkEntity
|
from .entity import AmbientNetworkEntity
|
||||||
|
|
||||||
@ -271,12 +270,12 @@ SENSOR_DESCRIPTIONS = (
|
|||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry: ConfigEntry,
|
entry: AmbientNetworkConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up the Ambient Network sensor entities."""
|
"""Set up the Ambient Network sensor entities."""
|
||||||
|
|
||||||
coordinator: AmbientNetworkDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
coordinator = entry.runtime_data
|
||||||
if coordinator.config_entry is not None:
|
if coordinator.config_entry is not None:
|
||||||
async_add_entities(
|
async_add_entities(
|
||||||
AmbientNetworkSensor(
|
AmbientNetworkSensor(
|
||||||
|
@ -17,7 +17,6 @@ from homeassistant.const import (
|
|||||||
)
|
)
|
||||||
from homeassistant.core import Event, HomeAssistant, callback
|
from homeassistant.core import Event, HomeAssistant, callback
|
||||||
from homeassistant.exceptions import ConfigEntryNotReady
|
from homeassistant.exceptions import ConfigEntryNotReady
|
||||||
from homeassistant.helpers import config_validation as cv
|
|
||||||
import homeassistant.helpers.device_registry as dr
|
import homeassistant.helpers.device_registry as dr
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||||
import homeassistant.helpers.entity_registry as er
|
import homeassistant.helpers.entity_registry as er
|
||||||
@ -25,7 +24,6 @@ import homeassistant.helpers.entity_registry as er
|
|||||||
from .const import (
|
from .const import (
|
||||||
ATTR_LAST_DATA,
|
ATTR_LAST_DATA,
|
||||||
CONF_APP_KEY,
|
CONF_APP_KEY,
|
||||||
DOMAIN,
|
|
||||||
LOGGER,
|
LOGGER,
|
||||||
TYPE_SOLARRADIATION,
|
TYPE_SOLARRADIATION,
|
||||||
TYPE_SOLARRADIATION_LX,
|
TYPE_SOLARRADIATION_LX,
|
||||||
@ -37,7 +35,6 @@ DATA_CONFIG = "config"
|
|||||||
|
|
||||||
DEFAULT_SOCKET_MIN_RETRY = 15
|
DEFAULT_SOCKET_MIN_RETRY = 15
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False)
|
|
||||||
|
|
||||||
type AmbientStationConfigEntry = ConfigEntry[AmbientStation]
|
type AmbientStationConfigEntry = ConfigEntry[AmbientStation]
|
||||||
|
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from aioambient import API
|
from aioambient import API
|
||||||
from aioambient.errors import AmbientError
|
from aioambient.errors import AmbientError
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
@ -32,7 +34,9 @@ class AmbientStationFlowHandler(ConfigFlow, domain=DOMAIN):
|
|||||||
errors=errors if errors else {},
|
errors=errors if errors else {},
|
||||||
)
|
)
|
||||||
|
|
||||||
async def async_step_user(self, user_input: dict | None = None) -> ConfigFlowResult:
|
async def async_step_user(
|
||||||
|
self, user_input: dict[str, Any] | None = None
|
||||||
|
) -> ConfigFlowResult:
|
||||||
"""Handle the start of the config flow."""
|
"""Handle the start of the config flow."""
|
||||||
if not user_input:
|
if not user_input:
|
||||||
return await self._show_form()
|
return await self._show_form()
|
||||||
|
@ -7,5 +7,5 @@
|
|||||||
"integration_type": "hub",
|
"integration_type": "hub",
|
||||||
"iot_class": "cloud_push",
|
"iot_class": "cloud_push",
|
||||||
"loggers": ["aioambient"],
|
"loggers": ["aioambient"],
|
||||||
"requirements": ["aioambient==2024.01.0"]
|
"requirements": ["aioambient==2024.08.0"]
|
||||||
}
|
}
|
||||||
|
@ -499,7 +499,7 @@ class AmcrestCam(Camera):
|
|||||||
await getattr(self, f"_async_set_{func}")(value)
|
await getattr(self, f"_async_set_{func}")(value)
|
||||||
new_value = await getattr(self, f"_async_get_{func}")()
|
new_value = await getattr(self, f"_async_get_{func}")()
|
||||||
if new_value != value:
|
if new_value != value:
|
||||||
raise AmcrestCommandFailed
|
raise AmcrestCommandFailed # noqa: TRY301
|
||||||
except (AmcrestError, AmcrestCommandFailed) as error:
|
except (AmcrestError, AmcrestCommandFailed) as error:
|
||||||
if tries == 1:
|
if tries == 1:
|
||||||
log_update_error(_LOGGER, action, self.name, description, error)
|
log_update_error(_LOGGER, action, self.name, description, error)
|
||||||
|
@ -1,15 +1,37 @@
|
|||||||
{
|
{
|
||||||
"services": {
|
"services": {
|
||||||
"enable_recording": "mdi:record-rec",
|
"enable_recording": {
|
||||||
"disable_recording": "mdi:stop",
|
"service": "mdi:record-rec"
|
||||||
"enable_audio": "mdi:volume-high",
|
},
|
||||||
"disable_audio": "mdi:volume-off",
|
"disable_recording": {
|
||||||
"enable_motion_recording": "mdi:motion-sensor",
|
"service": "mdi:stop"
|
||||||
"disable_motion_recording": "mdi:motion-sensor-off",
|
},
|
||||||
"goto_preset": "mdi:pan",
|
"enable_audio": {
|
||||||
"set_color_bw": "mdi:palette",
|
"service": "mdi:volume-high"
|
||||||
"start_tour": "mdi:panorama",
|
},
|
||||||
"stop_tour": "mdi:panorama-outline",
|
"disable_audio": {
|
||||||
"ptz_control": "mdi:pan"
|
"service": "mdi:volume-off"
|
||||||
|
},
|
||||||
|
"enable_motion_recording": {
|
||||||
|
"service": "mdi:motion-sensor"
|
||||||
|
},
|
||||||
|
"disable_motion_recording": {
|
||||||
|
"service": "mdi:motion-sensor-off"
|
||||||
|
},
|
||||||
|
"goto_preset": {
|
||||||
|
"service": "mdi:pan"
|
||||||
|
},
|
||||||
|
"set_color_bw": {
|
||||||
|
"service": "mdi:palette"
|
||||||
|
},
|
||||||
|
"start_tour": {
|
||||||
|
"service": "mdi:panorama"
|
||||||
|
},
|
||||||
|
"stop_tour": {
|
||||||
|
"service": "mdi:panorama-outline"
|
||||||
|
},
|
||||||
|
"ptz_control": {
|
||||||
|
"service": "mdi:pan"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,6 @@ 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
|
||||||
import homeassistant.helpers.config_validation as cv
|
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
from .coordinator import AndroidIPCamDataUpdateCoordinator
|
from .coordinator import AndroidIPCamDataUpdateCoordinator
|
||||||
@ -27,9 +26,6 @@ PLATFORMS: list[Platform] = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False)
|
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
"""Set up Android IP Webcam from a config entry."""
|
"""Set up Android IP Webcam from a config entry."""
|
||||||
websession = async_get_clientsession(hass)
|
websession = async_get_clientsession(hass)
|
||||||
|
@ -1,8 +1,16 @@
|
|||||||
{
|
{
|
||||||
"services": {
|
"services": {
|
||||||
"adb_command": "mdi:console",
|
"adb_command": {
|
||||||
"download": "mdi:download",
|
"service": "mdi:console"
|
||||||
"upload": "mdi:upload",
|
},
|
||||||
"learn_sendevent": "mdi:remote"
|
"download": {
|
||||||
|
"service": "mdi:download"
|
||||||
|
},
|
||||||
|
"upload": {
|
||||||
|
"service": "mdi:upload"
|
||||||
|
},
|
||||||
|
"learn_sendevent": {
|
||||||
|
"service": "mdi:remote"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,7 +87,7 @@ async def async_setup_entry(
|
|||||||
"adb_command",
|
"adb_command",
|
||||||
)
|
)
|
||||||
platform.async_register_entity_service(
|
platform.async_register_entity_service(
|
||||||
SERVICE_LEARN_SENDEVENT, {}, "learn_sendevent"
|
SERVICE_LEARN_SENDEVENT, None, "learn_sendevent"
|
||||||
)
|
)
|
||||||
platform.async_register_entity_service(
|
platform.async_register_entity_service(
|
||||||
SERVICE_DOWNLOAD,
|
SERVICE_DOWNLOAD,
|
||||||
|
@ -8,6 +8,6 @@
|
|||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["androidtvremote2"],
|
"loggers": ["androidtvremote2"],
|
||||||
"quality_scale": "platinum",
|
"quality_scale": "platinum",
|
||||||
"requirements": ["androidtvremote2==0.1.1"],
|
"requirements": ["androidtvremote2==0.1.2"],
|
||||||
"zeroconf": ["_androidtvremote2._tcp.local."]
|
"zeroconf": ["_androidtvremote2._tcp.local."]
|
||||||
}
|
}
|
||||||
|
@ -13,22 +13,20 @@ from anova_wifi import (
|
|||||||
WebsocketFailure,
|
WebsocketFailure,
|
||||||
)
|
)
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
|
||||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform
|
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import ConfigEntryNotReady
|
from homeassistant.exceptions import ConfigEntryNotReady
|
||||||
from homeassistant.helpers import aiohttp_client
|
from homeassistant.helpers import aiohttp_client
|
||||||
|
|
||||||
from .const import DOMAIN
|
|
||||||
from .coordinator import AnovaCoordinator
|
from .coordinator import AnovaCoordinator
|
||||||
from .models import AnovaData
|
from .models import AnovaConfigEntry, AnovaData
|
||||||
|
|
||||||
PLATFORMS = [Platform.SENSOR]
|
PLATFORMS = [Platform.SENSOR]
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_setup_entry(hass: HomeAssistant, entry: AnovaConfigEntry) -> bool:
|
||||||
"""Set up Anova from a config entry."""
|
"""Set up Anova from a config entry."""
|
||||||
api = AnovaApi(
|
api = AnovaApi(
|
||||||
aiohttp_client.async_get_clientsession(hass),
|
aiohttp_client.async_get_clientsession(hass),
|
||||||
@ -62,17 +60,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
assert api.websocket_handler is not None
|
assert api.websocket_handler is not None
|
||||||
devices = list(api.websocket_handler.devices.values())
|
devices = list(api.websocket_handler.devices.values())
|
||||||
coordinators = [AnovaCoordinator(hass, device) for device in devices]
|
coordinators = [AnovaCoordinator(hass, device) for device in devices]
|
||||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = AnovaData(
|
entry.runtime_data = AnovaData(api_jwt=api.jwt, coordinators=coordinators, api=api)
|
||||||
api_jwt=api.jwt, coordinators=coordinators, api=api
|
|
||||||
)
|
|
||||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_unload_entry(hass: HomeAssistant, entry: AnovaConfigEntry) -> bool:
|
||||||
"""Unload a config entry."""
|
"""Unload a config entry."""
|
||||||
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
|
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
|
||||||
anova_data: AnovaData = hass.data[DOMAIN].pop(entry.entry_id)
|
|
||||||
# Disconnect from WS
|
# Disconnect from WS
|
||||||
await anova_data.api.disconnect_websocket()
|
await entry.runtime_data.api.disconnect_websocket()
|
||||||
return unload_ok
|
return unload_ok
|
||||||
|
@ -4,8 +4,12 @@ from dataclasses import dataclass
|
|||||||
|
|
||||||
from anova_wifi import AnovaApi
|
from anova_wifi import AnovaApi
|
||||||
|
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
|
||||||
from .coordinator import AnovaCoordinator
|
from .coordinator import AnovaCoordinator
|
||||||
|
|
||||||
|
type AnovaConfigEntry = ConfigEntry[AnovaData]
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class AnovaData:
|
class AnovaData:
|
||||||
|
@ -7,7 +7,6 @@ from dataclasses import dataclass
|
|||||||
|
|
||||||
from anova_wifi import AnovaMode, AnovaState, APCUpdateSensor
|
from anova_wifi import AnovaMode, AnovaState, APCUpdateSensor
|
||||||
|
|
||||||
from homeassistant import config_entries
|
|
||||||
from homeassistant.components.sensor import (
|
from homeassistant.components.sensor import (
|
||||||
SensorDeviceClass,
|
SensorDeviceClass,
|
||||||
SensorEntity,
|
SensorEntity,
|
||||||
@ -19,10 +18,9 @@ from homeassistant.core import HomeAssistant
|
|||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.typing import StateType
|
from homeassistant.helpers.typing import StateType
|
||||||
|
|
||||||
from .const import DOMAIN
|
|
||||||
from .coordinator import AnovaCoordinator
|
from .coordinator import AnovaCoordinator
|
||||||
from .entity import AnovaDescriptionEntity
|
from .entity import AnovaDescriptionEntity
|
||||||
from .models import AnovaData
|
from .models import AnovaConfigEntry
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, kw_only=True)
|
@dataclass(frozen=True, kw_only=True)
|
||||||
@ -99,11 +97,11 @@ SENSOR_DESCRIPTIONS: list[AnovaSensorEntityDescription] = [
|
|||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry: config_entries.ConfigEntry,
|
entry: AnovaConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up Anova device."""
|
"""Set up Anova device."""
|
||||||
anova_data: AnovaData = hass.data[DOMAIN][entry.entry_id]
|
anova_data = entry.runtime_data
|
||||||
|
|
||||||
for coordinator in anova_data.coordinators:
|
for coordinator in anova_data.coordinators:
|
||||||
setup_coordinator(coordinator, async_add_entities)
|
setup_coordinator(coordinator, async_add_entities)
|
||||||
|
@ -13,14 +13,16 @@ from homeassistant.core import Event, HomeAssistant, callback
|
|||||||
from homeassistant.exceptions import ConfigEntryNotReady
|
from homeassistant.exceptions import ConfigEntryNotReady
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||||
|
|
||||||
from .const import ANTHEMAV_UPDATE_SIGNAL, DEVICE_TIMEOUT_SECONDS, DOMAIN
|
from .const import ANTHEMAV_UPDATE_SIGNAL, DEVICE_TIMEOUT_SECONDS
|
||||||
|
|
||||||
|
type AnthemavConfigEntry = ConfigEntry[anthemav.Connection]
|
||||||
|
|
||||||
PLATFORMS = [Platform.MEDIA_PLAYER]
|
PLATFORMS = [Platform.MEDIA_PLAYER]
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_setup_entry(hass: HomeAssistant, entry: AnthemavConfigEntry) -> bool:
|
||||||
"""Set up Anthem A/V Receivers from a config entry."""
|
"""Set up Anthem A/V Receivers from a config entry."""
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
@ -41,7 +43,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
except (OSError, DeviceError) as err:
|
except (OSError, DeviceError) as err:
|
||||||
raise ConfigEntryNotReady from err
|
raise ConfigEntryNotReady from err
|
||||||
|
|
||||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = avr
|
entry.runtime_data = avr
|
||||||
|
|
||||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
|
|
||||||
@ -56,16 +58,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_unload_entry(hass: HomeAssistant, entry: AnthemavConfigEntry) -> bool:
|
||||||
"""Unload a config entry."""
|
"""Unload a config entry."""
|
||||||
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||||
|
|
||||||
avr = hass.data[DOMAIN][entry.entry_id]
|
avr = entry.runtime_data
|
||||||
|
|
||||||
if avr is not None:
|
|
||||||
_LOGGER.debug("Close avr connection")
|
_LOGGER.debug("Close avr connection")
|
||||||
avr.close()
|
avr.close()
|
||||||
|
|
||||||
if unload_ok:
|
|
||||||
hass.data[DOMAIN].pop(entry.entry_id)
|
|
||||||
return unload_ok
|
return unload_ok
|
||||||
|
@ -4,7 +4,6 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from anthemav.connection import Connection
|
|
||||||
from anthemav.protocol import AVR
|
from anthemav.protocol import AVR
|
||||||
|
|
||||||
from homeassistant.components.media_player import (
|
from homeassistant.components.media_player import (
|
||||||
@ -13,13 +12,13 @@ from homeassistant.components.media_player import (
|
|||||||
MediaPlayerEntityFeature,
|
MediaPlayerEntityFeature,
|
||||||
MediaPlayerState,
|
MediaPlayerState,
|
||||||
)
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
|
||||||
from homeassistant.const import CONF_MAC, CONF_MODEL
|
from homeassistant.const import CONF_MAC, CONF_MODEL
|
||||||
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.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 AddEntitiesCallback
|
||||||
|
|
||||||
|
from . import AnthemavConfigEntry
|
||||||
from .const import ANTHEMAV_UPDATE_SIGNAL, DOMAIN, MANUFACTURER
|
from .const import ANTHEMAV_UPDATE_SIGNAL, DOMAIN, MANUFACTURER
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@ -27,7 +26,7 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config_entry: ConfigEntry,
|
config_entry: AnthemavConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up entry."""
|
"""Set up entry."""
|
||||||
@ -35,7 +34,7 @@ async def async_setup_entry(
|
|||||||
mac_address = config_entry.data[CONF_MAC]
|
mac_address = config_entry.data[CONF_MAC]
|
||||||
model = config_entry.data[CONF_MODEL]
|
model = config_entry.data[CONF_MODEL]
|
||||||
|
|
||||||
avr: Connection = hass.data[DOMAIN][config_entry.entry_id]
|
avr = config_entry.runtime_data
|
||||||
|
|
||||||
_LOGGER.debug("Connection data dump: %s", avr.dump_conndata)
|
_LOGGER.debug("Connection data dump: %s", avr.dump_conndata)
|
||||||
|
|
||||||
|
46
homeassistant/components/anthropic/__init__.py
Normal file
46
homeassistant/components/anthropic/__init__.py
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
"""The Anthropic integration."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import anthropic
|
||||||
|
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import CONF_API_KEY, Platform
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.exceptions import ConfigEntryNotReady
|
||||||
|
from homeassistant.helpers import config_validation as cv
|
||||||
|
|
||||||
|
from .const import DOMAIN, LOGGER
|
||||||
|
|
||||||
|
PLATFORMS = (Platform.CONVERSATION,)
|
||||||
|
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
|
||||||
|
|
||||||
|
type AnthropicConfigEntry = ConfigEntry[anthropic.AsyncClient]
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass: HomeAssistant, entry: AnthropicConfigEntry) -> bool:
|
||||||
|
"""Set up Anthropic from a config entry."""
|
||||||
|
client = anthropic.AsyncAnthropic(api_key=entry.data[CONF_API_KEY])
|
||||||
|
try:
|
||||||
|
await client.messages.create(
|
||||||
|
model="claude-3-haiku-20240307",
|
||||||
|
max_tokens=1,
|
||||||
|
messages=[{"role": "user", "content": "Hi"}],
|
||||||
|
timeout=10.0,
|
||||||
|
)
|
||||||
|
except anthropic.AuthenticationError as err:
|
||||||
|
LOGGER.error("Invalid API key: %s", err)
|
||||||
|
return False
|
||||||
|
except anthropic.AnthropicError as err:
|
||||||
|
raise ConfigEntryNotReady(err) from err
|
||||||
|
|
||||||
|
entry.runtime_data = client
|
||||||
|
|
||||||
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
|
"""Unload Anthropic."""
|
||||||
|
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
210
homeassistant/components/anthropic/config_flow.py
Normal file
210
homeassistant/components/anthropic/config_flow.py
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
"""Config flow for Anthropic integration."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from types import MappingProxyType
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import anthropic
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.config_entries import (
|
||||||
|
ConfigEntry,
|
||||||
|
ConfigFlow,
|
||||||
|
ConfigFlowResult,
|
||||||
|
OptionsFlow,
|
||||||
|
)
|
||||||
|
from homeassistant.const import CONF_API_KEY, CONF_LLM_HASS_API
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers import llm
|
||||||
|
from homeassistant.helpers.selector import (
|
||||||
|
NumberSelector,
|
||||||
|
NumberSelectorConfig,
|
||||||
|
SelectOptionDict,
|
||||||
|
SelectSelector,
|
||||||
|
SelectSelectorConfig,
|
||||||
|
TemplateSelector,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .const import (
|
||||||
|
CONF_CHAT_MODEL,
|
||||||
|
CONF_MAX_TOKENS,
|
||||||
|
CONF_PROMPT,
|
||||||
|
CONF_RECOMMENDED,
|
||||||
|
CONF_TEMPERATURE,
|
||||||
|
DOMAIN,
|
||||||
|
RECOMMENDED_CHAT_MODEL,
|
||||||
|
RECOMMENDED_MAX_TOKENS,
|
||||||
|
RECOMMENDED_TEMPERATURE,
|
||||||
|
)
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
STEP_USER_DATA_SCHEMA = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(CONF_API_KEY): str,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
RECOMMENDED_OPTIONS = {
|
||||||
|
CONF_RECOMMENDED: True,
|
||||||
|
CONF_LLM_HASS_API: llm.LLM_API_ASSIST,
|
||||||
|
CONF_PROMPT: llm.DEFAULT_INSTRUCTIONS_PROMPT,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> None:
|
||||||
|
"""Validate the user input allows us to connect.
|
||||||
|
|
||||||
|
Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user.
|
||||||
|
"""
|
||||||
|
client = anthropic.AsyncAnthropic(api_key=data[CONF_API_KEY])
|
||||||
|
await client.messages.create(
|
||||||
|
model="claude-3-haiku-20240307",
|
||||||
|
max_tokens=1,
|
||||||
|
messages=[{"role": "user", "content": "Hi"}],
|
||||||
|
timeout=10.0,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AnthropicConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||||
|
"""Handle a config flow for Anthropic."""
|
||||||
|
|
||||||
|
VERSION = 1
|
||||||
|
|
||||||
|
async def async_step_user(
|
||||||
|
self, user_input: dict[str, Any] | None = None
|
||||||
|
) -> ConfigFlowResult:
|
||||||
|
"""Handle the initial step."""
|
||||||
|
errors = {}
|
||||||
|
|
||||||
|
if user_input is not None:
|
||||||
|
try:
|
||||||
|
await validate_input(self.hass, user_input)
|
||||||
|
except anthropic.APITimeoutError:
|
||||||
|
errors["base"] = "timeout_connect"
|
||||||
|
except anthropic.APIConnectionError:
|
||||||
|
errors["base"] = "cannot_connect"
|
||||||
|
except anthropic.APIStatusError as e:
|
||||||
|
if isinstance(e.body, dict):
|
||||||
|
errors["base"] = e.body.get("error", {}).get("type", "unknown")
|
||||||
|
else:
|
||||||
|
errors["base"] = "unknown"
|
||||||
|
except Exception:
|
||||||
|
_LOGGER.exception("Unexpected exception")
|
||||||
|
errors["base"] = "unknown"
|
||||||
|
else:
|
||||||
|
return self.async_create_entry(
|
||||||
|
title="Claude",
|
||||||
|
data=user_input,
|
||||||
|
options=RECOMMENDED_OPTIONS,
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors or None
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def async_get_options_flow(
|
||||||
|
config_entry: ConfigEntry,
|
||||||
|
) -> OptionsFlow:
|
||||||
|
"""Create the options flow."""
|
||||||
|
return AnthropicOptionsFlow(config_entry)
|
||||||
|
|
||||||
|
|
||||||
|
class AnthropicOptionsFlow(OptionsFlow):
|
||||||
|
"""Anthropic config flow options handler."""
|
||||||
|
|
||||||
|
def __init__(self, config_entry: ConfigEntry) -> None:
|
||||||
|
"""Initialize options flow."""
|
||||||
|
self.config_entry = config_entry
|
||||||
|
self.last_rendered_recommended = config_entry.options.get(
|
||||||
|
CONF_RECOMMENDED, False
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_step_init(
|
||||||
|
self, user_input: dict[str, Any] | None = None
|
||||||
|
) -> ConfigFlowResult:
|
||||||
|
"""Manage the options."""
|
||||||
|
options: dict[str, Any] | MappingProxyType[str, Any] = self.config_entry.options
|
||||||
|
|
||||||
|
if user_input is not None:
|
||||||
|
if user_input[CONF_RECOMMENDED] == self.last_rendered_recommended:
|
||||||
|
if user_input[CONF_LLM_HASS_API] == "none":
|
||||||
|
user_input.pop(CONF_LLM_HASS_API)
|
||||||
|
return self.async_create_entry(title="", data=user_input)
|
||||||
|
|
||||||
|
# Re-render the options again, now with the recommended options shown/hidden
|
||||||
|
self.last_rendered_recommended = user_input[CONF_RECOMMENDED]
|
||||||
|
|
||||||
|
options = {
|
||||||
|
CONF_RECOMMENDED: user_input[CONF_RECOMMENDED],
|
||||||
|
CONF_PROMPT: user_input[CONF_PROMPT],
|
||||||
|
CONF_LLM_HASS_API: user_input[CONF_LLM_HASS_API],
|
||||||
|
}
|
||||||
|
|
||||||
|
suggested_values = options.copy()
|
||||||
|
if not suggested_values.get(CONF_PROMPT):
|
||||||
|
suggested_values[CONF_PROMPT] = llm.DEFAULT_INSTRUCTIONS_PROMPT
|
||||||
|
|
||||||
|
schema = self.add_suggested_values_to_schema(
|
||||||
|
vol.Schema(anthropic_config_option_schema(self.hass, options)),
|
||||||
|
suggested_values,
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="init",
|
||||||
|
data_schema=schema,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def anthropic_config_option_schema(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
options: dict[str, Any] | MappingProxyType[str, Any],
|
||||||
|
) -> dict:
|
||||||
|
"""Return a schema for Anthropic completion options."""
|
||||||
|
hass_apis: list[SelectOptionDict] = [
|
||||||
|
SelectOptionDict(
|
||||||
|
label="No control",
|
||||||
|
value="none",
|
||||||
|
)
|
||||||
|
]
|
||||||
|
hass_apis.extend(
|
||||||
|
SelectOptionDict(
|
||||||
|
label=api.name,
|
||||||
|
value=api.id,
|
||||||
|
)
|
||||||
|
for api in llm.async_get_apis(hass)
|
||||||
|
)
|
||||||
|
|
||||||
|
schema = {
|
||||||
|
vol.Optional(CONF_PROMPT): TemplateSelector(),
|
||||||
|
vol.Optional(CONF_LLM_HASS_API, default="none"): SelectSelector(
|
||||||
|
SelectSelectorConfig(options=hass_apis)
|
||||||
|
),
|
||||||
|
vol.Required(
|
||||||
|
CONF_RECOMMENDED, default=options.get(CONF_RECOMMENDED, False)
|
||||||
|
): bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.get(CONF_RECOMMENDED):
|
||||||
|
return schema
|
||||||
|
|
||||||
|
schema.update(
|
||||||
|
{
|
||||||
|
vol.Optional(
|
||||||
|
CONF_CHAT_MODEL,
|
||||||
|
default=RECOMMENDED_CHAT_MODEL,
|
||||||
|
): str,
|
||||||
|
vol.Optional(
|
||||||
|
CONF_MAX_TOKENS,
|
||||||
|
default=RECOMMENDED_MAX_TOKENS,
|
||||||
|
): int,
|
||||||
|
vol.Optional(
|
||||||
|
CONF_TEMPERATURE,
|
||||||
|
default=RECOMMENDED_TEMPERATURE,
|
||||||
|
): NumberSelector(NumberSelectorConfig(min=0, max=1, step=0.05)),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return schema
|
15
homeassistant/components/anthropic/const.py
Normal file
15
homeassistant/components/anthropic/const.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
"""Constants for the Anthropic integration."""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
DOMAIN = "anthropic"
|
||||||
|
LOGGER = logging.getLogger(__package__)
|
||||||
|
|
||||||
|
CONF_RECOMMENDED = "recommended"
|
||||||
|
CONF_PROMPT = "prompt"
|
||||||
|
CONF_CHAT_MODEL = "chat_model"
|
||||||
|
RECOMMENDED_CHAT_MODEL = "claude-3-haiku-20240307"
|
||||||
|
CONF_MAX_TOKENS = "max_tokens"
|
||||||
|
RECOMMENDED_MAX_TOKENS = 1024
|
||||||
|
CONF_TEMPERATURE = "temperature"
|
||||||
|
RECOMMENDED_TEMPERATURE = 1.0
|
316
homeassistant/components/anthropic/conversation.py
Normal file
316
homeassistant/components/anthropic/conversation.py
Normal file
@ -0,0 +1,316 @@
|
|||||||
|
"""Conversation support for Anthropic."""
|
||||||
|
|
||||||
|
from collections.abc import Callable
|
||||||
|
import json
|
||||||
|
from typing import Any, Literal, cast
|
||||||
|
|
||||||
|
import anthropic
|
||||||
|
from anthropic._types import NOT_GIVEN
|
||||||
|
from anthropic.types import (
|
||||||
|
Message,
|
||||||
|
MessageParam,
|
||||||
|
TextBlock,
|
||||||
|
TextBlockParam,
|
||||||
|
ToolParam,
|
||||||
|
ToolResultBlockParam,
|
||||||
|
ToolUseBlock,
|
||||||
|
ToolUseBlockParam,
|
||||||
|
)
|
||||||
|
import voluptuous as vol
|
||||||
|
from voluptuous_openapi import convert
|
||||||
|
|
||||||
|
from homeassistant.components import conversation
|
||||||
|
from homeassistant.components.conversation import trace
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import CONF_LLM_HASS_API, MATCH_ALL
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.exceptions import HomeAssistantError, TemplateError
|
||||||
|
from homeassistant.helpers import device_registry as dr, intent, llm, template
|
||||||
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
from homeassistant.util import ulid
|
||||||
|
|
||||||
|
from . import AnthropicConfigEntry
|
||||||
|
from .const import (
|
||||||
|
CONF_CHAT_MODEL,
|
||||||
|
CONF_MAX_TOKENS,
|
||||||
|
CONF_PROMPT,
|
||||||
|
CONF_TEMPERATURE,
|
||||||
|
DOMAIN,
|
||||||
|
LOGGER,
|
||||||
|
RECOMMENDED_CHAT_MODEL,
|
||||||
|
RECOMMENDED_MAX_TOKENS,
|
||||||
|
RECOMMENDED_TEMPERATURE,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Max number of back and forth with the LLM to generate a response
|
||||||
|
MAX_TOOL_ITERATIONS = 10
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config_entry: AnthropicConfigEntry,
|
||||||
|
async_add_entities: AddEntitiesCallback,
|
||||||
|
) -> None:
|
||||||
|
"""Set up conversation entities."""
|
||||||
|
agent = AnthropicConversationEntity(config_entry)
|
||||||
|
async_add_entities([agent])
|
||||||
|
|
||||||
|
|
||||||
|
def _format_tool(
|
||||||
|
tool: llm.Tool, custom_serializer: Callable[[Any], Any] | None
|
||||||
|
) -> ToolParam:
|
||||||
|
"""Format tool specification."""
|
||||||
|
return ToolParam(
|
||||||
|
name=tool.name,
|
||||||
|
description=tool.description or "",
|
||||||
|
input_schema=convert(tool.parameters, custom_serializer=custom_serializer),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _message_convert(
|
||||||
|
message: Message,
|
||||||
|
) -> MessageParam:
|
||||||
|
"""Convert from class to TypedDict."""
|
||||||
|
param_content: list[TextBlockParam | ToolUseBlockParam] = []
|
||||||
|
|
||||||
|
for message_content in message.content:
|
||||||
|
if isinstance(message_content, TextBlock):
|
||||||
|
param_content.append(TextBlockParam(type="text", text=message_content.text))
|
||||||
|
elif isinstance(message_content, ToolUseBlock):
|
||||||
|
param_content.append(
|
||||||
|
ToolUseBlockParam(
|
||||||
|
type="tool_use",
|
||||||
|
id=message_content.id,
|
||||||
|
name=message_content.name,
|
||||||
|
input=message_content.input,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return MessageParam(role=message.role, content=param_content)
|
||||||
|
|
||||||
|
|
||||||
|
class AnthropicConversationEntity(
|
||||||
|
conversation.ConversationEntity, conversation.AbstractConversationAgent
|
||||||
|
):
|
||||||
|
"""Anthropic conversation agent."""
|
||||||
|
|
||||||
|
_attr_has_entity_name = True
|
||||||
|
_attr_name = None
|
||||||
|
|
||||||
|
def __init__(self, entry: AnthropicConfigEntry) -> None:
|
||||||
|
"""Initialize the agent."""
|
||||||
|
self.entry = entry
|
||||||
|
self.history: dict[str, list[MessageParam]] = {}
|
||||||
|
self._attr_unique_id = entry.entry_id
|
||||||
|
self._attr_device_info = dr.DeviceInfo(
|
||||||
|
identifiers={(DOMAIN, entry.entry_id)},
|
||||||
|
manufacturer="Anthropic",
|
||||||
|
model="Claude",
|
||||||
|
entry_type=dr.DeviceEntryType.SERVICE,
|
||||||
|
)
|
||||||
|
if self.entry.options.get(CONF_LLM_HASS_API):
|
||||||
|
self._attr_supported_features = (
|
||||||
|
conversation.ConversationEntityFeature.CONTROL
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def supported_languages(self) -> list[str] | Literal["*"]:
|
||||||
|
"""Return a list of supported languages."""
|
||||||
|
return MATCH_ALL
|
||||||
|
|
||||||
|
async def async_added_to_hass(self) -> None:
|
||||||
|
"""When entity is added to Home Assistant."""
|
||||||
|
await super().async_added_to_hass()
|
||||||
|
self.entry.async_on_unload(
|
||||||
|
self.entry.add_update_listener(self._async_entry_update_listener)
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_process(
|
||||||
|
self, user_input: conversation.ConversationInput
|
||||||
|
) -> conversation.ConversationResult:
|
||||||
|
"""Process a sentence."""
|
||||||
|
options = self.entry.options
|
||||||
|
intent_response = intent.IntentResponse(language=user_input.language)
|
||||||
|
llm_api: llm.APIInstance | None = None
|
||||||
|
tools: list[ToolParam] | None = None
|
||||||
|
user_name: str | None = None
|
||||||
|
llm_context = llm.LLMContext(
|
||||||
|
platform=DOMAIN,
|
||||||
|
context=user_input.context,
|
||||||
|
user_prompt=user_input.text,
|
||||||
|
language=user_input.language,
|
||||||
|
assistant=conversation.DOMAIN,
|
||||||
|
device_id=user_input.device_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
if options.get(CONF_LLM_HASS_API):
|
||||||
|
try:
|
||||||
|
llm_api = await llm.async_get_api(
|
||||||
|
self.hass,
|
||||||
|
options[CONF_LLM_HASS_API],
|
||||||
|
llm_context,
|
||||||
|
)
|
||||||
|
except HomeAssistantError as err:
|
||||||
|
LOGGER.error("Error getting LLM API: %s", err)
|
||||||
|
intent_response.async_set_error(
|
||||||
|
intent.IntentResponseErrorCode.UNKNOWN,
|
||||||
|
f"Error preparing LLM API: {err}",
|
||||||
|
)
|
||||||
|
return conversation.ConversationResult(
|
||||||
|
response=intent_response, conversation_id=user_input.conversation_id
|
||||||
|
)
|
||||||
|
tools = [
|
||||||
|
_format_tool(tool, llm_api.custom_serializer) for tool in llm_api.tools
|
||||||
|
]
|
||||||
|
|
||||||
|
if user_input.conversation_id is None:
|
||||||
|
conversation_id = ulid.ulid_now()
|
||||||
|
messages = []
|
||||||
|
|
||||||
|
elif user_input.conversation_id in self.history:
|
||||||
|
conversation_id = user_input.conversation_id
|
||||||
|
messages = self.history[conversation_id]
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Conversation IDs are ULIDs. We generate a new one if not provided.
|
||||||
|
# If an old OLID is passed in, we will generate a new one to indicate
|
||||||
|
# a new conversation was started. If the user picks their own, they
|
||||||
|
# want to track a conversation and we respect it.
|
||||||
|
try:
|
||||||
|
ulid.ulid_to_bytes(user_input.conversation_id)
|
||||||
|
conversation_id = ulid.ulid_now()
|
||||||
|
except ValueError:
|
||||||
|
conversation_id = user_input.conversation_id
|
||||||
|
|
||||||
|
messages = []
|
||||||
|
|
||||||
|
if (
|
||||||
|
user_input.context
|
||||||
|
and user_input.context.user_id
|
||||||
|
and (
|
||||||
|
user := await self.hass.auth.async_get_user(user_input.context.user_id)
|
||||||
|
)
|
||||||
|
):
|
||||||
|
user_name = user.name
|
||||||
|
|
||||||
|
try:
|
||||||
|
prompt_parts = [
|
||||||
|
template.Template(
|
||||||
|
llm.BASE_PROMPT
|
||||||
|
+ options.get(CONF_PROMPT, llm.DEFAULT_INSTRUCTIONS_PROMPT),
|
||||||
|
self.hass,
|
||||||
|
).async_render(
|
||||||
|
{
|
||||||
|
"ha_name": self.hass.config.location_name,
|
||||||
|
"user_name": user_name,
|
||||||
|
"llm_context": llm_context,
|
||||||
|
},
|
||||||
|
parse_result=False,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
except TemplateError as err:
|
||||||
|
LOGGER.error("Error rendering prompt: %s", err)
|
||||||
|
intent_response.async_set_error(
|
||||||
|
intent.IntentResponseErrorCode.UNKNOWN,
|
||||||
|
f"Sorry, I had a problem with my template: {err}",
|
||||||
|
)
|
||||||
|
return conversation.ConversationResult(
|
||||||
|
response=intent_response, conversation_id=conversation_id
|
||||||
|
)
|
||||||
|
|
||||||
|
if llm_api:
|
||||||
|
prompt_parts.append(llm_api.api_prompt)
|
||||||
|
|
||||||
|
prompt = "\n".join(prompt_parts)
|
||||||
|
|
||||||
|
# Create a copy of the variable because we attach it to the trace
|
||||||
|
messages = [*messages, MessageParam(role="user", content=user_input.text)]
|
||||||
|
|
||||||
|
LOGGER.debug("Prompt: %s", messages)
|
||||||
|
LOGGER.debug("Tools: %s", tools)
|
||||||
|
trace.async_conversation_trace_append(
|
||||||
|
trace.ConversationTraceEventType.AGENT_DETAIL,
|
||||||
|
{"system": prompt, "messages": messages},
|
||||||
|
)
|
||||||
|
|
||||||
|
client = self.entry.runtime_data
|
||||||
|
|
||||||
|
# To prevent infinite loops, we limit the number of iterations
|
||||||
|
for _iteration in range(MAX_TOOL_ITERATIONS):
|
||||||
|
try:
|
||||||
|
response = await client.messages.create(
|
||||||
|
model=options.get(CONF_CHAT_MODEL, RECOMMENDED_CHAT_MODEL),
|
||||||
|
messages=messages,
|
||||||
|
tools=tools or NOT_GIVEN,
|
||||||
|
max_tokens=options.get(CONF_MAX_TOKENS, RECOMMENDED_MAX_TOKENS),
|
||||||
|
system=prompt,
|
||||||
|
temperature=options.get(CONF_TEMPERATURE, RECOMMENDED_TEMPERATURE),
|
||||||
|
)
|
||||||
|
except anthropic.AnthropicError as err:
|
||||||
|
intent_response.async_set_error(
|
||||||
|
intent.IntentResponseErrorCode.UNKNOWN,
|
||||||
|
f"Sorry, I had a problem talking to Anthropic: {err}",
|
||||||
|
)
|
||||||
|
return conversation.ConversationResult(
|
||||||
|
response=intent_response, conversation_id=conversation_id
|
||||||
|
)
|
||||||
|
|
||||||
|
LOGGER.debug("Response %s", response)
|
||||||
|
|
||||||
|
messages.append(_message_convert(response))
|
||||||
|
|
||||||
|
if response.stop_reason != "tool_use" or not llm_api:
|
||||||
|
break
|
||||||
|
|
||||||
|
tool_results: list[ToolResultBlockParam] = []
|
||||||
|
for tool_call in response.content:
|
||||||
|
if isinstance(tool_call, TextBlock):
|
||||||
|
LOGGER.info(tool_call.text)
|
||||||
|
|
||||||
|
if not isinstance(tool_call, ToolUseBlock):
|
||||||
|
continue
|
||||||
|
|
||||||
|
tool_input = llm.ToolInput(
|
||||||
|
tool_name=tool_call.name,
|
||||||
|
tool_args=cast(dict[str, Any], tool_call.input),
|
||||||
|
)
|
||||||
|
LOGGER.debug(
|
||||||
|
"Tool call: %s(%s)", tool_input.tool_name, tool_input.tool_args
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
tool_response = await llm_api.async_call_tool(tool_input)
|
||||||
|
except (HomeAssistantError, vol.Invalid) as e:
|
||||||
|
tool_response = {"error": type(e).__name__}
|
||||||
|
if str(e):
|
||||||
|
tool_response["error_text"] = str(e)
|
||||||
|
|
||||||
|
LOGGER.debug("Tool response: %s", tool_response)
|
||||||
|
tool_results.append(
|
||||||
|
ToolResultBlockParam(
|
||||||
|
type="tool_result",
|
||||||
|
tool_use_id=tool_call.id,
|
||||||
|
content=json.dumps(tool_response),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
messages.append(MessageParam(role="user", content=tool_results))
|
||||||
|
|
||||||
|
self.history[conversation_id] = messages
|
||||||
|
|
||||||
|
for content in response.content:
|
||||||
|
if isinstance(content, TextBlock):
|
||||||
|
intent_response.async_set_speech(content.text)
|
||||||
|
break
|
||||||
|
|
||||||
|
return conversation.ConversationResult(
|
||||||
|
response=intent_response, conversation_id=conversation_id
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _async_entry_update_listener(
|
||||||
|
self, hass: HomeAssistant, entry: ConfigEntry
|
||||||
|
) -> None:
|
||||||
|
"""Handle options update."""
|
||||||
|
# Reload as we update device info + entity name + supported features
|
||||||
|
await hass.config_entries.async_reload(entry.entry_id)
|
12
homeassistant/components/anthropic/manifest.json
Normal file
12
homeassistant/components/anthropic/manifest.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"domain": "anthropic",
|
||||||
|
"name": "Anthropic Conversation",
|
||||||
|
"after_dependencies": ["assist_pipeline", "intent"],
|
||||||
|
"codeowners": ["@Shulyaka"],
|
||||||
|
"config_flow": true,
|
||||||
|
"dependencies": ["conversation"],
|
||||||
|
"documentation": "https://www.home-assistant.io/integrations/anthropic",
|
||||||
|
"integration_type": "service",
|
||||||
|
"iot_class": "cloud_polling",
|
||||||
|
"requirements": ["anthropic==0.31.2"]
|
||||||
|
}
|
34
homeassistant/components/anthropic/strings.json
Normal file
34
homeassistant/components/anthropic/strings.json
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"api_key": "[%key:common::config_flow::data::api_key%]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||||
|
"timeout_connect": "[%key:common::config_flow::error::timeout_connect%]",
|
||||||
|
"authentication_error": "[%key:common::config_flow::error::invalid_auth%]",
|
||||||
|
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"step": {
|
||||||
|
"init": {
|
||||||
|
"data": {
|
||||||
|
"prompt": "Instructions",
|
||||||
|
"chat_model": "[%key:common::generic::model%]",
|
||||||
|
"max_tokens": "Maximum tokens to return in response",
|
||||||
|
"temperature": "Temperature",
|
||||||
|
"llm_hass_api": "[%key:common::config_flow::data::llm_hass_api%]",
|
||||||
|
"recommended": "Recommended model settings"
|
||||||
|
},
|
||||||
|
"data_description": {
|
||||||
|
"prompt": "Instruct how the LLM should respond. This can be a template."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
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