mirror of
https://github.com/home-assistant/core.git
synced 2025-07-21 12:17:07 +00:00
Merge pull request #76119 from home-assistant/rc
This commit is contained in:
commit
8ef3ca2daf
@ -52,6 +52,7 @@ components: &components
|
|||||||
- homeassistant/components/auth/**
|
- homeassistant/components/auth/**
|
||||||
- homeassistant/components/automation/**
|
- homeassistant/components/automation/**
|
||||||
- homeassistant/components/backup/**
|
- homeassistant/components/backup/**
|
||||||
|
- homeassistant/components/bluetooth/**
|
||||||
- homeassistant/components/cloud/**
|
- homeassistant/components/cloud/**
|
||||||
- homeassistant/components/config/**
|
- homeassistant/components/config/**
|
||||||
- homeassistant/components/configurator/**
|
- homeassistant/components/configurator/**
|
||||||
@ -87,6 +88,7 @@ components: &components
|
|||||||
- homeassistant/components/persistent_notification/**
|
- homeassistant/components/persistent_notification/**
|
||||||
- homeassistant/components/person/**
|
- homeassistant/components/person/**
|
||||||
- homeassistant/components/recorder/**
|
- homeassistant/components/recorder/**
|
||||||
|
- homeassistant/components/repairs/**
|
||||||
- homeassistant/components/safe_mode/**
|
- homeassistant/components/safe_mode/**
|
||||||
- homeassistant/components/script/**
|
- homeassistant/components/script/**
|
||||||
- homeassistant/components/shopping_list/**
|
- homeassistant/components/shopping_list/**
|
||||||
|
16
.coveragerc
16
.coveragerc
@ -23,6 +23,7 @@ omit =
|
|||||||
homeassistant/components/adax/climate.py
|
homeassistant/components/adax/climate.py
|
||||||
homeassistant/components/adguard/__init__.py
|
homeassistant/components/adguard/__init__.py
|
||||||
homeassistant/components/adguard/const.py
|
homeassistant/components/adguard/const.py
|
||||||
|
homeassistant/components/adguard/entity.py
|
||||||
homeassistant/components/adguard/sensor.py
|
homeassistant/components/adguard/sensor.py
|
||||||
homeassistant/components/adguard/switch.py
|
homeassistant/components/adguard/switch.py
|
||||||
homeassistant/components/ads/*
|
homeassistant/components/ads/*
|
||||||
@ -136,6 +137,7 @@ omit =
|
|||||||
homeassistant/components/bosch_shc/switch.py
|
homeassistant/components/bosch_shc/switch.py
|
||||||
homeassistant/components/braviatv/__init__.py
|
homeassistant/components/braviatv/__init__.py
|
||||||
homeassistant/components/braviatv/const.py
|
homeassistant/components/braviatv/const.py
|
||||||
|
homeassistant/components/braviatv/entity.py
|
||||||
homeassistant/components/braviatv/media_player.py
|
homeassistant/components/braviatv/media_player.py
|
||||||
homeassistant/components/braviatv/remote.py
|
homeassistant/components/braviatv/remote.py
|
||||||
homeassistant/components/broadlink/__init__.py
|
homeassistant/components/broadlink/__init__.py
|
||||||
@ -210,7 +212,6 @@ omit =
|
|||||||
homeassistant/components/denonavr/media_player.py
|
homeassistant/components/denonavr/media_player.py
|
||||||
homeassistant/components/denonavr/receiver.py
|
homeassistant/components/denonavr/receiver.py
|
||||||
homeassistant/components/deutsche_bahn/sensor.py
|
homeassistant/components/deutsche_bahn/sensor.py
|
||||||
homeassistant/components/devolo_home_control/light.py
|
|
||||||
homeassistant/components/devolo_home_control/sensor.py
|
homeassistant/components/devolo_home_control/sensor.py
|
||||||
homeassistant/components/devolo_home_control/switch.py
|
homeassistant/components/devolo_home_control/switch.py
|
||||||
homeassistant/components/digital_ocean/*
|
homeassistant/components/digital_ocean/*
|
||||||
@ -267,6 +268,7 @@ omit =
|
|||||||
homeassistant/components/eliqonline/sensor.py
|
homeassistant/components/eliqonline/sensor.py
|
||||||
homeassistant/components/elkm1/__init__.py
|
homeassistant/components/elkm1/__init__.py
|
||||||
homeassistant/components/elkm1/alarm_control_panel.py
|
homeassistant/components/elkm1/alarm_control_panel.py
|
||||||
|
homeassistant/components/elkm1/binary_sensor.py
|
||||||
homeassistant/components/elkm1/climate.py
|
homeassistant/components/elkm1/climate.py
|
||||||
homeassistant/components/elkm1/discovery.py
|
homeassistant/components/elkm1/discovery.py
|
||||||
homeassistant/components/elkm1/light.py
|
homeassistant/components/elkm1/light.py
|
||||||
@ -276,6 +278,7 @@ omit =
|
|||||||
homeassistant/components/elmax/__init__.py
|
homeassistant/components/elmax/__init__.py
|
||||||
homeassistant/components/elmax/common.py
|
homeassistant/components/elmax/common.py
|
||||||
homeassistant/components/elmax/const.py
|
homeassistant/components/elmax/const.py
|
||||||
|
homeassistant/components/elmax/binary_sensor.py
|
||||||
homeassistant/components/elmax/switch.py
|
homeassistant/components/elmax/switch.py
|
||||||
homeassistant/components/elv/*
|
homeassistant/components/elv/*
|
||||||
homeassistant/components/emby/media_player.py
|
homeassistant/components/emby/media_player.py
|
||||||
@ -439,7 +442,6 @@ omit =
|
|||||||
homeassistant/components/google_cloud/tts.py
|
homeassistant/components/google_cloud/tts.py
|
||||||
homeassistant/components/google_maps/device_tracker.py
|
homeassistant/components/google_maps/device_tracker.py
|
||||||
homeassistant/components/google_pubsub/__init__.py
|
homeassistant/components/google_pubsub/__init__.py
|
||||||
homeassistant/components/gpmdp/media_player.py
|
|
||||||
homeassistant/components/gpsd/sensor.py
|
homeassistant/components/gpsd/sensor.py
|
||||||
homeassistant/components/greenwave/light.py
|
homeassistant/components/greenwave/light.py
|
||||||
homeassistant/components/group/notify.py
|
homeassistant/components/group/notify.py
|
||||||
@ -449,6 +451,7 @@ omit =
|
|||||||
homeassistant/components/gtfs/sensor.py
|
homeassistant/components/gtfs/sensor.py
|
||||||
homeassistant/components/guardian/__init__.py
|
homeassistant/components/guardian/__init__.py
|
||||||
homeassistant/components/guardian/binary_sensor.py
|
homeassistant/components/guardian/binary_sensor.py
|
||||||
|
homeassistant/components/guardian/button.py
|
||||||
homeassistant/components/guardian/sensor.py
|
homeassistant/components/guardian/sensor.py
|
||||||
homeassistant/components/guardian/switch.py
|
homeassistant/components/guardian/switch.py
|
||||||
homeassistant/components/guardian/util.py
|
homeassistant/components/guardian/util.py
|
||||||
@ -555,6 +558,7 @@ omit =
|
|||||||
homeassistant/components/insteon/utils.py
|
homeassistant/components/insteon/utils.py
|
||||||
homeassistant/components/intellifire/__init__.py
|
homeassistant/components/intellifire/__init__.py
|
||||||
homeassistant/components/intellifire/coordinator.py
|
homeassistant/components/intellifire/coordinator.py
|
||||||
|
homeassistant/components/intellifire/climate.py
|
||||||
homeassistant/components/intellifire/binary_sensor.py
|
homeassistant/components/intellifire/binary_sensor.py
|
||||||
homeassistant/components/intellifire/sensor.py
|
homeassistant/components/intellifire/sensor.py
|
||||||
homeassistant/components/intellifire/switch.py
|
homeassistant/components/intellifire/switch.py
|
||||||
@ -642,9 +646,6 @@ omit =
|
|||||||
homeassistant/components/life360/const.py
|
homeassistant/components/life360/const.py
|
||||||
homeassistant/components/life360/coordinator.py
|
homeassistant/components/life360/coordinator.py
|
||||||
homeassistant/components/life360/device_tracker.py
|
homeassistant/components/life360/device_tracker.py
|
||||||
homeassistant/components/lifx/__init__.py
|
|
||||||
homeassistant/components/lifx/const.py
|
|
||||||
homeassistant/components/lifx/light.py
|
|
||||||
homeassistant/components/lifx_cloud/scene.py
|
homeassistant/components/lifx_cloud/scene.py
|
||||||
homeassistant/components/lightwave/*
|
homeassistant/components/lightwave/*
|
||||||
homeassistant/components/limitlessled/light.py
|
homeassistant/components/limitlessled/light.py
|
||||||
@ -716,7 +717,6 @@ omit =
|
|||||||
homeassistant/components/microsoft/tts.py
|
homeassistant/components/microsoft/tts.py
|
||||||
homeassistant/components/miflora/sensor.py
|
homeassistant/components/miflora/sensor.py
|
||||||
homeassistant/components/mikrotik/hub.py
|
homeassistant/components/mikrotik/hub.py
|
||||||
homeassistant/components/mikrotik/device_tracker.py
|
|
||||||
homeassistant/components/mill/climate.py
|
homeassistant/components/mill/climate.py
|
||||||
homeassistant/components/mill/const.py
|
homeassistant/components/mill/const.py
|
||||||
homeassistant/components/mill/sensor.py
|
homeassistant/components/mill/sensor.py
|
||||||
@ -927,7 +927,6 @@ omit =
|
|||||||
homeassistant/components/plex/cast.py
|
homeassistant/components/plex/cast.py
|
||||||
homeassistant/components/plex/media_player.py
|
homeassistant/components/plex/media_player.py
|
||||||
homeassistant/components/plex/view.py
|
homeassistant/components/plex/view.py
|
||||||
homeassistant/components/plugwise/select.py
|
|
||||||
homeassistant/components/plum_lightpad/light.py
|
homeassistant/components/plum_lightpad/light.py
|
||||||
homeassistant/components/pocketcasts/sensor.py
|
homeassistant/components/pocketcasts/sensor.py
|
||||||
homeassistant/components/point/__init__.py
|
homeassistant/components/point/__init__.py
|
||||||
@ -1109,7 +1108,6 @@ omit =
|
|||||||
homeassistant/components/smtp/notify.py
|
homeassistant/components/smtp/notify.py
|
||||||
homeassistant/components/snapcast/*
|
homeassistant/components/snapcast/*
|
||||||
homeassistant/components/snmp/*
|
homeassistant/components/snmp/*
|
||||||
homeassistant/components/sochain/sensor.py
|
|
||||||
homeassistant/components/solaredge/__init__.py
|
homeassistant/components/solaredge/__init__.py
|
||||||
homeassistant/components/solaredge/coordinator.py
|
homeassistant/components/solaredge/coordinator.py
|
||||||
homeassistant/components/solaredge/sensor.py
|
homeassistant/components/solaredge/sensor.py
|
||||||
@ -1494,6 +1492,7 @@ omit =
|
|||||||
homeassistant/components/yolink/climate.py
|
homeassistant/components/yolink/climate.py
|
||||||
homeassistant/components/yolink/const.py
|
homeassistant/components/yolink/const.py
|
||||||
homeassistant/components/yolink/coordinator.py
|
homeassistant/components/yolink/coordinator.py
|
||||||
|
homeassistant/components/yolink/cover.py
|
||||||
homeassistant/components/yolink/entity.py
|
homeassistant/components/yolink/entity.py
|
||||||
homeassistant/components/yolink/lock.py
|
homeassistant/components/yolink/lock.py
|
||||||
homeassistant/components/yolink/sensor.py
|
homeassistant/components/yolink/sensor.py
|
||||||
@ -1521,7 +1520,6 @@ omit =
|
|||||||
homeassistant/components/zha/light.py
|
homeassistant/components/zha/light.py
|
||||||
homeassistant/components/zha/sensor.py
|
homeassistant/components/zha/sensor.py
|
||||||
homeassistant/components/zhong_hong/climate.py
|
homeassistant/components/zhong_hong/climate.py
|
||||||
homeassistant/components/xbee/*
|
|
||||||
homeassistant/components/ziggo_mediabox_xl/media_player.py
|
homeassistant/components/ziggo_mediabox_xl/media_player.py
|
||||||
homeassistant/components/zoneminder/*
|
homeassistant/components/zoneminder/*
|
||||||
homeassistant/components/supla/*
|
homeassistant/components/supla/*
|
||||||
|
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -33,6 +33,7 @@
|
|||||||
- [ ] Bugfix (non-breaking change which fixes an issue)
|
- [ ] Bugfix (non-breaking change which fixes an issue)
|
||||||
- [ ] New integration (thank you!)
|
- [ ] New integration (thank you!)
|
||||||
- [ ] New feature (which adds functionality to an existing integration)
|
- [ ] New feature (which adds functionality to an existing integration)
|
||||||
|
- [ ] Deprecation (breaking change to happen in the future)
|
||||||
- [ ] Breaking change (fix/feature causing existing functionality to break)
|
- [ ] Breaking change (fix/feature causing existing functionality to break)
|
||||||
- [ ] Code quality improvements to existing code or addition of tests
|
- [ ] Code quality improvements to existing code or addition of tests
|
||||||
|
|
||||||
|
36
.github/workflows/builder.yml
vendored
36
.github/workflows/builder.yml
vendored
@ -29,7 +29,7 @@ jobs:
|
|||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
uses: actions/setup-python@v4.0.0
|
uses: actions/setup-python@v4.1.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
|
|
||||||
@ -70,7 +70,7 @@ jobs:
|
|||||||
uses: actions/checkout@v3.0.2
|
uses: actions/checkout@v3.0.2
|
||||||
|
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
uses: actions/setup-python@v4.0.0
|
uses: actions/setup-python@v4.1.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
|
|
||||||
@ -102,9 +102,20 @@ jobs:
|
|||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@v3.0.2
|
uses: actions/checkout@v3.0.2
|
||||||
|
|
||||||
|
- name: Download nightly wheels of frontend
|
||||||
|
if: needs.init.outputs.channel == 'dev'
|
||||||
|
uses: dawidd6/action-download-artifact@v2
|
||||||
|
with:
|
||||||
|
github_token: ${{secrets.GITHUB_TOKEN}}
|
||||||
|
repo: home-assistant/frontend
|
||||||
|
branch: dev
|
||||||
|
workflow: nightly.yaml
|
||||||
|
workflow_conclusion: success
|
||||||
|
name: wheels
|
||||||
|
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
if: needs.init.outputs.channel == 'dev'
|
if: needs.init.outputs.channel == 'dev'
|
||||||
uses: actions/setup-python@v4.0.0
|
uses: actions/setup-python@v4.1.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
|
|
||||||
@ -112,10 +123,23 @@ jobs:
|
|||||||
if: needs.init.outputs.channel == 'dev'
|
if: needs.init.outputs.channel == 'dev'
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
python3 -m pip install packaging
|
python3 -m pip install packaging tomli
|
||||||
python3 -m pip install --use-deprecated=legacy-resolver .
|
python3 -m pip install --use-deprecated=legacy-resolver .
|
||||||
version="$(python3 script/version_bump.py nightly)"
|
version="$(python3 script/version_bump.py nightly)"
|
||||||
|
|
||||||
|
if [[ "$(ls home_assistant_frontend*.whl)" =~ ^home_assistant_frontend-(.*)-py3-none-any.whl$ ]]; then
|
||||||
|
echo "Found frontend wheel, setting version to: ${BASH_REMATCH[1]}"
|
||||||
|
frontend_version="${BASH_REMATCH[1]}" yq \
|
||||||
|
--inplace e -o json \
|
||||||
|
'.requirements = ["home-assistant-frontend=="+env(frontend_version)]' \
|
||||||
|
homeassistant/components/frontend/manifest.json
|
||||||
|
|
||||||
|
sed -i "s|home-assistant-frontend==.*|home-assistant-frontend==${BASH_REMATCH[1]}|" \
|
||||||
|
homeassistant/package_constraints.txt
|
||||||
|
|
||||||
|
python -m script.gen_requirements_all
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Write meta info file
|
- name: Write meta info file
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
@ -135,7 +159,7 @@ jobs:
|
|||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Build base image
|
- name: Build base image
|
||||||
uses: home-assistant/builder@2022.06.2
|
uses: home-assistant/builder@2022.07.0
|
||||||
with:
|
with:
|
||||||
args: |
|
args: |
|
||||||
$BUILD_ARGS \
|
$BUILD_ARGS \
|
||||||
@ -201,7 +225,7 @@ jobs:
|
|||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Build base image
|
- name: Build base image
|
||||||
uses: home-assistant/builder@2022.06.2
|
uses: home-assistant/builder@2022.07.0
|
||||||
with:
|
with:
|
||||||
args: |
|
args: |
|
||||||
$BUILD_ARGS \
|
$BUILD_ARGS \
|
||||||
|
512
.github/workflows/ci.yaml
vendored
512
.github/workflows/ci.yaml
vendored
@ -20,9 +20,9 @@ on:
|
|||||||
type: boolean
|
type: boolean
|
||||||
|
|
||||||
env:
|
env:
|
||||||
CACHE_VERSION: 10
|
CACHE_VERSION: 1
|
||||||
PIP_CACHE_VERSION: 4
|
PIP_CACHE_VERSION: 1
|
||||||
HA_SHORT_VERSION: 2022.7
|
HA_SHORT_VERSION: 2022.8
|
||||||
DEFAULT_PYTHON: 3.9
|
DEFAULT_PYTHON: 3.9
|
||||||
PRE_COMMIT_CACHE: ~/.cache/pre-commit
|
PRE_COMMIT_CACHE: ~/.cache/pre-commit
|
||||||
PIP_CACHE: /tmp/pip-cache
|
PIP_CACHE: /tmp/pip-cache
|
||||||
@ -35,24 +35,38 @@ concurrency:
|
|||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
changes:
|
info:
|
||||||
name: Determine what has changed
|
name: Collect information & changes data
|
||||||
outputs:
|
outputs:
|
||||||
# In case of issues with the partial run, use the following line instead:
|
# In case of issues with the partial run, use the following line instead:
|
||||||
# test_full_suite: 'true'
|
# test_full_suite: 'true'
|
||||||
test_full_suite: ${{ steps.info.outputs.test_full_suite }}
|
|
||||||
core: ${{ steps.core.outputs.changes }}
|
core: ${{ steps.core.outputs.changes }}
|
||||||
integrations: ${{ steps.integrations.outputs.changes }}
|
|
||||||
integrations_glob: ${{ steps.info.outputs.integrations_glob }}
|
integrations_glob: ${{ steps.info.outputs.integrations_glob }}
|
||||||
tests: ${{ steps.info.outputs.tests }}
|
integrations: ${{ steps.integrations.outputs.changes }}
|
||||||
tests_glob: ${{ steps.info.outputs.tests_glob }}
|
pre-commit_cache_key: ${{ steps.generate_pre-commit_cache_key.outputs.key }}
|
||||||
test_groups: ${{ steps.info.outputs.test_groups }}
|
python_cache_key: ${{ steps.generate_python_cache_key.outputs.key }}
|
||||||
test_group_count: ${{ steps.info.outputs.test_group_count }}
|
|
||||||
requirements: ${{ steps.core.outputs.requirements }}
|
requirements: ${{ steps.core.outputs.requirements }}
|
||||||
runs-on: ubuntu-latest
|
test_full_suite: ${{ steps.info.outputs.test_full_suite }}
|
||||||
|
test_group_count: ${{ steps.info.outputs.test_group_count }}
|
||||||
|
test_groups: ${{ steps.info.outputs.test_groups }}
|
||||||
|
tests_glob: ${{ steps.info.outputs.tests_glob }}
|
||||||
|
tests: ${{ steps.info.outputs.tests }}
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v3.0.2
|
uses: actions/checkout@v3.0.2
|
||||||
|
- name: Generate partial Python venv restore key
|
||||||
|
id: generate_python_cache_key
|
||||||
|
run: >-
|
||||||
|
echo "::set-output name=key::venv-${{ env.CACHE_VERSION }}-${{
|
||||||
|
hashFiles('requirements_test.txt') }}-${{
|
||||||
|
hashFiles('requirements_all.txt') }}-${{
|
||||||
|
hashFiles('homeassistant/package_constraints.txt') }}"
|
||||||
|
- name: Generate partial pre-commit restore key
|
||||||
|
id: generate_pre-commit_cache_key
|
||||||
|
run: >-
|
||||||
|
echo "::set-output name=key::${{ env.CACHE_VERSION }}-${{ env.DEFAULT_PYTHON }}-${{
|
||||||
|
hashFiles('.pre-commit-config.yaml') }}"
|
||||||
- name: Filter for core changes
|
- name: Filter for core changes
|
||||||
uses: dorny/paths-filter@v2.10.2
|
uses: dorny/paths-filter@v2.10.2
|
||||||
id: core
|
id: core
|
||||||
@ -79,8 +93,8 @@ jobs:
|
|||||||
# Defaults
|
# Defaults
|
||||||
integrations_glob=""
|
integrations_glob=""
|
||||||
test_full_suite="true"
|
test_full_suite="true"
|
||||||
test_groups="[1, 2, 3, 4, 5, 6]"
|
test_groups="[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]"
|
||||||
test_group_count=6
|
test_group_count=10
|
||||||
tests="[]"
|
tests="[]"
|
||||||
tests_glob=""
|
tests_glob=""
|
||||||
|
|
||||||
@ -123,8 +137,8 @@ jobs:
|
|||||||
|| [[ "${{ github.event.inputs.full }}" == "true" ]] \
|
|| [[ "${{ github.event.inputs.full }}" == "true" ]] \
|
||||||
|| [[ "${{ contains(github.event.pull_request.labels.*.name, 'ci-full-run') }}" == "true" ]];
|
|| [[ "${{ contains(github.event.pull_request.labels.*.name, 'ci-full-run') }}" == "true" ]];
|
||||||
then
|
then
|
||||||
test_groups="[1, 2, 3, 4, 5, 6]"
|
test_groups="[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]"
|
||||||
test_group_count=6
|
test_group_count=10
|
||||||
test_full_suite="true"
|
test_full_suite="true"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -142,84 +156,39 @@ jobs:
|
|||||||
echo "tests_glob: ${tests_glob}"
|
echo "tests_glob: ${tests_glob}"
|
||||||
echo "::set-output name=tests_glob::${tests_glob}"
|
echo "::set-output name=tests_glob::${tests_glob}"
|
||||||
|
|
||||||
# Separate job to pre-populate the base dependency cache
|
pre-commit:
|
||||||
# This prevent upcoming jobs to do the same individually
|
name: Prepare pre-commit base
|
||||||
prepare-base:
|
runs-on: ubuntu-20.04
|
||||||
name: Prepare base dependencies
|
needs:
|
||||||
runs-on: ubuntu-latest
|
- info
|
||||||
timeout-minutes: 20
|
|
||||||
outputs:
|
|
||||||
python-key: ${{ steps.generate-python-key.outputs.key }}
|
|
||||||
pre-commit-key: ${{ steps.generate-pre-commit-key.outputs.key }}
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v3.0.2
|
uses: actions/checkout@v3.0.2
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
id: python
|
id: python
|
||||||
uses: actions/setup-python@v4.0.0
|
uses: actions/setup-python@v4.1.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
- name: Generate partial Python venv restore key
|
cache: "pip"
|
||||||
id: generate-python-key
|
|
||||||
run: >-
|
|
||||||
echo "::set-output name=key::base-venv-${{ env.CACHE_VERSION }}-${{
|
|
||||||
hashFiles('requirements.txt') }}-${{
|
|
||||||
hashFiles('requirements_test.txt') }}-${{
|
|
||||||
hashFiles('homeassistant/package_constraints.txt') }}"
|
|
||||||
- name: Generate partial pip restore key
|
|
||||||
id: generate-pip-key
|
|
||||||
run: >-
|
|
||||||
echo "::set-output name=key::base-pip-${{ env.PIP_CACHE_VERSION }}-${{
|
|
||||||
env.HA_SHORT_VERSION }}-$(date -u '+%Y-%m-%dT%H:%M:%s')"
|
|
||||||
- name: Restore base Python virtual environment
|
- name: Restore base Python virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache@v3.0.4
|
uses: actions/cache@v3.0.5
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
key: >-
|
key: ${{ runner.os }}-venv-${{ needs.info.outputs.pre-commit_cache_key }}
|
||||||
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
|
||||||
steps.generate-python-key.outputs.key }}
|
|
||||||
# Temporary disabling the restore of environments when bumping
|
|
||||||
# a dependency. It seems that we are experiencing issues with
|
|
||||||
# restoring environments in GitHub Actions, although unclear why.
|
|
||||||
# First attempt: https://github.com/home-assistant/core/pull/62383
|
|
||||||
#
|
|
||||||
# restore-keys: |
|
|
||||||
# ${{ runner.os }}-${{ steps.python.outputs.python-version }}-base-venv-${{ env.CACHE_VERSION }}-${{ hashFiles('requirements.txt') }}-${{ hashFiles('requirements_test.txt') }}-
|
|
||||||
# ${{ runner.os }}-${{ steps.python.outputs.python-version }}-base-venv-${{ env.CACHE_VERSION }}-${{ hashFiles('requirements.txt') }}-
|
|
||||||
# ${{ runner.os }}-${{ steps.python.outputs.python-version }}-base-venv-${{ env.CACHE_VERSION }}-
|
|
||||||
- name: Restore pip wheel cache
|
|
||||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
|
||||||
uses: actions/cache@v3.0.4
|
|
||||||
with:
|
|
||||||
path: ${{ env.PIP_CACHE }}
|
|
||||||
key: >-
|
|
||||||
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
|
||||||
steps.generate-pip-key.outputs.key }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-${{ steps.python.outputs.python-version }}-base-pip-${{ env.PIP_CACHE_VERSION }}-${{ env.HA_SHORT_VERSION }}-
|
|
||||||
- name: Create Python virtual environment
|
- name: Create Python virtual environment
|
||||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||||
run: |
|
run: |
|
||||||
python -m venv venv
|
python -m venv venv
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
python --version
|
python --version
|
||||||
pip install --cache-dir=$PIP_CACHE -U "pip>=21.0,<22.2" setuptools wheel
|
pip install "$(cat requirements_test.txt | grep pre-commit)"
|
||||||
pip install --cache-dir=$PIP_CACHE -r requirements.txt -r requirements_test.txt --use-deprecated=legacy-resolver
|
|
||||||
- name: Generate partial pre-commit restore key
|
|
||||||
id: generate-pre-commit-key
|
|
||||||
run: >-
|
|
||||||
echo "::set-output name=key::pre-commit-${{ env.CACHE_VERSION }}-${{
|
|
||||||
hashFiles('.pre-commit-config.yaml') }}"
|
|
||||||
- name: Restore pre-commit environment from cache
|
- name: Restore pre-commit environment from cache
|
||||||
id: cache-precommit
|
id: cache-precommit
|
||||||
uses: actions/cache@v3.0.4
|
uses: actions/cache@v3.0.5
|
||||||
with:
|
with:
|
||||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||||
key: >-
|
key: ${{ runner.os }}-pre-commit-${{ needs.info.outputs.pre-commit_cache_key }}
|
||||||
${{ runner.os }}-${{ steps.generate-pre-commit-key.outputs.key }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-pre-commit-${{ env.CACHE_VERSION }}-
|
|
||||||
- name: Install pre-commit dependencies
|
- name: Install pre-commit dependencies
|
||||||
if: steps.cache-precommit.outputs.cache-hit != 'true'
|
if: steps.cache-precommit.outputs.cache-hit != 'true'
|
||||||
run: |
|
run: |
|
||||||
@ -228,25 +197,24 @@ jobs:
|
|||||||
|
|
||||||
lint-black:
|
lint-black:
|
||||||
name: Check black
|
name: Check black
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-20.04
|
||||||
needs:
|
needs:
|
||||||
- changes
|
- info
|
||||||
- prepare-base
|
- pre-commit
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v3.0.2
|
uses: actions/checkout@v3.0.2
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
uses: actions/setup-python@v4.0.0
|
uses: actions/setup-python@v4.1.0
|
||||||
id: python
|
id: python
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
- name: Restore base Python virtual environment
|
- name: Restore base Python virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache@v3.0.4
|
uses: actions/cache@v3.0.5
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
key: ${{ runner.os }}-venv-${{ needs.info.outputs.pre-commit_cache_key }}
|
||||||
needs.prepare-base.outputs.python-key }}
|
|
||||||
- name: Fail job if Python cache restore failed
|
- name: Fail job if Python cache restore failed
|
||||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||||
run: |
|
run: |
|
||||||
@ -254,49 +222,48 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
- name: Restore pre-commit environment from cache
|
- name: Restore pre-commit environment from cache
|
||||||
id: cache-precommit
|
id: cache-precommit
|
||||||
uses: actions/cache@v3.0.4
|
uses: actions/cache@v3.0.5
|
||||||
with:
|
with:
|
||||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||||
key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }}
|
key: ${{ runner.os }}-pre-commit-${{ needs.info.outputs.pre-commit_cache_key }}
|
||||||
- name: Fail job if pre-commit cache restore failed
|
- name: Fail job if pre-commit cache restore failed
|
||||||
if: steps.cache-precommit.outputs.cache-hit != 'true'
|
if: steps.cache-precommit.outputs.cache-hit != 'true'
|
||||||
run: |
|
run: |
|
||||||
echo "Failed to restore pre-commit environment from cache"
|
echo "Failed to restore pre-commit environment from cache"
|
||||||
exit 1
|
exit 1
|
||||||
- name: Run black (fully)
|
- name: Run black (fully)
|
||||||
if: needs.changes.outputs.test_full_suite == 'true'
|
if: needs.info.outputs.test_full_suite == 'true'
|
||||||
run: |
|
run: |
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
pre-commit run --hook-stage manual black --all-files --show-diff-on-failure
|
pre-commit run --hook-stage manual black --all-files --show-diff-on-failure
|
||||||
- name: Run black (partially)
|
- name: Run black (partially)
|
||||||
if: needs.changes.outputs.test_full_suite == 'false'
|
if: needs.info.outputs.test_full_suite == 'false'
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
shopt -s globstar
|
shopt -s globstar
|
||||||
pre-commit run --hook-stage manual black --files {homeassistant,tests}/components/${{ needs.changes.outputs.integrations_glob }}/**/* --show-diff-on-failure
|
pre-commit run --hook-stage manual black --files {homeassistant,tests}/components/${{ needs.info.outputs.integrations_glob }}/**/* --show-diff-on-failure
|
||||||
|
|
||||||
lint-flake8:
|
lint-flake8:
|
||||||
name: Check flake8
|
name: Check flake8
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-20.04
|
||||||
needs:
|
needs:
|
||||||
- changes
|
- info
|
||||||
- prepare-base
|
- pre-commit
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v3.0.2
|
uses: actions/checkout@v3.0.2
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
uses: actions/setup-python@v4.0.0
|
uses: actions/setup-python@v4.1.0
|
||||||
id: python
|
id: python
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
- name: Restore base Python virtual environment
|
- name: Restore base Python virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache@v3.0.4
|
uses: actions/cache@v3.0.5
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
key: ${{ runner.os }}-venv-${{ needs.info.outputs.pre-commit_cache_key }}
|
||||||
needs.prepare-base.outputs.python-key }}
|
|
||||||
- name: Fail job if Python cache restore failed
|
- name: Fail job if Python cache restore failed
|
||||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||||
run: |
|
run: |
|
||||||
@ -304,10 +271,10 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
- name: Restore pre-commit environment from cache
|
- name: Restore pre-commit environment from cache
|
||||||
id: cache-precommit
|
id: cache-precommit
|
||||||
uses: actions/cache@v3.0.4
|
uses: actions/cache@v3.0.5
|
||||||
with:
|
with:
|
||||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||||
key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }}
|
key: ${{ runner.os }}-pre-commit-${{ needs.info.outputs.pre-commit_cache_key }}
|
||||||
- name: Fail job if pre-commit cache restore failed
|
- name: Fail job if pre-commit cache restore failed
|
||||||
if: steps.cache-precommit.outputs.cache-hit != 'true'
|
if: steps.cache-precommit.outputs.cache-hit != 'true'
|
||||||
run: |
|
run: |
|
||||||
@ -317,37 +284,38 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
echo "::add-matcher::.github/workflows/matchers/flake8.json"
|
echo "::add-matcher::.github/workflows/matchers/flake8.json"
|
||||||
- name: Run flake8 (fully)
|
- name: Run flake8 (fully)
|
||||||
if: needs.changes.outputs.test_full_suite == 'true'
|
if: needs.info.outputs.test_full_suite == 'true'
|
||||||
run: |
|
run: |
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
pre-commit run --hook-stage manual flake8 --all-files
|
pre-commit run --hook-stage manual flake8 --all-files
|
||||||
- name: Run flake8 (partially)
|
- name: Run flake8 (partially)
|
||||||
if: needs.changes.outputs.test_full_suite == 'false'
|
if: needs.info.outputs.test_full_suite == 'false'
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
shopt -s globstar
|
shopt -s globstar
|
||||||
pre-commit run --hook-stage manual flake8 --files {homeassistant,tests}/components/${{ needs.changes.outputs.integrations_glob }}/**/*
|
pre-commit run --hook-stage manual flake8 --files {homeassistant,tests}/components/${{ needs.info.outputs.integrations_glob }}/**/*
|
||||||
|
|
||||||
lint-isort:
|
lint-isort:
|
||||||
name: Check isort
|
name: Check isort
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-20.04
|
||||||
needs: prepare-base
|
needs:
|
||||||
|
- info
|
||||||
|
- pre-commit
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v3.0.2
|
uses: actions/checkout@v3.0.2
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
uses: actions/setup-python@v4.0.0
|
uses: actions/setup-python@v4.1.0
|
||||||
id: python
|
id: python
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
- name: Restore base Python virtual environment
|
- name: Restore base Python virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache@v3.0.4
|
uses: actions/cache@v3.0.5
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
key: ${{ runner.os }}-venv-${{ needs.info.outputs.pre-commit_cache_key }}
|
||||||
needs.prepare-base.outputs.python-key }}
|
|
||||||
- name: Fail job if Python cache restore failed
|
- name: Fail job if Python cache restore failed
|
||||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||||
run: |
|
run: |
|
||||||
@ -355,10 +323,10 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
- name: Restore pre-commit environment from cache
|
- name: Restore pre-commit environment from cache
|
||||||
id: cache-precommit
|
id: cache-precommit
|
||||||
uses: actions/cache@v3.0.4
|
uses: actions/cache@v3.0.5
|
||||||
with:
|
with:
|
||||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||||
key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }}
|
key: ${{ runner.os }}-pre-commit-${{ needs.info.outputs.pre-commit_cache_key }}
|
||||||
- name: Fail job if pre-commit cache restore failed
|
- name: Fail job if pre-commit cache restore failed
|
||||||
if: steps.cache-precommit.outputs.cache-hit != 'true'
|
if: steps.cache-precommit.outputs.cache-hit != 'true'
|
||||||
run: |
|
run: |
|
||||||
@ -371,25 +339,24 @@ jobs:
|
|||||||
|
|
||||||
lint-other:
|
lint-other:
|
||||||
name: Check other linters
|
name: Check other linters
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-20.04
|
||||||
needs:
|
needs:
|
||||||
- changes
|
- info
|
||||||
- prepare-base
|
- pre-commit
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v3.0.2
|
uses: actions/checkout@v3.0.2
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
uses: actions/setup-python@v4.0.0
|
uses: actions/setup-python@v4.1.0
|
||||||
id: python
|
id: python
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
- name: Restore base Python virtual environment
|
- name: Restore base Python virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache@v3.0.4
|
uses: actions/cache@v3.0.5
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
key: ${{ runner.os }}-venv-${{ needs.info.outputs.pre-commit_cache_key }}
|
||||||
needs.prepare-base.outputs.python-key }}
|
|
||||||
- name: Fail job if Python cache restore failed
|
- name: Fail job if Python cache restore failed
|
||||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||||
run: |
|
run: |
|
||||||
@ -397,10 +364,10 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
- name: Restore pre-commit environment from cache
|
- name: Restore pre-commit environment from cache
|
||||||
id: cache-precommit
|
id: cache-precommit
|
||||||
uses: actions/cache@v3.0.4
|
uses: actions/cache@v3.0.5
|
||||||
with:
|
with:
|
||||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||||
key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }}
|
key: ${{ runner.os }}-pre-commit-${{ needs.info.outputs.pre-commit_cache_key }}
|
||||||
- name: Fail job if pre-commit cache restore failed
|
- name: Fail job if pre-commit cache restore failed
|
||||||
if: steps.cache-precommit.outputs.cache-hit != 'true'
|
if: steps.cache-precommit.outputs.cache-hit != 'true'
|
||||||
run: |
|
run: |
|
||||||
@ -408,17 +375,17 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
|
|
||||||
- name: Run pyupgrade (fully)
|
- name: Run pyupgrade (fully)
|
||||||
if: needs.changes.outputs.test_full_suite == 'true'
|
if: needs.info.outputs.test_full_suite == 'true'
|
||||||
run: |
|
run: |
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
pre-commit run --hook-stage manual pyupgrade --all-files --show-diff-on-failure
|
pre-commit run --hook-stage manual pyupgrade --all-files --show-diff-on-failure
|
||||||
- name: Run pyupgrade (partially)
|
- name: Run pyupgrade (partially)
|
||||||
if: needs.changes.outputs.test_full_suite == 'false'
|
if: needs.info.outputs.test_full_suite == 'false'
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
shopt -s globstar
|
shopt -s globstar
|
||||||
pre-commit run --hook-stage manual pyupgrade --files {homeassistant,tests}/components/${{ needs.changes.outputs.integrations_glob }}/**/* --show-diff-on-failure
|
pre-commit run --hook-stage manual pyupgrade --files {homeassistant,tests}/components/${{ needs.info.outputs.integrations_glob }}/**/* --show-diff-on-failure
|
||||||
|
|
||||||
- name: Register yamllint problem matcher
|
- name: Register yamllint problem matcher
|
||||||
run: |
|
run: |
|
||||||
@ -437,17 +404,17 @@ jobs:
|
|||||||
pre-commit run --hook-stage manual check-json --all-files
|
pre-commit run --hook-stage manual check-json --all-files
|
||||||
|
|
||||||
- name: Run prettier (fully)
|
- name: Run prettier (fully)
|
||||||
if: needs.changes.outputs.test_full_suite == 'true'
|
if: needs.info.outputs.test_full_suite == 'true'
|
||||||
run: |
|
run: |
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
pre-commit run --hook-stage manual prettier --all-files
|
pre-commit run --hook-stage manual prettier --all-files
|
||||||
|
|
||||||
- name: Run prettier (partially)
|
- name: Run prettier (partially)
|
||||||
if: needs.changes.outputs.test_full_suite == 'false'
|
if: needs.info.outputs.test_full_suite == 'false'
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
pre-commit run --hook-stage manual prettier --files {homeassistant,tests}/components/${{ needs.changes.outputs.integrations_glob }}/**/*
|
pre-commit run --hook-stage manual prettier --files {homeassistant,tests}/components/${{ needs.info.outputs.integrations_glob }}/**/*
|
||||||
|
|
||||||
- name: Register check executables problem matcher
|
- name: Register check executables problem matcher
|
||||||
run: |
|
run: |
|
||||||
@ -478,36 +445,105 @@ jobs:
|
|||||||
args: hadolint Dockerfile.dev
|
args: hadolint Dockerfile.dev
|
||||||
|
|
||||||
- name: Run bandit (fully)
|
- name: Run bandit (fully)
|
||||||
if: needs.changes.outputs.test_full_suite == 'true'
|
if: needs.info.outputs.test_full_suite == 'true'
|
||||||
run: |
|
run: |
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
pre-commit run --hook-stage manual bandit --all-files --show-diff-on-failure
|
pre-commit run --hook-stage manual bandit --all-files --show-diff-on-failure
|
||||||
- name: Run bandit (partially)
|
- name: Run bandit (partially)
|
||||||
if: needs.changes.outputs.test_full_suite == 'false'
|
if: needs.info.outputs.test_full_suite == 'false'
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
shopt -s globstar
|
shopt -s globstar
|
||||||
pre-commit run --hook-stage manual bandit --files {homeassistant,tests}/components/${{ needs.changes.outputs.integrations_glob }}/**/* --show-diff-on-failure
|
pre-commit run --hook-stage manual bandit --files {homeassistant,tests}/components/${{ needs.info.outputs.integrations_glob }}/**/* --show-diff-on-failure
|
||||||
|
|
||||||
hassfest:
|
base:
|
||||||
name: Check hassfest
|
name: Prepare dependencies
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-20.04
|
||||||
needs: prepare-tests
|
needs: info
|
||||||
|
timeout-minutes: 60
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: [3.9]
|
python-version: ["3.9", "3.10"]
|
||||||
container: homeassistant/ci-azure:${{ matrix.python-version }}
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v3.0.2
|
uses: actions/checkout@v3.0.2
|
||||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
|
id: python
|
||||||
|
uses: actions/setup-python@v4.1.0
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python-version }}
|
||||||
|
- name: Generate partial pip restore key
|
||||||
|
id: generate-pip-key
|
||||||
|
run: >-
|
||||||
|
echo "::set-output name=key::pip-${{ env.PIP_CACHE_VERSION }}-${{
|
||||||
|
env.HA_SHORT_VERSION }}-$(date -u '+%Y-%m-%dT%H:%M:%s')"
|
||||||
|
- name: Restore base Python virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache@v3.0.4
|
uses: actions/cache@v3.0.5
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
key: ${{ runner.os }}-${{ matrix.python-version }}-${{
|
key: >-
|
||||||
needs.prepare-tests.outputs.python-key }}
|
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
||||||
|
needs.info.outputs.python_cache_key }}
|
||||||
|
- name: Restore pip wheel cache
|
||||||
|
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||||
|
uses: actions/cache@v3.0.5
|
||||||
|
with:
|
||||||
|
path: ${{ env.PIP_CACHE }}
|
||||||
|
key: >-
|
||||||
|
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
||||||
|
steps.generate-pip-key.outputs.key }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-${{ steps.python.outputs.python-version }}-pip-${{ env.PIP_CACHE_VERSION }}-${{ env.HA_SHORT_VERSION }}-
|
||||||
|
- name: Install additional OS dependencies
|
||||||
|
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get -y install \
|
||||||
|
bluez \
|
||||||
|
ffmpeg \
|
||||||
|
libavcodec-dev \
|
||||||
|
libavdevice-dev \
|
||||||
|
libavfilter-dev \
|
||||||
|
libavformat-dev \
|
||||||
|
libavutil-dev \
|
||||||
|
libswresample-dev \
|
||||||
|
libswscale-dev \
|
||||||
|
libudev-dev
|
||||||
|
- name: Create Python virtual environment
|
||||||
|
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||||
|
run: |
|
||||||
|
python -m venv venv
|
||||||
|
. venv/bin/activate
|
||||||
|
python --version
|
||||||
|
pip install --cache-dir=$PIP_CACHE -U "pip>=21.0,<22.3" setuptools wheel
|
||||||
|
pip install --cache-dir=$PIP_CACHE -r requirements_all.txt --use-deprecated=legacy-resolver
|
||||||
|
pip install --cache-dir=$PIP_CACHE -r requirements_test.txt --use-deprecated=legacy-resolver
|
||||||
|
pip install -e .
|
||||||
|
|
||||||
|
hassfest:
|
||||||
|
name: Check hassfest
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
needs:
|
||||||
|
- info
|
||||||
|
- base
|
||||||
|
steps:
|
||||||
|
- name: Check out code from GitHub
|
||||||
|
uses: actions/checkout@v3.0.2
|
||||||
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
|
id: python
|
||||||
|
uses: actions/setup-python@v4.1.0
|
||||||
|
with:
|
||||||
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
|
- name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment
|
||||||
|
id: cache-venv
|
||||||
|
uses: actions/cache@v3.0.5
|
||||||
|
with:
|
||||||
|
path: venv
|
||||||
|
key: >-
|
||||||
|
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
||||||
|
needs.info.outputs.python_cache_key }}
|
||||||
- name: Fail job if Python cache restore failed
|
- name: Fail job if Python cache restore failed
|
||||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||||
run: |
|
run: |
|
||||||
@ -520,23 +556,26 @@ jobs:
|
|||||||
|
|
||||||
gen-requirements-all:
|
gen-requirements-all:
|
||||||
name: Check all requirements
|
name: Check all requirements
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-20.04
|
||||||
needs: prepare-base
|
needs:
|
||||||
|
- info
|
||||||
|
- base
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v3.0.2
|
uses: actions/checkout@v3.0.2
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
uses: actions/setup-python@v4.0.0
|
|
||||||
id: python
|
id: python
|
||||||
|
uses: actions/setup-python@v4.1.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
- name: Restore base Python virtual environment
|
- name: Restore base Python virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache@v3.0.4
|
uses: actions/cache@v3.0.5
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
key: >-
|
||||||
needs.prepare-base.outputs.python-key }}
|
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
||||||
|
needs.info.outputs.python_cache_key }}
|
||||||
- name: Fail job if Python cache restore failed
|
- name: Fail job if Python cache restore failed
|
||||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||||
run: |
|
run: |
|
||||||
@ -547,94 +586,29 @@ jobs:
|
|||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
python -m script.gen_requirements_all validate
|
python -m script.gen_requirements_all validate
|
||||||
|
|
||||||
prepare-tests:
|
|
||||||
name: Prepare tests for Python ${{ matrix.python-version }}
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 60
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
python-version: ["3.9", "3.10"]
|
|
||||||
outputs:
|
|
||||||
python-key: ${{ steps.generate-python-key.outputs.key }}
|
|
||||||
container: homeassistant/ci-azure:${{ matrix.python-version }}
|
|
||||||
steps:
|
|
||||||
- name: Check out code from GitHub
|
|
||||||
uses: actions/checkout@v3.0.2
|
|
||||||
- name: Generate partial Python venv restore key
|
|
||||||
id: generate-python-key
|
|
||||||
run: >-
|
|
||||||
echo "::set-output name=key::venv-${{ env.CACHE_VERSION }}-${{
|
|
||||||
hashFiles('requirements_test.txt') }}-${{
|
|
||||||
hashFiles('requirements_all.txt') }}-${{
|
|
||||||
hashFiles('homeassistant/package_constraints.txt') }}"
|
|
||||||
- name: Generate partial pip restore key
|
|
||||||
id: generate-pip-key
|
|
||||||
run: >-
|
|
||||||
echo "::set-output name=key::pip-${{ env.PIP_CACHE_VERSION }}-${{
|
|
||||||
env.HA_SHORT_VERSION }}-$(date -u '+%Y-%m-%dT%H:%M:%s')"
|
|
||||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
|
||||||
id: cache-venv
|
|
||||||
uses: actions/cache@v3.0.4
|
|
||||||
with:
|
|
||||||
path: venv
|
|
||||||
key: >-
|
|
||||||
${{ runner.os }}-${{ matrix.python-version }}-${{
|
|
||||||
steps.generate-python-key.outputs.key }}
|
|
||||||
# Temporary disabling the restore of environments when bumping
|
|
||||||
# a dependency. It seems that we are experiencing issues with
|
|
||||||
# restoring environments in GitHub Actions, although unclear why.
|
|
||||||
# First attempt: https://github.com/home-assistant/core/pull/62383
|
|
||||||
#
|
|
||||||
# restore-keys: |
|
|
||||||
# ${{ runner.os }}-${{ matrix.python-version }}-venv-${{ env.CACHE_VERSION }}-${{ hashFiles('requirements_test.txt') }}-${{ hashFiles('requirements_all.txt') }}-
|
|
||||||
# ${{ runner.os }}-${{ matrix.python-version }}-venv-${{ env.CACHE_VERSION }}-${{ hashFiles('requirements_test.txt') }}-
|
|
||||||
# ${{ runner.os }}-${{ matrix.python-version }}-venv-${{ env.CACHE_VERSION }}-
|
|
||||||
- name: Restore pip wheel cache
|
|
||||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
|
||||||
uses: actions/cache@v3.0.4
|
|
||||||
with:
|
|
||||||
path: ${{ env.PIP_CACHE }}
|
|
||||||
key: >-
|
|
||||||
${{ runner.os }}-${{ matrix.python-version }}-${{
|
|
||||||
steps.generate-pip-key.outputs.key }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-${{ matrix.python-version }}-pip-${{ env.PIP_CACHE_VERSION }}-${{ env.HA_SHORT_VERSION }}-
|
|
||||||
- name: Create full Python ${{ matrix.python-version }} virtual environment
|
|
||||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
|
||||||
run: |
|
|
||||||
# Temporary addition of cmake, needed to build some Python 3.9 packages
|
|
||||||
apt-get update
|
|
||||||
apt-get -y install cmake
|
|
||||||
|
|
||||||
python -m venv venv
|
|
||||||
. venv/bin/activate
|
|
||||||
python --version
|
|
||||||
pip install --cache-dir=$PIP_CACHE -U "pip>=21.0,<22.2" setuptools wheel
|
|
||||||
pip install --cache-dir=$PIP_CACHE -r requirements_all.txt --use-deprecated=legacy-resolver
|
|
||||||
pip install --cache-dir=$PIP_CACHE -r requirements_test.txt --use-deprecated=legacy-resolver
|
|
||||||
pip install -e .
|
|
||||||
|
|
||||||
pylint:
|
pylint:
|
||||||
name: Check pylint
|
name: Check pylint
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-20.04
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
needs:
|
needs:
|
||||||
- changes
|
- info
|
||||||
- prepare-tests
|
- base
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
python-version: [3.9]
|
|
||||||
container: homeassistant/ci-azure:${{ matrix.python-version }}
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v3.0.2
|
uses: actions/checkout@v3.0.2
|
||||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
|
id: python
|
||||||
|
uses: actions/setup-python@v4.1.0
|
||||||
|
with:
|
||||||
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
|
- name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache@v3.0.4
|
uses: actions/cache@v3.0.5
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
key: ${{ runner.os }}-${{ matrix.python-version }}-${{
|
key: >-
|
||||||
needs.prepare-tests.outputs.python-key }}
|
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
||||||
|
needs.info.outputs.python_cache_key }}
|
||||||
- name: Fail job if Python cache restore failed
|
- name: Fail job if Python cache restore failed
|
||||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||||
run: |
|
run: |
|
||||||
@ -644,39 +618,41 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
echo "::add-matcher::.github/workflows/matchers/pylint.json"
|
echo "::add-matcher::.github/workflows/matchers/pylint.json"
|
||||||
- name: Run pylint (fully)
|
- name: Run pylint (fully)
|
||||||
if: needs.changes.outputs.test_full_suite == 'true'
|
if: needs.info.outputs.test_full_suite == 'true'
|
||||||
run: |
|
run: |
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
python --version
|
python --version
|
||||||
pylint homeassistant
|
pylint --ignore-missing-annotations=y homeassistant
|
||||||
- name: Run pylint (partially)
|
- name: Run pylint (partially)
|
||||||
if: needs.changes.outputs.test_full_suite == 'false'
|
if: needs.info.outputs.test_full_suite == 'false'
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
python --version
|
python --version
|
||||||
pylint homeassistant/components/${{ needs.changes.outputs.integrations_glob }}
|
pylint --ignore-missing-annotations=y homeassistant/components/${{ needs.info.outputs.integrations_glob }}
|
||||||
|
|
||||||
mypy:
|
mypy:
|
||||||
name: Check mypy
|
name: Check mypy
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-20.04
|
||||||
needs:
|
needs:
|
||||||
- changes
|
- info
|
||||||
- prepare-tests
|
- base
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
python-version: [3.9]
|
|
||||||
container: homeassistant/ci-azure:${{ matrix.python-version }}
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v3.0.2
|
uses: actions/checkout@v3.0.2
|
||||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
|
id: python
|
||||||
|
uses: actions/setup-python@v4.1.0
|
||||||
|
with:
|
||||||
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
|
- name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache@v3.0.4
|
uses: actions/cache@v3.0.5
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
key: ${{ runner.os }}-${{ matrix.python-version }}-${{
|
key: >-
|
||||||
needs.prepare-tests.outputs.python-key }}
|
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
||||||
|
needs.info.outputs.python_cache_key }}
|
||||||
- name: Fail job if Python cache restore failed
|
- name: Fail job if Python cache restore failed
|
||||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||||
run: |
|
run: |
|
||||||
@ -686,41 +662,45 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
echo "::add-matcher::.github/workflows/matchers/mypy.json"
|
echo "::add-matcher::.github/workflows/matchers/mypy.json"
|
||||||
- name: Run mypy (fully)
|
- name: Run mypy (fully)
|
||||||
if: needs.changes.outputs.test_full_suite == 'true'
|
if: needs.info.outputs.test_full_suite == 'true'
|
||||||
run: |
|
run: |
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
python --version
|
python --version
|
||||||
mypy homeassistant pylint
|
mypy homeassistant pylint
|
||||||
- name: Run mypy (partially)
|
- name: Run mypy (partially)
|
||||||
if: needs.changes.outputs.test_full_suite == 'false'
|
if: needs.info.outputs.test_full_suite == 'false'
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
python --version
|
python --version
|
||||||
mypy homeassistant/components/${{ needs.changes.outputs.integrations_glob }}
|
mypy homeassistant/components/${{ needs.info.outputs.integrations_glob }}
|
||||||
|
|
||||||
pip-check:
|
pip-check:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-20.04
|
||||||
if: needs.changes.outputs.requirements == 'true' || github.event.inputs.full == 'true'
|
|
||||||
needs:
|
needs:
|
||||||
- changes
|
- info
|
||||||
- prepare-tests
|
- base
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
python-version: [3.9]
|
python-version: ["3.9", "3.10"]
|
||||||
name: Run pip check ${{ matrix.python-version }}
|
name: Run pip check ${{ matrix.python-version }}
|
||||||
container: homeassistant/ci-azure:${{ matrix.python-version }}
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v3.0.2
|
uses: actions/checkout@v3.0.2
|
||||||
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
|
id: python
|
||||||
|
uses: actions/setup-python@v4.1.0
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python-version }}
|
||||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache@v3.0.4
|
uses: actions/cache@v3.0.5
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
key: ${{ runner.os }}-${{ matrix.python-version }}-${{
|
key: >-
|
||||||
needs.prepare-tests.outputs.python-key }}
|
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
||||||
|
needs.info.outputs.python_cache_key }}
|
||||||
- name: Fail job if Python cache restore failed
|
- name: Fail job if Python cache restore failed
|
||||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||||
run: |
|
run: |
|
||||||
@ -732,38 +712,48 @@ jobs:
|
|||||||
./script/pip_check $PIP_CACHE
|
./script/pip_check $PIP_CACHE
|
||||||
|
|
||||||
pytest:
|
pytest:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-20.04
|
||||||
if: |
|
if: |
|
||||||
(github.event_name != 'push' || github.event.repository.full_name == 'home-assistant/core')
|
(github.event_name != 'push' || github.event.repository.full_name == 'home-assistant/core')
|
||||||
&& github.event.inputs.lint-only != 'true'
|
&& github.event.inputs.lint-only != 'true'
|
||||||
&& (needs.changes.outputs.test_full_suite == 'true' || needs.changes.outputs.tests_glob)
|
&& (needs.info.outputs.test_full_suite == 'true' || needs.info.outputs.tests_glob)
|
||||||
needs:
|
needs:
|
||||||
- changes
|
- info
|
||||||
|
- base
|
||||||
- gen-requirements-all
|
- gen-requirements-all
|
||||||
- hassfest
|
- hassfest
|
||||||
- lint-black
|
- lint-black
|
||||||
- lint-other
|
- lint-other
|
||||||
- lint-isort
|
- lint-isort
|
||||||
- mypy
|
- mypy
|
||||||
- prepare-tests
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
group: ${{ fromJson(needs.changes.outputs.test_groups) }}
|
group: ${{ fromJson(needs.info.outputs.test_groups) }}
|
||||||
python-version: ["3.9", "3.10"]
|
python-version: ["3.9", "3.10"]
|
||||||
name: >-
|
name: >-
|
||||||
Run tests Python ${{ matrix.python-version }} (${{ matrix.group }})
|
Run tests Python ${{ matrix.python-version }} (${{ matrix.group }})
|
||||||
container: homeassistant/ci-azure:${{ matrix.python-version }}
|
|
||||||
steps:
|
steps:
|
||||||
|
- name: Install additional OS dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get -y install \
|
||||||
|
bluez \
|
||||||
|
ffmpeg
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v3.0.2
|
uses: actions/checkout@v3.0.2
|
||||||
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
|
id: python
|
||||||
|
uses: actions/setup-python@v4.1.0
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python-version }}
|
||||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache@v3.0.4
|
uses: actions/cache@v3.0.5
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
key: ${{ runner.os }}-${{ matrix.python-version }}-${{
|
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
||||||
needs.prepare-tests.outputs.python-key }}
|
needs.info.outputs.python_cache_key }}
|
||||||
- name: Fail job if Python cache restore failed
|
- name: Fail job if Python cache restore failed
|
||||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||||
run: |
|
run: |
|
||||||
@ -783,7 +773,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
echo "::add-matcher::.github/workflows/matchers/pytest-slow.json"
|
echo "::add-matcher::.github/workflows/matchers/pytest-slow.json"
|
||||||
- name: Run pytest (fully)
|
- name: Run pytest (fully)
|
||||||
if: needs.changes.outputs.test_full_suite == 'true'
|
if: needs.info.outputs.test_full_suite == 'true'
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
run: |
|
run: |
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
@ -794,7 +784,7 @@ jobs:
|
|||||||
--durations=10 \
|
--durations=10 \
|
||||||
-n auto \
|
-n auto \
|
||||||
--dist=loadfile \
|
--dist=loadfile \
|
||||||
--test-group-count ${{ needs.changes.outputs.test_group_count }} \
|
--test-group-count ${{ needs.info.outputs.test_group_count }} \
|
||||||
--test-group=${{ matrix.group }} \
|
--test-group=${{ matrix.group }} \
|
||||||
--cov="homeassistant" \
|
--cov="homeassistant" \
|
||||||
--cov-report=xml \
|
--cov-report=xml \
|
||||||
@ -802,8 +792,8 @@ jobs:
|
|||||||
-p no:sugar \
|
-p no:sugar \
|
||||||
tests
|
tests
|
||||||
- name: Run pytest (partially)
|
- name: Run pytest (partially)
|
||||||
if: needs.changes.outputs.test_full_suite == 'false'
|
if: needs.info.outputs.test_full_suite == 'false'
|
||||||
timeout-minutes: 20
|
timeout-minutes: 10
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
@ -838,9 +828,9 @@ jobs:
|
|||||||
|
|
||||||
coverage:
|
coverage:
|
||||||
name: Upload test coverage to Codecov
|
name: Upload test coverage to Codecov
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-20.04
|
||||||
needs:
|
needs:
|
||||||
- changes
|
- info
|
||||||
- pytest
|
- pytest
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
@ -848,10 +838,10 @@ jobs:
|
|||||||
- name: Download all coverage artifacts
|
- name: Download all coverage artifacts
|
||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@v3
|
||||||
- name: Upload coverage to Codecov (full coverage)
|
- name: Upload coverage to Codecov (full coverage)
|
||||||
if: needs.changes.outputs.test_full_suite == 'true'
|
if: needs.info.outputs.test_full_suite == 'true'
|
||||||
uses: codecov/codecov-action@v3.1.0
|
uses: codecov/codecov-action@v3.1.0
|
||||||
with:
|
with:
|
||||||
flags: full-suite
|
flags: full-suite
|
||||||
- name: Upload coverage to Codecov (partial coverage)
|
- name: Upload coverage to Codecov (partial coverage)
|
||||||
if: needs.changes.outputs.test_full_suite == 'false'
|
if: needs.info.outputs.test_full_suite == 'false'
|
||||||
uses: codecov/codecov-action@v3.1.0
|
uses: codecov/codecov-action@v3.1.0
|
||||||
|
4
.github/workflows/translations.yaml
vendored
4
.github/workflows/translations.yaml
vendored
@ -24,7 +24,7 @@ jobs:
|
|||||||
uses: actions/checkout@v3.0.2
|
uses: actions/checkout@v3.0.2
|
||||||
|
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
uses: actions/setup-python@v4.0.0
|
uses: actions/setup-python@v4.1.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
|
|
||||||
@ -43,7 +43,7 @@ jobs:
|
|||||||
uses: actions/checkout@v3.0.2
|
uses: actions/checkout@v3.0.2
|
||||||
|
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
uses: actions/setup-python@v4.0.0
|
uses: actions/setup-python@v4.1.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
|
|
||||||
|
3
.github/workflows/wheels.yml
vendored
3
.github/workflows/wheels.yml
vendored
@ -157,6 +157,9 @@ jobs:
|
|||||||
echo "cmake==3.22.2"
|
echo "cmake==3.22.2"
|
||||||
) >> homeassistant/package_constraints.txt
|
) >> homeassistant/package_constraints.txt
|
||||||
|
|
||||||
|
# Do not pin numpy in wheels building
|
||||||
|
sed -i "/numpy/d" homeassistant/package_constraints.txt
|
||||||
|
|
||||||
- name: Build wheels
|
- name: Build wheels
|
||||||
uses: home-assistant/wheels@2022.06.7
|
uses: home-assistant/wheels@2022.06.7
|
||||||
with:
|
with:
|
||||||
|
6
.ignore
6
.ignore
@ -1,6 +0,0 @@
|
|||||||
# Patterns matched in this file will be ignored by supported search utilities
|
|
||||||
|
|
||||||
# Ignore generated html and javascript files
|
|
||||||
/homeassistant/components/frontend/www_static/*.html
|
|
||||||
/homeassistant/components/frontend/www_static/*.js
|
|
||||||
/homeassistant/components/frontend/www_static/panels/*.html
|
|
@ -1,11 +1,11 @@
|
|||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/asottile/pyupgrade
|
- repo: https://github.com/asottile/pyupgrade
|
||||||
rev: v2.34.0
|
rev: v2.37.2
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyupgrade
|
- id: pyupgrade
|
||||||
args: [--py39-plus]
|
args: [--py39-plus]
|
||||||
- repo: https://github.com/psf/black
|
- repo: https://github.com/psf/black
|
||||||
rev: 22.3.0
|
rev: 22.6.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
args:
|
args:
|
||||||
@ -21,7 +21,7 @@ repos:
|
|||||||
- --skip="./.*,*.csv,*.json"
|
- --skip="./.*,*.csv,*.json"
|
||||||
- --quiet-level=2
|
- --quiet-level=2
|
||||||
exclude_types: [csv, json]
|
exclude_types: [csv, json]
|
||||||
exclude: ^tests/fixtures/
|
exclude: ^tests/fixtures/|homeassistant/generated/
|
||||||
- repo: https://github.com/PyCQA/flake8
|
- repo: https://github.com/PyCQA/flake8
|
||||||
rev: 4.0.1
|
rev: 4.0.1
|
||||||
hooks:
|
hooks:
|
||||||
@ -31,8 +31,8 @@ repos:
|
|||||||
- pyflakes==2.4.0
|
- pyflakes==2.4.0
|
||||||
- flake8-docstrings==1.6.0
|
- flake8-docstrings==1.6.0
|
||||||
- pydocstyle==6.1.1
|
- pydocstyle==6.1.1
|
||||||
- flake8-comprehensions==3.8.0
|
- flake8-comprehensions==3.10.0
|
||||||
- flake8-noqa==1.2.1
|
- flake8-noqa==1.2.5
|
||||||
- mccabe==0.6.1
|
- mccabe==0.6.1
|
||||||
files: ^(homeassistant|script|tests)/.+\.py$
|
files: ^(homeassistant|script|tests)/.+\.py$
|
||||||
- repo: https://github.com/PyCQA/bandit
|
- repo: https://github.com/PyCQA/bandit
|
||||||
@ -61,7 +61,7 @@ repos:
|
|||||||
- --branch=master
|
- --branch=master
|
||||||
- --branch=rc
|
- --branch=rc
|
||||||
- repo: https://github.com/adrienverge/yamllint.git
|
- repo: https://github.com/adrienverge/yamllint.git
|
||||||
rev: v1.26.3
|
rev: v1.27.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: yamllint
|
- id: yamllint
|
||||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||||
@ -96,7 +96,7 @@ repos:
|
|||||||
files: ^(homeassistant|pylint)/.+\.py$
|
files: ^(homeassistant|pylint)/.+\.py$
|
||||||
- id: pylint
|
- id: pylint
|
||||||
name: pylint
|
name: pylint
|
||||||
entry: script/run-in-env.sh pylint -j 0
|
entry: script/run-in-env.sh pylint -j 0 --ignore-missing-annotations=y
|
||||||
language: script
|
language: script
|
||||||
types: [python]
|
types: [python]
|
||||||
files: ^homeassistant/.+\.py$
|
files: ^homeassistant/.+\.py$
|
||||||
|
@ -15,12 +15,17 @@ homeassistant.auth.auth_store
|
|||||||
homeassistant.auth.providers.*
|
homeassistant.auth.providers.*
|
||||||
homeassistant.helpers.area_registry
|
homeassistant.helpers.area_registry
|
||||||
homeassistant.helpers.condition
|
homeassistant.helpers.condition
|
||||||
|
homeassistant.helpers.debounce
|
||||||
|
homeassistant.helpers.deprecation
|
||||||
homeassistant.helpers.discovery
|
homeassistant.helpers.discovery
|
||||||
|
homeassistant.helpers.dispatcher
|
||||||
homeassistant.helpers.entity
|
homeassistant.helpers.entity
|
||||||
|
homeassistant.helpers.entity_platform
|
||||||
homeassistant.helpers.entity_values
|
homeassistant.helpers.entity_values
|
||||||
homeassistant.helpers.event
|
homeassistant.helpers.event
|
||||||
homeassistant.helpers.reload
|
homeassistant.helpers.reload
|
||||||
homeassistant.helpers.script_variables
|
homeassistant.helpers.script_variables
|
||||||
|
homeassistant.helpers.singleton
|
||||||
homeassistant.helpers.sun
|
homeassistant.helpers.sun
|
||||||
homeassistant.helpers.translation
|
homeassistant.helpers.translation
|
||||||
homeassistant.util.async_
|
homeassistant.util.async_
|
||||||
@ -57,6 +62,7 @@ homeassistant.components.automation.*
|
|||||||
homeassistant.components.backup.*
|
homeassistant.components.backup.*
|
||||||
homeassistant.components.baf.*
|
homeassistant.components.baf.*
|
||||||
homeassistant.components.binary_sensor.*
|
homeassistant.components.binary_sensor.*
|
||||||
|
homeassistant.components.bluetooth.*
|
||||||
homeassistant.components.bluetooth_tracker.*
|
homeassistant.components.bluetooth_tracker.*
|
||||||
homeassistant.components.bmw_connected_drive.*
|
homeassistant.components.bmw_connected_drive.*
|
||||||
homeassistant.components.bond.*
|
homeassistant.components.bond.*
|
||||||
@ -109,6 +115,7 @@ homeassistant.components.group.*
|
|||||||
homeassistant.components.guardian.*
|
homeassistant.components.guardian.*
|
||||||
homeassistant.components.history.*
|
homeassistant.components.history.*
|
||||||
homeassistant.components.homeassistant.triggers.event
|
homeassistant.components.homeassistant.triggers.event
|
||||||
|
homeassistant.components.homeassistant_alerts.*
|
||||||
homeassistant.components.homekit
|
homeassistant.components.homekit
|
||||||
homeassistant.components.homekit.accessories
|
homeassistant.components.homekit.accessories
|
||||||
homeassistant.components.homekit.aidmanager
|
homeassistant.components.homekit.aidmanager
|
||||||
@ -145,6 +152,8 @@ homeassistant.components.lametric.*
|
|||||||
homeassistant.components.laundrify.*
|
homeassistant.components.laundrify.*
|
||||||
homeassistant.components.lcn.*
|
homeassistant.components.lcn.*
|
||||||
homeassistant.components.light.*
|
homeassistant.components.light.*
|
||||||
|
homeassistant.components.lifx.*
|
||||||
|
homeassistant.components.litterrobot.*
|
||||||
homeassistant.components.local_ip.*
|
homeassistant.components.local_ip.*
|
||||||
homeassistant.components.lock.*
|
homeassistant.components.lock.*
|
||||||
homeassistant.components.logbook.*
|
homeassistant.components.logbook.*
|
||||||
@ -153,6 +162,7 @@ homeassistant.components.luftdaten.*
|
|||||||
homeassistant.components.mailbox.*
|
homeassistant.components.mailbox.*
|
||||||
homeassistant.components.media_player.*
|
homeassistant.components.media_player.*
|
||||||
homeassistant.components.media_source.*
|
homeassistant.components.media_source.*
|
||||||
|
homeassistant.components.metoffice.*
|
||||||
homeassistant.components.mjpeg.*
|
homeassistant.components.mjpeg.*
|
||||||
homeassistant.components.modbus.*
|
homeassistant.components.modbus.*
|
||||||
homeassistant.components.modem_callerid.*
|
homeassistant.components.modem_callerid.*
|
||||||
@ -190,6 +200,8 @@ homeassistant.components.recollect_waste.*
|
|||||||
homeassistant.components.recorder.*
|
homeassistant.components.recorder.*
|
||||||
homeassistant.components.remote.*
|
homeassistant.components.remote.*
|
||||||
homeassistant.components.renault.*
|
homeassistant.components.renault.*
|
||||||
|
homeassistant.components.repairs.*
|
||||||
|
homeassistant.components.rhasspy.*
|
||||||
homeassistant.components.ridwell.*
|
homeassistant.components.ridwell.*
|
||||||
homeassistant.components.rituals_perfume_genie.*
|
homeassistant.components.rituals_perfume_genie.*
|
||||||
homeassistant.components.roku.*
|
homeassistant.components.roku.*
|
||||||
|
8
.vscode/launch.json
vendored
8
.vscode/launch.json
vendored
@ -12,6 +12,14 @@
|
|||||||
"justMyCode": false,
|
"justMyCode": false,
|
||||||
"args": ["--debug", "-c", "config"]
|
"args": ["--debug", "-c", "config"]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Home Assistant (skip pip)",
|
||||||
|
"type": "python",
|
||||||
|
"request": "launch",
|
||||||
|
"module": "homeassistant",
|
||||||
|
"justMyCode": false,
|
||||||
|
"args": ["--debug", "-c", "config", "--skip-pip"]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
// Debug by attaching to local Home Asistant server using Remote Python Debugger.
|
// Debug by attaching to local Home Asistant server using Remote Python Debugger.
|
||||||
// See https://www.home-assistant.io/integrations/debugpy/
|
// See https://www.home-assistant.io/integrations/debugpy/
|
||||||
|
41
CODEOWNERS
41
CODEOWNERS
@ -74,6 +74,8 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/analytics/ @home-assistant/core @ludeeus
|
/tests/components/analytics/ @home-assistant/core @ludeeus
|
||||||
/homeassistant/components/androidtv/ @JeffLIrion @ollo69
|
/homeassistant/components/androidtv/ @JeffLIrion @ollo69
|
||||||
/tests/components/androidtv/ @JeffLIrion @ollo69
|
/tests/components/androidtv/ @JeffLIrion @ollo69
|
||||||
|
/homeassistant/components/anthemav/ @hyralex
|
||||||
|
/tests/components/anthemav/ @hyralex
|
||||||
/homeassistant/components/apache_kafka/ @bachya
|
/homeassistant/components/apache_kafka/ @bachya
|
||||||
/tests/components/apache_kafka/ @bachya
|
/tests/components/apache_kafka/ @bachya
|
||||||
/homeassistant/components/api/ @home-assistant/core
|
/homeassistant/components/api/ @home-assistant/core
|
||||||
@ -136,6 +138,8 @@ build.json @home-assistant/supervisor
|
|||||||
/homeassistant/components/blueprint/ @home-assistant/core
|
/homeassistant/components/blueprint/ @home-assistant/core
|
||||||
/tests/components/blueprint/ @home-assistant/core
|
/tests/components/blueprint/ @home-assistant/core
|
||||||
/homeassistant/components/bluesound/ @thrawnarn
|
/homeassistant/components/bluesound/ @thrawnarn
|
||||||
|
/homeassistant/components/bluetooth/ @bdraco
|
||||||
|
/tests/components/bluetooth/ @bdraco
|
||||||
/homeassistant/components/bmw_connected_drive/ @gerard33 @rikroe
|
/homeassistant/components/bmw_connected_drive/ @gerard33 @rikroe
|
||||||
/tests/components/bmw_connected_drive/ @gerard33 @rikroe
|
/tests/components/bmw_connected_drive/ @gerard33 @rikroe
|
||||||
/homeassistant/components/bond/ @bdraco @prystupa @joshs85 @marciogranzotto
|
/homeassistant/components/bond/ @bdraco @prystupa @joshs85 @marciogranzotto
|
||||||
@ -407,6 +411,8 @@ build.json @home-assistant/supervisor
|
|||||||
/homeassistant/components/google_cloud/ @lufton
|
/homeassistant/components/google_cloud/ @lufton
|
||||||
/homeassistant/components/google_travel_time/ @eifinger
|
/homeassistant/components/google_travel_time/ @eifinger
|
||||||
/tests/components/google_travel_time/ @eifinger
|
/tests/components/google_travel_time/ @eifinger
|
||||||
|
/homeassistant/components/govee_ble/ @bdraco
|
||||||
|
/tests/components/govee_ble/ @bdraco
|
||||||
/homeassistant/components/gpsd/ @fabaff
|
/homeassistant/components/gpsd/ @fabaff
|
||||||
/homeassistant/components/gree/ @cmroche
|
/homeassistant/components/gree/ @cmroche
|
||||||
/tests/components/gree/ @cmroche
|
/tests/components/gree/ @cmroche
|
||||||
@ -449,6 +455,8 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/home_plus_control/ @chemaaa
|
/tests/components/home_plus_control/ @chemaaa
|
||||||
/homeassistant/components/homeassistant/ @home-assistant/core
|
/homeassistant/components/homeassistant/ @home-assistant/core
|
||||||
/tests/components/homeassistant/ @home-assistant/core
|
/tests/components/homeassistant/ @home-assistant/core
|
||||||
|
/homeassistant/components/homeassistant_alerts/ @home-assistant/core
|
||||||
|
/tests/components/homeassistant_alerts/ @home-assistant/core
|
||||||
/homeassistant/components/homeassistant_yellow/ @home-assistant/core
|
/homeassistant/components/homeassistant_yellow/ @home-assistant/core
|
||||||
/tests/components/homeassistant_yellow/ @home-assistant/core
|
/tests/components/homeassistant_yellow/ @home-assistant/core
|
||||||
/homeassistant/components/homekit/ @bdraco
|
/homeassistant/components/homekit/ @bdraco
|
||||||
@ -494,6 +502,8 @@ build.json @home-assistant/supervisor
|
|||||||
/homeassistant/components/incomfort/ @zxdavb
|
/homeassistant/components/incomfort/ @zxdavb
|
||||||
/homeassistant/components/influxdb/ @mdegat01
|
/homeassistant/components/influxdb/ @mdegat01
|
||||||
/tests/components/influxdb/ @mdegat01
|
/tests/components/influxdb/ @mdegat01
|
||||||
|
/homeassistant/components/inkbird/ @bdraco
|
||||||
|
/tests/components/inkbird/ @bdraco
|
||||||
/homeassistant/components/input_boolean/ @home-assistant/core
|
/homeassistant/components/input_boolean/ @home-assistant/core
|
||||||
/tests/components/input_boolean/ @home-assistant/core
|
/tests/components/input_boolean/ @home-assistant/core
|
||||||
/homeassistant/components/input_button/ @home-assistant/core
|
/homeassistant/components/input_button/ @home-assistant/core
|
||||||
@ -573,7 +583,8 @@ build.json @home-assistant/supervisor
|
|||||||
/homeassistant/components/lg_netcast/ @Drafteed
|
/homeassistant/components/lg_netcast/ @Drafteed
|
||||||
/homeassistant/components/life360/ @pnbruckner
|
/homeassistant/components/life360/ @pnbruckner
|
||||||
/tests/components/life360/ @pnbruckner
|
/tests/components/life360/ @pnbruckner
|
||||||
/homeassistant/components/lifx/ @Djelibeybi
|
/homeassistant/components/lifx/ @bdraco @Djelibeybi
|
||||||
|
/tests/components/lifx/ @bdraco @Djelibeybi
|
||||||
/homeassistant/components/light/ @home-assistant/core
|
/homeassistant/components/light/ @home-assistant/core
|
||||||
/tests/components/light/ @home-assistant/core
|
/tests/components/light/ @home-assistant/core
|
||||||
/homeassistant/components/linux_battery/ @fabaff
|
/homeassistant/components/linux_battery/ @fabaff
|
||||||
@ -628,8 +639,8 @@ build.json @home-assistant/supervisor
|
|||||||
/homeassistant/components/meteoalarm/ @rolfberkenbosch
|
/homeassistant/components/meteoalarm/ @rolfberkenbosch
|
||||||
/homeassistant/components/meteoclimatic/ @adrianmo
|
/homeassistant/components/meteoclimatic/ @adrianmo
|
||||||
/tests/components/meteoclimatic/ @adrianmo
|
/tests/components/meteoclimatic/ @adrianmo
|
||||||
/homeassistant/components/metoffice/ @MrHarcombe
|
/homeassistant/components/metoffice/ @MrHarcombe @avee87
|
||||||
/tests/components/metoffice/ @MrHarcombe
|
/tests/components/metoffice/ @MrHarcombe @avee87
|
||||||
/homeassistant/components/miflora/ @danielhiversen @basnijholt
|
/homeassistant/components/miflora/ @danielhiversen @basnijholt
|
||||||
/homeassistant/components/mikrotik/ @engrbm87
|
/homeassistant/components/mikrotik/ @engrbm87
|
||||||
/tests/components/mikrotik/ @engrbm87
|
/tests/components/mikrotik/ @engrbm87
|
||||||
@ -641,6 +652,8 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/minecraft_server/ @elmurato
|
/tests/components/minecraft_server/ @elmurato
|
||||||
/homeassistant/components/minio/ @tkislan
|
/homeassistant/components/minio/ @tkislan
|
||||||
/tests/components/minio/ @tkislan
|
/tests/components/minio/ @tkislan
|
||||||
|
/homeassistant/components/moat/ @bdraco
|
||||||
|
/tests/components/moat/ @bdraco
|
||||||
/homeassistant/components/mobile_app/ @home-assistant/core
|
/homeassistant/components/mobile_app/ @home-assistant/core
|
||||||
/tests/components/mobile_app/ @home-assistant/core
|
/tests/components/mobile_app/ @home-assistant/core
|
||||||
/homeassistant/components/modbus/ @adamchengtkc @janiversen @vzahradnik
|
/homeassistant/components/modbus/ @adamchengtkc @janiversen @vzahradnik
|
||||||
@ -696,6 +709,8 @@ build.json @home-assistant/supervisor
|
|||||||
/homeassistant/components/nextbus/ @vividboarder
|
/homeassistant/components/nextbus/ @vividboarder
|
||||||
/tests/components/nextbus/ @vividboarder
|
/tests/components/nextbus/ @vividboarder
|
||||||
/homeassistant/components/nextcloud/ @meichthys
|
/homeassistant/components/nextcloud/ @meichthys
|
||||||
|
/homeassistant/components/nextdns/ @bieniu
|
||||||
|
/tests/components/nextdns/ @bieniu
|
||||||
/homeassistant/components/nfandroidtv/ @tkdrob
|
/homeassistant/components/nfandroidtv/ @tkdrob
|
||||||
/tests/components/nfandroidtv/ @tkdrob
|
/tests/components/nfandroidtv/ @tkdrob
|
||||||
/homeassistant/components/nightscout/ @marciogranzotto
|
/homeassistant/components/nightscout/ @marciogranzotto
|
||||||
@ -852,11 +867,15 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/remote/ @home-assistant/core
|
/tests/components/remote/ @home-assistant/core
|
||||||
/homeassistant/components/renault/ @epenet
|
/homeassistant/components/renault/ @epenet
|
||||||
/tests/components/renault/ @epenet
|
/tests/components/renault/ @epenet
|
||||||
|
/homeassistant/components/repairs/ @home-assistant/core
|
||||||
|
/tests/components/repairs/ @home-assistant/core
|
||||||
/homeassistant/components/repetier/ @MTrab @ShadowBr0ther
|
/homeassistant/components/repetier/ @MTrab @ShadowBr0ther
|
||||||
/homeassistant/components/rflink/ @javicalle
|
/homeassistant/components/rflink/ @javicalle
|
||||||
/tests/components/rflink/ @javicalle
|
/tests/components/rflink/ @javicalle
|
||||||
/homeassistant/components/rfxtrx/ @danielhiversen @elupus @RobBie1221
|
/homeassistant/components/rfxtrx/ @danielhiversen @elupus @RobBie1221
|
||||||
/tests/components/rfxtrx/ @danielhiversen @elupus @RobBie1221
|
/tests/components/rfxtrx/ @danielhiversen @elupus @RobBie1221
|
||||||
|
/homeassistant/components/rhasspy/ @balloob @synesthesiam
|
||||||
|
/tests/components/rhasspy/ @balloob @synesthesiam
|
||||||
/homeassistant/components/ridwell/ @bachya
|
/homeassistant/components/ridwell/ @bachya
|
||||||
/tests/components/ridwell/ @bachya
|
/tests/components/ridwell/ @bachya
|
||||||
/homeassistant/components/ring/ @balloob
|
/homeassistant/components/ring/ @balloob
|
||||||
@ -911,6 +930,8 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/sensibo/ @andrey-git @gjohansson-ST
|
/tests/components/sensibo/ @andrey-git @gjohansson-ST
|
||||||
/homeassistant/components/sensor/ @home-assistant/core
|
/homeassistant/components/sensor/ @home-assistant/core
|
||||||
/tests/components/sensor/ @home-assistant/core
|
/tests/components/sensor/ @home-assistant/core
|
||||||
|
/homeassistant/components/sensorpush/ @bdraco
|
||||||
|
/tests/components/sensorpush/ @bdraco
|
||||||
/homeassistant/components/sentry/ @dcramer @frenck
|
/homeassistant/components/sentry/ @dcramer @frenck
|
||||||
/tests/components/sentry/ @dcramer @frenck
|
/tests/components/sentry/ @dcramer @frenck
|
||||||
/homeassistant/components/senz/ @milanmeu
|
/homeassistant/components/senz/ @milanmeu
|
||||||
@ -977,6 +998,8 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/songpal/ @rytilahti @shenxn
|
/tests/components/songpal/ @rytilahti @shenxn
|
||||||
/homeassistant/components/sonos/ @cgtobi @jjlawren
|
/homeassistant/components/sonos/ @cgtobi @jjlawren
|
||||||
/tests/components/sonos/ @cgtobi @jjlawren
|
/tests/components/sonos/ @cgtobi @jjlawren
|
||||||
|
/homeassistant/components/soundtouch/ @kroimon
|
||||||
|
/tests/components/soundtouch/ @kroimon
|
||||||
/homeassistant/components/spaceapi/ @fabaff
|
/homeassistant/components/spaceapi/ @fabaff
|
||||||
/tests/components/spaceapi/ @fabaff
|
/tests/components/spaceapi/ @fabaff
|
||||||
/homeassistant/components/speedtestdotnet/ @rohankapoorcom @engrbm87
|
/homeassistant/components/speedtestdotnet/ @rohankapoorcom @engrbm87
|
||||||
@ -1021,11 +1044,11 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/switch/ @home-assistant/core
|
/tests/components/switch/ @home-assistant/core
|
||||||
/homeassistant/components/switch_as_x/ @home-assistant/core
|
/homeassistant/components/switch_as_x/ @home-assistant/core
|
||||||
/tests/components/switch_as_x/ @home-assistant/core
|
/tests/components/switch_as_x/ @home-assistant/core
|
||||||
/homeassistant/components/switchbot/ @danielhiversen @RenierM26
|
/homeassistant/components/switchbot/ @bdraco @danielhiversen @RenierM26 @murtas @Eloston
|
||||||
/tests/components/switchbot/ @danielhiversen @RenierM26
|
/tests/components/switchbot/ @bdraco @danielhiversen @RenierM26 @murtas @Eloston
|
||||||
/homeassistant/components/switcher_kis/ @tomerfi @thecode
|
/homeassistant/components/switcher_kis/ @tomerfi @thecode
|
||||||
/tests/components/switcher_kis/ @tomerfi @thecode
|
/tests/components/switcher_kis/ @tomerfi @thecode
|
||||||
/homeassistant/components/switchmate/ @danielhiversen
|
/homeassistant/components/switchmate/ @danielhiversen @qiz-li
|
||||||
/homeassistant/components/syncthing/ @zhulik
|
/homeassistant/components/syncthing/ @zhulik
|
||||||
/tests/components/syncthing/ @zhulik
|
/tests/components/syncthing/ @zhulik
|
||||||
/homeassistant/components/syncthru/ @nielstron
|
/homeassistant/components/syncthru/ @nielstron
|
||||||
@ -1203,6 +1226,8 @@ build.json @home-assistant/supervisor
|
|||||||
/homeassistant/components/xbox_live/ @MartinHjelmare
|
/homeassistant/components/xbox_live/ @MartinHjelmare
|
||||||
/homeassistant/components/xiaomi_aqara/ @danielhiversen @syssi
|
/homeassistant/components/xiaomi_aqara/ @danielhiversen @syssi
|
||||||
/tests/components/xiaomi_aqara/ @danielhiversen @syssi
|
/tests/components/xiaomi_aqara/ @danielhiversen @syssi
|
||||||
|
/homeassistant/components/xiaomi_ble/ @Jc2k @Ernst79
|
||||||
|
/tests/components/xiaomi_ble/ @Jc2k @Ernst79
|
||||||
/homeassistant/components/xiaomi_miio/ @rytilahti @syssi @starkillerOG @bieniu
|
/homeassistant/components/xiaomi_miio/ @rytilahti @syssi @starkillerOG @bieniu
|
||||||
/tests/components/xiaomi_miio/ @rytilahti @syssi @starkillerOG @bieniu
|
/tests/components/xiaomi_miio/ @rytilahti @syssi @starkillerOG @bieniu
|
||||||
/homeassistant/components/xiaomi_tv/ @simse
|
/homeassistant/components/xiaomi_tv/ @simse
|
||||||
@ -1226,8 +1251,8 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/zeroconf/ @bdraco
|
/tests/components/zeroconf/ @bdraco
|
||||||
/homeassistant/components/zerproc/ @emlove
|
/homeassistant/components/zerproc/ @emlove
|
||||||
/tests/components/zerproc/ @emlove
|
/tests/components/zerproc/ @emlove
|
||||||
/homeassistant/components/zha/ @dmulcahey @adminiuga
|
/homeassistant/components/zha/ @dmulcahey @adminiuga @puddly
|
||||||
/tests/components/zha/ @dmulcahey @adminiuga
|
/tests/components/zha/ @dmulcahey @adminiuga @puddly
|
||||||
/homeassistant/components/zodiac/ @JulienTant
|
/homeassistant/components/zodiac/ @JulienTant
|
||||||
/tests/components/zodiac/ @JulienTant
|
/tests/components/zodiac/ @JulienTant
|
||||||
/homeassistant/components/zone/ @home-assistant/core
|
/homeassistant/components/zone/ @home-assistant/core
|
||||||
|
@ -13,9 +13,12 @@ COPY homeassistant/package_constraints.txt homeassistant/homeassistant/
|
|||||||
RUN \
|
RUN \
|
||||||
pip3 install --no-cache-dir --no-index --only-binary=:all: --find-links "${WHEELS_LINKS}" \
|
pip3 install --no-cache-dir --no-index --only-binary=:all: --find-links "${WHEELS_LINKS}" \
|
||||||
-r homeassistant/requirements.txt --use-deprecated=legacy-resolver
|
-r homeassistant/requirements.txt --use-deprecated=legacy-resolver
|
||||||
COPY requirements_all.txt homeassistant/
|
COPY requirements_all.txt home_assistant_frontend-* homeassistant/
|
||||||
RUN \
|
RUN \
|
||||||
pip3 install --no-cache-dir --no-index --only-binary=:all: --find-links "${WHEELS_LINKS}" \
|
if ls homeassistant/home_assistant_frontend*.whl 1> /dev/null 2>&1; then \
|
||||||
|
pip3 install --no-cache-dir --no-index homeassistant/home_assistant_frontend-*.whl; \
|
||||||
|
fi \
|
||||||
|
&& pip3 install --no-cache-dir --no-index --only-binary=:all: --find-links "${WHEELS_LINKS}" \
|
||||||
-r homeassistant/requirements_all.txt --use-deprecated=legacy-resolver
|
-r homeassistant/requirements_all.txt --use-deprecated=legacy-resolver
|
||||||
|
|
||||||
## Setup Home Assistant Core
|
## Setup Home Assistant Core
|
||||||
|
10
build.yaml
10
build.yaml
@ -1,11 +1,11 @@
|
|||||||
image: homeassistant/{arch}-homeassistant
|
image: homeassistant/{arch}-homeassistant
|
||||||
shadow_repository: ghcr.io/home-assistant
|
shadow_repository: ghcr.io/home-assistant
|
||||||
build_from:
|
build_from:
|
||||||
aarch64: ghcr.io/home-assistant/aarch64-homeassistant-base:2022.06.2
|
aarch64: ghcr.io/home-assistant/aarch64-homeassistant-base:2022.07.0
|
||||||
armhf: ghcr.io/home-assistant/armhf-homeassistant-base:2022.06.2
|
armhf: ghcr.io/home-assistant/armhf-homeassistant-base:2022.07.0
|
||||||
armv7: ghcr.io/home-assistant/armv7-homeassistant-base:2022.06.2
|
armv7: ghcr.io/home-assistant/armv7-homeassistant-base:2022.07.0
|
||||||
amd64: ghcr.io/home-assistant/amd64-homeassistant-base:2022.06.2
|
amd64: ghcr.io/home-assistant/amd64-homeassistant-base:2022.07.0
|
||||||
i386: ghcr.io/home-assistant/i386-homeassistant-base:2022.06.2
|
i386: ghcr.io/home-assistant/i386-homeassistant-base:2022.07.0
|
||||||
codenotary:
|
codenotary:
|
||||||
signer: notary@home-assistant.io
|
signer: notary@home-assistant.io
|
||||||
base_image: notary@home-assistant.io
|
base_image: notary@home-assistant.io
|
||||||
|
@ -46,7 +46,7 @@ class AuthStore:
|
|||||||
self._users: dict[str, models.User] | None = None
|
self._users: dict[str, models.User] | None = None
|
||||||
self._groups: dict[str, models.Group] | None = None
|
self._groups: dict[str, models.Group] | None = None
|
||||||
self._perm_lookup: PermissionLookup | None = None
|
self._perm_lookup: PermissionLookup | None = None
|
||||||
self._store = Store(
|
self._store = Store[dict[str, list[dict[str, Any]]]](
|
||||||
hass, STORAGE_VERSION, STORAGE_KEY, private=True, atomic_writes=True
|
hass, STORAGE_VERSION, STORAGE_KEY, private=True, atomic_writes=True
|
||||||
)
|
)
|
||||||
self._lock = asyncio.Lock()
|
self._lock = asyncio.Lock()
|
||||||
@ -483,9 +483,10 @@ class AuthStore:
|
|||||||
jwt_key=rt_dict["jwt_key"],
|
jwt_key=rt_dict["jwt_key"],
|
||||||
last_used_at=last_used_at,
|
last_used_at=last_used_at,
|
||||||
last_used_ip=rt_dict.get("last_used_ip"),
|
last_used_ip=rt_dict.get("last_used_ip"),
|
||||||
credential=credentials.get(rt_dict.get("credential_id")),
|
|
||||||
version=rt_dict.get("version"),
|
version=rt_dict.get("version"),
|
||||||
)
|
)
|
||||||
|
if "credential_id" in rt_dict:
|
||||||
|
token.credential = credentials.get(rt_dict["credential_id"])
|
||||||
users[rt_dict["user_id"]].refresh_tokens[token.id] = token
|
users[rt_dict["user_id"]].refresh_tokens[token.id] = token
|
||||||
|
|
||||||
self._groups = groups
|
self._groups = groups
|
||||||
|
@ -7,7 +7,7 @@ from __future__ import annotations
|
|||||||
import asyncio
|
import asyncio
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
import logging
|
import logging
|
||||||
from typing import Any
|
from typing import Any, cast
|
||||||
|
|
||||||
import attr
|
import attr
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
@ -100,7 +100,7 @@ class NotifyAuthModule(MultiFactorAuthModule):
|
|||||||
"""Initialize the user data store."""
|
"""Initialize the user data store."""
|
||||||
super().__init__(hass, config)
|
super().__init__(hass, config)
|
||||||
self._user_settings: _UsersDict | None = None
|
self._user_settings: _UsersDict | None = None
|
||||||
self._user_store = Store(
|
self._user_store = Store[dict[str, dict[str, Any]]](
|
||||||
hass, STORAGE_VERSION, STORAGE_KEY, private=True, atomic_writes=True
|
hass, STORAGE_VERSION, STORAGE_KEY, private=True, atomic_writes=True
|
||||||
)
|
)
|
||||||
self._include = config.get(CONF_INCLUDE, [])
|
self._include = config.get(CONF_INCLUDE, [])
|
||||||
@ -119,10 +119,8 @@ class NotifyAuthModule(MultiFactorAuthModule):
|
|||||||
if self._user_settings is not None:
|
if self._user_settings is not None:
|
||||||
return
|
return
|
||||||
|
|
||||||
if (data := await self._user_store.async_load()) is None or not isinstance(
|
if (data := await self._user_store.async_load()) is None:
|
||||||
data, dict
|
data = cast(dict[str, dict[str, Any]], {STORAGE_USERS: {}})
|
||||||
):
|
|
||||||
data = {STORAGE_USERS: {}}
|
|
||||||
|
|
||||||
self._user_settings = {
|
self._user_settings = {
|
||||||
user_id: NotifySetting(**setting)
|
user_id: NotifySetting(**setting)
|
||||||
@ -322,6 +320,7 @@ class NotifySetupFlow(SetupFlow):
|
|||||||
errors: dict[str, str] = {}
|
errors: dict[str, str] = {}
|
||||||
|
|
||||||
hass = self._auth_module.hass
|
hass = self._auth_module.hass
|
||||||
|
assert self._secret and self._count
|
||||||
if user_input:
|
if user_input:
|
||||||
verified = await hass.async_add_executor_job(
|
verified = await hass.async_add_executor_job(
|
||||||
_verify_otp, self._secret, user_input["code"], self._count
|
_verify_otp, self._secret, user_input["code"], self._count
|
||||||
@ -336,7 +335,6 @@ class NotifySetupFlow(SetupFlow):
|
|||||||
errors["base"] = "invalid_code"
|
errors["base"] = "invalid_code"
|
||||||
|
|
||||||
# generate code every time, no retry logic
|
# generate code every time, no retry logic
|
||||||
assert self._secret and self._count
|
|
||||||
code = await hass.async_add_executor_job(
|
code = await hass.async_add_executor_job(
|
||||||
_generate_otp, self._secret, self._count
|
_generate_otp, self._secret, self._count
|
||||||
)
|
)
|
||||||
|
@ -3,7 +3,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from typing import Any
|
from typing import Any, cast
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
@ -77,7 +77,7 @@ class TotpAuthModule(MultiFactorAuthModule):
|
|||||||
"""Initialize the user data store."""
|
"""Initialize the user data store."""
|
||||||
super().__init__(hass, config)
|
super().__init__(hass, config)
|
||||||
self._users: dict[str, str] | None = None
|
self._users: dict[str, str] | None = None
|
||||||
self._user_store = Store(
|
self._user_store = Store[dict[str, dict[str, str]]](
|
||||||
hass, STORAGE_VERSION, STORAGE_KEY, private=True, atomic_writes=True
|
hass, STORAGE_VERSION, STORAGE_KEY, private=True, atomic_writes=True
|
||||||
)
|
)
|
||||||
self._init_lock = asyncio.Lock()
|
self._init_lock = asyncio.Lock()
|
||||||
@ -93,16 +93,14 @@ class TotpAuthModule(MultiFactorAuthModule):
|
|||||||
if self._users is not None:
|
if self._users is not None:
|
||||||
return
|
return
|
||||||
|
|
||||||
if (data := await self._user_store.async_load()) is None or not isinstance(
|
if (data := await self._user_store.async_load()) is None:
|
||||||
data, dict
|
data = cast(dict[str, dict[str, str]], {STORAGE_USERS: {}})
|
||||||
):
|
|
||||||
data = {STORAGE_USERS: {}}
|
|
||||||
|
|
||||||
self._users = data.get(STORAGE_USERS, {})
|
self._users = data.get(STORAGE_USERS, {})
|
||||||
|
|
||||||
async def _async_save(self) -> None:
|
async def _async_save(self) -> None:
|
||||||
"""Save data."""
|
"""Save data."""
|
||||||
await self._user_store.async_save({STORAGE_USERS: self._users})
|
await self._user_store.async_save({STORAGE_USERS: self._users or {}})
|
||||||
|
|
||||||
def _add_ota_secret(self, user_id: str, secret: str | None = None) -> str:
|
def _add_ota_secret(self, user_id: str, secret: str | None = None) -> str:
|
||||||
"""Create a ota_secret for user."""
|
"""Create a ota_secret for user."""
|
||||||
|
@ -61,10 +61,10 @@ class Data:
|
|||||||
def __init__(self, hass: HomeAssistant) -> None:
|
def __init__(self, hass: HomeAssistant) -> None:
|
||||||
"""Initialize the user data store."""
|
"""Initialize the user data store."""
|
||||||
self.hass = hass
|
self.hass = hass
|
||||||
self._store = Store(
|
self._store = Store[dict[str, list[dict[str, str]]]](
|
||||||
hass, STORAGE_VERSION, STORAGE_KEY, private=True, atomic_writes=True
|
hass, STORAGE_VERSION, STORAGE_KEY, private=True, atomic_writes=True
|
||||||
)
|
)
|
||||||
self._data: dict[str, Any] | None = None
|
self._data: dict[str, list[dict[str, str]]] | None = None
|
||||||
# Legacy mode will allow usernames to start/end with whitespace
|
# Legacy mode will allow usernames to start/end with whitespace
|
||||||
# and will compare usernames case-insensitive.
|
# and will compare usernames case-insensitive.
|
||||||
# Remove in 2020 or when we launch 1.0.
|
# Remove in 2020 or when we launch 1.0.
|
||||||
@ -80,10 +80,8 @@ class Data:
|
|||||||
|
|
||||||
async def async_load(self) -> None:
|
async def async_load(self) -> None:
|
||||||
"""Load stored data."""
|
"""Load stored data."""
|
||||||
if (data := await self._store.async_load()) is None or not isinstance(
|
if (data := await self._store.async_load()) is None:
|
||||||
data, dict
|
data = cast(dict[str, list[dict[str, str]]], {"users": []})
|
||||||
):
|
|
||||||
data = {"users": []}
|
|
||||||
|
|
||||||
seen: set[str] = set()
|
seen: set[str] = set()
|
||||||
|
|
||||||
@ -123,7 +121,8 @@ class Data:
|
|||||||
@property
|
@property
|
||||||
def users(self) -> list[dict[str, str]]:
|
def users(self) -> list[dict[str, str]]:
|
||||||
"""Return users."""
|
"""Return users."""
|
||||||
return self._data["users"] # type: ignore[index,no-any-return]
|
assert self._data is not None
|
||||||
|
return self._data["users"]
|
||||||
|
|
||||||
def validate_login(self, username: str, password: str) -> None:
|
def validate_login(self, username: str, password: str) -> None:
|
||||||
"""Validate a username and password.
|
"""Validate a username and password.
|
||||||
|
@ -4,15 +4,15 @@ from __future__ import annotations
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Any, TypeVar
|
from typing import Any, TypeVar
|
||||||
|
|
||||||
_StrEnumT = TypeVar("_StrEnumT", bound="StrEnum")
|
_StrEnumSelfT = TypeVar("_StrEnumSelfT", bound="StrEnum")
|
||||||
|
|
||||||
|
|
||||||
class StrEnum(str, Enum):
|
class StrEnum(str, Enum):
|
||||||
"""Partial backport of Python 3.11's StrEnum for our basic use cases."""
|
"""Partial backport of Python 3.11's StrEnum for our basic use cases."""
|
||||||
|
|
||||||
def __new__(
|
def __new__(
|
||||||
cls: type[_StrEnumT], value: str, *args: Any, **kwargs: Any
|
cls: type[_StrEnumSelfT], value: str, *args: Any, **kwargs: Any
|
||||||
) -> _StrEnumT:
|
) -> _StrEnumSelfT:
|
||||||
"""Create a new StrEnum instance."""
|
"""Create a new StrEnum instance."""
|
||||||
if not isinstance(value, str):
|
if not isinstance(value, str):
|
||||||
raise TypeError(f"{value!r} is not a string")
|
raise TypeError(f"{value!r} is not a string")
|
||||||
|
@ -24,7 +24,7 @@ from .const import (
|
|||||||
SIGNAL_BOOTSTRAP_INTEGRATONS,
|
SIGNAL_BOOTSTRAP_INTEGRATONS,
|
||||||
)
|
)
|
||||||
from .exceptions import HomeAssistantError
|
from .exceptions import HomeAssistantError
|
||||||
from .helpers import area_registry, device_registry, entity_registry
|
from .helpers import area_registry, device_registry, entity_registry, recorder
|
||||||
from .helpers.dispatcher import async_dispatcher_send
|
from .helpers.dispatcher import async_dispatcher_send
|
||||||
from .helpers.typing import ConfigType
|
from .helpers.typing import ConfigType
|
||||||
from .setup import (
|
from .setup import (
|
||||||
@ -35,7 +35,6 @@ from .setup import (
|
|||||||
async_setup_component,
|
async_setup_component,
|
||||||
)
|
)
|
||||||
from .util import dt as dt_util
|
from .util import dt as dt_util
|
||||||
from .util.async_ import gather_with_concurrency
|
|
||||||
from .util.logging import async_activate_log_queue_handler
|
from .util.logging import async_activate_log_queue_handler
|
||||||
from .util.package import async_get_user_site, is_virtual_env
|
from .util.package import async_get_user_site, is_virtual_env
|
||||||
|
|
||||||
@ -67,10 +66,19 @@ LOGGING_INTEGRATIONS = {
|
|||||||
# Error logging
|
# Error logging
|
||||||
"system_log",
|
"system_log",
|
||||||
"sentry",
|
"sentry",
|
||||||
|
}
|
||||||
|
FRONTEND_INTEGRATIONS = {
|
||||||
|
# Get the frontend up and running as soon as possible so problem
|
||||||
|
# integrations can be removed and database migration status is
|
||||||
|
# visible in frontend
|
||||||
|
"frontend",
|
||||||
|
}
|
||||||
|
RECORDER_INTEGRATIONS = {
|
||||||
|
# Setup after frontend
|
||||||
# To record data
|
# To record data
|
||||||
"recorder",
|
"recorder",
|
||||||
}
|
}
|
||||||
DISCOVERY_INTEGRATIONS = ("dhcp", "ssdp", "usb", "zeroconf")
|
DISCOVERY_INTEGRATIONS = ("bluetooth", "dhcp", "ssdp", "usb", "zeroconf")
|
||||||
STAGE_1_INTEGRATIONS = {
|
STAGE_1_INTEGRATIONS = {
|
||||||
# We need to make sure discovery integrations
|
# We need to make sure discovery integrations
|
||||||
# update their deps before stage 2 integrations
|
# update their deps before stage 2 integrations
|
||||||
@ -84,10 +92,6 @@ STAGE_1_INTEGRATIONS = {
|
|||||||
"cloud",
|
"cloud",
|
||||||
# Ensure supervisor is available
|
# Ensure supervisor is available
|
||||||
"hassio",
|
"hassio",
|
||||||
# Get the frontend up and running as soon
|
|
||||||
# as possible so problem integrations can
|
|
||||||
# be removed
|
|
||||||
"frontend",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -285,7 +289,9 @@ def async_enable_logging(
|
|||||||
|
|
||||||
This method must be run in the event loop.
|
This method must be run in the event loop.
|
||||||
"""
|
"""
|
||||||
fmt = "%(asctime)s %(levelname)s (%(threadName)s) [%(name)s] %(message)s"
|
fmt = (
|
||||||
|
"%(asctime)s.%(msecs)03d %(levelname)s (%(threadName)s) [%(name)s] %(message)s"
|
||||||
|
)
|
||||||
datefmt = "%Y-%m-%d %H:%M:%S"
|
datefmt = "%Y-%m-%d %H:%M:%S"
|
||||||
|
|
||||||
if not log_no_color:
|
if not log_no_color:
|
||||||
@ -477,14 +483,9 @@ async def _async_set_up_integrations(
|
|||||||
|
|
||||||
integrations_to_process = [
|
integrations_to_process = [
|
||||||
int_or_exc
|
int_or_exc
|
||||||
for int_or_exc in await gather_with_concurrency(
|
for int_or_exc in (
|
||||||
loader.MAX_LOAD_CONCURRENTLY,
|
await loader.async_get_integrations(hass, old_to_resolve)
|
||||||
*(
|
).values()
|
||||||
loader.async_get_integration(hass, domain)
|
|
||||||
for domain in old_to_resolve
|
|
||||||
),
|
|
||||||
return_exceptions=True,
|
|
||||||
)
|
|
||||||
if isinstance(int_or_exc, loader.Integration)
|
if isinstance(int_or_exc, loader.Integration)
|
||||||
]
|
]
|
||||||
resolve_dependencies_tasks = [
|
resolve_dependencies_tasks = [
|
||||||
@ -508,11 +509,43 @@ async def _async_set_up_integrations(
|
|||||||
|
|
||||||
_LOGGER.info("Domains to be set up: %s", domains_to_setup)
|
_LOGGER.info("Domains to be set up: %s", domains_to_setup)
|
||||||
|
|
||||||
|
def _cache_uname_processor() -> None:
|
||||||
|
"""Cache the result of platform.uname().processor in the executor.
|
||||||
|
|
||||||
|
Multiple modules call this function at startup which
|
||||||
|
executes a blocking subprocess call. This is a problem for the
|
||||||
|
asyncio event loop. By primeing the cache of uname we can
|
||||||
|
avoid the blocking call in the event loop.
|
||||||
|
"""
|
||||||
|
platform.uname().processor # pylint: disable=expression-not-assigned
|
||||||
|
|
||||||
|
# Load the registries and cache the result of platform.uname().processor
|
||||||
|
await asyncio.gather(
|
||||||
|
device_registry.async_load(hass),
|
||||||
|
entity_registry.async_load(hass),
|
||||||
|
area_registry.async_load(hass),
|
||||||
|
hass.async_add_executor_job(_cache_uname_processor),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Initialize recorder
|
||||||
|
if "recorder" in domains_to_setup:
|
||||||
|
recorder.async_initialize_recorder(hass)
|
||||||
|
|
||||||
# Load logging as soon as possible
|
# Load logging as soon as possible
|
||||||
if logging_domains := domains_to_setup & LOGGING_INTEGRATIONS:
|
if logging_domains := domains_to_setup & LOGGING_INTEGRATIONS:
|
||||||
_LOGGER.info("Setting up logging: %s", logging_domains)
|
_LOGGER.info("Setting up logging: %s", logging_domains)
|
||||||
await async_setup_multi_components(hass, logging_domains, config)
|
await async_setup_multi_components(hass, logging_domains, config)
|
||||||
|
|
||||||
|
# Setup frontend
|
||||||
|
if frontend_domains := domains_to_setup & FRONTEND_INTEGRATIONS:
|
||||||
|
_LOGGER.info("Setting up frontend: %s", frontend_domains)
|
||||||
|
await async_setup_multi_components(hass, frontend_domains, config)
|
||||||
|
|
||||||
|
# Setup recorder
|
||||||
|
if recorder_domains := domains_to_setup & RECORDER_INTEGRATIONS:
|
||||||
|
_LOGGER.info("Setting up recorder: %s", recorder_domains)
|
||||||
|
await async_setup_multi_components(hass, recorder_domains, config)
|
||||||
|
|
||||||
# Start up debuggers. Start these first in case they want to wait.
|
# Start up debuggers. Start these first in case they want to wait.
|
||||||
if debuggers := domains_to_setup & DEBUGGER_INTEGRATIONS:
|
if debuggers := domains_to_setup & DEBUGGER_INTEGRATIONS:
|
||||||
_LOGGER.debug("Setting up debuggers: %s", debuggers)
|
_LOGGER.debug("Setting up debuggers: %s", debuggers)
|
||||||
@ -522,7 +555,8 @@ async def _async_set_up_integrations(
|
|||||||
stage_1_domains: set[str] = set()
|
stage_1_domains: set[str] = set()
|
||||||
|
|
||||||
# Find all dependencies of any dependency of any stage 1 integration that
|
# Find all dependencies of any dependency of any stage 1 integration that
|
||||||
# we plan on loading and promote them to stage 1
|
# we plan on loading and promote them to stage 1. This is done only to not
|
||||||
|
# get misleading log messages
|
||||||
deps_promotion: set[str] = STAGE_1_INTEGRATIONS
|
deps_promotion: set[str] = STAGE_1_INTEGRATIONS
|
||||||
while deps_promotion:
|
while deps_promotion:
|
||||||
old_deps_promotion = deps_promotion
|
old_deps_promotion = deps_promotion
|
||||||
@ -539,24 +573,13 @@ async def _async_set_up_integrations(
|
|||||||
|
|
||||||
deps_promotion.update(dep_itg.all_dependencies)
|
deps_promotion.update(dep_itg.all_dependencies)
|
||||||
|
|
||||||
stage_2_domains = domains_to_setup - logging_domains - debuggers - stage_1_domains
|
stage_2_domains = (
|
||||||
|
domains_to_setup
|
||||||
def _cache_uname_processor() -> None:
|
- logging_domains
|
||||||
"""Cache the result of platform.uname().processor in the executor.
|
- frontend_domains
|
||||||
|
- recorder_domains
|
||||||
Multiple modules call this function at startup which
|
- debuggers
|
||||||
executes a blocking subprocess call. This is a problem for the
|
- stage_1_domains
|
||||||
asyncio event loop. By primeing the cache of uname we can
|
|
||||||
avoid the blocking call in the event loop.
|
|
||||||
"""
|
|
||||||
platform.uname().processor # pylint: disable=expression-not-assigned
|
|
||||||
|
|
||||||
# Load the registries
|
|
||||||
await asyncio.gather(
|
|
||||||
device_registry.async_load(hass),
|
|
||||||
entity_registry.async_load(hass),
|
|
||||||
area_registry.async_load(hass),
|
|
||||||
hass.async_add_executor_job(_cache_uname_processor),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Start setup
|
# Start setup
|
||||||
|
@ -103,7 +103,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
|
|
||||||
hass.data[DOMAIN] = AbodeSystem(abode, polling)
|
hass.data[DOMAIN] = AbodeSystem(abode, polling)
|
||||||
|
|
||||||
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
|
|
||||||
await setup_hass_events(hass)
|
await setup_hass_events(hass)
|
||||||
await hass.async_add_executor_job(setup_hass_services, hass)
|
await hass.async_add_executor_job(setup_hass_services, hass)
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"config": {
|
"config": {
|
||||||
"abort": {
|
"abort": {
|
||||||
"reauth_successful": "\u518d\u8a8d\u8a3c\u306b\u6210\u529f\u3057\u307e\u3057\u305f",
|
"reauth_successful": "\u518d\u8a8d\u8a3c\u306b\u6210\u529f\u3057\u307e\u3057\u305f",
|
||||||
"single_instance_allowed": "\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059\u3002\u5358\u4e00\u306e\u8a2d\u5b9a\u3057\u304b\u3067\u304d\u307e\u305b\u3093\u3002"
|
"single_instance_allowed": "\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059\u3002\u8a2d\u5b9a\u3067\u304d\u308b\u306e\u306f1\u3064\u3060\u3051\u3067\u3059\u3002"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f",
|
"cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f",
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
"password": "Palavra-passe",
|
"password": "Palavra-passe",
|
||||||
"username": "Endere\u00e7o de e-mail"
|
"username": "Email"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,12 +11,14 @@ from aiohttp.client_exceptions import ClientConnectorError
|
|||||||
from async_timeout import timeout
|
from async_timeout import timeout
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import CONF_API_KEY, Platform
|
from homeassistant.const import CONF_API_KEY, CONF_NAME, Platform
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
from homeassistant.helpers.device_registry import DeviceEntryType
|
||||||
|
from homeassistant.helpers.entity import DeviceInfo
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||||
|
|
||||||
from .const import ATTR_FORECAST, CONF_FORECAST, DOMAIN
|
from .const import ATTR_FORECAST, CONF_FORECAST, DOMAIN, MANUFACTURER
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -26,6 +28,7 @@ PLATFORMS = [Platform.SENSOR, Platform.WEATHER]
|
|||||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
"""Set up AccuWeather as config entry."""
|
"""Set up AccuWeather as config entry."""
|
||||||
api_key: str = entry.data[CONF_API_KEY]
|
api_key: str = entry.data[CONF_API_KEY]
|
||||||
|
name: str = entry.data[CONF_NAME]
|
||||||
assert entry.unique_id is not None
|
assert entry.unique_id is not None
|
||||||
location_key = entry.unique_id
|
location_key = entry.unique_id
|
||||||
forecast: bool = entry.options.get(CONF_FORECAST, False)
|
forecast: bool = entry.options.get(CONF_FORECAST, False)
|
||||||
@ -35,7 +38,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
websession = async_get_clientsession(hass)
|
websession = async_get_clientsession(hass)
|
||||||
|
|
||||||
coordinator = AccuWeatherDataUpdateCoordinator(
|
coordinator = AccuWeatherDataUpdateCoordinator(
|
||||||
hass, websession, api_key, location_key, forecast
|
hass, websession, api_key, location_key, forecast, name
|
||||||
)
|
)
|
||||||
await coordinator.async_config_entry_first_refresh()
|
await coordinator.async_config_entry_first_refresh()
|
||||||
|
|
||||||
@ -43,7 +46,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
|
|
||||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
|
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
|
||||||
|
|
||||||
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -73,12 +76,27 @@ class AccuWeatherDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
|||||||
api_key: str,
|
api_key: str,
|
||||||
location_key: str,
|
location_key: str,
|
||||||
forecast: bool,
|
forecast: bool,
|
||||||
|
name: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize."""
|
"""Initialize."""
|
||||||
self.location_key = location_key
|
self.location_key = location_key
|
||||||
self.forecast = forecast
|
self.forecast = forecast
|
||||||
self.is_metric = hass.config.units.is_metric
|
self.is_metric = hass.config.units.is_metric
|
||||||
self.accuweather = AccuWeather(api_key, session, location_key=self.location_key)
|
self.accuweather = AccuWeather(api_key, session, location_key=location_key)
|
||||||
|
self.device_info = DeviceInfo(
|
||||||
|
entry_type=DeviceEntryType.SERVICE,
|
||||||
|
identifiers={(DOMAIN, location_key)},
|
||||||
|
manufacturer=MANUFACTURER,
|
||||||
|
name=name,
|
||||||
|
# You don't need to provide specific details for the URL,
|
||||||
|
# so passing in _ characters is fine if the location key
|
||||||
|
# is correct
|
||||||
|
configuration_url=(
|
||||||
|
"http://accuweather.com/en/"
|
||||||
|
f"_/_/{location_key}/"
|
||||||
|
f"weather-forecast/{location_key}/"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
# Enabling the forecast download increases the number of requests per data
|
# Enabling the forecast download increases the number of requests per data
|
||||||
# update, we use 40 minutes for current condition only and 80 minutes for
|
# update, we use 40 minutes for current condition only and 80 minutes for
|
||||||
|
@ -3,7 +3,6 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from typing import Final
|
from typing import Final
|
||||||
|
|
||||||
from homeassistant.components.sensor import SensorDeviceClass, SensorStateClass
|
|
||||||
from homeassistant.components.weather import (
|
from homeassistant.components.weather import (
|
||||||
ATTR_CONDITION_CLEAR_NIGHT,
|
ATTR_CONDITION_CLEAR_NIGHT,
|
||||||
ATTR_CONDITION_CLOUDY,
|
ATTR_CONDITION_CLOUDY,
|
||||||
@ -20,22 +19,6 @@ from homeassistant.components.weather import (
|
|||||||
ATTR_CONDITION_SUNNY,
|
ATTR_CONDITION_SUNNY,
|
||||||
ATTR_CONDITION_WINDY,
|
ATTR_CONDITION_WINDY,
|
||||||
)
|
)
|
||||||
from homeassistant.const import (
|
|
||||||
CONCENTRATION_PARTS_PER_CUBIC_METER,
|
|
||||||
LENGTH_FEET,
|
|
||||||
LENGTH_INCHES,
|
|
||||||
LENGTH_METERS,
|
|
||||||
LENGTH_MILLIMETERS,
|
|
||||||
PERCENTAGE,
|
|
||||||
SPEED_KILOMETERS_PER_HOUR,
|
|
||||||
SPEED_MILES_PER_HOUR,
|
|
||||||
TEMP_CELSIUS,
|
|
||||||
TEMP_FAHRENHEIT,
|
|
||||||
TIME_HOURS,
|
|
||||||
UV_INDEX,
|
|
||||||
)
|
|
||||||
|
|
||||||
from .model import AccuWeatherSensorDescription
|
|
||||||
|
|
||||||
API_IMPERIAL: Final = "Imperial"
|
API_IMPERIAL: Final = "Imperial"
|
||||||
API_METRIC: Final = "Metric"
|
API_METRIC: Final = "Metric"
|
||||||
@ -45,7 +28,6 @@ CONF_FORECAST: Final = "forecast"
|
|||||||
DOMAIN: Final = "accuweather"
|
DOMAIN: Final = "accuweather"
|
||||||
MANUFACTURER: Final = "AccuWeather, Inc."
|
MANUFACTURER: Final = "AccuWeather, Inc."
|
||||||
MAX_FORECAST_DAYS: Final = 4
|
MAX_FORECAST_DAYS: Final = 4
|
||||||
NAME: Final = "AccuWeather"
|
|
||||||
|
|
||||||
CONDITION_CLASSES: Final[dict[str, list[int]]] = {
|
CONDITION_CLASSES: Final[dict[str, list[int]]] = {
|
||||||
ATTR_CONDITION_CLEAR_NIGHT: [33, 34, 37],
|
ATTR_CONDITION_CLEAR_NIGHT: [33, 34, 37],
|
||||||
@ -63,264 +45,3 @@ CONDITION_CLASSES: Final[dict[str, list[int]]] = {
|
|||||||
ATTR_CONDITION_SUNNY: [1, 2, 5],
|
ATTR_CONDITION_SUNNY: [1, 2, 5],
|
||||||
ATTR_CONDITION_WINDY: [32],
|
ATTR_CONDITION_WINDY: [32],
|
||||||
}
|
}
|
||||||
|
|
||||||
FORECAST_SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
|
|
||||||
AccuWeatherSensorDescription(
|
|
||||||
key="CloudCoverDay",
|
|
||||||
icon="mdi:weather-cloudy",
|
|
||||||
name="Cloud Cover Day",
|
|
||||||
unit_metric=PERCENTAGE,
|
|
||||||
unit_imperial=PERCENTAGE,
|
|
||||||
entity_registry_enabled_default=False,
|
|
||||||
),
|
|
||||||
AccuWeatherSensorDescription(
|
|
||||||
key="CloudCoverNight",
|
|
||||||
icon="mdi:weather-cloudy",
|
|
||||||
name="Cloud Cover Night",
|
|
||||||
unit_metric=PERCENTAGE,
|
|
||||||
unit_imperial=PERCENTAGE,
|
|
||||||
entity_registry_enabled_default=False,
|
|
||||||
),
|
|
||||||
AccuWeatherSensorDescription(
|
|
||||||
key="Grass",
|
|
||||||
icon="mdi:grass",
|
|
||||||
name="Grass Pollen",
|
|
||||||
unit_metric=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
|
||||||
unit_imperial=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
|
||||||
entity_registry_enabled_default=False,
|
|
||||||
),
|
|
||||||
AccuWeatherSensorDescription(
|
|
||||||
key="HoursOfSun",
|
|
||||||
icon="mdi:weather-partly-cloudy",
|
|
||||||
name="Hours Of Sun",
|
|
||||||
unit_metric=TIME_HOURS,
|
|
||||||
unit_imperial=TIME_HOURS,
|
|
||||||
),
|
|
||||||
AccuWeatherSensorDescription(
|
|
||||||
key="Mold",
|
|
||||||
icon="mdi:blur",
|
|
||||||
name="Mold Pollen",
|
|
||||||
unit_metric=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
|
||||||
unit_imperial=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
|
||||||
entity_registry_enabled_default=False,
|
|
||||||
),
|
|
||||||
AccuWeatherSensorDescription(
|
|
||||||
key="Ozone",
|
|
||||||
icon="mdi:vector-triangle",
|
|
||||||
name="Ozone",
|
|
||||||
unit_metric=None,
|
|
||||||
unit_imperial=None,
|
|
||||||
entity_registry_enabled_default=False,
|
|
||||||
),
|
|
||||||
AccuWeatherSensorDescription(
|
|
||||||
key="Ragweed",
|
|
||||||
icon="mdi:sprout",
|
|
||||||
name="Ragweed Pollen",
|
|
||||||
unit_metric=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
|
||||||
unit_imperial=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
|
||||||
entity_registry_enabled_default=False,
|
|
||||||
),
|
|
||||||
AccuWeatherSensorDescription(
|
|
||||||
key="RealFeelTemperatureMax",
|
|
||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
|
||||||
name="RealFeel Temperature Max",
|
|
||||||
unit_metric=TEMP_CELSIUS,
|
|
||||||
unit_imperial=TEMP_FAHRENHEIT,
|
|
||||||
),
|
|
||||||
AccuWeatherSensorDescription(
|
|
||||||
key="RealFeelTemperatureMin",
|
|
||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
|
||||||
name="RealFeel Temperature Min",
|
|
||||||
unit_metric=TEMP_CELSIUS,
|
|
||||||
unit_imperial=TEMP_FAHRENHEIT,
|
|
||||||
),
|
|
||||||
AccuWeatherSensorDescription(
|
|
||||||
key="RealFeelTemperatureShadeMax",
|
|
||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
|
||||||
name="RealFeel Temperature Shade Max",
|
|
||||||
unit_metric=TEMP_CELSIUS,
|
|
||||||
unit_imperial=TEMP_FAHRENHEIT,
|
|
||||||
entity_registry_enabled_default=False,
|
|
||||||
),
|
|
||||||
AccuWeatherSensorDescription(
|
|
||||||
key="RealFeelTemperatureShadeMin",
|
|
||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
|
||||||
name="RealFeel Temperature Shade Min",
|
|
||||||
unit_metric=TEMP_CELSIUS,
|
|
||||||
unit_imperial=TEMP_FAHRENHEIT,
|
|
||||||
entity_registry_enabled_default=False,
|
|
||||||
),
|
|
||||||
AccuWeatherSensorDescription(
|
|
||||||
key="ThunderstormProbabilityDay",
|
|
||||||
icon="mdi:weather-lightning",
|
|
||||||
name="Thunderstorm Probability Day",
|
|
||||||
unit_metric=PERCENTAGE,
|
|
||||||
unit_imperial=PERCENTAGE,
|
|
||||||
),
|
|
||||||
AccuWeatherSensorDescription(
|
|
||||||
key="ThunderstormProbabilityNight",
|
|
||||||
icon="mdi:weather-lightning",
|
|
||||||
name="Thunderstorm Probability Night",
|
|
||||||
unit_metric=PERCENTAGE,
|
|
||||||
unit_imperial=PERCENTAGE,
|
|
||||||
),
|
|
||||||
AccuWeatherSensorDescription(
|
|
||||||
key="Tree",
|
|
||||||
icon="mdi:tree-outline",
|
|
||||||
name="Tree Pollen",
|
|
||||||
unit_metric=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
|
||||||
unit_imperial=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
|
||||||
entity_registry_enabled_default=False,
|
|
||||||
),
|
|
||||||
AccuWeatherSensorDescription(
|
|
||||||
key="UVIndex",
|
|
||||||
icon="mdi:weather-sunny",
|
|
||||||
name="UV Index",
|
|
||||||
unit_metric=UV_INDEX,
|
|
||||||
unit_imperial=UV_INDEX,
|
|
||||||
),
|
|
||||||
AccuWeatherSensorDescription(
|
|
||||||
key="WindGustDay",
|
|
||||||
icon="mdi:weather-windy",
|
|
||||||
name="Wind Gust Day",
|
|
||||||
unit_metric=SPEED_KILOMETERS_PER_HOUR,
|
|
||||||
unit_imperial=SPEED_MILES_PER_HOUR,
|
|
||||||
entity_registry_enabled_default=False,
|
|
||||||
),
|
|
||||||
AccuWeatherSensorDescription(
|
|
||||||
key="WindGustNight",
|
|
||||||
icon="mdi:weather-windy",
|
|
||||||
name="Wind Gust Night",
|
|
||||||
unit_metric=SPEED_KILOMETERS_PER_HOUR,
|
|
||||||
unit_imperial=SPEED_MILES_PER_HOUR,
|
|
||||||
entity_registry_enabled_default=False,
|
|
||||||
),
|
|
||||||
AccuWeatherSensorDescription(
|
|
||||||
key="WindDay",
|
|
||||||
icon="mdi:weather-windy",
|
|
||||||
name="Wind Day",
|
|
||||||
unit_metric=SPEED_KILOMETERS_PER_HOUR,
|
|
||||||
unit_imperial=SPEED_MILES_PER_HOUR,
|
|
||||||
),
|
|
||||||
AccuWeatherSensorDescription(
|
|
||||||
key="WindNight",
|
|
||||||
icon="mdi:weather-windy",
|
|
||||||
name="Wind Night",
|
|
||||||
unit_metric=SPEED_KILOMETERS_PER_HOUR,
|
|
||||||
unit_imperial=SPEED_MILES_PER_HOUR,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
|
|
||||||
AccuWeatherSensorDescription(
|
|
||||||
key="ApparentTemperature",
|
|
||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
|
||||||
name="Apparent Temperature",
|
|
||||||
unit_metric=TEMP_CELSIUS,
|
|
||||||
unit_imperial=TEMP_FAHRENHEIT,
|
|
||||||
entity_registry_enabled_default=False,
|
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
|
||||||
),
|
|
||||||
AccuWeatherSensorDescription(
|
|
||||||
key="Ceiling",
|
|
||||||
icon="mdi:weather-fog",
|
|
||||||
name="Cloud Ceiling",
|
|
||||||
unit_metric=LENGTH_METERS,
|
|
||||||
unit_imperial=LENGTH_FEET,
|
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
|
||||||
),
|
|
||||||
AccuWeatherSensorDescription(
|
|
||||||
key="CloudCover",
|
|
||||||
icon="mdi:weather-cloudy",
|
|
||||||
name="Cloud Cover",
|
|
||||||
unit_metric=PERCENTAGE,
|
|
||||||
unit_imperial=PERCENTAGE,
|
|
||||||
entity_registry_enabled_default=False,
|
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
|
||||||
),
|
|
||||||
AccuWeatherSensorDescription(
|
|
||||||
key="DewPoint",
|
|
||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
|
||||||
name="Dew Point",
|
|
||||||
unit_metric=TEMP_CELSIUS,
|
|
||||||
unit_imperial=TEMP_FAHRENHEIT,
|
|
||||||
entity_registry_enabled_default=False,
|
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
|
||||||
),
|
|
||||||
AccuWeatherSensorDescription(
|
|
||||||
key="RealFeelTemperature",
|
|
||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
|
||||||
name="RealFeel Temperature",
|
|
||||||
unit_metric=TEMP_CELSIUS,
|
|
||||||
unit_imperial=TEMP_FAHRENHEIT,
|
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
|
||||||
),
|
|
||||||
AccuWeatherSensorDescription(
|
|
||||||
key="RealFeelTemperatureShade",
|
|
||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
|
||||||
name="RealFeel Temperature Shade",
|
|
||||||
unit_metric=TEMP_CELSIUS,
|
|
||||||
unit_imperial=TEMP_FAHRENHEIT,
|
|
||||||
entity_registry_enabled_default=False,
|
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
|
||||||
),
|
|
||||||
AccuWeatherSensorDescription(
|
|
||||||
key="Precipitation",
|
|
||||||
icon="mdi:weather-rainy",
|
|
||||||
name="Precipitation",
|
|
||||||
unit_metric=LENGTH_MILLIMETERS,
|
|
||||||
unit_imperial=LENGTH_INCHES,
|
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
|
||||||
),
|
|
||||||
AccuWeatherSensorDescription(
|
|
||||||
key="PressureTendency",
|
|
||||||
device_class="accuweather__pressure_tendency",
|
|
||||||
icon="mdi:gauge",
|
|
||||||
name="Pressure Tendency",
|
|
||||||
unit_metric=None,
|
|
||||||
unit_imperial=None,
|
|
||||||
),
|
|
||||||
AccuWeatherSensorDescription(
|
|
||||||
key="UVIndex",
|
|
||||||
icon="mdi:weather-sunny",
|
|
||||||
name="UV Index",
|
|
||||||
unit_metric=UV_INDEX,
|
|
||||||
unit_imperial=UV_INDEX,
|
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
|
||||||
),
|
|
||||||
AccuWeatherSensorDescription(
|
|
||||||
key="WetBulbTemperature",
|
|
||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
|
||||||
name="Wet Bulb Temperature",
|
|
||||||
unit_metric=TEMP_CELSIUS,
|
|
||||||
unit_imperial=TEMP_FAHRENHEIT,
|
|
||||||
entity_registry_enabled_default=False,
|
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
|
||||||
),
|
|
||||||
AccuWeatherSensorDescription(
|
|
||||||
key="WindChillTemperature",
|
|
||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
|
||||||
name="Wind Chill Temperature",
|
|
||||||
unit_metric=TEMP_CELSIUS,
|
|
||||||
unit_imperial=TEMP_FAHRENHEIT,
|
|
||||||
entity_registry_enabled_default=False,
|
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
|
||||||
),
|
|
||||||
AccuWeatherSensorDescription(
|
|
||||||
key="Wind",
|
|
||||||
icon="mdi:weather-windy",
|
|
||||||
name="Wind",
|
|
||||||
unit_metric=SPEED_KILOMETERS_PER_HOUR,
|
|
||||||
unit_imperial=SPEED_MILES_PER_HOUR,
|
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
|
||||||
),
|
|
||||||
AccuWeatherSensorDescription(
|
|
||||||
key="WindGust",
|
|
||||||
icon="mdi:weather-windy",
|
|
||||||
name="Wind Gust",
|
|
||||||
unit_metric=SPEED_KILOMETERS_PER_HOUR,
|
|
||||||
unit_imperial=SPEED_MILES_PER_HOUR,
|
|
||||||
entity_registry_enabled_default=False,
|
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
"""Type definitions for AccuWeather integration."""
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from dataclasses import dataclass
|
|
||||||
|
|
||||||
from homeassistant.components.sensor import SensorEntityDescription
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class AccuWeatherSensorDescription(SensorEntityDescription):
|
|
||||||
"""Class describing AccuWeather sensor entities."""
|
|
||||||
|
|
||||||
unit_metric: str | None = None
|
|
||||||
unit_imperial: str | None = None
|
|
@ -1,14 +1,31 @@
|
|||||||
"""Support for the AccuWeather service."""
|
"""Support for the AccuWeather service."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
from typing import Any, cast
|
from typing import Any, cast
|
||||||
|
|
||||||
from homeassistant.components.sensor import SensorDeviceClass, SensorEntity
|
from homeassistant.components.sensor import (
|
||||||
|
SensorDeviceClass,
|
||||||
|
SensorEntity,
|
||||||
|
SensorEntityDescription,
|
||||||
|
SensorStateClass,
|
||||||
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import CONF_NAME
|
from homeassistant.const import (
|
||||||
|
CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
|
LENGTH_FEET,
|
||||||
|
LENGTH_INCHES,
|
||||||
|
LENGTH_METERS,
|
||||||
|
LENGTH_MILLIMETERS,
|
||||||
|
PERCENTAGE,
|
||||||
|
SPEED_KILOMETERS_PER_HOUR,
|
||||||
|
SPEED_MILES_PER_HOUR,
|
||||||
|
TEMP_CELSIUS,
|
||||||
|
TEMP_FAHRENHEIT,
|
||||||
|
TIME_HOURS,
|
||||||
|
UV_INDEX,
|
||||||
|
)
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.device_registry import DeviceEntryType
|
|
||||||
from homeassistant.helpers.entity import DeviceInfo
|
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.typing import StateType
|
from homeassistant.helpers.typing import StateType
|
||||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
@ -20,28 +37,292 @@ from .const import (
|
|||||||
ATTR_FORECAST,
|
ATTR_FORECAST,
|
||||||
ATTRIBUTION,
|
ATTRIBUTION,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
FORECAST_SENSOR_TYPES,
|
|
||||||
MANUFACTURER,
|
|
||||||
MAX_FORECAST_DAYS,
|
MAX_FORECAST_DAYS,
|
||||||
NAME,
|
|
||||||
SENSOR_TYPES,
|
|
||||||
)
|
)
|
||||||
from .model import AccuWeatherSensorDescription
|
|
||||||
|
|
||||||
PARALLEL_UPDATES = 1
|
PARALLEL_UPDATES = 1
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class AccuWeatherSensorDescription(SensorEntityDescription):
|
||||||
|
"""Class describing AccuWeather sensor entities."""
|
||||||
|
|
||||||
|
unit_metric: str | None = None
|
||||||
|
unit_imperial: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
FORECAST_SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
|
||||||
|
AccuWeatherSensorDescription(
|
||||||
|
key="CloudCoverDay",
|
||||||
|
icon="mdi:weather-cloudy",
|
||||||
|
name="Cloud cover day",
|
||||||
|
unit_metric=PERCENTAGE,
|
||||||
|
unit_imperial=PERCENTAGE,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
),
|
||||||
|
AccuWeatherSensorDescription(
|
||||||
|
key="CloudCoverNight",
|
||||||
|
icon="mdi:weather-cloudy",
|
||||||
|
name="Cloud cover night",
|
||||||
|
unit_metric=PERCENTAGE,
|
||||||
|
unit_imperial=PERCENTAGE,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
),
|
||||||
|
AccuWeatherSensorDescription(
|
||||||
|
key="Grass",
|
||||||
|
icon="mdi:grass",
|
||||||
|
name="Grass pollen",
|
||||||
|
unit_metric=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
|
unit_imperial=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
),
|
||||||
|
AccuWeatherSensorDescription(
|
||||||
|
key="HoursOfSun",
|
||||||
|
icon="mdi:weather-partly-cloudy",
|
||||||
|
name="Hours of sun",
|
||||||
|
unit_metric=TIME_HOURS,
|
||||||
|
unit_imperial=TIME_HOURS,
|
||||||
|
),
|
||||||
|
AccuWeatherSensorDescription(
|
||||||
|
key="Mold",
|
||||||
|
icon="mdi:blur",
|
||||||
|
name="Mold pollen",
|
||||||
|
unit_metric=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
|
unit_imperial=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
),
|
||||||
|
AccuWeatherSensorDescription(
|
||||||
|
key="Ozone",
|
||||||
|
icon="mdi:vector-triangle",
|
||||||
|
name="Ozone",
|
||||||
|
unit_metric=None,
|
||||||
|
unit_imperial=None,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
),
|
||||||
|
AccuWeatherSensorDescription(
|
||||||
|
key="Ragweed",
|
||||||
|
icon="mdi:sprout",
|
||||||
|
name="Ragweed pollen",
|
||||||
|
unit_metric=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
|
unit_imperial=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
),
|
||||||
|
AccuWeatherSensorDescription(
|
||||||
|
key="RealFeelTemperatureMax",
|
||||||
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
|
name="RealFeel temperature max",
|
||||||
|
unit_metric=TEMP_CELSIUS,
|
||||||
|
unit_imperial=TEMP_FAHRENHEIT,
|
||||||
|
),
|
||||||
|
AccuWeatherSensorDescription(
|
||||||
|
key="RealFeelTemperatureMin",
|
||||||
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
|
name="RealFeel temperature min",
|
||||||
|
unit_metric=TEMP_CELSIUS,
|
||||||
|
unit_imperial=TEMP_FAHRENHEIT,
|
||||||
|
),
|
||||||
|
AccuWeatherSensorDescription(
|
||||||
|
key="RealFeelTemperatureShadeMax",
|
||||||
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
|
name="RealFeel temperature shade max",
|
||||||
|
unit_metric=TEMP_CELSIUS,
|
||||||
|
unit_imperial=TEMP_FAHRENHEIT,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
),
|
||||||
|
AccuWeatherSensorDescription(
|
||||||
|
key="RealFeelTemperatureShadeMin",
|
||||||
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
|
name="RealFeel temperature shade min",
|
||||||
|
unit_metric=TEMP_CELSIUS,
|
||||||
|
unit_imperial=TEMP_FAHRENHEIT,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
),
|
||||||
|
AccuWeatherSensorDescription(
|
||||||
|
key="ThunderstormProbabilityDay",
|
||||||
|
icon="mdi:weather-lightning",
|
||||||
|
name="Thunderstorm probability day",
|
||||||
|
unit_metric=PERCENTAGE,
|
||||||
|
unit_imperial=PERCENTAGE,
|
||||||
|
),
|
||||||
|
AccuWeatherSensorDescription(
|
||||||
|
key="ThunderstormProbabilityNight",
|
||||||
|
icon="mdi:weather-lightning",
|
||||||
|
name="Thunderstorm probability night",
|
||||||
|
unit_metric=PERCENTAGE,
|
||||||
|
unit_imperial=PERCENTAGE,
|
||||||
|
),
|
||||||
|
AccuWeatherSensorDescription(
|
||||||
|
key="Tree",
|
||||||
|
icon="mdi:tree-outline",
|
||||||
|
name="Tree pollen",
|
||||||
|
unit_metric=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
|
unit_imperial=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
),
|
||||||
|
AccuWeatherSensorDescription(
|
||||||
|
key="UVIndex",
|
||||||
|
icon="mdi:weather-sunny",
|
||||||
|
name="UV index",
|
||||||
|
unit_metric=UV_INDEX,
|
||||||
|
unit_imperial=UV_INDEX,
|
||||||
|
),
|
||||||
|
AccuWeatherSensorDescription(
|
||||||
|
key="WindGustDay",
|
||||||
|
icon="mdi:weather-windy",
|
||||||
|
name="Wind gust day",
|
||||||
|
unit_metric=SPEED_KILOMETERS_PER_HOUR,
|
||||||
|
unit_imperial=SPEED_MILES_PER_HOUR,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
),
|
||||||
|
AccuWeatherSensorDescription(
|
||||||
|
key="WindGustNight",
|
||||||
|
icon="mdi:weather-windy",
|
||||||
|
name="Wind gust night",
|
||||||
|
unit_metric=SPEED_KILOMETERS_PER_HOUR,
|
||||||
|
unit_imperial=SPEED_MILES_PER_HOUR,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
),
|
||||||
|
AccuWeatherSensorDescription(
|
||||||
|
key="WindDay",
|
||||||
|
icon="mdi:weather-windy",
|
||||||
|
name="Wind day",
|
||||||
|
unit_metric=SPEED_KILOMETERS_PER_HOUR,
|
||||||
|
unit_imperial=SPEED_MILES_PER_HOUR,
|
||||||
|
),
|
||||||
|
AccuWeatherSensorDescription(
|
||||||
|
key="WindNight",
|
||||||
|
icon="mdi:weather-windy",
|
||||||
|
name="Wind night",
|
||||||
|
unit_metric=SPEED_KILOMETERS_PER_HOUR,
|
||||||
|
unit_imperial=SPEED_MILES_PER_HOUR,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
|
||||||
|
AccuWeatherSensorDescription(
|
||||||
|
key="ApparentTemperature",
|
||||||
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
|
name="Apparent temperature",
|
||||||
|
unit_metric=TEMP_CELSIUS,
|
||||||
|
unit_imperial=TEMP_FAHRENHEIT,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
),
|
||||||
|
AccuWeatherSensorDescription(
|
||||||
|
key="Ceiling",
|
||||||
|
icon="mdi:weather-fog",
|
||||||
|
name="Cloud ceiling",
|
||||||
|
unit_metric=LENGTH_METERS,
|
||||||
|
unit_imperial=LENGTH_FEET,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
),
|
||||||
|
AccuWeatherSensorDescription(
|
||||||
|
key="CloudCover",
|
||||||
|
icon="mdi:weather-cloudy",
|
||||||
|
name="Cloud cover",
|
||||||
|
unit_metric=PERCENTAGE,
|
||||||
|
unit_imperial=PERCENTAGE,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
),
|
||||||
|
AccuWeatherSensorDescription(
|
||||||
|
key="DewPoint",
|
||||||
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
|
name="Dew point",
|
||||||
|
unit_metric=TEMP_CELSIUS,
|
||||||
|
unit_imperial=TEMP_FAHRENHEIT,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
),
|
||||||
|
AccuWeatherSensorDescription(
|
||||||
|
key="RealFeelTemperature",
|
||||||
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
|
name="RealFeel temperature",
|
||||||
|
unit_metric=TEMP_CELSIUS,
|
||||||
|
unit_imperial=TEMP_FAHRENHEIT,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
),
|
||||||
|
AccuWeatherSensorDescription(
|
||||||
|
key="RealFeelTemperatureShade",
|
||||||
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
|
name="RealFeel temperature shade",
|
||||||
|
unit_metric=TEMP_CELSIUS,
|
||||||
|
unit_imperial=TEMP_FAHRENHEIT,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
),
|
||||||
|
AccuWeatherSensorDescription(
|
||||||
|
key="Precipitation",
|
||||||
|
icon="mdi:weather-rainy",
|
||||||
|
name="Precipitation",
|
||||||
|
unit_metric=LENGTH_MILLIMETERS,
|
||||||
|
unit_imperial=LENGTH_INCHES,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
),
|
||||||
|
AccuWeatherSensorDescription(
|
||||||
|
key="PressureTendency",
|
||||||
|
device_class="accuweather__pressure_tendency",
|
||||||
|
icon="mdi:gauge",
|
||||||
|
name="Pressure tendency",
|
||||||
|
unit_metric=None,
|
||||||
|
unit_imperial=None,
|
||||||
|
),
|
||||||
|
AccuWeatherSensorDescription(
|
||||||
|
key="UVIndex",
|
||||||
|
icon="mdi:weather-sunny",
|
||||||
|
name="UV index",
|
||||||
|
unit_metric=UV_INDEX,
|
||||||
|
unit_imperial=UV_INDEX,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
),
|
||||||
|
AccuWeatherSensorDescription(
|
||||||
|
key="WetBulbTemperature",
|
||||||
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
|
name="Wet bulb temperature",
|
||||||
|
unit_metric=TEMP_CELSIUS,
|
||||||
|
unit_imperial=TEMP_FAHRENHEIT,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
),
|
||||||
|
AccuWeatherSensorDescription(
|
||||||
|
key="WindChillTemperature",
|
||||||
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
|
name="Wind chill temperature",
|
||||||
|
unit_metric=TEMP_CELSIUS,
|
||||||
|
unit_imperial=TEMP_FAHRENHEIT,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
),
|
||||||
|
AccuWeatherSensorDescription(
|
||||||
|
key="Wind",
|
||||||
|
icon="mdi:weather-windy",
|
||||||
|
name="Wind",
|
||||||
|
unit_metric=SPEED_KILOMETERS_PER_HOUR,
|
||||||
|
unit_imperial=SPEED_MILES_PER_HOUR,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
),
|
||||||
|
AccuWeatherSensorDescription(
|
||||||
|
key="WindGust",
|
||||||
|
icon="mdi:weather-windy",
|
||||||
|
name="Wind gust",
|
||||||
|
unit_metric=SPEED_KILOMETERS_PER_HOUR,
|
||||||
|
unit_imperial=SPEED_MILES_PER_HOUR,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Add AccuWeather entities from a config_entry."""
|
"""Add AccuWeather entities from a config_entry."""
|
||||||
name: str = entry.data[CONF_NAME]
|
|
||||||
|
|
||||||
coordinator: AccuWeatherDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
coordinator: AccuWeatherDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||||
|
|
||||||
sensors: list[AccuWeatherSensor] = []
|
sensors: list[AccuWeatherSensor] = []
|
||||||
for description in SENSOR_TYPES:
|
for description in SENSOR_TYPES:
|
||||||
sensors.append(AccuWeatherSensor(name, coordinator, description))
|
sensors.append(AccuWeatherSensor(coordinator, description))
|
||||||
|
|
||||||
if coordinator.forecast:
|
if coordinator.forecast:
|
||||||
for description in FORECAST_SENSOR_TYPES:
|
for description in FORECAST_SENSOR_TYPES:
|
||||||
@ -50,9 +331,7 @@ async def async_setup_entry(
|
|||||||
# locations.
|
# locations.
|
||||||
if description.key in coordinator.data[ATTR_FORECAST][0]:
|
if description.key in coordinator.data[ATTR_FORECAST][0]:
|
||||||
sensors.append(
|
sensors.append(
|
||||||
AccuWeatherSensor(
|
AccuWeatherSensor(coordinator, description, forecast_day=day)
|
||||||
name, coordinator, description, forecast_day=day
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
async_add_entities(sensors)
|
async_add_entities(sensors)
|
||||||
@ -64,11 +343,11 @@ class AccuWeatherSensor(
|
|||||||
"""Define an AccuWeather entity."""
|
"""Define an AccuWeather entity."""
|
||||||
|
|
||||||
_attr_attribution = ATTRIBUTION
|
_attr_attribution = ATTRIBUTION
|
||||||
|
_attr_has_entity_name = True
|
||||||
entity_description: AccuWeatherSensorDescription
|
entity_description: AccuWeatherSensorDescription
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
name: str,
|
|
||||||
coordinator: AccuWeatherDataUpdateCoordinator,
|
coordinator: AccuWeatherDataUpdateCoordinator,
|
||||||
description: AccuWeatherSensorDescription,
|
description: AccuWeatherSensorDescription,
|
||||||
forecast_day: int | None = None,
|
forecast_day: int | None = None,
|
||||||
@ -81,12 +360,11 @@ class AccuWeatherSensor(
|
|||||||
)
|
)
|
||||||
self._attrs: dict[str, Any] = {}
|
self._attrs: dict[str, Any] = {}
|
||||||
if forecast_day is not None:
|
if forecast_day is not None:
|
||||||
self._attr_name = f"{name} {description.name} {forecast_day}d"
|
self._attr_name = f"{description.name} {forecast_day}d"
|
||||||
self._attr_unique_id = (
|
self._attr_unique_id = (
|
||||||
f"{coordinator.location_key}-{description.key}-{forecast_day}".lower()
|
f"{coordinator.location_key}-{description.key}-{forecast_day}".lower()
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self._attr_name = f"{name} {description.name}"
|
|
||||||
self._attr_unique_id = (
|
self._attr_unique_id = (
|
||||||
f"{coordinator.location_key}-{description.key}".lower()
|
f"{coordinator.location_key}-{description.key}".lower()
|
||||||
)
|
)
|
||||||
@ -96,12 +374,7 @@ class AccuWeatherSensor(
|
|||||||
else:
|
else:
|
||||||
self._unit_system = API_IMPERIAL
|
self._unit_system = API_IMPERIAL
|
||||||
self._attr_native_unit_of_measurement = description.unit_imperial
|
self._attr_native_unit_of_measurement = description.unit_imperial
|
||||||
self._attr_device_info = DeviceInfo(
|
self._attr_device_info = coordinator.device_info
|
||||||
entry_type=DeviceEntryType.SERVICE,
|
|
||||||
identifiers={(DOMAIN, coordinator.location_key)},
|
|
||||||
manufacturer=MANUFACTURER,
|
|
||||||
name=NAME,
|
|
||||||
)
|
|
||||||
self.forecast_day = forecast_day
|
self.forecast_day = forecast_day
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
"abort": {
|
"abort": {
|
||||||
"single_instance_allowed": "\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059\u3002\u5358\u4e00\u306e\u8a2d\u5b9a\u3057\u304b\u3067\u304d\u307e\u305b\u3093\u3002"
|
"single_instance_allowed": "\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059\u3002\u8a2d\u5b9a\u3067\u304d\u308b\u306e\u306f1\u3064\u3060\u3051\u3067\u3059\u3002"
|
||||||
},
|
},
|
||||||
"create_entry": {
|
"create_entry": {
|
||||||
"default": "\u4e00\u90e8\u306e\u30bb\u30f3\u30b5\u30fc\u306f\u3001\u30c7\u30d5\u30a9\u30eb\u30c8\u3067\u306f\u6709\u52b9\u306b\u306a\u3063\u3066\u3044\u307e\u305b\u3093\u3002\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u306e\u69cb\u6210\u5f8c\u3001\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3\u30ec\u30b8\u30b9\u30c8\u30ea\u3067\u305d\u308c\u3089\u3092\u6709\u52b9\u306b\u3067\u304d\u307e\u3059\u3002\n\u5929\u6c17\u4e88\u5831\u306f\u30c7\u30d5\u30a9\u30eb\u30c8\u3067\u306f\u6709\u52b9\u306b\u306a\u3063\u3066\u3044\u307e\u305b\u3093\u3002\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u306e\u30aa\u30d7\u30b7\u30e7\u30f3\u3067\u6709\u52b9\u306b\u3067\u304d\u307e\u3059\u3002"
|
"default": "\u4e00\u90e8\u306e\u30bb\u30f3\u30b5\u30fc\u306f\u3001\u30c7\u30d5\u30a9\u30eb\u30c8\u3067\u306f\u6709\u52b9\u306b\u306a\u3063\u3066\u3044\u307e\u305b\u3093\u3002\u7d71\u5408\u306e\u69cb\u6210\u5f8c\u3001\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3\u30ec\u30b8\u30b9\u30c8\u30ea\u3067\u305d\u308c\u3089\u3092\u6709\u52b9\u306b\u3067\u304d\u307e\u3059\u3002\n\u5929\u6c17\u4e88\u5831\u306f\u30c7\u30d5\u30a9\u30eb\u30c8\u3067\u306f\u6709\u52b9\u306b\u306a\u3063\u3066\u3044\u307e\u305b\u3093\u3002\u7d71\u5408\u306e\u30aa\u30d7\u30b7\u30e7\u30f3\u3067\u6709\u52b9\u306b\u3067\u304d\u307e\u3059\u3002"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f",
|
"cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f",
|
||||||
|
@ -34,7 +34,7 @@
|
|||||||
},
|
},
|
||||||
"system_health": {
|
"system_health": {
|
||||||
"info": {
|
"info": {
|
||||||
"can_reach_server": "Dost\u0119p do serwera AccuWeather",
|
"can_reach_server": "Dost\u0119p do serwera",
|
||||||
"remaining_requests": "Pozosta\u0142o dozwolonych \u017c\u0105da\u0144"
|
"remaining_requests": "Pozosta\u0142o dozwolonych \u017c\u0105da\u0144"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,9 @@
|
|||||||
"abort": {
|
"abort": {
|
||||||
"single_instance_allowed": "J\u00e1 configurado. Apenas uma \u00fanica configura\u00e7\u00e3o \u00e9 poss\u00edvel."
|
"single_instance_allowed": "J\u00e1 configurado. Apenas uma \u00fanica configura\u00e7\u00e3o \u00e9 poss\u00edvel."
|
||||||
},
|
},
|
||||||
|
"create_entry": {
|
||||||
|
"default": "Alguns sensores n\u00e3o s\u00e3o ativados por defeito. Podem ser ativados no registo da entidade ap\u00f3s a configura\u00e7\u00e3o da integra\u00e7\u00e3o.\nA previs\u00e3o do tempo n\u00e3o est\u00e1 ativada por defeito. Pode ativ\u00e1-la nas op\u00e7\u00f5es de integra\u00e7\u00e3o."
|
||||||
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"cannot_connect": "Falha na liga\u00e7\u00e3o",
|
"cannot_connect": "Falha na liga\u00e7\u00e3o",
|
||||||
"invalid_api_key": "Chave de API inv\u00e1lida"
|
"invalid_api_key": "Chave de API inv\u00e1lida"
|
||||||
|
@ -18,7 +18,6 @@ from homeassistant.components.weather import (
|
|||||||
)
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_NAME,
|
|
||||||
LENGTH_INCHES,
|
LENGTH_INCHES,
|
||||||
LENGTH_KILOMETERS,
|
LENGTH_KILOMETERS,
|
||||||
LENGTH_MILES,
|
LENGTH_MILES,
|
||||||
@ -31,8 +30,6 @@ from homeassistant.const import (
|
|||||||
TEMP_FAHRENHEIT,
|
TEMP_FAHRENHEIT,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.device_registry import DeviceEntryType
|
|
||||||
from homeassistant.helpers.entity import DeviceInfo
|
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
from homeassistant.util.dt import utc_from_timestamp
|
from homeassistant.util.dt import utc_from_timestamp
|
||||||
@ -45,8 +42,6 @@ from .const import (
|
|||||||
ATTRIBUTION,
|
ATTRIBUTION,
|
||||||
CONDITION_CLASSES,
|
CONDITION_CLASSES,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
MANUFACTURER,
|
|
||||||
NAME,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
PARALLEL_UPDATES = 1
|
PARALLEL_UPDATES = 1
|
||||||
@ -56,11 +51,10 @@ async def async_setup_entry(
|
|||||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Add a AccuWeather weather entity from a config_entry."""
|
"""Add a AccuWeather weather entity from a config_entry."""
|
||||||
name: str = entry.data[CONF_NAME]
|
|
||||||
|
|
||||||
coordinator: AccuWeatherDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
coordinator: AccuWeatherDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||||
|
|
||||||
async_add_entities([AccuWeatherEntity(name, coordinator)])
|
async_add_entities([AccuWeatherEntity(coordinator)])
|
||||||
|
|
||||||
|
|
||||||
class AccuWeatherEntity(
|
class AccuWeatherEntity(
|
||||||
@ -68,9 +62,9 @@ class AccuWeatherEntity(
|
|||||||
):
|
):
|
||||||
"""Define an AccuWeather entity."""
|
"""Define an AccuWeather entity."""
|
||||||
|
|
||||||
def __init__(
|
_attr_has_entity_name = True
|
||||||
self, name: str, coordinator: AccuWeatherDataUpdateCoordinator
|
|
||||||
) -> None:
|
def __init__(self, coordinator: AccuWeatherDataUpdateCoordinator) -> None:
|
||||||
"""Initialize."""
|
"""Initialize."""
|
||||||
super().__init__(coordinator)
|
super().__init__(coordinator)
|
||||||
# Coordinator data is used also for sensors which don't have units automatically
|
# Coordinator data is used also for sensors which don't have units automatically
|
||||||
@ -90,21 +84,9 @@ class AccuWeatherEntity(
|
|||||||
self._attr_native_temperature_unit = TEMP_FAHRENHEIT
|
self._attr_native_temperature_unit = TEMP_FAHRENHEIT
|
||||||
self._attr_native_visibility_unit = LENGTH_MILES
|
self._attr_native_visibility_unit = LENGTH_MILES
|
||||||
self._attr_native_wind_speed_unit = SPEED_MILES_PER_HOUR
|
self._attr_native_wind_speed_unit = SPEED_MILES_PER_HOUR
|
||||||
self._attr_name = name
|
|
||||||
self._attr_unique_id = coordinator.location_key
|
self._attr_unique_id = coordinator.location_key
|
||||||
self._attr_attribution = ATTRIBUTION
|
self._attr_attribution = ATTRIBUTION
|
||||||
self._attr_device_info = DeviceInfo(
|
self._attr_device_info = coordinator.device_info
|
||||||
entry_type=DeviceEntryType.SERVICE,
|
|
||||||
identifiers={(DOMAIN, coordinator.location_key)},
|
|
||||||
manufacturer=MANUFACTURER,
|
|
||||||
name=NAME,
|
|
||||||
# You don't need to provide specific details for the URL,
|
|
||||||
# so passing in _ characters is fine if the location key
|
|
||||||
# is correct
|
|
||||||
configuration_url="http://accuweather.com/en/"
|
|
||||||
f"_/_/{coordinator.location_key}/"
|
|
||||||
f"weather-forecast/{coordinator.location_key}/",
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def condition(self) -> str | None:
|
def condition(self) -> str | None:
|
||||||
|
@ -19,7 +19,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
hass.data.setdefault(DOMAIN, {})[config_entry.entry_id] = hub
|
hass.data.setdefault(DOMAIN, {})[config_entry.entry_id] = hub
|
||||||
hass.config_entries.async_setup_platforms(config_entry, PLATFORMS)
|
await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ PLATFORMS = [Platform.CLIMATE]
|
|||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
"""Set up Adax from a config entry."""
|
"""Set up Adax from a config entry."""
|
||||||
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
17
homeassistant/components/adax/translations/pt.json
Normal file
17
homeassistant/components/adax/translations/pt.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"cannot_connect": "Falha na liga\u00e7\u00e3o"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"cloud": {
|
||||||
|
"data": {
|
||||||
|
"password": "Palavra-passe"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,10 @@
|
|||||||
"""Support for AdGuard Home."""
|
"""Support for AdGuard Home."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
from adguardhome import AdGuardHome, AdGuardHomeConnectionError
|
||||||
|
|
||||||
from adguardhome import AdGuardHome, AdGuardHomeConnectionError, AdGuardHomeError
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.config_entries import SOURCE_HASSIO, ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_HOST,
|
CONF_HOST,
|
||||||
CONF_NAME,
|
CONF_NAME,
|
||||||
@ -22,13 +20,10 @@ from homeassistant.core import HomeAssistant, ServiceCall
|
|||||||
from homeassistant.exceptions import ConfigEntryNotReady
|
from homeassistant.exceptions import ConfigEntryNotReady
|
||||||
from homeassistant.helpers import config_validation as cv
|
from homeassistant.helpers import config_validation as cv
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
from homeassistant.helpers.device_registry import DeviceEntryType
|
|
||||||
from homeassistant.helpers.entity import DeviceInfo, Entity
|
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
CONF_FORCE,
|
CONF_FORCE,
|
||||||
DATA_ADGUARD_CLIENT,
|
DATA_ADGUARD_CLIENT,
|
||||||
DATA_ADGUARD_VERSION,
|
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
SERVICE_ADD_URL,
|
SERVICE_ADD_URL,
|
||||||
SERVICE_DISABLE_URL,
|
SERVICE_DISABLE_URL,
|
||||||
@ -37,8 +32,6 @@ from .const import (
|
|||||||
SERVICE_REMOVE_URL,
|
SERVICE_REMOVE_URL,
|
||||||
)
|
)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
SERVICE_URL_SCHEMA = vol.Schema({vol.Required(CONF_URL): cv.url})
|
SERVICE_URL_SCHEMA = vol.Schema({vol.Required(CONF_URL): cv.url})
|
||||||
SERVICE_ADD_URL_SCHEMA = vol.Schema(
|
SERVICE_ADD_URL_SCHEMA = vol.Schema(
|
||||||
{vol.Required(CONF_NAME): cv.string, vol.Required(CONF_URL): cv.url}
|
{vol.Required(CONF_NAME): cv.string, vol.Required(CONF_URL): cv.url}
|
||||||
@ -70,7 +63,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
except AdGuardHomeConnectionError as exception:
|
except AdGuardHomeConnectionError as exception:
|
||||||
raise ConfigEntryNotReady from exception
|
raise ConfigEntryNotReady from exception
|
||||||
|
|
||||||
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
|
|
||||||
async def add_url(call: ServiceCall) -> None:
|
async def add_url(call: ServiceCall) -> None:
|
||||||
"""Service call to add a new filter subscription to AdGuard Home."""
|
"""Service call to add a new filter subscription to AdGuard Home."""
|
||||||
@ -127,91 +120,3 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
del hass.data[DOMAIN]
|
del hass.data[DOMAIN]
|
||||||
|
|
||||||
return unload_ok
|
return unload_ok
|
||||||
|
|
||||||
|
|
||||||
class AdGuardHomeEntity(Entity):
|
|
||||||
"""Defines a base AdGuard Home entity."""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
adguard: AdGuardHome,
|
|
||||||
entry: ConfigEntry,
|
|
||||||
name: str,
|
|
||||||
icon: str,
|
|
||||||
enabled_default: bool = True,
|
|
||||||
) -> None:
|
|
||||||
"""Initialize the AdGuard Home entity."""
|
|
||||||
self._available = True
|
|
||||||
self._enabled_default = enabled_default
|
|
||||||
self._icon = icon
|
|
||||||
self._name = name
|
|
||||||
self._entry = entry
|
|
||||||
self.adguard = adguard
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self) -> str:
|
|
||||||
"""Return the name of the entity."""
|
|
||||||
return self._name
|
|
||||||
|
|
||||||
@property
|
|
||||||
def icon(self) -> str:
|
|
||||||
"""Return the mdi icon of the entity."""
|
|
||||||
return self._icon
|
|
||||||
|
|
||||||
@property
|
|
||||||
def entity_registry_enabled_default(self) -> bool:
|
|
||||||
"""Return if the entity should be enabled when first added to the entity registry."""
|
|
||||||
return self._enabled_default
|
|
||||||
|
|
||||||
@property
|
|
||||||
def available(self) -> bool:
|
|
||||||
"""Return True if entity is available."""
|
|
||||||
return self._available
|
|
||||||
|
|
||||||
async def async_update(self) -> None:
|
|
||||||
"""Update AdGuard Home entity."""
|
|
||||||
if not self.enabled:
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
await self._adguard_update()
|
|
||||||
self._available = True
|
|
||||||
except AdGuardHomeError:
|
|
||||||
if self._available:
|
|
||||||
_LOGGER.debug(
|
|
||||||
"An error occurred while updating AdGuard Home sensor",
|
|
||||||
exc_info=True,
|
|
||||||
)
|
|
||||||
self._available = False
|
|
||||||
|
|
||||||
async def _adguard_update(self) -> None:
|
|
||||||
"""Update AdGuard Home entity."""
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
|
|
||||||
class AdGuardHomeDeviceEntity(AdGuardHomeEntity):
|
|
||||||
"""Defines a AdGuard Home device entity."""
|
|
||||||
|
|
||||||
@property
|
|
||||||
def device_info(self) -> DeviceInfo:
|
|
||||||
"""Return device information about this AdGuard Home instance."""
|
|
||||||
if self._entry.source == SOURCE_HASSIO:
|
|
||||||
config_url = "homeassistant://hassio/ingress/a0d7b954_adguard"
|
|
||||||
else:
|
|
||||||
if self.adguard.tls:
|
|
||||||
config_url = f"https://{self.adguard.host}:{self.adguard.port}"
|
|
||||||
else:
|
|
||||||
config_url = f"http://{self.adguard.host}:{self.adguard.port}"
|
|
||||||
|
|
||||||
return DeviceInfo(
|
|
||||||
entry_type=DeviceEntryType.SERVICE,
|
|
||||||
identifiers={
|
|
||||||
(DOMAIN, self.adguard.host, self.adguard.port, self.adguard.base_path) # type: ignore[arg-type]
|
|
||||||
},
|
|
||||||
manufacturer="AdGuard Team",
|
|
||||||
name="AdGuard Home",
|
|
||||||
sw_version=self.hass.data[DOMAIN][self._entry.entry_id].get(
|
|
||||||
DATA_ADGUARD_VERSION
|
|
||||||
),
|
|
||||||
configuration_url=config_url,
|
|
||||||
)
|
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
"""Constants for the AdGuard Home integration."""
|
"""Constants for the AdGuard Home integration."""
|
||||||
|
import logging
|
||||||
|
|
||||||
DOMAIN = "adguard"
|
DOMAIN = "adguard"
|
||||||
|
|
||||||
|
LOGGER = logging.getLogger(__package__)
|
||||||
|
|
||||||
DATA_ADGUARD_CLIENT = "adguard_client"
|
DATA_ADGUARD_CLIENT = "adguard_client"
|
||||||
DATA_ADGUARD_VERSION = "adguard_version"
|
DATA_ADGUARD_VERSION = "adguard_version"
|
||||||
|
|
||||||
|
69
homeassistant/components/adguard/entity.py
Normal file
69
homeassistant/components/adguard/entity.py
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
"""AdGuard Home base entity."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from adguardhome import AdGuardHome, AdGuardHomeError
|
||||||
|
|
||||||
|
from homeassistant.config_entries import SOURCE_HASSIO, ConfigEntry
|
||||||
|
from homeassistant.helpers.device_registry import DeviceEntryType
|
||||||
|
from homeassistant.helpers.entity import DeviceInfo, Entity
|
||||||
|
|
||||||
|
from .const import DATA_ADGUARD_VERSION, DOMAIN, LOGGER
|
||||||
|
|
||||||
|
|
||||||
|
class AdGuardHomeEntity(Entity):
|
||||||
|
"""Defines a base AdGuard Home entity."""
|
||||||
|
|
||||||
|
_attr_has_entity_name = True
|
||||||
|
_attr_available = True
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
adguard: AdGuardHome,
|
||||||
|
entry: ConfigEntry,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize the AdGuard Home entity."""
|
||||||
|
self._entry = entry
|
||||||
|
self.adguard = adguard
|
||||||
|
|
||||||
|
async def async_update(self) -> None:
|
||||||
|
"""Update AdGuard Home entity."""
|
||||||
|
if not self.enabled:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
await self._adguard_update()
|
||||||
|
self._attr_available = True
|
||||||
|
except AdGuardHomeError:
|
||||||
|
if self._attr_available:
|
||||||
|
LOGGER.debug(
|
||||||
|
"An error occurred while updating AdGuard Home sensor",
|
||||||
|
exc_info=True,
|
||||||
|
)
|
||||||
|
self._attr_available = False
|
||||||
|
|
||||||
|
async def _adguard_update(self) -> None:
|
||||||
|
"""Update AdGuard Home entity."""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_info(self) -> DeviceInfo:
|
||||||
|
"""Return device information about this AdGuard Home instance."""
|
||||||
|
if self._entry.source == SOURCE_HASSIO:
|
||||||
|
config_url = "homeassistant://hassio/ingress/a0d7b954_adguard"
|
||||||
|
elif self.adguard.tls:
|
||||||
|
config_url = f"https://{self.adguard.host}:{self.adguard.port}"
|
||||||
|
else:
|
||||||
|
config_url = f"http://{self.adguard.host}:{self.adguard.port}"
|
||||||
|
|
||||||
|
return DeviceInfo(
|
||||||
|
entry_type=DeviceEntryType.SERVICE,
|
||||||
|
identifiers={
|
||||||
|
(DOMAIN, self.adguard.host, self.adguard.port, self.adguard.base_path) # type: ignore[arg-type]
|
||||||
|
},
|
||||||
|
manufacturer="AdGuard Team",
|
||||||
|
name="AdGuard Home",
|
||||||
|
sw_version=self.hass.data[DOMAIN][self._entry.entry_id].get(
|
||||||
|
DATA_ADGUARD_VERSION
|
||||||
|
),
|
||||||
|
configuration_url=config_url,
|
||||||
|
)
|
@ -1,24 +1,102 @@
|
|||||||
"""Support for AdGuard Home sensors."""
|
"""Support for AdGuard Home sensors."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Callable, Coroutine
|
||||||
|
from dataclasses import dataclass
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from adguardhome import AdGuardHome, AdGuardHomeConnectionError
|
from adguardhome import AdGuardHome, AdGuardHomeConnectionError
|
||||||
|
|
||||||
from homeassistant.components.sensor import SensorEntity
|
from homeassistant.components.sensor import SensorEntity, SensorEntityDescription
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import PERCENTAGE, TIME_MILLISECONDS
|
from homeassistant.const import PERCENTAGE, TIME_MILLISECONDS
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import PlatformNotReady
|
from homeassistant.exceptions import PlatformNotReady
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
from . import AdGuardHomeDeviceEntity
|
|
||||||
from .const import DATA_ADGUARD_CLIENT, DATA_ADGUARD_VERSION, DOMAIN
|
from .const import DATA_ADGUARD_CLIENT, DATA_ADGUARD_VERSION, DOMAIN
|
||||||
|
from .entity import AdGuardHomeEntity
|
||||||
|
|
||||||
SCAN_INTERVAL = timedelta(seconds=300)
|
SCAN_INTERVAL = timedelta(seconds=300)
|
||||||
PARALLEL_UPDATES = 4
|
PARALLEL_UPDATES = 4
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class AdGuardHomeEntityDescriptionMixin:
|
||||||
|
"""Mixin for required keys."""
|
||||||
|
|
||||||
|
value_fn: Callable[[AdGuardHome], Coroutine[Any, Any, int | float]]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class AdGuardHomeEntityDescription(
|
||||||
|
SensorEntityDescription, AdGuardHomeEntityDescriptionMixin
|
||||||
|
):
|
||||||
|
"""Describes AdGuard Home sensor entity."""
|
||||||
|
|
||||||
|
|
||||||
|
SENSORS: tuple[AdGuardHomeEntityDescription, ...] = (
|
||||||
|
AdGuardHomeEntityDescription(
|
||||||
|
key="dns_queries",
|
||||||
|
name="DNS queries",
|
||||||
|
icon="mdi:magnify",
|
||||||
|
native_unit_of_measurement="queries",
|
||||||
|
value_fn=lambda adguard: adguard.stats.dns_queries(),
|
||||||
|
),
|
||||||
|
AdGuardHomeEntityDescription(
|
||||||
|
key="blocked_filtering",
|
||||||
|
name="DNS queries blocked",
|
||||||
|
icon="mdi:magnify-close",
|
||||||
|
native_unit_of_measurement="queries",
|
||||||
|
value_fn=lambda adguard: adguard.stats.blocked_filtering(),
|
||||||
|
),
|
||||||
|
AdGuardHomeEntityDescription(
|
||||||
|
key="blocked_percentage",
|
||||||
|
name="DNS queries blocked ratio",
|
||||||
|
icon="mdi:magnify-close",
|
||||||
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
|
value_fn=lambda adguard: adguard.stats.blocked_percentage(),
|
||||||
|
),
|
||||||
|
AdGuardHomeEntityDescription(
|
||||||
|
key="blocked_parental",
|
||||||
|
name="Parental control blocked",
|
||||||
|
icon="mdi:human-male-girl",
|
||||||
|
native_unit_of_measurement="requests",
|
||||||
|
value_fn=lambda adguard: adguard.stats.replaced_parental(),
|
||||||
|
),
|
||||||
|
AdGuardHomeEntityDescription(
|
||||||
|
key="blocked_safebrowsing",
|
||||||
|
name="Safe browsing blocked",
|
||||||
|
icon="mdi:shield-half-full",
|
||||||
|
native_unit_of_measurement="requests",
|
||||||
|
value_fn=lambda adguard: adguard.stats.replaced_safebrowsing(),
|
||||||
|
),
|
||||||
|
AdGuardHomeEntityDescription(
|
||||||
|
key="enforced_safesearch",
|
||||||
|
name="Safe searches enforced",
|
||||||
|
icon="mdi:shield-search",
|
||||||
|
native_unit_of_measurement="requests",
|
||||||
|
value_fn=lambda adguard: adguard.stats.replaced_safesearch(),
|
||||||
|
),
|
||||||
|
AdGuardHomeEntityDescription(
|
||||||
|
key="average_speed",
|
||||||
|
name="Average processing speed",
|
||||||
|
icon="mdi:speedometer",
|
||||||
|
native_unit_of_measurement=TIME_MILLISECONDS,
|
||||||
|
value_fn=lambda adguard: adguard.stats.avg_processing_time(),
|
||||||
|
),
|
||||||
|
AdGuardHomeEntityDescription(
|
||||||
|
key="rules_count",
|
||||||
|
name="Rules count",
|
||||||
|
icon="mdi:counter",
|
||||||
|
native_unit_of_measurement="rules",
|
||||||
|
value_fn=lambda adguard: adguard.filtering.rules_count(allowlist=False),
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry: ConfigEntry,
|
entry: ConfigEntry,
|
||||||
@ -34,215 +112,39 @@ async def async_setup_entry(
|
|||||||
|
|
||||||
hass.data[DOMAIN][entry.entry_id][DATA_ADGUARD_VERSION] = version
|
hass.data[DOMAIN][entry.entry_id][DATA_ADGUARD_VERSION] = version
|
||||||
|
|
||||||
sensors = [
|
async_add_entities(
|
||||||
AdGuardHomeDNSQueriesSensor(adguard, entry),
|
[AdGuardHomeSensor(adguard, entry, description) for description in SENSORS],
|
||||||
AdGuardHomeBlockedFilteringSensor(adguard, entry),
|
True,
|
||||||
AdGuardHomePercentageBlockedSensor(adguard, entry),
|
)
|
||||||
AdGuardHomeReplacedParentalSensor(adguard, entry),
|
|
||||||
AdGuardHomeReplacedSafeBrowsingSensor(adguard, entry),
|
|
||||||
AdGuardHomeReplacedSafeSearchSensor(adguard, entry),
|
|
||||||
AdGuardHomeAverageProcessingTimeSensor(adguard, entry),
|
|
||||||
AdGuardHomeRulesCountSensor(adguard, entry),
|
|
||||||
]
|
|
||||||
|
|
||||||
async_add_entities(sensors, True)
|
|
||||||
|
|
||||||
|
|
||||||
class AdGuardHomeSensor(AdGuardHomeDeviceEntity, SensorEntity):
|
class AdGuardHomeSensor(AdGuardHomeEntity, SensorEntity):
|
||||||
"""Defines a AdGuard Home sensor."""
|
"""Defines a AdGuard Home sensor."""
|
||||||
|
|
||||||
|
entity_description: AdGuardHomeEntityDescription
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
adguard: AdGuardHome,
|
adguard: AdGuardHome,
|
||||||
entry: ConfigEntry,
|
entry: ConfigEntry,
|
||||||
name: str,
|
description: AdGuardHomeEntityDescription,
|
||||||
icon: str,
|
|
||||||
measurement: str,
|
|
||||||
unit_of_measurement: str,
|
|
||||||
enabled_default: bool = True,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize AdGuard Home sensor."""
|
"""Initialize AdGuard Home sensor."""
|
||||||
self._state: int | str | None = None
|
super().__init__(adguard, entry)
|
||||||
self._unit_of_measurement = unit_of_measurement
|
self.entity_description = description
|
||||||
self.measurement = measurement
|
self._attr_unique_id = "_".join(
|
||||||
|
|
||||||
super().__init__(adguard, entry, name, icon, enabled_default)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def unique_id(self) -> str:
|
|
||||||
"""Return the unique ID for this sensor."""
|
|
||||||
return "_".join(
|
|
||||||
[
|
[
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
self.adguard.host,
|
adguard.host,
|
||||||
str(self.adguard.port),
|
str(adguard.port),
|
||||||
"sensor",
|
"sensor",
|
||||||
self.measurement,
|
description.key,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
|
||||||
def native_value(self) -> int | str | None:
|
|
||||||
"""Return the state of the sensor."""
|
|
||||||
return self._state
|
|
||||||
|
|
||||||
@property
|
|
||||||
def native_unit_of_measurement(self) -> str | None:
|
|
||||||
"""Return the unit this state is expressed in."""
|
|
||||||
return self._unit_of_measurement
|
|
||||||
|
|
||||||
|
|
||||||
class AdGuardHomeDNSQueriesSensor(AdGuardHomeSensor):
|
|
||||||
"""Defines a AdGuard Home DNS Queries sensor."""
|
|
||||||
|
|
||||||
def __init__(self, adguard: AdGuardHome, entry: ConfigEntry) -> None:
|
|
||||||
"""Initialize AdGuard Home sensor."""
|
|
||||||
super().__init__(
|
|
||||||
adguard,
|
|
||||||
entry,
|
|
||||||
"AdGuard DNS Queries",
|
|
||||||
"mdi:magnify",
|
|
||||||
"dns_queries",
|
|
||||||
"queries",
|
|
||||||
)
|
|
||||||
|
|
||||||
async def _adguard_update(self) -> None:
|
async def _adguard_update(self) -> None:
|
||||||
"""Update AdGuard Home entity."""
|
"""Update AdGuard Home entity."""
|
||||||
self._state = await self.adguard.stats.dns_queries()
|
value = await self.entity_description.value_fn(self.adguard)
|
||||||
|
self._attr_native_value = value
|
||||||
|
if isinstance(value, float):
|
||||||
class AdGuardHomeBlockedFilteringSensor(AdGuardHomeSensor):
|
self._attr_native_value = f"{value:.2f}"
|
||||||
"""Defines a AdGuard Home blocked by filtering sensor."""
|
|
||||||
|
|
||||||
def __init__(self, adguard: AdGuardHome, entry: ConfigEntry) -> None:
|
|
||||||
"""Initialize AdGuard Home sensor."""
|
|
||||||
super().__init__(
|
|
||||||
adguard,
|
|
||||||
entry,
|
|
||||||
"AdGuard DNS Queries Blocked",
|
|
||||||
"mdi:magnify-close",
|
|
||||||
"blocked_filtering",
|
|
||||||
"queries",
|
|
||||||
enabled_default=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
async def _adguard_update(self) -> None:
|
|
||||||
"""Update AdGuard Home entity."""
|
|
||||||
self._state = await self.adguard.stats.blocked_filtering()
|
|
||||||
|
|
||||||
|
|
||||||
class AdGuardHomePercentageBlockedSensor(AdGuardHomeSensor):
|
|
||||||
"""Defines a AdGuard Home blocked percentage sensor."""
|
|
||||||
|
|
||||||
def __init__(self, adguard: AdGuardHome, entry: ConfigEntry) -> None:
|
|
||||||
"""Initialize AdGuard Home sensor."""
|
|
||||||
super().__init__(
|
|
||||||
adguard,
|
|
||||||
entry,
|
|
||||||
"AdGuard DNS Queries Blocked Ratio",
|
|
||||||
"mdi:magnify-close",
|
|
||||||
"blocked_percentage",
|
|
||||||
PERCENTAGE,
|
|
||||||
)
|
|
||||||
|
|
||||||
async def _adguard_update(self) -> None:
|
|
||||||
"""Update AdGuard Home entity."""
|
|
||||||
percentage = await self.adguard.stats.blocked_percentage()
|
|
||||||
self._state = f"{percentage:.2f}"
|
|
||||||
|
|
||||||
|
|
||||||
class AdGuardHomeReplacedParentalSensor(AdGuardHomeSensor):
|
|
||||||
"""Defines a AdGuard Home replaced by parental control sensor."""
|
|
||||||
|
|
||||||
def __init__(self, adguard: AdGuardHome, entry: ConfigEntry) -> None:
|
|
||||||
"""Initialize AdGuard Home sensor."""
|
|
||||||
super().__init__(
|
|
||||||
adguard,
|
|
||||||
entry,
|
|
||||||
"AdGuard Parental Control Blocked",
|
|
||||||
"mdi:human-male-girl",
|
|
||||||
"blocked_parental",
|
|
||||||
"requests",
|
|
||||||
)
|
|
||||||
|
|
||||||
async def _adguard_update(self) -> None:
|
|
||||||
"""Update AdGuard Home entity."""
|
|
||||||
self._state = await self.adguard.stats.replaced_parental()
|
|
||||||
|
|
||||||
|
|
||||||
class AdGuardHomeReplacedSafeBrowsingSensor(AdGuardHomeSensor):
|
|
||||||
"""Defines a AdGuard Home replaced by safe browsing sensor."""
|
|
||||||
|
|
||||||
def __init__(self, adguard: AdGuardHome, entry: ConfigEntry) -> None:
|
|
||||||
"""Initialize AdGuard Home sensor."""
|
|
||||||
super().__init__(
|
|
||||||
adguard,
|
|
||||||
entry,
|
|
||||||
"AdGuard Safe Browsing Blocked",
|
|
||||||
"mdi:shield-half-full",
|
|
||||||
"blocked_safebrowsing",
|
|
||||||
"requests",
|
|
||||||
)
|
|
||||||
|
|
||||||
async def _adguard_update(self) -> None:
|
|
||||||
"""Update AdGuard Home entity."""
|
|
||||||
self._state = await self.adguard.stats.replaced_safebrowsing()
|
|
||||||
|
|
||||||
|
|
||||||
class AdGuardHomeReplacedSafeSearchSensor(AdGuardHomeSensor):
|
|
||||||
"""Defines a AdGuard Home replaced by safe search sensor."""
|
|
||||||
|
|
||||||
def __init__(self, adguard: AdGuardHome, entry: ConfigEntry) -> None:
|
|
||||||
"""Initialize AdGuard Home sensor."""
|
|
||||||
super().__init__(
|
|
||||||
adguard,
|
|
||||||
entry,
|
|
||||||
"AdGuard Safe Searches Enforced",
|
|
||||||
"mdi:shield-search",
|
|
||||||
"enforced_safesearch",
|
|
||||||
"requests",
|
|
||||||
)
|
|
||||||
|
|
||||||
async def _adguard_update(self) -> None:
|
|
||||||
"""Update AdGuard Home entity."""
|
|
||||||
self._state = await self.adguard.stats.replaced_safesearch()
|
|
||||||
|
|
||||||
|
|
||||||
class AdGuardHomeAverageProcessingTimeSensor(AdGuardHomeSensor):
|
|
||||||
"""Defines a AdGuard Home average processing time sensor."""
|
|
||||||
|
|
||||||
def __init__(self, adguard: AdGuardHome, entry: ConfigEntry) -> None:
|
|
||||||
"""Initialize AdGuard Home sensor."""
|
|
||||||
super().__init__(
|
|
||||||
adguard,
|
|
||||||
entry,
|
|
||||||
"AdGuard Average Processing Speed",
|
|
||||||
"mdi:speedometer",
|
|
||||||
"average_speed",
|
|
||||||
TIME_MILLISECONDS,
|
|
||||||
)
|
|
||||||
|
|
||||||
async def _adguard_update(self) -> None:
|
|
||||||
"""Update AdGuard Home entity."""
|
|
||||||
average = await self.adguard.stats.avg_processing_time()
|
|
||||||
self._state = f"{average:.2f}"
|
|
||||||
|
|
||||||
|
|
||||||
class AdGuardHomeRulesCountSensor(AdGuardHomeSensor):
|
|
||||||
"""Defines a AdGuard Home rules count sensor."""
|
|
||||||
|
|
||||||
def __init__(self, adguard: AdGuardHome, entry: ConfigEntry) -> None:
|
|
||||||
"""Initialize AdGuard Home sensor."""
|
|
||||||
super().__init__(
|
|
||||||
adguard,
|
|
||||||
entry,
|
|
||||||
"AdGuard Rules Count",
|
|
||||||
"mdi:counter",
|
|
||||||
"rules_count",
|
|
||||||
"rules",
|
|
||||||
enabled_default=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
async def _adguard_update(self) -> None:
|
|
||||||
"""Update AdGuard Home entity."""
|
|
||||||
self._state = await self.adguard.filtering.rules_count(allowlist=False)
|
|
||||||
|
@ -1,27 +1,94 @@
|
|||||||
"""Support for AdGuard Home switches."""
|
"""Support for AdGuard Home switches."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Callable, Coroutine
|
||||||
|
from dataclasses import dataclass
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import logging
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from adguardhome import AdGuardHome, AdGuardHomeConnectionError, AdGuardHomeError
|
from adguardhome import AdGuardHome, AdGuardHomeConnectionError, AdGuardHomeError
|
||||||
|
|
||||||
from homeassistant.components.switch import SwitchEntity
|
from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import PlatformNotReady
|
from homeassistant.exceptions import PlatformNotReady
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
from . import AdGuardHomeDeviceEntity
|
from .const import DATA_ADGUARD_CLIENT, DATA_ADGUARD_VERSION, DOMAIN, LOGGER
|
||||||
from .const import DATA_ADGUARD_CLIENT, DATA_ADGUARD_VERSION, DOMAIN
|
from .entity import AdGuardHomeEntity
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
SCAN_INTERVAL = timedelta(seconds=10)
|
SCAN_INTERVAL = timedelta(seconds=10)
|
||||||
PARALLEL_UPDATES = 1
|
PARALLEL_UPDATES = 1
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class AdGuardHomeSwitchEntityDescriptionMixin:
|
||||||
|
"""Mixin for required keys."""
|
||||||
|
|
||||||
|
is_on_fn: Callable[[AdGuardHome], Callable[[], Coroutine[Any, Any, bool]]]
|
||||||
|
turn_on_fn: Callable[[AdGuardHome], Callable[[], Coroutine[Any, Any, None]]]
|
||||||
|
turn_off_fn: Callable[[AdGuardHome], Callable[[], Coroutine[Any, Any, None]]]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class AdGuardHomeSwitchEntityDescription(
|
||||||
|
SwitchEntityDescription, AdGuardHomeSwitchEntityDescriptionMixin
|
||||||
|
):
|
||||||
|
"""Describes AdGuard Home switch entity."""
|
||||||
|
|
||||||
|
|
||||||
|
SWITCHES: tuple[AdGuardHomeSwitchEntityDescription, ...] = (
|
||||||
|
AdGuardHomeSwitchEntityDescription(
|
||||||
|
key="protection",
|
||||||
|
name="Protection",
|
||||||
|
icon="mdi:shield-check",
|
||||||
|
is_on_fn=lambda adguard: adguard.protection_enabled,
|
||||||
|
turn_on_fn=lambda adguard: adguard.enable_protection,
|
||||||
|
turn_off_fn=lambda adguard: adguard.disable_protection,
|
||||||
|
),
|
||||||
|
AdGuardHomeSwitchEntityDescription(
|
||||||
|
key="parental",
|
||||||
|
name="Parental control",
|
||||||
|
icon="mdi:shield-check",
|
||||||
|
is_on_fn=lambda adguard: adguard.parental.enabled,
|
||||||
|
turn_on_fn=lambda adguard: adguard.parental.enable,
|
||||||
|
turn_off_fn=lambda adguard: adguard.parental.disable,
|
||||||
|
),
|
||||||
|
AdGuardHomeSwitchEntityDescription(
|
||||||
|
key="safesearch",
|
||||||
|
name="Safe search",
|
||||||
|
icon="mdi:shield-check",
|
||||||
|
is_on_fn=lambda adguard: adguard.safesearch.enabled,
|
||||||
|
turn_on_fn=lambda adguard: adguard.safesearch.enable,
|
||||||
|
turn_off_fn=lambda adguard: adguard.safesearch.disable,
|
||||||
|
),
|
||||||
|
AdGuardHomeSwitchEntityDescription(
|
||||||
|
key="safebrowsing",
|
||||||
|
name="Safe browsing",
|
||||||
|
icon="mdi:shield-check",
|
||||||
|
is_on_fn=lambda adguard: adguard.safebrowsing.enabled,
|
||||||
|
turn_on_fn=lambda adguard: adguard.safebrowsing.enable,
|
||||||
|
turn_off_fn=lambda adguard: adguard.safebrowsing.disable,
|
||||||
|
),
|
||||||
|
AdGuardHomeSwitchEntityDescription(
|
||||||
|
key="filtering",
|
||||||
|
name="Filtering",
|
||||||
|
icon="mdi:shield-check",
|
||||||
|
is_on_fn=lambda adguard: adguard.filtering.enabled,
|
||||||
|
turn_on_fn=lambda adguard: adguard.filtering.enable,
|
||||||
|
turn_off_fn=lambda adguard: adguard.filtering.disable,
|
||||||
|
),
|
||||||
|
AdGuardHomeSwitchEntityDescription(
|
||||||
|
key="querylog",
|
||||||
|
name="Query log",
|
||||||
|
icon="mdi:shield-check",
|
||||||
|
is_on_fn=lambda adguard: adguard.querylog.enabled,
|
||||||
|
turn_on_fn=lambda adguard: adguard.querylog.enable,
|
||||||
|
turn_off_fn=lambda adguard: adguard.querylog.disable,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry: ConfigEntry,
|
entry: ConfigEntry,
|
||||||
@ -37,203 +104,46 @@ async def async_setup_entry(
|
|||||||
|
|
||||||
hass.data[DOMAIN][entry.entry_id][DATA_ADGUARD_VERSION] = version
|
hass.data[DOMAIN][entry.entry_id][DATA_ADGUARD_VERSION] = version
|
||||||
|
|
||||||
switches = [
|
async_add_entities(
|
||||||
AdGuardHomeProtectionSwitch(adguard, entry),
|
[AdGuardHomeSwitch(adguard, entry, description) for description in SWITCHES],
|
||||||
AdGuardHomeFilteringSwitch(adguard, entry),
|
True,
|
||||||
AdGuardHomeParentalSwitch(adguard, entry),
|
)
|
||||||
AdGuardHomeSafeBrowsingSwitch(adguard, entry),
|
|
||||||
AdGuardHomeSafeSearchSwitch(adguard, entry),
|
|
||||||
AdGuardHomeQueryLogSwitch(adguard, entry),
|
|
||||||
]
|
|
||||||
async_add_entities(switches, True)
|
|
||||||
|
|
||||||
|
|
||||||
class AdGuardHomeSwitch(AdGuardHomeDeviceEntity, SwitchEntity):
|
class AdGuardHomeSwitch(AdGuardHomeEntity, SwitchEntity):
|
||||||
"""Defines a AdGuard Home switch."""
|
"""Defines a AdGuard Home switch."""
|
||||||
|
|
||||||
|
entity_description: AdGuardHomeSwitchEntityDescription
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
adguard: AdGuardHome,
|
adguard: AdGuardHome,
|
||||||
entry: ConfigEntry,
|
entry: ConfigEntry,
|
||||||
name: str,
|
description: AdGuardHomeSwitchEntityDescription,
|
||||||
icon: str,
|
|
||||||
key: str,
|
|
||||||
enabled_default: bool = True,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize AdGuard Home switch."""
|
"""Initialize AdGuard Home switch."""
|
||||||
self._state = False
|
super().__init__(adguard, entry)
|
||||||
self._key = key
|
self.entity_description = description
|
||||||
super().__init__(adguard, entry, name, icon, enabled_default)
|
self._attr_unique_id = "_".join(
|
||||||
|
[DOMAIN, adguard.host, str(adguard.port), "switch", description.key]
|
||||||
@property
|
|
||||||
def unique_id(self) -> str:
|
|
||||||
"""Return the unique ID for this sensor."""
|
|
||||||
return "_".join(
|
|
||||||
[DOMAIN, self.adguard.host, str(self.adguard.port), "switch", self._key]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
|
||||||
def is_on(self) -> bool:
|
|
||||||
"""Return the state of the switch."""
|
|
||||||
return self._state
|
|
||||||
|
|
||||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||||
"""Turn off the switch."""
|
"""Turn off the switch."""
|
||||||
try:
|
try:
|
||||||
await self._adguard_turn_off()
|
await self.entity_description.turn_off_fn(self.adguard)()
|
||||||
except AdGuardHomeError:
|
except AdGuardHomeError:
|
||||||
_LOGGER.error("An error occurred while turning off AdGuard Home switch")
|
LOGGER.error("An error occurred while turning off AdGuard Home switch")
|
||||||
self._available = False
|
self._attr_available = False
|
||||||
|
|
||||||
async def _adguard_turn_off(self) -> None:
|
|
||||||
"""Turn off the switch."""
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||||
"""Turn on the switch."""
|
"""Turn on the switch."""
|
||||||
try:
|
try:
|
||||||
await self._adguard_turn_on()
|
await self.entity_description.turn_on_fn(self.adguard)()
|
||||||
except AdGuardHomeError:
|
except AdGuardHomeError:
|
||||||
_LOGGER.error("An error occurred while turning on AdGuard Home switch")
|
LOGGER.error("An error occurred while turning on AdGuard Home switch")
|
||||||
self._available = False
|
self._attr_available = False
|
||||||
|
|
||||||
async def _adguard_turn_on(self) -> None:
|
|
||||||
"""Turn on the switch."""
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
|
|
||||||
class AdGuardHomeProtectionSwitch(AdGuardHomeSwitch):
|
|
||||||
"""Defines a AdGuard Home protection switch."""
|
|
||||||
|
|
||||||
def __init__(self, adguard: AdGuardHome, entry: ConfigEntry) -> None:
|
|
||||||
"""Initialize AdGuard Home switch."""
|
|
||||||
super().__init__(
|
|
||||||
adguard, entry, "AdGuard Protection", "mdi:shield-check", "protection"
|
|
||||||
)
|
|
||||||
|
|
||||||
async def _adguard_turn_off(self) -> None:
|
|
||||||
"""Turn off the switch."""
|
|
||||||
await self.adguard.disable_protection()
|
|
||||||
|
|
||||||
async def _adguard_turn_on(self) -> None:
|
|
||||||
"""Turn on the switch."""
|
|
||||||
await self.adguard.enable_protection()
|
|
||||||
|
|
||||||
async def _adguard_update(self) -> None:
|
async def _adguard_update(self) -> None:
|
||||||
"""Update AdGuard Home entity."""
|
"""Update AdGuard Home entity."""
|
||||||
self._state = await self.adguard.protection_enabled()
|
self._attr_is_on = await self.entity_description.is_on_fn(self.adguard)()
|
||||||
|
|
||||||
|
|
||||||
class AdGuardHomeParentalSwitch(AdGuardHomeSwitch):
|
|
||||||
"""Defines a AdGuard Home parental control switch."""
|
|
||||||
|
|
||||||
def __init__(self, adguard: AdGuardHome, entry: ConfigEntry) -> None:
|
|
||||||
"""Initialize AdGuard Home switch."""
|
|
||||||
super().__init__(
|
|
||||||
adguard, entry, "AdGuard Parental Control", "mdi:shield-check", "parental"
|
|
||||||
)
|
|
||||||
|
|
||||||
async def _adguard_turn_off(self) -> None:
|
|
||||||
"""Turn off the switch."""
|
|
||||||
await self.adguard.parental.disable()
|
|
||||||
|
|
||||||
async def _adguard_turn_on(self) -> None:
|
|
||||||
"""Turn on the switch."""
|
|
||||||
await self.adguard.parental.enable()
|
|
||||||
|
|
||||||
async def _adguard_update(self) -> None:
|
|
||||||
"""Update AdGuard Home entity."""
|
|
||||||
self._state = await self.adguard.parental.enabled()
|
|
||||||
|
|
||||||
|
|
||||||
class AdGuardHomeSafeSearchSwitch(AdGuardHomeSwitch):
|
|
||||||
"""Defines a AdGuard Home safe search switch."""
|
|
||||||
|
|
||||||
def __init__(self, adguard: AdGuardHome, entry: ConfigEntry) -> None:
|
|
||||||
"""Initialize AdGuard Home switch."""
|
|
||||||
super().__init__(
|
|
||||||
adguard, entry, "AdGuard Safe Search", "mdi:shield-check", "safesearch"
|
|
||||||
)
|
|
||||||
|
|
||||||
async def _adguard_turn_off(self) -> None:
|
|
||||||
"""Turn off the switch."""
|
|
||||||
await self.adguard.safesearch.disable()
|
|
||||||
|
|
||||||
async def _adguard_turn_on(self) -> None:
|
|
||||||
"""Turn on the switch."""
|
|
||||||
await self.adguard.safesearch.enable()
|
|
||||||
|
|
||||||
async def _adguard_update(self) -> None:
|
|
||||||
"""Update AdGuard Home entity."""
|
|
||||||
self._state = await self.adguard.safesearch.enabled()
|
|
||||||
|
|
||||||
|
|
||||||
class AdGuardHomeSafeBrowsingSwitch(AdGuardHomeSwitch):
|
|
||||||
"""Defines a AdGuard Home safe search switch."""
|
|
||||||
|
|
||||||
def __init__(self, adguard: AdGuardHome, entry: ConfigEntry) -> None:
|
|
||||||
"""Initialize AdGuard Home switch."""
|
|
||||||
super().__init__(
|
|
||||||
adguard, entry, "AdGuard Safe Browsing", "mdi:shield-check", "safebrowsing"
|
|
||||||
)
|
|
||||||
|
|
||||||
async def _adguard_turn_off(self) -> None:
|
|
||||||
"""Turn off the switch."""
|
|
||||||
await self.adguard.safebrowsing.disable()
|
|
||||||
|
|
||||||
async def _adguard_turn_on(self) -> None:
|
|
||||||
"""Turn on the switch."""
|
|
||||||
await self.adguard.safebrowsing.enable()
|
|
||||||
|
|
||||||
async def _adguard_update(self) -> None:
|
|
||||||
"""Update AdGuard Home entity."""
|
|
||||||
self._state = await self.adguard.safebrowsing.enabled()
|
|
||||||
|
|
||||||
|
|
||||||
class AdGuardHomeFilteringSwitch(AdGuardHomeSwitch):
|
|
||||||
"""Defines a AdGuard Home filtering switch."""
|
|
||||||
|
|
||||||
def __init__(self, adguard: AdGuardHome, entry: ConfigEntry) -> None:
|
|
||||||
"""Initialize AdGuard Home switch."""
|
|
||||||
super().__init__(
|
|
||||||
adguard, entry, "AdGuard Filtering", "mdi:shield-check", "filtering"
|
|
||||||
)
|
|
||||||
|
|
||||||
async def _adguard_turn_off(self) -> None:
|
|
||||||
"""Turn off the switch."""
|
|
||||||
await self.adguard.filtering.disable()
|
|
||||||
|
|
||||||
async def _adguard_turn_on(self) -> None:
|
|
||||||
"""Turn on the switch."""
|
|
||||||
await self.adguard.filtering.enable()
|
|
||||||
|
|
||||||
async def _adguard_update(self) -> None:
|
|
||||||
"""Update AdGuard Home entity."""
|
|
||||||
self._state = await self.adguard.filtering.enabled()
|
|
||||||
|
|
||||||
|
|
||||||
class AdGuardHomeQueryLogSwitch(AdGuardHomeSwitch):
|
|
||||||
"""Defines a AdGuard Home query log switch."""
|
|
||||||
|
|
||||||
def __init__(self, adguard: AdGuardHome, entry: ConfigEntry) -> None:
|
|
||||||
"""Initialize AdGuard Home switch."""
|
|
||||||
super().__init__(
|
|
||||||
adguard,
|
|
||||||
entry,
|
|
||||||
"AdGuard Query Log",
|
|
||||||
"mdi:shield-check",
|
|
||||||
"querylog",
|
|
||||||
enabled_default=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
async def _adguard_turn_off(self) -> None:
|
|
||||||
"""Turn off the switch."""
|
|
||||||
await self.adguard.querylog.disable()
|
|
||||||
|
|
||||||
async def _adguard_turn_on(self) -> None:
|
|
||||||
"""Turn on the switch."""
|
|
||||||
await self.adguard.querylog.enable()
|
|
||||||
|
|
||||||
async def _adguard_update(self) -> None:
|
|
||||||
"""Update AdGuard Home entity."""
|
|
||||||
self._state = await self.adguard.querylog.enabled()
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"config": {
|
"config": {
|
||||||
"abort": {
|
"abort": {
|
||||||
"already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van",
|
"already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van",
|
||||||
"existing_instance_updated": "Friss\u00edtette a megl\u00e9v\u0151 konfigur\u00e1ci\u00f3t."
|
"existing_instance_updated": "A megl\u00e9v\u0151 konfigur\u00e1ci\u00f3 friss\u00edtve."
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"cannot_connect": "Sikertelen csatlakoz\u00e1s"
|
"cannot_connect": "Sikertelen csatlakoz\u00e1s"
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "O servi\u00e7o j\u00e1 est\u00e1 configurado"
|
||||||
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"cannot_connect": "Falha na liga\u00e7\u00e3o"
|
"cannot_connect": "Falha na liga\u00e7\u00e3o"
|
||||||
},
|
},
|
||||||
@ -14,7 +17,7 @@
|
|||||||
"port": "Porta",
|
"port": "Porta",
|
||||||
"ssl": "Utiliza um certificado SSL",
|
"ssl": "Utiliza um certificado SSL",
|
||||||
"username": "Nome de Utilizador",
|
"username": "Nome de Utilizador",
|
||||||
"verify_ssl": "Verificar certificado SSL"
|
"verify_ssl": "Verificar o certificado SSL"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
"async_change": async_change,
|
"async_change": async_change,
|
||||||
}
|
}
|
||||||
|
|
||||||
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ from homeassistant.helpers.entity import EntityCategory
|
|||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
from .const import DOMAIN as ADVANTAGE_AIR_DOMAIN
|
from .const import DOMAIN as ADVANTAGE_AIR_DOMAIN
|
||||||
from .entity import AdvantageAirEntity
|
from .entity import AdvantageAirAcEntity, AdvantageAirZoneEntity
|
||||||
|
|
||||||
PARALLEL_UPDATES = 0
|
PARALLEL_UPDATES = 0
|
||||||
|
|
||||||
@ -21,13 +21,13 @@ async def async_setup_entry(
|
|||||||
config_entry: ConfigEntry,
|
config_entry: ConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up AdvantageAir motion platform."""
|
"""Set up AdvantageAir Binary Sensor platform."""
|
||||||
|
|
||||||
instance = hass.data[ADVANTAGE_AIR_DOMAIN][config_entry.entry_id]
|
instance = hass.data[ADVANTAGE_AIR_DOMAIN][config_entry.entry_id]
|
||||||
|
|
||||||
entities: list[BinarySensorEntity] = []
|
entities: list[BinarySensorEntity] = []
|
||||||
for ac_key, ac_device in instance["coordinator"].data["aircons"].items():
|
for ac_key, ac_device in instance["coordinator"].data["aircons"].items():
|
||||||
entities.append(AdvantageAirZoneFilter(instance, ac_key))
|
entities.append(AdvantageAirFilter(instance, ac_key))
|
||||||
for zone_key, zone in ac_device["zones"].items():
|
for zone_key, zone in ac_device["zones"].items():
|
||||||
# Only add motion sensor when motion is enabled
|
# Only add motion sensor when motion is enabled
|
||||||
if zone["motionConfig"] >= 2:
|
if zone["motionConfig"] >= 2:
|
||||||
@ -38,19 +38,17 @@ async def async_setup_entry(
|
|||||||
async_add_entities(entities)
|
async_add_entities(entities)
|
||||||
|
|
||||||
|
|
||||||
class AdvantageAirZoneFilter(AdvantageAirEntity, BinarySensorEntity):
|
class AdvantageAirFilter(AdvantageAirAcEntity, BinarySensorEntity):
|
||||||
"""Advantage Air Filter."""
|
"""Advantage Air Filter sensor."""
|
||||||
|
|
||||||
_attr_device_class = BinarySensorDeviceClass.PROBLEM
|
_attr_device_class = BinarySensorDeviceClass.PROBLEM
|
||||||
_attr_entity_category = EntityCategory.DIAGNOSTIC
|
_attr_entity_category = EntityCategory.DIAGNOSTIC
|
||||||
|
_attr_name = "Filter"
|
||||||
|
|
||||||
def __init__(self, instance, ac_key):
|
def __init__(self, instance, ac_key):
|
||||||
"""Initialize an Advantage Air Filter."""
|
"""Initialize an Advantage Air Filter sensor."""
|
||||||
super().__init__(instance, ac_key)
|
super().__init__(instance, ac_key)
|
||||||
self._attr_name = f'{self._ac["name"]} Filter'
|
self._attr_unique_id += "-filter"
|
||||||
self._attr_unique_id = (
|
|
||||||
f'{self.coordinator.data["system"]["rid"]}-{ac_key}-filter'
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def is_on(self):
|
||||||
@ -58,18 +56,16 @@ class AdvantageAirZoneFilter(AdvantageAirEntity, BinarySensorEntity):
|
|||||||
return self._ac["filterCleanStatus"]
|
return self._ac["filterCleanStatus"]
|
||||||
|
|
||||||
|
|
||||||
class AdvantageAirZoneMotion(AdvantageAirEntity, BinarySensorEntity):
|
class AdvantageAirZoneMotion(AdvantageAirZoneEntity, BinarySensorEntity):
|
||||||
"""Advantage Air Zone Motion."""
|
"""Advantage Air Zone Motion sensor."""
|
||||||
|
|
||||||
_attr_device_class = BinarySensorDeviceClass.MOTION
|
_attr_device_class = BinarySensorDeviceClass.MOTION
|
||||||
|
|
||||||
def __init__(self, instance, ac_key, zone_key):
|
def __init__(self, instance, ac_key, zone_key):
|
||||||
"""Initialize an Advantage Air Zone Motion."""
|
"""Initialize an Advantage Air Zone Motion sensor."""
|
||||||
super().__init__(instance, ac_key, zone_key)
|
super().__init__(instance, ac_key, zone_key)
|
||||||
self._attr_name = f'{self._zone["name"]} Motion'
|
self._attr_name = f'{self._zone["name"]} motion'
|
||||||
self._attr_unique_id = (
|
self._attr_unique_id += "-motion"
|
||||||
f'{self.coordinator.data["system"]["rid"]}-{ac_key}-{zone_key}-motion'
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def is_on(self):
|
||||||
@ -77,19 +73,17 @@ class AdvantageAirZoneMotion(AdvantageAirEntity, BinarySensorEntity):
|
|||||||
return self._zone["motion"] == 20
|
return self._zone["motion"] == 20
|
||||||
|
|
||||||
|
|
||||||
class AdvantageAirZoneMyZone(AdvantageAirEntity, BinarySensorEntity):
|
class AdvantageAirZoneMyZone(AdvantageAirZoneEntity, BinarySensorEntity):
|
||||||
"""Advantage Air Zone MyZone."""
|
"""Advantage Air Zone MyZone sensor."""
|
||||||
|
|
||||||
_attr_entity_registry_enabled_default = False
|
_attr_entity_registry_enabled_default = False
|
||||||
_attr_entity_category = EntityCategory.DIAGNOSTIC
|
_attr_entity_category = EntityCategory.DIAGNOSTIC
|
||||||
|
|
||||||
def __init__(self, instance, ac_key, zone_key):
|
def __init__(self, instance, ac_key, zone_key):
|
||||||
"""Initialize an Advantage Air Zone MyZone."""
|
"""Initialize an Advantage Air Zone MyZone sensor."""
|
||||||
super().__init__(instance, ac_key, zone_key)
|
super().__init__(instance, ac_key, zone_key)
|
||||||
self._attr_name = f'{self._zone["name"]} MyZone'
|
self._attr_name = f'{self._zone["name"]} myZone'
|
||||||
self._attr_unique_id = (
|
self._attr_unique_id += "-myzone"
|
||||||
f'{self.coordinator.data["system"]["rid"]}-{ac_key}-{zone_key}-myzone'
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def is_on(self):
|
||||||
|
@ -15,7 +15,6 @@ from homeassistant.components.climate.const import (
|
|||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import ATTR_TEMPERATURE, PRECISION_WHOLE, TEMP_CELSIUS
|
from homeassistant.const import ATTR_TEMPERATURE, PRECISION_WHOLE, TEMP_CELSIUS
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import entity_platform
|
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
@ -25,7 +24,7 @@ from .const import (
|
|||||||
ADVANTAGE_AIR_STATE_OPEN,
|
ADVANTAGE_AIR_STATE_OPEN,
|
||||||
DOMAIN as ADVANTAGE_AIR_DOMAIN,
|
DOMAIN as ADVANTAGE_AIR_DOMAIN,
|
||||||
)
|
)
|
||||||
from .entity import AdvantageAirEntity
|
from .entity import AdvantageAirAcEntity, AdvantageAirZoneEntity
|
||||||
|
|
||||||
ADVANTAGE_AIR_HVAC_MODES = {
|
ADVANTAGE_AIR_HVAC_MODES = {
|
||||||
"heat": HVACMode.HEAT,
|
"heat": HVACMode.HEAT,
|
||||||
@ -53,7 +52,6 @@ ADVANTAGE_AIR_FAN_MODES = {
|
|||||||
HASS_FAN_MODES = {v: k for k, v in ADVANTAGE_AIR_FAN_MODES.items()}
|
HASS_FAN_MODES = {v: k for k, v in ADVANTAGE_AIR_FAN_MODES.items()}
|
||||||
FAN_SPEEDS = {FAN_LOW: 30, FAN_MEDIUM: 60, FAN_HIGH: 100}
|
FAN_SPEEDS = {FAN_LOW: 30, FAN_MEDIUM: 60, FAN_HIGH: 100}
|
||||||
|
|
||||||
ADVANTAGE_AIR_SERVICE_SET_MYZONE = "set_myzone"
|
|
||||||
ZONE_HVAC_MODES = [HVACMode.OFF, HVACMode.HEAT_COOL]
|
ZONE_HVAC_MODES = [HVACMode.OFF, HVACMode.HEAT_COOL]
|
||||||
|
|
||||||
PARALLEL_UPDATES = 0
|
PARALLEL_UPDATES = 0
|
||||||
@ -79,26 +77,14 @@ async def async_setup_entry(
|
|||||||
entities.append(AdvantageAirZone(instance, ac_key, zone_key))
|
entities.append(AdvantageAirZone(instance, ac_key, zone_key))
|
||||||
async_add_entities(entities)
|
async_add_entities(entities)
|
||||||
|
|
||||||
platform = entity_platform.async_get_current_platform()
|
|
||||||
platform.async_register_entity_service(
|
|
||||||
ADVANTAGE_AIR_SERVICE_SET_MYZONE,
|
|
||||||
{},
|
|
||||||
"set_myzone",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
class AdvantageAirAC(AdvantageAirAcEntity, ClimateEntity):
|
||||||
class AdvantageAirClimateEntity(AdvantageAirEntity, ClimateEntity):
|
"""AdvantageAir AC unit."""
|
||||||
"""AdvantageAir Climate class."""
|
|
||||||
|
|
||||||
_attr_temperature_unit = TEMP_CELSIUS
|
_attr_temperature_unit = TEMP_CELSIUS
|
||||||
_attr_target_temperature_step = PRECISION_WHOLE
|
_attr_target_temperature_step = PRECISION_WHOLE
|
||||||
_attr_max_temp = 32
|
_attr_max_temp = 32
|
||||||
_attr_min_temp = 16
|
_attr_min_temp = 16
|
||||||
|
|
||||||
|
|
||||||
class AdvantageAirAC(AdvantageAirClimateEntity):
|
|
||||||
"""AdvantageAir AC unit."""
|
|
||||||
|
|
||||||
_attr_fan_modes = [FAN_AUTO, FAN_LOW, FAN_MEDIUM, FAN_HIGH]
|
_attr_fan_modes = [FAN_AUTO, FAN_LOW, FAN_MEDIUM, FAN_HIGH]
|
||||||
_attr_hvac_modes = AC_HVAC_MODES
|
_attr_hvac_modes = AC_HVAC_MODES
|
||||||
_attr_supported_features = (
|
_attr_supported_features = (
|
||||||
@ -108,8 +94,6 @@ class AdvantageAirAC(AdvantageAirClimateEntity):
|
|||||||
def __init__(self, instance, ac_key):
|
def __init__(self, instance, ac_key):
|
||||||
"""Initialize an AdvantageAir AC unit."""
|
"""Initialize an AdvantageAir AC unit."""
|
||||||
super().__init__(instance, ac_key)
|
super().__init__(instance, ac_key)
|
||||||
self._attr_name = self._ac["name"]
|
|
||||||
self._attr_unique_id = f'{self.coordinator.data["system"]["rid"]}-{ac_key}'
|
|
||||||
if self._ac.get("myAutoModeEnabled"):
|
if self._ac.get("myAutoModeEnabled"):
|
||||||
self._attr_hvac_modes = AC_HVAC_MODES + [HVACMode.AUTO]
|
self._attr_hvac_modes = AC_HVAC_MODES + [HVACMode.AUTO]
|
||||||
|
|
||||||
@ -160,9 +144,13 @@ class AdvantageAirAC(AdvantageAirClimateEntity):
|
|||||||
await self.async_change({self.ac_key: {"info": {"setTemp": temp}}})
|
await self.async_change({self.ac_key: {"info": {"setTemp": temp}}})
|
||||||
|
|
||||||
|
|
||||||
class AdvantageAirZone(AdvantageAirClimateEntity):
|
class AdvantageAirZone(AdvantageAirZoneEntity, ClimateEntity):
|
||||||
"""AdvantageAir Zone control."""
|
"""AdvantageAir Zone control."""
|
||||||
|
|
||||||
|
_attr_temperature_unit = TEMP_CELSIUS
|
||||||
|
_attr_target_temperature_step = PRECISION_WHOLE
|
||||||
|
_attr_max_temp = 32
|
||||||
|
_attr_min_temp = 16
|
||||||
_attr_hvac_modes = ZONE_HVAC_MODES
|
_attr_hvac_modes = ZONE_HVAC_MODES
|
||||||
_attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
|
_attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
|
||||||
|
|
||||||
@ -216,12 +204,3 @@ class AdvantageAirZone(AdvantageAirClimateEntity):
|
|||||||
await self.async_change(
|
await self.async_change(
|
||||||
{self.ac_key: {"zones": {self.zone_key: {"setTemp": temp}}}}
|
{self.ac_key: {"zones": {self.zone_key: {"setTemp": temp}}}}
|
||||||
)
|
)
|
||||||
|
|
||||||
async def set_myzone(self, **kwargs):
|
|
||||||
"""Set this zone as the 'MyZone'."""
|
|
||||||
_LOGGER.warning(
|
|
||||||
"The advantage_air.set_myzone service has been deprecated and will be removed in a future version, please use the select.select_option service on the MyZone entity"
|
|
||||||
)
|
|
||||||
await self.async_change(
|
|
||||||
{self.ac_key: {"info": {"myZone": self._zone["number"]}}}
|
|
||||||
)
|
|
||||||
|
@ -16,7 +16,7 @@ from .const import (
|
|||||||
ADVANTAGE_AIR_STATE_OPEN,
|
ADVANTAGE_AIR_STATE_OPEN,
|
||||||
DOMAIN as ADVANTAGE_AIR_DOMAIN,
|
DOMAIN as ADVANTAGE_AIR_DOMAIN,
|
||||||
)
|
)
|
||||||
from .entity import AdvantageAirEntity
|
from .entity import AdvantageAirZoneEntity
|
||||||
|
|
||||||
PARALLEL_UPDATES = 0
|
PARALLEL_UPDATES = 0
|
||||||
|
|
||||||
@ -39,8 +39,8 @@ async def async_setup_entry(
|
|||||||
async_add_entities(entities)
|
async_add_entities(entities)
|
||||||
|
|
||||||
|
|
||||||
class AdvantageAirZoneVent(AdvantageAirEntity, CoverEntity):
|
class AdvantageAirZoneVent(AdvantageAirZoneEntity, CoverEntity):
|
||||||
"""Advantage Air Cover Class."""
|
"""Advantage Air Zone Vent."""
|
||||||
|
|
||||||
_attr_device_class = CoverDeviceClass.DAMPER
|
_attr_device_class = CoverDeviceClass.DAMPER
|
||||||
_attr_supported_features = (
|
_attr_supported_features = (
|
||||||
@ -50,12 +50,9 @@ class AdvantageAirZoneVent(AdvantageAirEntity, CoverEntity):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, instance, ac_key, zone_key):
|
def __init__(self, instance, ac_key, zone_key):
|
||||||
"""Initialize an Advantage Air Cover Class."""
|
"""Initialize an Advantage Air Zone Vent."""
|
||||||
super().__init__(instance, ac_key, zone_key)
|
super().__init__(instance, ac_key, zone_key)
|
||||||
self._attr_name = f'{self._zone["name"]}'
|
self._attr_name = self._zone["name"]
|
||||||
self._attr_unique_id = (
|
|
||||||
f'{self.coordinator.data["system"]["rid"]}-{ac_key}-{zone_key}'
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_closed(self) -> bool:
|
def is_closed(self) -> bool:
|
||||||
|
@ -9,24 +9,46 @@ from .const import DOMAIN
|
|||||||
class AdvantageAirEntity(CoordinatorEntity):
|
class AdvantageAirEntity(CoordinatorEntity):
|
||||||
"""Parent class for Advantage Air Entities."""
|
"""Parent class for Advantage Air Entities."""
|
||||||
|
|
||||||
def __init__(self, instance, ac_key, zone_key=None):
|
_attr_has_entity_name = True
|
||||||
"""Initialize common aspects of an Advantage Air sensor."""
|
|
||||||
|
def __init__(self, instance):
|
||||||
|
"""Initialize common aspects of an Advantage Air entity."""
|
||||||
super().__init__(instance["coordinator"])
|
super().__init__(instance["coordinator"])
|
||||||
|
self._attr_unique_id = self.coordinator.data["system"]["rid"]
|
||||||
|
|
||||||
|
|
||||||
|
class AdvantageAirAcEntity(AdvantageAirEntity):
|
||||||
|
"""Parent class for Advantage Air AC Entities."""
|
||||||
|
|
||||||
|
def __init__(self, instance, ac_key):
|
||||||
|
"""Initialize common aspects of an Advantage Air ac entity."""
|
||||||
|
super().__init__(instance)
|
||||||
self.async_change = instance["async_change"]
|
self.async_change = instance["async_change"]
|
||||||
self.ac_key = ac_key
|
self.ac_key = ac_key
|
||||||
self.zone_key = zone_key
|
self._attr_unique_id += f"-{ac_key}"
|
||||||
|
|
||||||
self._attr_device_info = DeviceInfo(
|
self._attr_device_info = DeviceInfo(
|
||||||
identifiers={(DOMAIN, self.coordinator.data["system"]["rid"])},
|
via_device=(DOMAIN, self.coordinator.data["system"]["rid"]),
|
||||||
|
identifiers={(DOMAIN, self._attr_unique_id)},
|
||||||
manufacturer="Advantage Air",
|
manufacturer="Advantage Air",
|
||||||
model=self.coordinator.data["system"]["sysType"],
|
model=self.coordinator.data["system"]["sysType"],
|
||||||
name=self.coordinator.data["system"]["name"],
|
name=self.coordinator.data["aircons"][self.ac_key]["info"]["name"],
|
||||||
sw_version=self.coordinator.data["system"]["myAppRev"],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _ac(self):
|
def _ac(self):
|
||||||
return self.coordinator.data["aircons"][self.ac_key]["info"]
|
return self.coordinator.data["aircons"][self.ac_key]["info"]
|
||||||
|
|
||||||
|
|
||||||
|
class AdvantageAirZoneEntity(AdvantageAirAcEntity):
|
||||||
|
"""Parent class for Advantage Air Zone Entities."""
|
||||||
|
|
||||||
|
def __init__(self, instance, ac_key, zone_key):
|
||||||
|
"""Initialize common aspects of an Advantage Air zone entity."""
|
||||||
|
super().__init__(instance, ac_key)
|
||||||
|
self.zone_key = zone_key
|
||||||
|
self._attr_unique_id += f"-{zone_key}"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _zone(self):
|
def _zone(self):
|
||||||
return self.coordinator.data["aircons"][self.ac_key]["zones"][self.zone_key]
|
return self.coordinator.data["aircons"][self.ac_key]["zones"][self.zone_key]
|
||||||
|
@ -5,7 +5,7 @@ from homeassistant.core import HomeAssistant
|
|||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
from .const import DOMAIN as ADVANTAGE_AIR_DOMAIN
|
from .const import DOMAIN as ADVANTAGE_AIR_DOMAIN
|
||||||
from .entity import AdvantageAirEntity
|
from .entity import AdvantageAirAcEntity
|
||||||
|
|
||||||
ADVANTAGE_AIR_INACTIVE = "Inactive"
|
ADVANTAGE_AIR_INACTIVE = "Inactive"
|
||||||
|
|
||||||
@ -15,7 +15,7 @@ async def async_setup_entry(
|
|||||||
config_entry: ConfigEntry,
|
config_entry: ConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up AdvantageAir toggle platform."""
|
"""Set up AdvantageAir select platform."""
|
||||||
|
|
||||||
instance = hass.data[ADVANTAGE_AIR_DOMAIN][config_entry.entry_id]
|
instance = hass.data[ADVANTAGE_AIR_DOMAIN][config_entry.entry_id]
|
||||||
|
|
||||||
@ -25,21 +25,19 @@ async def async_setup_entry(
|
|||||||
async_add_entities(entities)
|
async_add_entities(entities)
|
||||||
|
|
||||||
|
|
||||||
class AdvantageAirMyZone(AdvantageAirEntity, SelectEntity):
|
class AdvantageAirMyZone(AdvantageAirAcEntity, SelectEntity):
|
||||||
"""Representation of Advantage Air MyZone control."""
|
"""Representation of Advantage Air MyZone control."""
|
||||||
|
|
||||||
_attr_icon = "mdi:home-thermometer"
|
_attr_icon = "mdi:home-thermometer"
|
||||||
_attr_options = [ADVANTAGE_AIR_INACTIVE]
|
_attr_options = [ADVANTAGE_AIR_INACTIVE]
|
||||||
_number_to_name = {0: ADVANTAGE_AIR_INACTIVE}
|
_number_to_name = {0: ADVANTAGE_AIR_INACTIVE}
|
||||||
_name_to_number = {ADVANTAGE_AIR_INACTIVE: 0}
|
_name_to_number = {ADVANTAGE_AIR_INACTIVE: 0}
|
||||||
|
_attr_name = "MyZone"
|
||||||
|
|
||||||
def __init__(self, instance, ac_key):
|
def __init__(self, instance, ac_key):
|
||||||
"""Initialize an Advantage Air MyZone control."""
|
"""Initialize an Advantage Air MyZone control."""
|
||||||
super().__init__(instance, ac_key)
|
super().__init__(instance, ac_key)
|
||||||
self._attr_name = f'{self._ac["name"]} MyZone'
|
self._attr_unique_id += "-myzone"
|
||||||
self._attr_unique_id = (
|
|
||||||
f'{self.coordinator.data["system"]["rid"]}-{ac_key}-myzone'
|
|
||||||
)
|
|
||||||
|
|
||||||
for zone in instance["coordinator"].data["aircons"][ac_key]["zones"].values():
|
for zone in instance["coordinator"].data["aircons"][ac_key]["zones"].values():
|
||||||
if zone["type"] > 0:
|
if zone["type"] > 0:
|
||||||
@ -49,7 +47,7 @@ class AdvantageAirMyZone(AdvantageAirEntity, SelectEntity):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def current_option(self):
|
def current_option(self):
|
||||||
"""Return the fresh air status."""
|
"""Return the current MyZone."""
|
||||||
return self._number_to_name[self._ac["myZone"]]
|
return self._number_to_name[self._ac["myZone"]]
|
||||||
|
|
||||||
async def async_select_option(self, option):
|
async def async_select_option(self, option):
|
||||||
|
@ -16,7 +16,7 @@ from homeassistant.helpers.entity import EntityCategory
|
|||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
from .const import ADVANTAGE_AIR_STATE_OPEN, DOMAIN as ADVANTAGE_AIR_DOMAIN
|
from .const import ADVANTAGE_AIR_STATE_OPEN, DOMAIN as ADVANTAGE_AIR_DOMAIN
|
||||||
from .entity import AdvantageAirEntity
|
from .entity import AdvantageAirAcEntity, AdvantageAirZoneEntity
|
||||||
|
|
||||||
ADVANTAGE_AIR_SET_COUNTDOWN_VALUE = "minutes"
|
ADVANTAGE_AIR_SET_COUNTDOWN_VALUE = "minutes"
|
||||||
ADVANTAGE_AIR_SET_COUNTDOWN_UNIT = "min"
|
ADVANTAGE_AIR_SET_COUNTDOWN_UNIT = "min"
|
||||||
@ -56,7 +56,7 @@ async def async_setup_entry(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class AdvantageAirTimeTo(AdvantageAirEntity, SensorEntity):
|
class AdvantageAirTimeTo(AdvantageAirAcEntity, SensorEntity):
|
||||||
"""Representation of Advantage Air timer control."""
|
"""Representation of Advantage Air timer control."""
|
||||||
|
|
||||||
_attr_native_unit_of_measurement = ADVANTAGE_AIR_SET_COUNTDOWN_UNIT
|
_attr_native_unit_of_measurement = ADVANTAGE_AIR_SET_COUNTDOWN_UNIT
|
||||||
@ -67,10 +67,8 @@ class AdvantageAirTimeTo(AdvantageAirEntity, SensorEntity):
|
|||||||
super().__init__(instance, ac_key)
|
super().__init__(instance, ac_key)
|
||||||
self.action = action
|
self.action = action
|
||||||
self._time_key = f"countDownTo{action}"
|
self._time_key = f"countDownTo{action}"
|
||||||
self._attr_name = f'{self._ac["name"]} Time To {action}'
|
self._attr_name = f"Time to {action}"
|
||||||
self._attr_unique_id = (
|
self._attr_unique_id += f"-timeto{action}"
|
||||||
f'{self.coordinator.data["system"]["rid"]}-{self.ac_key}-timeto{action}'
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def native_value(self):
|
def native_value(self):
|
||||||
@ -90,7 +88,7 @@ class AdvantageAirTimeTo(AdvantageAirEntity, SensorEntity):
|
|||||||
await self.async_change({self.ac_key: {"info": {self._time_key: value}}})
|
await self.async_change({self.ac_key: {"info": {self._time_key: value}}})
|
||||||
|
|
||||||
|
|
||||||
class AdvantageAirZoneVent(AdvantageAirEntity, SensorEntity):
|
class AdvantageAirZoneVent(AdvantageAirZoneEntity, SensorEntity):
|
||||||
"""Representation of Advantage Air Zone Vent Sensor."""
|
"""Representation of Advantage Air Zone Vent Sensor."""
|
||||||
|
|
||||||
_attr_native_unit_of_measurement = PERCENTAGE
|
_attr_native_unit_of_measurement = PERCENTAGE
|
||||||
@ -100,10 +98,8 @@ class AdvantageAirZoneVent(AdvantageAirEntity, SensorEntity):
|
|||||||
def __init__(self, instance, ac_key, zone_key):
|
def __init__(self, instance, ac_key, zone_key):
|
||||||
"""Initialize an Advantage Air Zone Vent Sensor."""
|
"""Initialize an Advantage Air Zone Vent Sensor."""
|
||||||
super().__init__(instance, ac_key, zone_key=zone_key)
|
super().__init__(instance, ac_key, zone_key=zone_key)
|
||||||
self._attr_name = f'{self._zone["name"]} Vent'
|
self._attr_name = f'{self._zone["name"]} vent'
|
||||||
self._attr_unique_id = (
|
self._attr_unique_id += "-vent"
|
||||||
f'{self.coordinator.data["system"]["rid"]}-{ac_key}-{zone_key}-vent'
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def native_value(self):
|
def native_value(self):
|
||||||
@ -120,7 +116,7 @@ class AdvantageAirZoneVent(AdvantageAirEntity, SensorEntity):
|
|||||||
return "mdi:fan-off"
|
return "mdi:fan-off"
|
||||||
|
|
||||||
|
|
||||||
class AdvantageAirZoneSignal(AdvantageAirEntity, SensorEntity):
|
class AdvantageAirZoneSignal(AdvantageAirZoneEntity, SensorEntity):
|
||||||
"""Representation of Advantage Air Zone wireless signal sensor."""
|
"""Representation of Advantage Air Zone wireless signal sensor."""
|
||||||
|
|
||||||
_attr_native_unit_of_measurement = PERCENTAGE
|
_attr_native_unit_of_measurement = PERCENTAGE
|
||||||
@ -130,10 +126,8 @@ class AdvantageAirZoneSignal(AdvantageAirEntity, SensorEntity):
|
|||||||
def __init__(self, instance, ac_key, zone_key):
|
def __init__(self, instance, ac_key, zone_key):
|
||||||
"""Initialize an Advantage Air Zone wireless signal sensor."""
|
"""Initialize an Advantage Air Zone wireless signal sensor."""
|
||||||
super().__init__(instance, ac_key, zone_key)
|
super().__init__(instance, ac_key, zone_key)
|
||||||
self._attr_name = f'{self._zone["name"]} Signal'
|
self._attr_name = f'{self._zone["name"]} signal'
|
||||||
self._attr_unique_id = (
|
self._attr_unique_id += "-signal"
|
||||||
f'{self.coordinator.data["system"]["rid"]}-{ac_key}-{zone_key}-signal'
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def native_value(self):
|
def native_value(self):
|
||||||
@ -154,7 +148,7 @@ class AdvantageAirZoneSignal(AdvantageAirEntity, SensorEntity):
|
|||||||
return "mdi:wifi-strength-outline"
|
return "mdi:wifi-strength-outline"
|
||||||
|
|
||||||
|
|
||||||
class AdvantageAirZoneTemp(AdvantageAirEntity, SensorEntity):
|
class AdvantageAirZoneTemp(AdvantageAirZoneEntity, SensorEntity):
|
||||||
"""Representation of Advantage Air Zone temperature sensor."""
|
"""Representation of Advantage Air Zone temperature sensor."""
|
||||||
|
|
||||||
_attr_native_unit_of_measurement = TEMP_CELSIUS
|
_attr_native_unit_of_measurement = TEMP_CELSIUS
|
||||||
@ -166,10 +160,8 @@ class AdvantageAirZoneTemp(AdvantageAirEntity, SensorEntity):
|
|||||||
def __init__(self, instance, ac_key, zone_key):
|
def __init__(self, instance, ac_key, zone_key):
|
||||||
"""Initialize an Advantage Air Zone Temp Sensor."""
|
"""Initialize an Advantage Air Zone Temp Sensor."""
|
||||||
super().__init__(instance, ac_key, zone_key)
|
super().__init__(instance, ac_key, zone_key)
|
||||||
self._attr_name = f'{self._zone["name"]} Temperature'
|
self._attr_name = f'{self._zone["name"]} temperature'
|
||||||
self._attr_unique_id = (
|
self._attr_unique_id += "-temp"
|
||||||
f'{self.coordinator.data["system"]["rid"]}-{ac_key}-{zone_key}-temp'
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def native_value(self):
|
def native_value(self):
|
||||||
|
@ -15,11 +15,3 @@ set_time_to:
|
|||||||
min: 0
|
min: 0
|
||||||
max: 1440
|
max: 1440
|
||||||
unit_of_measurement: minutes
|
unit_of_measurement: minutes
|
||||||
|
|
||||||
set_myzone:
|
|
||||||
name: Set MyZone
|
|
||||||
description: Change which zone is set as the reference for temperature control (deprecated)
|
|
||||||
target:
|
|
||||||
entity:
|
|
||||||
integration: advantage_air
|
|
||||||
domain: climate
|
|
||||||
|
@ -9,7 +9,7 @@ from .const import (
|
|||||||
ADVANTAGE_AIR_STATE_ON,
|
ADVANTAGE_AIR_STATE_ON,
|
||||||
DOMAIN as ADVANTAGE_AIR_DOMAIN,
|
DOMAIN as ADVANTAGE_AIR_DOMAIN,
|
||||||
)
|
)
|
||||||
from .entity import AdvantageAirEntity
|
from .entity import AdvantageAirAcEntity
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
@ -17,7 +17,7 @@ async def async_setup_entry(
|
|||||||
config_entry: ConfigEntry,
|
config_entry: ConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up AdvantageAir toggle platform."""
|
"""Set up AdvantageAir switch platform."""
|
||||||
|
|
||||||
instance = hass.data[ADVANTAGE_AIR_DOMAIN][config_entry.entry_id]
|
instance = hass.data[ADVANTAGE_AIR_DOMAIN][config_entry.entry_id]
|
||||||
|
|
||||||
@ -28,18 +28,16 @@ async def async_setup_entry(
|
|||||||
async_add_entities(entities)
|
async_add_entities(entities)
|
||||||
|
|
||||||
|
|
||||||
class AdvantageAirFreshAir(AdvantageAirEntity, SwitchEntity):
|
class AdvantageAirFreshAir(AdvantageAirAcEntity, SwitchEntity):
|
||||||
"""Representation of Advantage Air fresh air control."""
|
"""Representation of Advantage Air fresh air control."""
|
||||||
|
|
||||||
_attr_icon = "mdi:air-filter"
|
_attr_icon = "mdi:air-filter"
|
||||||
|
_attr_name = "Fresh air"
|
||||||
|
|
||||||
def __init__(self, instance, ac_key):
|
def __init__(self, instance, ac_key):
|
||||||
"""Initialize an Advantage Air fresh air control."""
|
"""Initialize an Advantage Air fresh air control."""
|
||||||
super().__init__(instance, ac_key)
|
super().__init__(instance, ac_key)
|
||||||
self._attr_name = f'{self._ac["name"]} Fresh Air'
|
self._attr_unique_id += "-freshair"
|
||||||
self._attr_unique_id = (
|
|
||||||
f'{self.coordinator.data["system"]["rid"]}-{ac_key}-freshair'
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def is_on(self):
|
||||||
|
@ -40,7 +40,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
ENTRY_WEATHER_COORDINATOR: weather_coordinator,
|
ENTRY_WEATHER_COORDINATOR: weather_coordinator,
|
||||||
}
|
}
|
||||||
|
|
||||||
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
|
|
||||||
entry.async_on_unload(entry.add_update_listener(async_update_options))
|
entry.async_on_unload(entry.add_update_listener(async_update_options))
|
||||||
|
|
||||||
|
@ -12,9 +12,9 @@
|
|||||||
"api_key": "API\u30ad\u30fc",
|
"api_key": "API\u30ad\u30fc",
|
||||||
"latitude": "\u7def\u5ea6",
|
"latitude": "\u7def\u5ea6",
|
||||||
"longitude": "\u7d4c\u5ea6",
|
"longitude": "\u7d4c\u5ea6",
|
||||||
"name": "\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u306e\u540d\u524d"
|
"name": "\u7d71\u5408\u306e\u540d\u524d"
|
||||||
},
|
},
|
||||||
"description": "AEMET OpenData\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u3002 API\u30ad\u30fc\u3092\u751f\u6210\u3059\u308b\u306b\u306f\u3001https://opendata.aemet.es/centrodedescargas/altaUsuario \u306b\u30a2\u30af\u30bb\u30b9\u3057\u3066\u304f\u3060\u3055\u3044"
|
"description": "AEMET OpenData\u7d71\u5408\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u3002 API\u30ad\u30fc\u3092\u751f\u6210\u3059\u308b\u306b\u306f\u3001https://opendata.aemet.es/centrodedescargas/altaUsuario \u306b\u30a2\u30af\u30bb\u30b9\u3057\u3066\u304f\u3060\u3055\u3044"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
7
homeassistant/components/aemet/translations/pt.json
Normal file
7
homeassistant/components/aemet/translations/pt.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"error": {
|
||||||
|
"invalid_api_key": "Chave de API inv\u00e1lida"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -48,7 +48,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
|
|||||||
sw_version=agent_client.version,
|
sw_version=agent_client.version,
|
||||||
)
|
)
|
||||||
|
|
||||||
hass.config_entries.async_setup_platforms(config_entry, PLATFORMS)
|
await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -110,7 +110,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
hass.data.setdefault(DOMAIN, {})
|
hass.data.setdefault(DOMAIN, {})
|
||||||
hass.data[DOMAIN][entry.entry_id] = coordinator
|
hass.data[DOMAIN][entry.entry_id] = coordinator
|
||||||
|
|
||||||
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
|
|
||||||
# Remove air_quality entities from registry if they exist
|
# Remove air_quality entities from registry if they exist
|
||||||
ent_reg = er.async_get(hass)
|
ent_reg = er.async_get(hass)
|
||||||
|
@ -25,7 +25,6 @@ SUFFIX_LIMIT: Final = "LIMIT"
|
|||||||
|
|
||||||
ATTRIBUTION: Final = "Data provided by Airly"
|
ATTRIBUTION: Final = "Data provided by Airly"
|
||||||
CONF_USE_NEAREST: Final = "use_nearest"
|
CONF_USE_NEAREST: Final = "use_nearest"
|
||||||
DEFAULT_NAME: Final = "Airly"
|
|
||||||
DOMAIN: Final = "airly"
|
DOMAIN: Final = "airly"
|
||||||
LABEL_ADVICE: Final = "advice"
|
LABEL_ADVICE: Final = "advice"
|
||||||
MANUFACTURER: Final = "Airly sp. z o.o."
|
MANUFACTURER: Final = "Airly sp. z o.o."
|
||||||
|
@ -45,7 +45,6 @@ from .const import (
|
|||||||
ATTR_LIMIT,
|
ATTR_LIMIT,
|
||||||
ATTR_PERCENT,
|
ATTR_PERCENT,
|
||||||
ATTRIBUTION,
|
ATTRIBUTION,
|
||||||
DEFAULT_NAME,
|
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
MANUFACTURER,
|
MANUFACTURER,
|
||||||
SUFFIX_LIMIT,
|
SUFFIX_LIMIT,
|
||||||
@ -137,6 +136,7 @@ async def async_setup_entry(
|
|||||||
class AirlySensor(CoordinatorEntity[AirlyDataUpdateCoordinator], SensorEntity):
|
class AirlySensor(CoordinatorEntity[AirlyDataUpdateCoordinator], SensorEntity):
|
||||||
"""Define an Airly sensor."""
|
"""Define an Airly sensor."""
|
||||||
|
|
||||||
|
_attr_has_entity_name = True
|
||||||
entity_description: AirlySensorEntityDescription
|
entity_description: AirlySensorEntityDescription
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
@ -151,12 +151,11 @@ class AirlySensor(CoordinatorEntity[AirlyDataUpdateCoordinator], SensorEntity):
|
|||||||
entry_type=DeviceEntryType.SERVICE,
|
entry_type=DeviceEntryType.SERVICE,
|
||||||
identifiers={(DOMAIN, f"{coordinator.latitude}-{coordinator.longitude}")},
|
identifiers={(DOMAIN, f"{coordinator.latitude}-{coordinator.longitude}")},
|
||||||
manufacturer=MANUFACTURER,
|
manufacturer=MANUFACTURER,
|
||||||
name=DEFAULT_NAME,
|
name=name,
|
||||||
configuration_url=URL.format(
|
configuration_url=URL.format(
|
||||||
latitude=coordinator.latitude, longitude=coordinator.longitude
|
latitude=coordinator.latitude, longitude=coordinator.longitude
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
self._attr_name = f"{name} {description.name}"
|
|
||||||
self._attr_unique_id = (
|
self._attr_unique_id = (
|
||||||
f"{coordinator.latitude}-{coordinator.longitude}-{description.key}".lower()
|
f"{coordinator.latitude}-{coordinator.longitude}-{description.key}".lower()
|
||||||
)
|
)
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
"longitude": "\u7d4c\u5ea6",
|
"longitude": "\u7d4c\u5ea6",
|
||||||
"name": "\u540d\u524d"
|
"name": "\u540d\u524d"
|
||||||
},
|
},
|
||||||
"description": "Airly\u306e\u7a7a\u6c17\u54c1\u8cea\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u3002 API\u30ad\u30fc\u3092\u751f\u6210\u3059\u308b\u306b\u306f\u3001https://developer.airly.eu/register \u306b\u30a2\u30af\u30bb\u30b9\u3057\u3066\u304f\u3060\u3055\u3044"
|
"description": "API\u30ad\u30fc\u3092\u751f\u6210\u3059\u308b\u306b\u306f\u3001https://developer.airly.eu/register \u306b\u30a2\u30af\u30bb\u30b9\u3057\u3066\u304f\u3060\u3055\u3044"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
},
|
},
|
||||||
"system_health": {
|
"system_health": {
|
||||||
"info": {
|
"info": {
|
||||||
"can_reach_server": "Dost\u0119p do serwera Airly",
|
"can_reach_server": "Dost\u0119p do serwera",
|
||||||
"requests_per_day": "Dozwolone dzienne \u017c\u0105dania",
|
"requests_per_day": "Dozwolone dzienne \u017c\u0105dania",
|
||||||
"requests_remaining": "Pozosta\u0142o dozwolonych \u017c\u0105da\u0144"
|
"requests_remaining": "Pozosta\u0142o dozwolonych \u017c\u0105da\u0144"
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
hass.data.setdefault(DOMAIN, {})
|
hass.data.setdefault(DOMAIN, {})
|
||||||
hass.data[DOMAIN][entry.entry_id] = coordinator
|
hass.data[DOMAIN][entry.entry_id] = coordinator
|
||||||
|
|
||||||
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
"longitude": "\u7d4c\u5ea6",
|
"longitude": "\u7d4c\u5ea6",
|
||||||
"radius": "\u30b9\u30c6\u30fc\u30b7\u30e7\u30f3\u534a\u5f84(\u30de\u30a4\u30eb; \u30aa\u30d7\u30b7\u30e7\u30f3)"
|
"radius": "\u30b9\u30c6\u30fc\u30b7\u30e7\u30f3\u534a\u5f84(\u30de\u30a4\u30eb; \u30aa\u30d7\u30b7\u30e7\u30f3)"
|
||||||
},
|
},
|
||||||
"description": "AirNow\u306e\u7a7a\u6c17\u54c1\u8cea\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u3002 API\u30ad\u30fc\u3092\u751f\u6210\u3059\u308b\u306b\u306f\u3001https://docs.airnowapi.org/account/request/ \u306b\u30a2\u30af\u30bb\u30b9\u3057\u3066\u304f\u3060\u3055\u3044"
|
"description": "AirNow\u306e\u7a7a\u6c17\u54c1\u8cea\u7d71\u5408\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u3002 API\u30ad\u30fc\u3092\u751f\u6210\u3059\u308b\u306b\u306f\u3001https://docs.airnowapi.org/account/request/ \u306b\u30a2\u30af\u30bb\u30b9\u3057\u3066\u304f\u3060\u3055\u3044"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
|
|
||||||
hass.data[DOMAIN][entry.entry_id] = coordinator
|
hass.data[DOMAIN][entry.entry_id] = coordinator
|
||||||
|
|
||||||
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
7
homeassistant/components/airthings/translations/pt.json
Normal file
7
homeassistant/components/airthings/translations/pt.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"error": {
|
||||||
|
"cannot_connect": "Falha na liga\u00e7\u00e3o"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -31,7 +31,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
await coordinator.async_config_entry_first_refresh()
|
await coordinator.async_config_entry_first_refresh()
|
||||||
hass.data[DOMAIN][entry.entry_id] = coordinator
|
hass.data[DOMAIN][entry.entry_id] = coordinator
|
||||||
|
|
||||||
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
11
homeassistant/components/airtouch4/translations/pt.json
Normal file
11
homeassistant/components/airtouch4/translations/pt.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"host": "Anfitri\u00e3o"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -273,7 +273,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
if CONF_API_KEY in entry.data:
|
if CONF_API_KEY in entry.data:
|
||||||
async_sync_geo_coordinator_update_intervals(hass, entry.data[CONF_API_KEY])
|
async_sync_geo_coordinator_update_intervals(hass, entry.data[CONF_API_KEY])
|
||||||
|
|
||||||
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -62,20 +62,20 @@ SENSOR_KIND_VOC = "voc"
|
|||||||
GEOGRAPHY_SENSOR_DESCRIPTIONS = (
|
GEOGRAPHY_SENSOR_DESCRIPTIONS = (
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key=SENSOR_KIND_LEVEL,
|
key=SENSOR_KIND_LEVEL,
|
||||||
name="Air Pollution Level",
|
name="Air pollution level",
|
||||||
device_class=DEVICE_CLASS_POLLUTANT_LEVEL,
|
device_class=DEVICE_CLASS_POLLUTANT_LEVEL,
|
||||||
icon="mdi:gauge",
|
icon="mdi:gauge",
|
||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key=SENSOR_KIND_AQI,
|
key=SENSOR_KIND_AQI,
|
||||||
name="Air Quality Index",
|
name="Air quality index",
|
||||||
device_class=SensorDeviceClass.AQI,
|
device_class=SensorDeviceClass.AQI,
|
||||||
native_unit_of_measurement="AQI",
|
native_unit_of_measurement="AQI",
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key=SENSOR_KIND_POLLUTANT,
|
key=SENSOR_KIND_POLLUTANT,
|
||||||
name="Main Pollutant",
|
name="Main pollutant",
|
||||||
device_class=DEVICE_CLASS_POLLUTANT_LABEL,
|
device_class=DEVICE_CLASS_POLLUTANT_LABEL,
|
||||||
icon="mdi:chemical-weapon",
|
icon="mdi:chemical-weapon",
|
||||||
),
|
),
|
||||||
@ -85,7 +85,7 @@ GEOGRAPHY_SENSOR_LOCALES = {"cn": "Chinese", "us": "U.S."}
|
|||||||
NODE_PRO_SENSOR_DESCRIPTIONS = (
|
NODE_PRO_SENSOR_DESCRIPTIONS = (
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key=SENSOR_KIND_AQI,
|
key=SENSOR_KIND_AQI,
|
||||||
name="Air Quality Index",
|
name="Air quality index",
|
||||||
device_class=SensorDeviceClass.AQI,
|
device_class=SensorDeviceClass.AQI,
|
||||||
native_unit_of_measurement="AQI",
|
native_unit_of_measurement="AQI",
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
@ -292,6 +292,8 @@ class AirVisualGeographySensor(AirVisualEntity, SensorEntity):
|
|||||||
class AirVisualNodeProSensor(AirVisualEntity, SensorEntity):
|
class AirVisualNodeProSensor(AirVisualEntity, SensorEntity):
|
||||||
"""Define an AirVisual sensor related to a Node/Pro unit."""
|
"""Define an AirVisual sensor related to a Node/Pro unit."""
|
||||||
|
|
||||||
|
_attr_has_entity_name = True
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
coordinator: DataUpdateCoordinator,
|
coordinator: DataUpdateCoordinator,
|
||||||
@ -301,9 +303,6 @@ class AirVisualNodeProSensor(AirVisualEntity, SensorEntity):
|
|||||||
"""Initialize."""
|
"""Initialize."""
|
||||||
super().__init__(coordinator, entry, description)
|
super().__init__(coordinator, entry, description)
|
||||||
|
|
||||||
self._attr_name = (
|
|
||||||
f"{coordinator.data['settings']['node_name']} Node/Pro: {description.name}"
|
|
||||||
)
|
|
||||||
self._attr_unique_id = f"{coordinator.data['serial_number']}_{description.key}"
|
self._attr_unique_id = f"{coordinator.data['serial_number']}_{description.key}"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -10,6 +10,11 @@
|
|||||||
"invalid_api_key": "Chave de API inv\u00e1lida"
|
"invalid_api_key": "Chave de API inv\u00e1lida"
|
||||||
},
|
},
|
||||||
"step": {
|
"step": {
|
||||||
|
"geography_by_coords": {
|
||||||
|
"data": {
|
||||||
|
"latitude": "Latitude"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_pro": {
|
"node_pro": {
|
||||||
"data": {
|
"data": {
|
||||||
"ip_address": "Servidor",
|
"ip_address": "Servidor",
|
||||||
@ -18,7 +23,7 @@
|
|||||||
},
|
},
|
||||||
"reauth_confirm": {
|
"reauth_confirm": {
|
||||||
"data": {
|
"data": {
|
||||||
"api_key": ""
|
"api_key": "Chave da API"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"state": {
|
||||||
|
"airvisual__pollutant_label": {
|
||||||
|
"co": "Mon\u00f3xido de carbono",
|
||||||
|
"n2": "Di\u00f3xido de nitrog\u00e9nio",
|
||||||
|
"o3": "Ozono",
|
||||||
|
"p1": "PM10"
|
||||||
|
},
|
||||||
|
"airvisual__pollutant_level": {
|
||||||
|
"moderate": "Moderado"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -79,7 +79,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
|
|
||||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
|
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
|
||||||
|
|
||||||
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"name": "Airzone",
|
"name": "Airzone",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/airzone",
|
"documentation": "https://www.home-assistant.io/integrations/airzone",
|
||||||
"requirements": ["aioairzone==0.4.5"],
|
"requirements": ["aioairzone==0.4.6"],
|
||||||
"codeowners": ["@Noltari"],
|
"codeowners": ["@Noltari"],
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["aioairzone"]
|
"loggers": ["aioairzone"]
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
"host": "\u30db\u30b9\u30c8",
|
"host": "\u30db\u30b9\u30c8",
|
||||||
"port": "\u30dd\u30fc\u30c8"
|
"port": "\u30dd\u30fc\u30c8"
|
||||||
},
|
},
|
||||||
"description": "Airzone\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7"
|
"description": "Airzone\u7d71\u5408\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
12
homeassistant/components/airzone/translations/pt.json
Normal file
12
homeassistant/components/airzone/translations/pt.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"host": "Servidor",
|
||||||
|
"port": "Porta"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -16,7 +16,7 @@ from .const import CLIENT_ID, DOMAIN
|
|||||||
|
|
||||||
_LOGGER: Final = logging.getLogger(__name__)
|
_LOGGER: Final = logging.getLogger(__name__)
|
||||||
|
|
||||||
PLATFORMS: list[Platform] = [Platform.COVER]
|
PLATFORMS: list[Platform] = [Platform.COVER, Platform.SENSOR]
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
@ -33,7 +33,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
raise ConfigEntryNotReady("Can not connect to host") from ex
|
raise ConfigEntryNotReady("Can not connect to host") from ex
|
||||||
|
|
||||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = acc
|
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = acc
|
||||||
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ from homeassistant.const import (
|
|||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import PlatformNotReady
|
from homeassistant.exceptions import PlatformNotReady
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
from homeassistant.helpers.entity import DeviceInfo
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||||
|
|
||||||
@ -87,9 +88,19 @@ class AladdinDevice(CoverEntity):
|
|||||||
|
|
||||||
self._device_id = device["device_id"]
|
self._device_id = device["device_id"]
|
||||||
self._number = device["door_number"]
|
self._number = device["door_number"]
|
||||||
self._attr_name = device["name"]
|
self._name = device["name"]
|
||||||
self._serial = device["serial"]
|
self._serial = device["serial"]
|
||||||
self._attr_unique_id = f"{self._device_id}-{self._number}"
|
self._attr_unique_id = f"{self._device_id}-{self._number}"
|
||||||
|
self._attr_has_entity_name = True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_info(self) -> DeviceInfo | None:
|
||||||
|
"""Device information for Aladdin Connect cover."""
|
||||||
|
return DeviceInfo(
|
||||||
|
identifiers={(DOMAIN, self._device_id)},
|
||||||
|
name=self._name,
|
||||||
|
manufacturer="Overhead Door",
|
||||||
|
)
|
||||||
|
|
||||||
async def async_added_to_hass(self) -> None:
|
async def async_added_to_hass(self) -> None:
|
||||||
"""Connect Aladdin Connect to the cloud."""
|
"""Connect Aladdin Connect to the cloud."""
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"domain": "aladdin_connect",
|
"domain": "aladdin_connect",
|
||||||
"name": "Aladdin Connect",
|
"name": "Aladdin Connect",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/aladdin_connect",
|
"documentation": "https://www.home-assistant.io/integrations/aladdin_connect",
|
||||||
"requirements": ["AIOAladdinConnect==0.1.27"],
|
"requirements": ["AIOAladdinConnect==0.1.39"],
|
||||||
"codeowners": ["@mkmer"],
|
"codeowners": ["@mkmer"],
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["aladdin_connect"],
|
"loggers": ["aladdin_connect"],
|
||||||
|
116
homeassistant/components/aladdin_connect/sensor.py
Normal file
116
homeassistant/components/aladdin_connect/sensor.py
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
"""Support for Aladdin Connect Garage Door sensors."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Callable
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import cast
|
||||||
|
|
||||||
|
from AIOAladdinConnect import AladdinConnectClient
|
||||||
|
|
||||||
|
from homeassistant.components.sensor import (
|
||||||
|
SensorDeviceClass,
|
||||||
|
SensorEntity,
|
||||||
|
SensorEntityDescription,
|
||||||
|
SensorStateClass,
|
||||||
|
)
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import PERCENTAGE, SIGNAL_STRENGTH_DECIBELS
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.entity import DeviceInfo
|
||||||
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
|
from .const import DOMAIN
|
||||||
|
from .model import DoorDevice
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class AccSensorEntityDescriptionMixin:
|
||||||
|
"""Mixin for required keys."""
|
||||||
|
|
||||||
|
value_fn: Callable
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class AccSensorEntityDescription(
|
||||||
|
SensorEntityDescription, AccSensorEntityDescriptionMixin
|
||||||
|
):
|
||||||
|
"""Describes AladdinConnect sensor entity."""
|
||||||
|
|
||||||
|
|
||||||
|
SENSORS: tuple[AccSensorEntityDescription, ...] = (
|
||||||
|
AccSensorEntityDescription(
|
||||||
|
key="battery_level",
|
||||||
|
name="Battery level",
|
||||||
|
device_class=SensorDeviceClass.BATTERY,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
value_fn=AladdinConnectClient.get_battery_status,
|
||||||
|
),
|
||||||
|
AccSensorEntityDescription(
|
||||||
|
key="rssi",
|
||||||
|
name="Wi-Fi RSSI",
|
||||||
|
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
value_fn=AladdinConnectClient.get_rssi_status,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(
|
||||||
|
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||||
|
) -> None:
|
||||||
|
"""Set up Aladdin Connect sensor devices."""
|
||||||
|
|
||||||
|
acc: AladdinConnectClient = hass.data[DOMAIN][entry.entry_id]
|
||||||
|
|
||||||
|
entities = []
|
||||||
|
doors = await acc.get_doors()
|
||||||
|
|
||||||
|
for door in doors:
|
||||||
|
entities.extend(
|
||||||
|
[AladdinConnectSensor(acc, door, description) for description in SENSORS]
|
||||||
|
)
|
||||||
|
|
||||||
|
async_add_entities(entities)
|
||||||
|
|
||||||
|
|
||||||
|
class AladdinConnectSensor(SensorEntity):
|
||||||
|
"""A sensor implementation for Aladdin Connect devices."""
|
||||||
|
|
||||||
|
_device: AladdinConnectSensor
|
||||||
|
entity_description: AccSensorEntityDescription
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
acc: AladdinConnectClient,
|
||||||
|
device: DoorDevice,
|
||||||
|
description: AccSensorEntityDescription,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize a sensor for an Abode device."""
|
||||||
|
self._device_id = device["device_id"]
|
||||||
|
self._number = device["door_number"]
|
||||||
|
self._name = device["name"]
|
||||||
|
self._acc = acc
|
||||||
|
self.entity_description = description
|
||||||
|
self._attr_unique_id = f"{self._device_id}-{self._number}-{description.key}"
|
||||||
|
self._attr_has_entity_name = True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_info(self) -> DeviceInfo | None:
|
||||||
|
"""Device information for Aladdin Connect sensors."""
|
||||||
|
return DeviceInfo(
|
||||||
|
identifiers={(DOMAIN, self._device_id)},
|
||||||
|
name=self._name,
|
||||||
|
manufacturer="Overhead Door",
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def native_value(self) -> float | None:
|
||||||
|
"""Return the state of the sensor."""
|
||||||
|
return cast(
|
||||||
|
float,
|
||||||
|
self.entity_description.value_fn(self._acc, self._device_id, self._number),
|
||||||
|
)
|
@ -13,8 +13,8 @@
|
|||||||
"data": {
|
"data": {
|
||||||
"password": "\u30d1\u30b9\u30ef\u30fc\u30c9"
|
"password": "\u30d1\u30b9\u30ef\u30fc\u30c9"
|
||||||
},
|
},
|
||||||
"description": "Aladdin Connect\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3067\u306f\u3001\u30a2\u30ab\u30a6\u30f3\u30c8\u3092\u518d\u8a8d\u8a3c\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059",
|
"description": "Aladdin Connect\u7d71\u5408\u3067\u306f\u3001\u30a2\u30ab\u30a6\u30f3\u30c8\u3092\u518d\u8a8d\u8a3c\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059",
|
||||||
"title": "\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u306e\u518d\u8a8d\u8a3c"
|
"title": "\u7d71\u5408\u306e\u518d\u8a8d\u8a3c"
|
||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
|
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"reauth_successful": "Reautentica\u00e7\u00e3o bem sucedida"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"username": "Nome de Utilizador"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -13,6 +13,7 @@
|
|||||||
"armed_custom_bypass": "Armado com desvio personalizado",
|
"armed_custom_bypass": "Armado com desvio personalizado",
|
||||||
"armed_home": "Armado Casa",
|
"armed_home": "Armado Casa",
|
||||||
"armed_night": "Armado noite",
|
"armed_night": "Armado noite",
|
||||||
|
"armed_vacation": "Armado f\u00e9rias",
|
||||||
"arming": "A armar",
|
"arming": "A armar",
|
||||||
"disarmed": "Desarmado",
|
"disarmed": "Desarmado",
|
||||||
"disarming": "A desarmar",
|
"disarming": "A desarmar",
|
||||||
|
@ -131,7 +131,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
|
|
||||||
await open_connection()
|
await open_connection()
|
||||||
|
|
||||||
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -3,6 +3,9 @@
|
|||||||
"abort": {
|
"abort": {
|
||||||
"already_configured": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e"
|
"already_configured": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e"
|
||||||
},
|
},
|
||||||
|
"error": {
|
||||||
|
"cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435"
|
||||||
|
},
|
||||||
"step": {
|
"step": {
|
||||||
"protocol": {
|
"protocol": {
|
||||||
"data": {
|
"data": {
|
||||||
|
@ -903,6 +903,7 @@ class AlexaContactSensor(AlexaCapability):
|
|||||||
"en-CA",
|
"en-CA",
|
||||||
"en-IN",
|
"en-IN",
|
||||||
"en-US",
|
"en-US",
|
||||||
|
"en-GB",
|
||||||
"es-ES",
|
"es-ES",
|
||||||
"it-IT",
|
"it-IT",
|
||||||
"ja-JP",
|
"ja-JP",
|
||||||
@ -951,6 +952,7 @@ class AlexaMotionSensor(AlexaCapability):
|
|||||||
"en-CA",
|
"en-CA",
|
||||||
"en-IN",
|
"en-IN",
|
||||||
"en-US",
|
"en-US",
|
||||||
|
"en-GB",
|
||||||
"es-ES",
|
"es-ES",
|
||||||
"it-IT",
|
"it-IT",
|
||||||
"ja-JP",
|
"ja-JP",
|
||||||
|
@ -65,6 +65,7 @@ class AbstractConfig(ABC):
|
|||||||
|
|
||||||
async def async_enable_proactive_mode(self):
|
async def async_enable_proactive_mode(self):
|
||||||
"""Enable proactive mode."""
|
"""Enable proactive mode."""
|
||||||
|
_LOGGER.debug("Enable proactive mode")
|
||||||
if self._unsub_proactive_report is None:
|
if self._unsub_proactive_report is None:
|
||||||
self._unsub_proactive_report = self.hass.async_create_task(
|
self._unsub_proactive_report = self.hass.async_create_task(
|
||||||
async_enable_proactive_mode(self.hass, self)
|
async_enable_proactive_mode(self.hass, self)
|
||||||
@ -77,6 +78,7 @@ class AbstractConfig(ABC):
|
|||||||
|
|
||||||
async def async_disable_proactive_mode(self):
|
async def async_disable_proactive_mode(self):
|
||||||
"""Disable proactive mode."""
|
"""Disable proactive mode."""
|
||||||
|
_LOGGER.debug("Disable proactive mode")
|
||||||
if unsub_func := await self._unsub_proactive_report:
|
if unsub_func := await self._unsub_proactive_report:
|
||||||
unsub_func()
|
unsub_func()
|
||||||
self._unsub_proactive_report = None
|
self._unsub_proactive_report = None
|
||||||
@ -113,7 +115,6 @@ class AbstractConfig(ABC):
|
|||||||
self._store.set_authorized(authorized)
|
self._store.set_authorized(authorized)
|
||||||
if self.should_report_state != self.is_reporting_states:
|
if self.should_report_state != self.is_reporting_states:
|
||||||
if self.should_report_state:
|
if self.should_report_state:
|
||||||
_LOGGER.debug("Enable proactive mode")
|
|
||||||
try:
|
try:
|
||||||
await self.async_enable_proactive_mode()
|
await self.async_enable_proactive_mode()
|
||||||
except Exception:
|
except Exception:
|
||||||
@ -121,7 +122,6 @@ class AbstractConfig(ABC):
|
|||||||
self._store.set_authorized(False)
|
self._store.set_authorized(False)
|
||||||
raise
|
raise
|
||||||
else:
|
else:
|
||||||
_LOGGER.debug("Disable proactive mode")
|
|
||||||
await self.async_disable_proactive_mode()
|
await self.async_disable_proactive_mode()
|
||||||
|
|
||||||
|
|
||||||
|
@ -12,7 +12,6 @@ from .auth import Auth
|
|||||||
from .config import AbstractConfig
|
from .config import AbstractConfig
|
||||||
from .const import CONF_ENDPOINT, CONF_ENTITY_CONFIG, CONF_FILTER, CONF_LOCALE
|
from .const import CONF_ENDPOINT, CONF_ENTITY_CONFIG, CONF_FILTER, CONF_LOCALE
|
||||||
from .smart_home import async_handle_message
|
from .smart_home import async_handle_message
|
||||||
from .state_report import async_enable_proactive_mode
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
SMART_HOME_HTTP_ENDPOINT = "/api/alexa/smart_home"
|
SMART_HOME_HTTP_ENDPOINT = "/api/alexa/smart_home"
|
||||||
@ -104,7 +103,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> None:
|
|||||||
hass.http.register_view(SmartHomeView(smart_home_config))
|
hass.http.register_view(SmartHomeView(smart_home_config))
|
||||||
|
|
||||||
if smart_home_config.should_report_state:
|
if smart_home_config.should_report_state:
|
||||||
await async_enable_proactive_mode(hass, smart_home_config)
|
await smart_home_config.async_enable_proactive_mode()
|
||||||
|
|
||||||
|
|
||||||
class SmartHomeView(HomeAssistantView):
|
class SmartHomeView(HomeAssistantView):
|
||||||
|
@ -5,7 +5,7 @@ import asyncio
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
from typing import Optional, cast
|
from typing import Any
|
||||||
|
|
||||||
from aiohttp import ClientError, ClientSession
|
from aiohttp import ClientError, ClientSession
|
||||||
import async_timeout
|
import async_timeout
|
||||||
@ -167,8 +167,8 @@ async def _configure_almond_for_ha(
|
|||||||
return
|
return
|
||||||
|
|
||||||
_LOGGER.debug("Configuring Almond to connect to Home Assistant at %s", hass_url)
|
_LOGGER.debug("Configuring Almond to connect to Home Assistant at %s", hass_url)
|
||||||
store = storage.Store(hass, STORAGE_VERSION, STORAGE_KEY)
|
store = storage.Store[dict[str, Any]](hass, STORAGE_VERSION, STORAGE_KEY)
|
||||||
data = cast(Optional[dict], await store.async_load())
|
data = await store.async_load()
|
||||||
|
|
||||||
if data is None:
|
if data is None:
|
||||||
data = {}
|
data = {}
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
"cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f",
|
"cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f",
|
||||||
"missing_configuration": "\u30b3\u30f3\u30dd\u30fc\u30cd\u30f3\u30c8\u304c\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8\u306b\u5f93\u3063\u3066\u304f\u3060\u3055\u3044\u3002",
|
"missing_configuration": "\u30b3\u30f3\u30dd\u30fc\u30cd\u30f3\u30c8\u304c\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8\u306b\u5f93\u3063\u3066\u304f\u3060\u3055\u3044\u3002",
|
||||||
"no_url_available": "\u4f7f\u7528\u53ef\u80fd\u306aURL\u304c\u3042\u308a\u307e\u305b\u3093\u3002\u3053\u306e\u30a8\u30e9\u30fc\u306e\u8a73\u7d30\u306b\u3064\u3044\u3066\u306f\u3001[\u30d8\u30eb\u30d7\u30bb\u30af\u30b7\u30e7\u30f3\u3092\u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044]({docs_url})",
|
"no_url_available": "\u4f7f\u7528\u53ef\u80fd\u306aURL\u304c\u3042\u308a\u307e\u305b\u3093\u3002\u3053\u306e\u30a8\u30e9\u30fc\u306e\u8a73\u7d30\u306b\u3064\u3044\u3066\u306f\u3001[\u30d8\u30eb\u30d7\u30bb\u30af\u30b7\u30e7\u30f3\u3092\u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044]({docs_url})",
|
||||||
"single_instance_allowed": "\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059\u3002\u5358\u4e00\u306e\u8a2d\u5b9a\u3057\u304b\u3067\u304d\u307e\u305b\u3093\u3002"
|
"single_instance_allowed": "\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059\u3002\u8a2d\u5b9a\u3067\u304d\u308b\u306e\u306f1\u3064\u3060\u3051\u3067\u3059\u3002"
|
||||||
},
|
},
|
||||||
"step": {
|
"step": {
|
||||||
"hassio_confirm": {
|
"hassio_confirm": {
|
||||||
|
@ -3,10 +3,16 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from ambee import AirQuality, Ambee, AmbeeAuthenticationError, Pollen
|
from ambee import AirQuality, Ambee, AmbeeAuthenticationError, Pollen
|
||||||
|
|
||||||
|
from homeassistant.components.repairs import (
|
||||||
|
IssueSeverity,
|
||||||
|
async_create_issue,
|
||||||
|
async_delete_issue,
|
||||||
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, Platform
|
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, Platform
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import ConfigEntryAuthFailed
|
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||||
|
from homeassistant.helpers.typing import ConfigType
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||||
|
|
||||||
from .const import DOMAIN, LOGGER, SCAN_INTERVAL, SERVICE_AIR_QUALITY, SERVICE_POLLEN
|
from .const import DOMAIN, LOGGER, SCAN_INTERVAL, SERVICE_AIR_QUALITY, SERVICE_POLLEN
|
||||||
@ -14,6 +20,20 @@ from .const import DOMAIN, LOGGER, SCAN_INTERVAL, SERVICE_AIR_QUALITY, SERVICE_P
|
|||||||
PLATFORMS = [Platform.SENSOR]
|
PLATFORMS = [Platform.SENSOR]
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||||
|
"""Set up the Ambee integration."""
|
||||||
|
async_create_issue(
|
||||||
|
hass,
|
||||||
|
DOMAIN,
|
||||||
|
"pending_removal",
|
||||||
|
breaks_in_ha_version="2022.10.0",
|
||||||
|
is_fixable=False,
|
||||||
|
severity=IssueSeverity.WARNING,
|
||||||
|
translation_key="pending_removal",
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
"""Set up Ambee from a config entry."""
|
"""Set up Ambee from a config entry."""
|
||||||
hass.data.setdefault(DOMAIN, {}).setdefault(entry.entry_id, {})
|
hass.data.setdefault(DOMAIN, {}).setdefault(entry.entry_id, {})
|
||||||
@ -58,7 +78,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
await pollen.async_config_entry_first_refresh()
|
await pollen.async_config_entry_first_refresh()
|
||||||
hass.data[DOMAIN][entry.entry_id][SERVICE_POLLEN] = pollen
|
hass.data[DOMAIN][entry.entry_id][SERVICE_POLLEN] = pollen
|
||||||
|
|
||||||
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@ -67,4 +87,6 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||||
if unload_ok:
|
if unload_ok:
|
||||||
del hass.data[DOMAIN][entry.entry_id]
|
del hass.data[DOMAIN][entry.entry_id]
|
||||||
|
if not hass.data[DOMAIN]:
|
||||||
|
async_delete_issue(hass, DOMAIN, "pending_removal")
|
||||||
return unload_ok
|
return unload_ok
|
||||||
|
@ -27,7 +27,7 @@ SERVICE_AIR_QUALITY: Final = "air_quality"
|
|||||||
SERVICE_POLLEN: Final = "pollen"
|
SERVICE_POLLEN: Final = "pollen"
|
||||||
|
|
||||||
SERVICES: dict[str, str] = {
|
SERVICES: dict[str, str] = {
|
||||||
SERVICE_AIR_QUALITY: "Air Quality",
|
SERVICE_AIR_QUALITY: "Air quality",
|
||||||
SERVICE_POLLEN: "Pollen",
|
SERVICE_POLLEN: "Pollen",
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,25 +35,25 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
|
|||||||
SERVICE_AIR_QUALITY: [
|
SERVICE_AIR_QUALITY: [
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key="particulate_matter_2_5",
|
key="particulate_matter_2_5",
|
||||||
name="Particulate Matter < 2.5 μm",
|
name="Particulate matter < 2.5 μm",
|
||||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key="particulate_matter_10",
|
key="particulate_matter_10",
|
||||||
name="Particulate Matter < 10 μm",
|
name="Particulate matter < 10 μm",
|
||||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key="sulphur_dioxide",
|
key="sulphur_dioxide",
|
||||||
name="Sulphur Dioxide (SO2)",
|
name="Sulphur dioxide (SO2)",
|
||||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
|
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key="nitrogen_dioxide",
|
key="nitrogen_dioxide",
|
||||||
name="Nitrogen Dioxide (NO2)",
|
name="Nitrogen dioxide (NO2)",
|
||||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
|
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
@ -65,60 +65,60 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
|
|||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key="carbon_monoxide",
|
key="carbon_monoxide",
|
||||||
name="Carbon Monoxide (CO)",
|
name="Carbon monoxide (CO)",
|
||||||
device_class=SensorDeviceClass.CO,
|
device_class=SensorDeviceClass.CO,
|
||||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key="air_quality_index",
|
key="air_quality_index",
|
||||||
name="Air Quality Index (AQI)",
|
name="Air quality index (AQI)",
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
SERVICE_POLLEN: [
|
SERVICE_POLLEN: [
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key="grass",
|
key="grass",
|
||||||
name="Grass Pollen",
|
name="Grass",
|
||||||
icon="mdi:grass",
|
icon="mdi:grass",
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key="tree",
|
key="tree",
|
||||||
name="Tree Pollen",
|
name="Tree",
|
||||||
icon="mdi:tree",
|
icon="mdi:tree",
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key="weed",
|
key="weed",
|
||||||
name="Weed Pollen",
|
name="Weed",
|
||||||
icon="mdi:sprout",
|
icon="mdi:sprout",
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key="grass_risk",
|
key="grass_risk",
|
||||||
name="Grass Pollen Risk",
|
name="Grass risk",
|
||||||
icon="mdi:grass",
|
icon="mdi:grass",
|
||||||
device_class=DEVICE_CLASS_AMBEE_RISK,
|
device_class=DEVICE_CLASS_AMBEE_RISK,
|
||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key="tree_risk",
|
key="tree_risk",
|
||||||
name="Tree Pollen Risk",
|
name="Tree risk",
|
||||||
icon="mdi:tree",
|
icon="mdi:tree",
|
||||||
device_class=DEVICE_CLASS_AMBEE_RISK,
|
device_class=DEVICE_CLASS_AMBEE_RISK,
|
||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key="weed_risk",
|
key="weed_risk",
|
||||||
name="Weed Pollen Risk",
|
name="Weed risk",
|
||||||
icon="mdi:sprout",
|
icon="mdi:sprout",
|
||||||
device_class=DEVICE_CLASS_AMBEE_RISK,
|
device_class=DEVICE_CLASS_AMBEE_RISK,
|
||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key="grass_poaceae",
|
key="grass_poaceae",
|
||||||
name="Poaceae Grass Pollen",
|
name="Poaceae grass",
|
||||||
icon="mdi:grass",
|
icon="mdi:grass",
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
@ -126,7 +126,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
|
|||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key="tree_alder",
|
key="tree_alder",
|
||||||
name="Alder Tree Pollen",
|
name="Alder tree",
|
||||||
icon="mdi:tree",
|
icon="mdi:tree",
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
@ -134,7 +134,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
|
|||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key="tree_birch",
|
key="tree_birch",
|
||||||
name="Birch Tree Pollen",
|
name="Birch tree",
|
||||||
icon="mdi:tree",
|
icon="mdi:tree",
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
@ -142,7 +142,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
|
|||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key="tree_cypress",
|
key="tree_cypress",
|
||||||
name="Cypress Tree Pollen",
|
name="Cypress tree",
|
||||||
icon="mdi:tree",
|
icon="mdi:tree",
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
@ -150,7 +150,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
|
|||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key="tree_elm",
|
key="tree_elm",
|
||||||
name="Elm Tree Pollen",
|
name="Elm tree",
|
||||||
icon="mdi:tree",
|
icon="mdi:tree",
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
@ -158,7 +158,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
|
|||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key="tree_hazel",
|
key="tree_hazel",
|
||||||
name="Hazel Tree Pollen",
|
name="Hazel tree",
|
||||||
icon="mdi:tree",
|
icon="mdi:tree",
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
@ -166,7 +166,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
|
|||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key="tree_oak",
|
key="tree_oak",
|
||||||
name="Oak Tree Pollen",
|
name="Oak tree",
|
||||||
icon="mdi:tree",
|
icon="mdi:tree",
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
@ -174,7 +174,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
|
|||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key="tree_pine",
|
key="tree_pine",
|
||||||
name="Pine Tree Pollen",
|
name="Pine tree",
|
||||||
icon="mdi:tree",
|
icon="mdi:tree",
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
@ -182,7 +182,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
|
|||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key="tree_plane",
|
key="tree_plane",
|
||||||
name="Plane Tree Pollen",
|
name="Plane tree",
|
||||||
icon="mdi:tree",
|
icon="mdi:tree",
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
@ -190,7 +190,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
|
|||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key="tree_poplar",
|
key="tree_poplar",
|
||||||
name="Poplar Tree Pollen",
|
name="Poplar tree",
|
||||||
icon="mdi:tree",
|
icon="mdi:tree",
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
@ -198,7 +198,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
|
|||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key="weed_chenopod",
|
key="weed_chenopod",
|
||||||
name="Chenopod Weed Pollen",
|
name="Chenopod weed",
|
||||||
icon="mdi:sprout",
|
icon="mdi:sprout",
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
@ -206,7 +206,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
|
|||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key="weed_mugwort",
|
key="weed_mugwort",
|
||||||
name="Mugwort Weed Pollen",
|
name="Mugwort weed",
|
||||||
icon="mdi:sprout",
|
icon="mdi:sprout",
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
@ -214,7 +214,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
|
|||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key="weed_nettle",
|
key="weed_nettle",
|
||||||
name="Nettle Weed Pollen",
|
name="Nettle weed",
|
||||||
icon="mdi:sprout",
|
icon="mdi:sprout",
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
@ -222,7 +222,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
|
|||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key="weed_ragweed",
|
key="weed_ragweed",
|
||||||
name="Ragweed Weed Pollen",
|
name="Ragweed weed",
|
||||||
icon="mdi:sprout",
|
icon="mdi:sprout",
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/ambee",
|
"documentation": "https://www.home-assistant.io/integrations/ambee",
|
||||||
"requirements": ["ambee==0.4.0"],
|
"requirements": ["ambee==0.4.0"],
|
||||||
|
"dependencies": ["repairs"],
|
||||||
"codeowners": ["@frenck"],
|
"codeowners": ["@frenck"],
|
||||||
"quality_scale": "platinum",
|
"quality_scale": "platinum",
|
||||||
"iot_class": "cloud_polling"
|
"iot_class": "cloud_polling"
|
||||||
|
@ -42,6 +42,8 @@ async def async_setup_entry(
|
|||||||
class AmbeeSensorEntity(CoordinatorEntity, SensorEntity):
|
class AmbeeSensorEntity(CoordinatorEntity, SensorEntity):
|
||||||
"""Defines an Ambee sensor."""
|
"""Defines an Ambee sensor."""
|
||||||
|
|
||||||
|
_attr_has_entity_name = True
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
|
@ -24,5 +24,11 @@
|
|||||||
"abort": {
|
"abort": {
|
||||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
|
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"issues": {
|
||||||
|
"pending_removal": {
|
||||||
|
"title": "The Ambee integration is being removed",
|
||||||
|
"description": "The Ambee integration is pending removal from Home Assistant and will no longer be available as of Home Assistant 2022.10.\n\nThe integration is being removed, because Ambee removed their free (limited) accounts and doesn't provide a way for regular users to sign up for a paid plan anymore.\n\nRemove the Ambee integration entry from your instance to fix this issue."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,5 +24,11 @@
|
|||||||
"description": "Richte Ambee f\u00fcr die Integration mit Home Assistant ein."
|
"description": "Richte Ambee f\u00fcr die Integration mit Home Assistant ein."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"issues": {
|
||||||
|
"pending_removal": {
|
||||||
|
"description": "Die Ambee-Integration ist dabei, aus Home Assistant entfernt zu werden und wird ab Home Assistant 2022.10 nicht mehr verf\u00fcgbar sein.\n\nDie Integration wird entfernt, weil Ambee seine kostenlosen (begrenzten) Konten entfernt hat und keine M\u00f6glichkeit mehr f\u00fcr regul\u00e4re Nutzer bietet, sich f\u00fcr einen kostenpflichtigen Plan anzumelden.\n\nEntferne den Ambee-Integrationseintrag aus deiner Instanz, um dieses Problem zu beheben.",
|
||||||
|
"title": "Die Ambee-Integration wird entfernt"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -24,5 +24,11 @@
|
|||||||
"description": "Set up Ambee to integrate with Home Assistant."
|
"description": "Set up Ambee to integrate with Home Assistant."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"issues": {
|
||||||
|
"pending_removal": {
|
||||||
|
"description": "The Ambee integration is pending removal from Home Assistant and will no longer be available as of Home Assistant 2022.10.\n\nThe integration is being removed, because Ambee removed their free (limited) accounts and doesn't provide a way for regular users to sign up for a paid plan anymore.\n\nRemove the Ambee integration entry from your instance to fix this issue.",
|
||||||
|
"title": "The Ambee integration is being removed"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -24,5 +24,11 @@
|
|||||||
"description": "Configura Ambee per l'integrazione con Home Assistant."
|
"description": "Configura Ambee per l'integrazione con Home Assistant."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"issues": {
|
||||||
|
"pending_removal": {
|
||||||
|
"description": "L'integrazione Ambee \u00e8 in attesa di rimozione da Home Assistant e non sar\u00e0 pi\u00f9 disponibile a partire da Home Assistant 2022.10. \n\nL'integrazione \u00e8 stata rimossa, perch\u00e9 Ambee ha rimosso i loro account gratuiti (limitati) e non offre pi\u00f9 agli utenti regolari un modo per iscriversi a un piano a pagamento. \n\nRimuovi la voce di integrazione Ambee dalla tua istanza per risolvere questo problema.",
|
||||||
|
"title": "L'integrazione Ambee verr\u00e0 rimossa"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -24,5 +24,11 @@
|
|||||||
"description": "Skonfiguruj Ambee, aby zintegrowa\u0107 go z Home Assistantem."
|
"description": "Skonfiguruj Ambee, aby zintegrowa\u0107 go z Home Assistantem."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"issues": {
|
||||||
|
"pending_removal": {
|
||||||
|
"description": "Integracja Ambee oczekuje na usuni\u0119cie z Home Assistanta i nie b\u0119dzie ju\u017c dost\u0119pna od Home Assistant 2022.10. \n\nIntegracja jest usuwana, poniewa\u017c Ambee usun\u0105\u0142 ich bezp\u0142atne (ograniczone) konta i nie zapewnia ju\u017c zwyk\u0142ym u\u017cytkownikom mo\u017cliwo\u015bci zarejestrowania si\u0119 w p\u0142atnym planie. \n\nUsu\u0144 integracj\u0119 Ambee z Home Assistanta, aby rozwi\u0105za\u0107 ten problem.",
|
||||||
|
"title": "Integracja Ambee zostanie usuni\u0119ta"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -24,5 +24,11 @@
|
|||||||
"description": "Configure o Ambee para integrar com o Home Assistant."
|
"description": "Configure o Ambee para integrar com o Home Assistant."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"issues": {
|
||||||
|
"pending_removal": {
|
||||||
|
"description": "A integra\u00e7\u00e3o do Ambee est\u00e1 com remo\u00e7\u00e3o pendente do Home Assistant e n\u00e3o estar\u00e1 mais dispon\u00edvel a partir do Home Assistant 2022.10. \n\n A integra\u00e7\u00e3o est\u00e1 sendo removida, porque a Ambee removeu suas contas gratuitas (limitadas) e n\u00e3o oferece mais uma maneira de usu\u00e1rios regulares se inscreverem em um plano pago. \n\n Remova a entrada de integra\u00e7\u00e3o Ambee de sua inst\u00e2ncia para corrigir esse problema.",
|
||||||
|
"title": "A integra\u00e7\u00e3o Ambee est\u00e1 sendo removida"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
17
homeassistant/components/ambee/translations/pt.json
Normal file
17
homeassistant/components/ambee/translations/pt.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"step": {
|
||||||
|
"reauth_confirm": {
|
||||||
|
"data": {
|
||||||
|
"api_key": "Chave da API"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"latitude": "Latitude",
|
||||||
|
"name": "Nome"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -24,5 +24,11 @@
|
|||||||
"description": "\u8a2d\u5b9a Ambee \u4ee5\u6574\u5408\u81f3 Home Assistant\u3002"
|
"description": "\u8a2d\u5b9a Ambee \u4ee5\u6574\u5408\u81f3 Home Assistant\u3002"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"issues": {
|
||||||
|
"pending_removal": {
|
||||||
|
"description": "Ambee \u6574\u5408\u5373\u5c07\u7531 Home Assistant \u4e2d\u79fb\u9664\u3001\u4e26\u65bc Home Assistant 2022.10 \u7248\u5f8c\u7121\u6cd5\u518d\u4f7f\u7528\u3002\n\n\u7531\u65bc Ambee \u79fb\u9664\u4e86\u5176\u514d\u8cbb\uff08\u6709\u9650\uff09\u5e33\u865f\u3001\u4e26\u4e14\u4e0d\u518d\u63d0\u4f9b\u4e00\u822c\u4f7f\u7528\u8005\u8a3b\u518a\u4ed8\u8cbb\u670d\u52d9\u3001\u6574\u5408\u5373\u5c07\u79fb\u9664\u3002\n\n\u7531 configuration.yaml \u6a94\u6848\u4e2d\u79fb\u9664 YAML \u8a2d\u5b9a\u4e26\u91cd\u555f Home Assistant to \u4ee5\u4fee\u6b63\u6b64\u554f\u984c\u3002",
|
||||||
|
"title": "Ambee \u6574\u5408\u5373\u5c07\u79fb\u9664"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -19,7 +19,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
coordinator = AmberUpdateCoordinator(hass, api_instance, site_id)
|
coordinator = AmberUpdateCoordinator(hass, api_instance, site_id)
|
||||||
await coordinator.async_config_entry_first_refresh()
|
await coordinator.async_config_entry_first_refresh()
|
||||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
|
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
|
||||||
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user