This commit is contained in:
Franck Nijhof
2023-07-05 17:20:03 +02:00
committed by GitHub
1803 changed files with 58326 additions and 14646 deletions

View File

@@ -27,6 +27,7 @@ base_platforms: &base_platforms
- homeassistant/components/fan/** - homeassistant/components/fan/**
- homeassistant/components/geo_location/** - homeassistant/components/geo_location/**
- homeassistant/components/humidifier/** - homeassistant/components/humidifier/**
- homeassistant/components/image/**
- homeassistant/components/image_processing/** - homeassistant/components/image_processing/**
- homeassistant/components/light/** - homeassistant/components/light/**
- homeassistant/components/lock/** - homeassistant/components/lock/**

View File

@@ -90,6 +90,8 @@ omit =
homeassistant/components/atome/* homeassistant/components/atome/*
homeassistant/components/aurora/__init__.py homeassistant/components/aurora/__init__.py
homeassistant/components/aurora/binary_sensor.py homeassistant/components/aurora/binary_sensor.py
homeassistant/components/aurora/coordinator.py
homeassistant/components/aurora/entity.py
homeassistant/components/aurora/sensor.py homeassistant/components/aurora/sensor.py
homeassistant/components/avea/light.py homeassistant/components/avea/light.py
homeassistant/components/avion/light.py homeassistant/components/avion/light.py
@@ -122,7 +124,6 @@ omit =
homeassistant/components/bluetooth_tracker/* homeassistant/components/bluetooth_tracker/*
homeassistant/components/bmw_connected_drive/__init__.py homeassistant/components/bmw_connected_drive/__init__.py
homeassistant/components/bmw_connected_drive/binary_sensor.py homeassistant/components/bmw_connected_drive/binary_sensor.py
homeassistant/components/bmw_connected_drive/button.py
homeassistant/components/bmw_connected_drive/coordinator.py homeassistant/components/bmw_connected_drive/coordinator.py
homeassistant/components/bmw_connected_drive/lock.py homeassistant/components/bmw_connected_drive/lock.py
homeassistant/components/bmw_connected_drive/notify.py homeassistant/components/bmw_connected_drive/notify.py
@@ -202,6 +203,9 @@ omit =
homeassistant/components/discogs/sensor.py homeassistant/components/discogs/sensor.py
homeassistant/components/discord/__init__.py homeassistant/components/discord/__init__.py
homeassistant/components/discord/notify.py homeassistant/components/discord/notify.py
homeassistant/components/discovergy/__init__.py
homeassistant/components/discovergy/sensor.py
homeassistant/components/discovergy/coordinator.py
homeassistant/components/dlib_face_detect/image_processing.py homeassistant/components/dlib_face_detect/image_processing.py
homeassistant/components/dlib_face_identify/image_processing.py homeassistant/components/dlib_face_identify/image_processing.py
homeassistant/components/dlink/data.py homeassistant/components/dlink/data.py
@@ -302,22 +306,10 @@ omit =
homeassistant/components/escea/climate.py homeassistant/components/escea/climate.py
homeassistant/components/escea/discovery.py homeassistant/components/escea/discovery.py
homeassistant/components/esphome/__init__.py homeassistant/components/esphome/__init__.py
homeassistant/components/esphome/binary_sensor.py
homeassistant/components/esphome/bluetooth/* homeassistant/components/esphome/bluetooth/*
homeassistant/components/esphome/button.py
homeassistant/components/esphome/camera.py homeassistant/components/esphome/camera.py
homeassistant/components/esphome/climate.py
homeassistant/components/esphome/cover.py
homeassistant/components/esphome/domain_data.py homeassistant/components/esphome/domain_data.py
homeassistant/components/esphome/entry_data.py homeassistant/components/esphome/entry_data.py
homeassistant/components/esphome/fan.py
homeassistant/components/esphome/light.py
homeassistant/components/esphome/lock.py
homeassistant/components/esphome/media_player.py
homeassistant/components/esphome/number.py
homeassistant/components/esphome/select.py
homeassistant/components/esphome/sensor.py
homeassistant/components/esphome/switch.py
homeassistant/components/etherscan/sensor.py homeassistant/components/etherscan/sensor.py
homeassistant/components/eufy/* homeassistant/components/eufy/*
homeassistant/components/eufylife_ble/__init__.py homeassistant/components/eufylife_ble/__init__.py
@@ -327,6 +319,7 @@ omit =
homeassistant/components/ezviz/__init__.py homeassistant/components/ezviz/__init__.py
homeassistant/components/ezviz/binary_sensor.py homeassistant/components/ezviz/binary_sensor.py
homeassistant/components/ezviz/camera.py homeassistant/components/ezviz/camera.py
homeassistant/components/ezviz/light.py
homeassistant/components/ezviz/coordinator.py homeassistant/components/ezviz/coordinator.py
homeassistant/components/ezviz/number.py homeassistant/components/ezviz/number.py
homeassistant/components/ezviz/entity.py homeassistant/components/ezviz/entity.py
@@ -362,10 +355,13 @@ omit =
homeassistant/components/fitbit/* homeassistant/components/fitbit/*
homeassistant/components/fivem/__init__.py homeassistant/components/fivem/__init__.py
homeassistant/components/fivem/binary_sensor.py homeassistant/components/fivem/binary_sensor.py
homeassistant/components/fivem/coordinator.py
homeassistant/components/fivem/entity.py
homeassistant/components/fivem/sensor.py homeassistant/components/fivem/sensor.py
homeassistant/components/fixer/sensor.py homeassistant/components/fixer/sensor.py
homeassistant/components/fjaraskupan/__init__.py homeassistant/components/fjaraskupan/__init__.py
homeassistant/components/fjaraskupan/binary_sensor.py homeassistant/components/fjaraskupan/binary_sensor.py
homeassistant/components/fjaraskupan/coordinator.py
homeassistant/components/fjaraskupan/fan.py homeassistant/components/fjaraskupan/fan.py
homeassistant/components/fjaraskupan/light.py homeassistant/components/fjaraskupan/light.py
homeassistant/components/fjaraskupan/number.py homeassistant/components/fjaraskupan/number.py
@@ -615,7 +611,6 @@ omit =
homeassistant/components/kwb/sensor.py homeassistant/components/kwb/sensor.py
homeassistant/components/lacrosse/sensor.py homeassistant/components/lacrosse/sensor.py
homeassistant/components/lannouncer/notify.py homeassistant/components/lannouncer/notify.py
homeassistant/components/lastfm/sensor.py
homeassistant/components/launch_library/__init__.py homeassistant/components/launch_library/__init__.py
homeassistant/components/launch_library/sensor.py homeassistant/components/launch_library/sensor.py
homeassistant/components/lcn/climate.py homeassistant/components/lcn/climate.py
@@ -946,6 +941,8 @@ omit =
homeassistant/components/pyload/sensor.py homeassistant/components/pyload/sensor.py
homeassistant/components/qbittorrent/__init__.py homeassistant/components/qbittorrent/__init__.py
homeassistant/components/qbittorrent/sensor.py homeassistant/components/qbittorrent/sensor.py
homeassistant/components/qnap/__init__.py
homeassistant/components/qnap/coordinator.py
homeassistant/components/qnap/sensor.py homeassistant/components/qnap/sensor.py
homeassistant/components/qrcode/image_processing.py homeassistant/components/qrcode/image_processing.py
homeassistant/components/quantum_gateway/device_tracker.py homeassistant/components/quantum_gateway/device_tracker.py
@@ -973,6 +970,10 @@ omit =
homeassistant/components/rainmachine/switch.py homeassistant/components/rainmachine/switch.py
homeassistant/components/rainmachine/update.py homeassistant/components/rainmachine/update.py
homeassistant/components/rainmachine/util.py homeassistant/components/rainmachine/util.py
homeassistant/components/renson/__init__.py
homeassistant/components/renson/const.py
homeassistant/components/renson/entity.py
homeassistant/components/renson/sensor.py
homeassistant/components/raspyrfm/* homeassistant/components/raspyrfm/*
homeassistant/components/recollect_waste/sensor.py homeassistant/components/recollect_waste/sensor.py
homeassistant/components/recorder/repack.py homeassistant/components/recorder/repack.py
@@ -1046,12 +1047,6 @@ omit =
homeassistant/components/sense/__init__.py homeassistant/components/sense/__init__.py
homeassistant/components/sense/binary_sensor.py homeassistant/components/sense/binary_sensor.py
homeassistant/components/sense/sensor.py homeassistant/components/sense/sensor.py
homeassistant/components/senseme/__init__.py
homeassistant/components/senseme/discovery.py
homeassistant/components/senseme/entity.py
homeassistant/components/senseme/fan.py
homeassistant/components/senseme/light.py
homeassistant/components/senseme/switch.py
homeassistant/components/senz/__init__.py homeassistant/components/senz/__init__.py
homeassistant/components/senz/api.py homeassistant/components/senz/api.py
homeassistant/components/senz/climate.py homeassistant/components/senz/climate.py

View File

@@ -10,7 +10,7 @@ on:
env: env:
BUILD_TYPE: core BUILD_TYPE: core
DEFAULT_PYTHON: "3.10" DEFAULT_PYTHON: "3.11"
jobs: jobs:
init: init:
@@ -24,7 +24,7 @@ jobs:
publish: ${{ steps.version.outputs.publish }} publish: ${{ steps.version.outputs.publish }}
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v3.5.2 uses: actions/checkout@v3.5.3
with: with:
fetch-depth: 0 fetch-depth: 0
@@ -48,18 +48,6 @@ jobs:
with: with:
ignore-dev: true ignore-dev: true
- name: Generate meta info
shell: bash
run: |
echo "${{ github.sha }};${{ github.ref }};${{ github.event_name }};${{ github.actor }}" > OFFICIAL_IMAGE
- name: Signing meta info file
uses: home-assistant/actions/helpers/codenotary@master
with:
source: file://${{ github.workspace }}/OFFICIAL_IMAGE
asset: OFFICIAL_IMAGE-${{ steps.version.outputs.version }}
token: ${{ secrets.CAS_TOKEN }}
build_python: build_python:
name: Build PyPi package name: Build PyPi package
environment: ${{ needs.init.outputs.channel }} environment: ${{ needs.init.outputs.channel }}
@@ -68,7 +56,7 @@ jobs:
if: github.repository_owner == 'home-assistant' && needs.init.outputs.publish == 'true' if: github.repository_owner == 'home-assistant' && needs.init.outputs.publish == 'true'
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v3.5.2 uses: actions/checkout@v3.5.3
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v4.6.1 uses: actions/setup-python@v4.6.1
@@ -101,12 +89,16 @@ jobs:
if: github.repository_owner == 'home-assistant' if: github.repository_owner == 'home-assistant'
needs: init needs: init
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
contents: read
packages: write
id-token: write
strategy: strategy:
matrix: matrix:
arch: ${{ fromJson(needs.init.outputs.architectures) }} arch: ${{ fromJson(needs.init.outputs.architectures) }}
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v3.5.2 uses: actions/checkout@v3.5.3
- name: Download nightly wheels of frontend - name: Download nightly wheels of frontend
if: needs.init.outputs.channel == 'dev' if: needs.init.outputs.channel == 'dev'
@@ -182,7 +174,7 @@ jobs:
# will drop the platform in the near future (they consider it # will drop the platform in the near future (they consider it
# "flimsy" on 386). The following packages depend on pandas, # "flimsy" on 386). The following packages depend on pandas,
# so we comment them out. # so we comment them out.
sed -i "s|env_canada|# env_canada|g" requirements_all.txt sed -i "s|env-canada|# env-canada|g" requirements_all.txt
sed -i "s|noaa-coops|# noaa-coops|g" requirements_all.txt sed -i "s|noaa-coops|# noaa-coops|g" requirements_all.txt
sed -i "s|pyezviz|# pyezviz|g" requirements_all.txt sed -i "s|pyezviz|# pyezviz|g" requirements_all.txt
sed -i "s|pykrakenapi|# pykrakenapi|g" requirements_all.txt sed -i "s|pykrakenapi|# pykrakenapi|g" requirements_all.txt
@@ -197,25 +189,20 @@ jobs:
run: | run: |
echo "${{ github.sha }};${{ github.ref }};${{ github.event_name }};${{ github.actor }}" > rootfs/OFFICIAL_IMAGE echo "${{ github.sha }};${{ github.ref }};${{ github.event_name }};${{ github.actor }}" > rootfs/OFFICIAL_IMAGE
- name: Login to DockerHub
uses: docker/login-action@v2.1.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v2.1.0 uses: docker/login-action@v2.2.0
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Build base image - name: Build base image
uses: home-assistant/builder@2023.03.0 uses: home-assistant/builder@2023.06.1
with: with:
args: | args: |
$BUILD_ARGS \ $BUILD_ARGS \
--${{ matrix.arch }} \ --${{ matrix.arch }} \
--cosign \
--target /data \ --target /data \
--generic ${{ needs.init.outputs.version }} --generic ${{ needs.init.outputs.version }}
env: env:
@@ -237,6 +224,10 @@ jobs:
if: github.repository_owner == 'home-assistant' if: github.repository_owner == 'home-assistant'
needs: ["init", "build_base"] needs: ["init", "build_base"]
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
contents: read
packages: write
id-token: write
strategy: strategy:
matrix: matrix:
machine: machine:
@@ -262,7 +253,7 @@ jobs:
- yellow - yellow
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v3.5.2 uses: actions/checkout@v3.5.3
- name: Set build additional args - name: Set build additional args
run: | run: |
@@ -275,25 +266,20 @@ jobs:
echo "BUILD_ARGS=--additional-tag stable" >> $GITHUB_ENV echo "BUILD_ARGS=--additional-tag stable" >> $GITHUB_ENV
fi fi
- name: Login to DockerHub
uses: docker/login-action@v2.1.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v2.1.0 uses: docker/login-action@v2.2.0
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Build base image - name: Build base image
uses: home-assistant/builder@2023.03.0 uses: home-assistant/builder@2023.06.1
with: with:
args: | args: |
$BUILD_ARGS \ $BUILD_ARGS \
--target /data/machine \ --target /data/machine \
--cosign \
--machine "${{ needs.init.outputs.version }}=${{ matrix.machine }}" --machine "${{ needs.init.outputs.version }}=${{ matrix.machine }}"
env: env:
CAS_API_KEY: ${{ secrets.CAS_TOKEN }} CAS_API_KEY: ${{ secrets.CAS_TOKEN }}
@@ -306,7 +292,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v3.5.2 uses: actions/checkout@v3.5.3
- name: Initialize git - name: Initialize git
uses: home-assistant/actions/helpers/git-init@master uses: home-assistant/actions/helpers/git-init@master
@@ -338,34 +324,32 @@ jobs:
if: github.repository_owner == 'home-assistant' if: github.repository_owner == 'home-assistant'
needs: ["init", "build_base"] needs: ["init", "build_base"]
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: permissions:
fail-fast: false contents: read
matrix: packages: write
registry: id-token: write
- "ghcr.io/home-assistant"
- "homeassistant"
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v3.5.2 uses: actions/checkout@v3.5.3
- name: Install Cosign
uses: sigstore/cosign-installer@v3.1.1
with:
cosign-release: "v2.0.2"
- name: Login to DockerHub - name: Login to DockerHub
if: matrix.registry == 'homeassistant' uses: docker/login-action@v2.2.0
uses: docker/login-action@v2.1.0
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
if: matrix.registry == 'ghcr.io/home-assistant' uses: docker/login-action@v2.2.0
uses: docker/login-action@v2.1.0
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Install CAS tools
uses: home-assistant/actions/helpers/cas@master
- name: Build Meta Image - name: Build Meta Image
shell: bash shell: bash
run: | run: |
@@ -375,55 +359,78 @@ jobs:
local tag_l=${1} local tag_l=${1}
local tag_r=${2} local tag_r=${2}
docker manifest create "${{ matrix.registry }}/home-assistant:${tag_l}" \ for registry in "ghcr.io/home-assistant" "docker.io/homeassistant"
"${{ matrix.registry }}/amd64-homeassistant:${tag_r}" \ do
"${{ matrix.registry }}/i386-homeassistant:${tag_r}" \
"${{ matrix.registry }}/armhf-homeassistant:${tag_r}" \
"${{ matrix.registry }}/armv7-homeassistant:${tag_r}" \
"${{ matrix.registry }}/aarch64-homeassistant:${tag_r}"
docker manifest annotate "${{ matrix.registry }}/home-assistant:${tag_l}" \ docker manifest create "${registry}/home-assistant:${tag_l}" \
"${{ matrix.registry }}/amd64-homeassistant:${tag_r}" \ "${registry}/amd64-homeassistant:${tag_r}" \
--os linux --arch amd64 "${registry}/i386-homeassistant:${tag_r}" \
"${registry}/armhf-homeassistant:${tag_r}" \
"${registry}/armv7-homeassistant:${tag_r}" \
"${registry}/aarch64-homeassistant:${tag_r}"
docker manifest annotate "${{ matrix.registry }}/home-assistant:${tag_l}" \ docker manifest annotate "${registry}/home-assistant:${tag_l}" \
"${{ matrix.registry }}/i386-homeassistant:${tag_r}" \ "${registry}/amd64-homeassistant:${tag_r}" \
--os linux --arch 386 --os linux --arch amd64
docker manifest annotate "${{ matrix.registry }}/home-assistant:${tag_l}" \ docker manifest annotate "${registry}/home-assistant:${tag_l}" \
"${{ matrix.registry }}/armhf-homeassistant:${tag_r}" \ "${registry}/i386-homeassistant:${tag_r}" \
--os linux --arch arm --variant=v6 --os linux --arch 386
docker manifest annotate "${{ matrix.registry }}/home-assistant:${tag_l}" \ docker manifest annotate "${registry}/home-assistant:${tag_l}" \
"${{ matrix.registry }}/armv7-homeassistant:${tag_r}" \ "${registry}/armhf-homeassistant:${tag_r}" \
--os linux --arch arm --variant=v7 --os linux --arch arm --variant=v6
docker manifest annotate "${{ matrix.registry }}/home-assistant:${tag_l}" \ docker manifest annotate "${registry}/home-assistant:${tag_l}" \
"${{ matrix.registry }}/aarch64-homeassistant:${tag_r}" \ "${registry}/armv7-homeassistant:${tag_r}" \
--os linux --arch arm64 --variant=v8 --os linux --arch arm --variant=v7
docker manifest push --purge "${{ matrix.registry }}/home-assistant:${tag_l}" docker manifest annotate "${registry}/home-assistant:${tag_l}" \
"${registry}/aarch64-homeassistant:${tag_r}" \
--os linux --arch arm64 --variant=v8
docker manifest push --purge "${registry}/home-assistant:${tag_l}"
cosign sign --yes "${registry}/home-assistant:${tag_l}"
done
} }
function validate_image() { function validate_image() {
local image=${1} local image=${1}
if ! cas authenticate --signerID notary@home-assistant.io "docker://${image}"; then if ! cosign verify --certificate-oidc-issuer https://token.actions.githubusercontent.com --certificate-identity-regexp https://github.com/home-assistant/core/.* "${image}"; then
echo "Invalid signature!" echo "Invalid signature!"
exit 1 exit 1
fi fi
} }
docker pull "${{ matrix.registry }}/amd64-homeassistant:${{ needs.init.outputs.version }}" function push_dockerhub() {
docker pull "${{ matrix.registry }}/i386-homeassistant:${{ needs.init.outputs.version }}" local image=${1}
docker pull "${{ matrix.registry }}/armhf-homeassistant:${{ needs.init.outputs.version }}" local tag=${2}
docker pull "${{ matrix.registry }}/armv7-homeassistant:${{ needs.init.outputs.version }}"
docker pull "${{ matrix.registry }}/aarch64-homeassistant:${{ needs.init.outputs.version }}"
validate_image "${{ matrix.registry }}/amd64-homeassistant:${{ needs.init.outputs.version }}" docker tag "ghcr.io/home-assistant/${image}:${tag}" "docker.io/homeassistant/${image}:${tag}"
validate_image "${{ matrix.registry }}/i386-homeassistant:${{ needs.init.outputs.version }}" docker push "docker.io/homeassistant/${image}:${tag}"
validate_image "${{ matrix.registry }}/armhf-homeassistant:${{ needs.init.outputs.version }}" cosign sign --yes "docker.io/homeassistant/${image}:${tag}"
validate_image "${{ matrix.registry }}/armv7-homeassistant:${{ needs.init.outputs.version }}" }
validate_image "${{ matrix.registry }}/aarch64-homeassistant:${{ needs.init.outputs.version }}"
# Pull images from github container registry and verify signature
docker pull "ghcr.io/home-assistant/amd64-homeassistant:${{ needs.init.outputs.version }}"
docker pull "ghcr.io/home-assistant/i386-homeassistant:${{ needs.init.outputs.version }}"
docker pull "ghcr.io/home-assistant/armhf-homeassistant:${{ needs.init.outputs.version }}"
docker pull "ghcr.io/home-assistant/armv7-homeassistant:${{ needs.init.outputs.version }}"
docker pull "ghcr.io/home-assistant/aarch64-homeassistant:${{ needs.init.outputs.version }}"
validate_image "ghcr.io/home-assistant/amd64-homeassistant:${{ needs.init.outputs.version }}"
validate_image "ghcr.io/home-assistant/i386-homeassistant:${{ needs.init.outputs.version }}"
validate_image "ghcr.io/home-assistant/armhf-homeassistant:${{ needs.init.outputs.version }}"
validate_image "ghcr.io/home-assistant/armv7-homeassistant:${{ needs.init.outputs.version }}"
validate_image "ghcr.io/home-assistant/aarch64-homeassistant:${{ needs.init.outputs.version }}"
# Upload images to dockerhub
push_dockerhub "amd64-homeassistant" "${{ needs.init.outputs.version }}"
push_dockerhub "i386-homeassistant" "${{ needs.init.outputs.version }}"
push_dockerhub "armhf-homeassistant" "${{ needs.init.outputs.version }}"
push_dockerhub "armv7-homeassistant" "${{ needs.init.outputs.version }}"
push_dockerhub "aarch64-homeassistant" "${{ needs.init.outputs.version }}"
# Create version tag # Create version tag
create_manifest "${{ needs.init.outputs.version }}" "${{ needs.init.outputs.version }}" create_manifest "${{ needs.init.outputs.version }}" "${{ needs.init.outputs.version }}"

View File

@@ -32,7 +32,7 @@ env:
CACHE_VERSION: 5 CACHE_VERSION: 5
PIP_CACHE_VERSION: 4 PIP_CACHE_VERSION: 4
MYPY_CACHE_VERSION: 4 MYPY_CACHE_VERSION: 4
HA_SHORT_VERSION: 2023.6 HA_SHORT_VERSION: 2023.7
DEFAULT_PYTHON: "3.10" DEFAULT_PYTHON: "3.10"
ALL_PYTHON_VERSIONS: "['3.10', '3.11']" ALL_PYTHON_VERSIONS: "['3.10', '3.11']"
# 10.3 is the oldest supported version # 10.3 is the oldest supported version
@@ -82,7 +82,7 @@ jobs:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v3.5.2 uses: actions/checkout@v3.5.3
- name: Generate partial Python venv restore key - name: Generate partial Python venv restore key
id: generate_python_cache_key id: generate_python_cache_key
run: >- run: >-
@@ -206,7 +206,7 @@ jobs:
- info - info
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v3.5.2 uses: actions/checkout@v3.5.3
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python id: python
uses: actions/setup-python@v4.6.1 uses: actions/setup-python@v4.6.1
@@ -251,7 +251,7 @@ jobs:
- pre-commit - pre-commit
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v3.5.2 uses: actions/checkout@v3.5.3
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v4.6.1 uses: actions/setup-python@v4.6.1
id: python id: python
@@ -297,7 +297,7 @@ jobs:
- pre-commit - pre-commit
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v3.5.2 uses: actions/checkout@v3.5.3
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v4.6.1 uses: actions/setup-python@v4.6.1
id: python id: python
@@ -338,44 +338,6 @@ jobs:
shopt -s globstar shopt -s globstar
pre-commit run --hook-stage manual ruff --files {homeassistant,tests}/components/${{ needs.info.outputs.integrations_glob }}/{*,**/*} --show-diff-on-failure pre-commit run --hook-stage manual ruff --files {homeassistant,tests}/components/${{ needs.info.outputs.integrations_glob }}/{*,**/*} --show-diff-on-failure
lint-isort:
name: Check isort
runs-on: ubuntu-22.04
needs:
- info
- pre-commit
steps:
- name: Check out code from GitHub
uses: actions/checkout@v3.5.2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v4.6.1
id: python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
check-latest: true
- name: Restore base Python virtual environment
id: cache-venv
uses: actions/cache/restore@v3.3.1
with:
path: venv
fail-on-cache-miss: true
key: >-
${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{
needs.info.outputs.pre-commit_cache_key }}
- name: Restore pre-commit environment from cache
id: cache-precommit
uses: actions/cache/restore@v3.3.1
with:
path: ${{ env.PRE_COMMIT_CACHE }}
fail-on-cache-miss: true
key: >-
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
needs.info.outputs.pre-commit_cache_key }}
- name: Run isort
run: |
. venv/bin/activate
pre-commit run --hook-stage manual isort --all-files --show-diff-on-failure
lint-other: lint-other:
name: Check other linters name: Check other linters
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
@@ -384,7 +346,7 @@ jobs:
- pre-commit - pre-commit
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v3.5.2 uses: actions/checkout@v3.5.3
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v4.6.1 uses: actions/setup-python@v4.6.1
id: python id: python
@@ -468,19 +430,6 @@ jobs:
with: with:
args: hadolint Dockerfile.dev args: hadolint Dockerfile.dev
- name: Run bandit (fully)
if: needs.info.outputs.test_full_suite == 'true'
run: |
. venv/bin/activate
pre-commit run --hook-stage manual bandit --all-files --show-diff-on-failure
- name: Run bandit (partially)
if: needs.info.outputs.test_full_suite == 'false'
shell: bash
run: |
. venv/bin/activate
shopt -s globstar
pre-commit run --hook-stage manual bandit --files {homeassistant,tests}/components/${{ needs.info.outputs.integrations_glob }}/{*,**/*} --show-diff-on-failure
base: base:
name: Prepare dependencies name: Prepare dependencies
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
@@ -491,7 +440,7 @@ jobs:
python-version: ${{ fromJSON(needs.info.outputs.python_versions) }} python-version: ${{ fromJSON(needs.info.outputs.python_versions) }}
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v3.5.2 uses: actions/checkout@v3.5.3
- name: Set up Python ${{ matrix.python-version }} - name: Set up Python ${{ matrix.python-version }}
id: python id: python
uses: actions/setup-python@v4.6.1 uses: actions/setup-python@v4.6.1
@@ -543,10 +492,10 @@ jobs:
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,<23.2" setuptools wheel pip install --cache-dir=$PIP_CACHE -U "pip>=21.3.1,<23.2" setuptools wheel
pip install --cache-dir=$PIP_CACHE -r requirements_all.txt pip install --cache-dir=$PIP_CACHE -r requirements_all.txt
pip install --cache-dir=$PIP_CACHE -r requirements_test.txt pip install --cache-dir=$PIP_CACHE -r requirements_test.txt
pip install -e . pip install -e . --config-settings editable_mode=compat
hassfest: hassfest:
name: Check hassfest name: Check hassfest
@@ -559,7 +508,7 @@ jobs:
- base - base
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v3.5.2 uses: actions/checkout@v3.5.3
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python id: python
uses: actions/setup-python@v4.6.1 uses: actions/setup-python@v4.6.1
@@ -591,7 +540,7 @@ jobs:
- base - base
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v3.5.2 uses: actions/checkout@v3.5.3
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python id: python
uses: actions/setup-python@v4.6.1 uses: actions/setup-python@v4.6.1
@@ -624,7 +573,7 @@ jobs:
- base - base
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v3.5.2 uses: actions/checkout@v3.5.3
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python id: python
uses: actions/setup-python@v4.6.1 uses: actions/setup-python@v4.6.1
@@ -668,7 +617,7 @@ jobs:
- base - base
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v3.5.2 uses: actions/checkout@v3.5.3
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python id: python
uses: actions/setup-python@v4.6.1 uses: actions/setup-python@v4.6.1
@@ -732,7 +681,6 @@ jobs:
- base - base
- gen-requirements-all - gen-requirements-all
- hassfest - hassfest
- lint-isort
- lint-other - lint-other
- lint-ruff - lint-ruff
- mypy - mypy
@@ -751,7 +699,7 @@ jobs:
bluez \ bluez \
ffmpeg ffmpeg
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v3.5.2 uses: actions/checkout@v3.5.3
- name: Set up Python ${{ matrix.python-version }} - name: Set up Python ${{ matrix.python-version }}
id: python id: python
uses: actions/setup-python@v4.6.1 uses: actions/setup-python@v4.6.1
@@ -857,7 +805,6 @@ jobs:
- base - base
- gen-requirements-all - gen-requirements-all
- hassfest - hassfest
- lint-isort
- lint-other - lint-other
- lint-ruff - lint-ruff
- mypy - mypy
@@ -877,7 +824,7 @@ jobs:
ffmpeg \ ffmpeg \
libmariadb-dev-compat libmariadb-dev-compat
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v3.5.2 uses: actions/checkout@v3.5.3
- name: Set up Python ${{ matrix.python-version }} - name: Set up Python ${{ matrix.python-version }}
id: python id: python
uses: actions/setup-python@v4.6.1 uses: actions/setup-python@v4.6.1
@@ -965,7 +912,6 @@ jobs:
- base - base
- gen-requirements-all - gen-requirements-all
- hassfest - hassfest
- lint-isort
- lint-other - lint-other
- lint-ruff - lint-ruff
- mypy - mypy
@@ -985,7 +931,7 @@ jobs:
ffmpeg \ ffmpeg \
postgresql-server-dev-14 postgresql-server-dev-14
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v3.5.2 uses: actions/checkout@v3.5.3
- name: Set up Python ${{ matrix.python-version }} - name: Set up Python ${{ matrix.python-version }}
id: python id: python
uses: actions/setup-python@v4.6.1 uses: actions/setup-python@v4.6.1
@@ -1062,12 +1008,12 @@ jobs:
timeout-minutes: 10 timeout-minutes: 10
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v3.5.2 uses: actions/checkout@v3.5.3
- 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.info.outputs.test_full_suite == 'true' if: needs.info.outputs.test_full_suite == 'true'
uses: Wandalen/wretry.action@v1.0.36 uses: Wandalen/wretry.action@v1.3.0
with: with:
action: codecov/codecov-action@v3.1.3 action: codecov/codecov-action@v3.1.3
with: | with: |
@@ -1077,7 +1023,7 @@ jobs:
attempt_delay: 30000 attempt_delay: 30000
- name: Upload coverage to Codecov (partial coverage) - name: Upload coverage to Codecov (partial coverage)
if: needs.info.outputs.test_full_suite == 'false' if: needs.info.outputs.test_full_suite == 'false'
uses: Wandalen/wretry.action@v1.0.36 uses: Wandalen/wretry.action@v1.3.0
with: with:
action: codecov/codecov-action@v3.1.3 action: codecov/codecov-action@v3.1.3
with: | with: |

View File

@@ -10,7 +10,7 @@ jobs:
if: github.repository_owner == 'home-assistant' if: github.repository_owner == 'home-assistant'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: dessant/lock-threads@v4.0.0 - uses: dessant/lock-threads@v4.0.1
with: with:
github-token: ${{ github.token }} github-token: ${{ github.token }}
issue-inactive-days: "30" issue-inactive-days: "30"

View File

@@ -10,7 +10,7 @@ on:
- "**strings.json" - "**strings.json"
env: env:
DEFAULT_PYTHON: "3.10" DEFAULT_PYTHON: "3.11"
jobs: jobs:
upload: upload:
@@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v3.5.2 uses: actions/checkout@v3.5.3
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v4.6.1 uses: actions/setup-python@v4.6.1

View File

@@ -26,7 +26,7 @@ jobs:
architectures: ${{ steps.info.outputs.architectures }} architectures: ${{ steps.info.outputs.architectures }}
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v3.5.2 uses: actions/checkout@v3.5.3
- name: Get information - name: Get information
id: info id: info
@@ -47,10 +47,7 @@ jobs:
echo "GRPC_PYTHON_BUILD_SYSTEM_OPENSSL=true" echo "GRPC_PYTHON_BUILD_SYSTEM_OPENSSL=true"
echo "GRPC_PYTHON_BUILD_WITH_CYTHON=true" echo "GRPC_PYTHON_BUILD_WITH_CYTHON=true"
echo "GRPC_PYTHON_DISABLE_LIBC_COMPATIBILITY=true" echo "GRPC_PYTHON_DISABLE_LIBC_COMPATIBILITY=true"
# GRPC on armv7 needs -lexecinfo (issue #56669) since home assistant installs echo "GRPC_PYTHON_LDFLAGS=-lpthread -Wl,-wrap,memcpy -static-libgcc"
# execinfo-dev when building wheels. The setuptools build setup does not have an option for
# adding a single LDFLAG so copy all relevant linux flags here (as of 1.43.0)
echo "GRPC_PYTHON_LDFLAGS=-lpthread -Wl,-wrap,memcpy -static-libgcc -lexecinfo"
# Fix out of memory issues with rust # Fix out of memory issues with rust
echo "CARGO_NET_GIT_FETCH_WITH_CLI=true" echo "CARGO_NET_GIT_FETCH_WITH_CLI=true"
@@ -83,11 +80,11 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
abi: ["cp310", "cp311"] abi: ["cp311"]
arch: ${{ fromJson(needs.init.outputs.architectures) }} arch: ${{ fromJson(needs.init.outputs.architectures) }}
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v3.5.2 uses: actions/checkout@v3.5.3
- name: Download env_file - name: Download env_file
uses: actions/download-artifact@v3 uses: actions/download-artifact@v3
@@ -113,100 +110,6 @@ jobs:
requirements-diff: "requirements_diff.txt" requirements-diff: "requirements_diff.txt"
requirements: "requirements.txt" requirements: "requirements.txt"
integrations_cp310:
name: Build wheels ${{ matrix.abi }} for ${{ matrix.arch }}
if: github.repository_owner == 'home-assistant'
needs: init
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
abi: ["cp310"]
arch: ${{ fromJson(needs.init.outputs.architectures) }}
steps:
- name: Checkout the repository
uses: actions/checkout@v3.5.2
- name: Download env_file
uses: actions/download-artifact@v3
with:
name: env_file
- name: Download requirements_diff
uses: actions/download-artifact@v3
with:
name: requirements_diff
- name: Uncomment packages
run: |
requirement_files="requirements_all.txt requirements_diff.txt"
for requirement_file in ${requirement_files}; do
sed -i "s|# pybluez|pybluez|g" ${requirement_file}
sed -i "s|# beacontools|beacontools|g" ${requirement_file}
sed -i "s|# fritzconnection|fritzconnection|g" ${requirement_file}
sed -i "s|# pyuserinput|pyuserinput|g" ${requirement_file}
sed -i "s|# evdev|evdev|g" ${requirement_file}
sed -i "s|# pycups|pycups|g" ${requirement_file}
sed -i "s|# homekit|homekit|g" ${requirement_file}
sed -i "s|# decora_wifi|decora_wifi|g" ${requirement_file}
sed -i "s|# python-gammu|python-gammu|g" ${requirement_file}
sed -i "s|# opencv-python-headless|opencv-python-headless|g" ${requirement_file}
done
- name: Split requirements all
run: |
# We split requirements all into two different files.
# This is to prevent the build from running out of memory when
# resolving packages on 32-bits systems (like armhf, armv7).
split -l $(expr $(expr $(cat requirements_all.txt | wc -l) + 1) / 2) requirements_all.txt requirements_all.txt
- name: Adjust build env
run: |
if [ "${{ matrix.arch }}" = "i386" ]; then
echo "NPY_DISABLE_SVML=1" >> .env_file
fi
(
# cmake > 3.22.2 have issue on arm
# Tested until 3.22.5
echo "cmake==3.22.2"
) >> homeassistant/package_constraints.txt
# Do not pin numpy in wheels building
sed -i "/numpy/d" homeassistant/package_constraints.txt
- name: Build wheels (part 1)
uses: home-assistant/wheels@2023.04.0
with:
abi: ${{ matrix.abi }}
tag: musllinux_1_2
arch: ${{ matrix.arch }}
wheels-key: ${{ secrets.WHEELS_KEY }}
env-file: true
apk: "bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;cups-dev;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev;gammu-dev;yaml-dev;openblas-dev;fftw-dev;lapack-dev;gfortran;blas-dev;eigen-dev;freetype-dev;glew-dev;harfbuzz-dev;hdf5-dev;libdc1394-dev;libtbb-dev;mesa-dev;openexr-dev;openjpeg-dev;uchardet-dev"
skip-binary: aiohttp;grpcio;sqlalchemy;protobuf
constraints: "homeassistant/package_constraints.txt"
requirements-diff: "requirements_diff.txt"
requirements: "requirements_all.txtaa"
- name: Build wheels (part 2)
uses: home-assistant/wheels@2023.04.0
with:
abi: ${{ matrix.abi }}
tag: musllinux_1_2
arch: ${{ matrix.arch }}
wheels-key: ${{ secrets.WHEELS_KEY }}
env-file: true
apk: "bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;cups-dev;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev;gammu-dev;yaml-dev;openblas-dev;fftw-dev;lapack-dev;gfortran;blas-dev;eigen-dev;freetype-dev;glew-dev;harfbuzz-dev;hdf5-dev;libdc1394-dev;libtbb-dev;mesa-dev;openexr-dev;openjpeg-dev;uchardet-dev"
skip-binary: aiohttp;grpcio;sqlalchemy;protobuf
constraints: "homeassistant/package_constraints.txt"
requirements-diff: "requirements_diff.txt"
requirements: "requirements_all.txtab"
# Wheels building for the cp311 ABI is currently split
# This is mainly until we have figured out to get all wheels built.
# Without harming our current workflow.
integrations_cp311: integrations_cp311:
name: Build wheels ${{ matrix.abi }} for ${{ matrix.arch }} name: Build wheels ${{ matrix.abi }} for ${{ matrix.arch }}
if: github.repository_owner == 'home-assistant' if: github.repository_owner == 'home-assistant'
@@ -219,31 +122,12 @@ jobs:
arch: ${{ fromJson(needs.init.outputs.architectures) }} arch: ${{ fromJson(needs.init.outputs.architectures) }}
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v3.5.2 uses: actions/checkout@v3.5.3
- name: Write alternative env-file for cp311 - name: Download env_file
run: | uses: actions/download-artifact@v3
( with:
echo "GRPC_BUILD_WITH_BORING_SSL_ASM=false" name: env_file
echo "GRPC_PYTHON_BUILD_SYSTEM_OPENSSL=true"
echo "GRPC_PYTHON_BUILD_WITH_CYTHON=true"
echo "GRPC_PYTHON_DISABLE_LIBC_COMPATIBILITY=true"
# GRPC on armv7 needed -lexecinfo (issue #56669) since home assistant installed
# execinfo-dev when building wheels. However, this package is no longer available
# Alpine 3.17, which we use for the cp311 ABI, so the flag should no longer be needed.
echo "GRPC_PYTHON_LDFLAGS=-lpthread -Wl,-wrap,memcpy -static-libgcc" # -lexecinfo
# Fix out of memory issues with rust
echo "CARGO_NET_GIT_FETCH_WITH_CLI=true"
# OpenCV headless installation
echo "CI_BUILD=1"
echo "ENABLE_HEADLESS=1"
# Use C-Extension for sqlalchemy
echo "REQUIRE_SQLALCHEMY_CEXT=1"
) > .env_file
- name: Download requirements_diff - name: Download requirements_diff
uses: actions/download-artifact@v3 uses: actions/download-artifact@v3
@@ -254,27 +138,12 @@ jobs:
run: | run: |
requirement_files="requirements_all.txt requirements_diff.txt" requirement_files="requirements_all.txt requirements_diff.txt"
for requirement_file in ${requirement_files}; do for requirement_file in ${requirement_files}; do
# PyBluez no longer compiles. Commented it out for now.
# It need further cleanup down the line, as all machine images
# try to install it.
# sed -i "s|# pybluez|pybluez|g" ${requirement_file}
# beacontools requires PyBluez.
# sed -i "s|# beacontools|beacontools|g" ${requirement_file}
# It doesn't build for some reason, so we skip it for now.
# Bumping to the latest version (4.7.0.72) supporting Python 3.11
# doesn't help. Reverted bump in #91871. There are 8 registered
# instances using this integration according to analytics.
# sed -i "s|# opencv-python-headless|opencv-python-headless|g" ${requirement_file}
sed -i "s|# fritzconnection|fritzconnection|g" ${requirement_file} sed -i "s|# fritzconnection|fritzconnection|g" ${requirement_file}
sed -i "s|# pyuserinput|pyuserinput|g" ${requirement_file} sed -i "s|# pyuserinput|pyuserinput|g" ${requirement_file}
sed -i "s|# evdev|evdev|g" ${requirement_file} sed -i "s|# evdev|evdev|g" ${requirement_file}
sed -i "s|# pycups|pycups|g" ${requirement_file} sed -i "s|# pycups|pycups|g" ${requirement_file}
sed -i "s|# homekit|homekit|g" ${requirement_file} sed -i "s|# homekit|homekit|g" ${requirement_file}
sed -i "s|# decora_wifi|decora_wifi|g" ${requirement_file} sed -i "s|# decora-wifi|decora-wifi|g" ${requirement_file}
sed -i "s|# python-gammu|python-gammu|g" ${requirement_file} sed -i "s|# python-gammu|python-gammu|g" ${requirement_file}
# Some packages are not buildable on armhf anymore # Some packages are not buildable on armhf anymore
@@ -284,7 +153,7 @@ jobs:
# will drop the platform in the near future (they consider it # will drop the platform in the near future (they consider it
# "flimsy" on 386). The following packages depend on pandas, # "flimsy" on 386). The following packages depend on pandas,
# so we comment them out. # so we comment them out.
sed -i "s|env_canada|# env_canada|g" ${requirement_file} sed -i "s|env-canada|# env-canada|g" ${requirement_file}
sed -i "s|noaa-coops|# noaa-coops|g" ${requirement_file} sed -i "s|noaa-coops|# noaa-coops|g" ${requirement_file}
sed -i "s|pyezviz|# pyezviz|g" ${requirement_file} sed -i "s|pyezviz|# pyezviz|g" ${requirement_file}
sed -i "s|pykrakenapi|# pykrakenapi|g" ${requirement_file} sed -i "s|pykrakenapi|# pykrakenapi|g" ${requirement_file}
@@ -297,7 +166,7 @@ jobs:
# This is to prevent the build from running out of memory when # This is to prevent the build from running out of memory when
# resolving packages on 32-bits systems (like armhf, armv7). # resolving packages on 32-bits systems (like armhf, armv7).
split -l $(expr $(expr $(cat requirements_all.txt | wc -l) + 1) / 2) requirements_all.txt requirements_all.txt split -l $(expr $(expr $(cat requirements_all.txt | wc -l) + 1) / 3) requirements_all.txt requirements_all.txt
- name: Adjust build env - name: Adjust build env
run: | run: |
@@ -305,13 +174,6 @@ jobs:
echo "NPY_DISABLE_SVML=1" >> .env_file echo "NPY_DISABLE_SVML=1" >> .env_file
fi fi
# Probably not an issue anymore. Removing for now.
# (
# # cmake > 3.22.2 have issue on arm
# # Tested until 3.22.5
# echo "cmake==3.22.2"
# ) >> homeassistant/package_constraints.txt
# Do not pin numpy in wheels building # Do not pin numpy in wheels building
sed -i "/numpy/d" homeassistant/package_constraints.txt sed -i "/numpy/d" homeassistant/package_constraints.txt
@@ -342,3 +204,17 @@ jobs:
constraints: "homeassistant/package_constraints.txt" constraints: "homeassistant/package_constraints.txt"
requirements-diff: "requirements_diff.txt" requirements-diff: "requirements_diff.txt"
requirements: "requirements_all.txtab" requirements: "requirements_all.txtab"
- name: Build wheels (part 3)
uses: home-assistant/wheels@2023.04.0
with:
abi: ${{ matrix.abi }}
tag: musllinux_1_2
arch: ${{ matrix.arch }}
wheels-key: ${{ secrets.WHEELS_KEY }}
env-file: true
apk: "bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;cups-dev;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev;gammu-dev;yaml-dev;openblas-dev;fftw-dev;lapack-dev;gfortran;blas-dev;eigen-dev;freetype-dev;glew-dev;harfbuzz-dev;hdf5-dev;libdc1394-dev;libtbb-dev;mesa-dev;openexr-dev;openjpeg-dev;uchardet-dev"
skip-binary: aiohttp;grpcio;sqlalchemy;protobuf
constraints: "homeassistant/package_constraints.txt"
requirements-diff: "requirements_diff.txt"
requirements: "requirements_all.txtac"

View File

@@ -1,6 +1,6 @@
repos: repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit - repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.262 rev: v0.0.272
hooks: hooks:
- id: ruff - id: ruff
args: args:
@@ -22,19 +22,6 @@ repos:
- --quiet-level=2 - --quiet-level=2
exclude_types: [csv, json] exclude_types: [csv, json]
exclude: ^tests/fixtures/|homeassistant/generated/ exclude: ^tests/fixtures/|homeassistant/generated/
- repo: https://github.com/PyCQA/bandit
rev: 1.7.4
hooks:
- id: bandit
args:
- --quiet
- --format=custom
- --configfile=tests/bandit.yaml
files: ^(homeassistant|script|tests)/.+\.py$
- repo: https://github.com/PyCQA/isort
rev: 5.12.0
hooks:
- id: isort
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0 rev: v4.4.0
hooks: hooks:

View File

@@ -172,6 +172,7 @@ homeassistant.components.huawei_lte.*
homeassistant.components.hydrawise.* homeassistant.components.hydrawise.*
homeassistant.components.hyperion.* homeassistant.components.hyperion.*
homeassistant.components.ibeacon.* homeassistant.components.ibeacon.*
homeassistant.components.image.*
homeassistant.components.image_processing.* homeassistant.components.image_processing.*
homeassistant.components.image_upload.* homeassistant.components.image_upload.*
homeassistant.components.imap.* homeassistant.components.imap.*
@@ -243,6 +244,7 @@ homeassistant.components.overkiz.*
homeassistant.components.peco.* homeassistant.components.peco.*
homeassistant.components.persistent_notification.* homeassistant.components.persistent_notification.*
homeassistant.components.pi_hole.* homeassistant.components.pi_hole.*
homeassistant.components.ping.*
homeassistant.components.powerwall.* homeassistant.components.powerwall.*
homeassistant.components.proximity.* homeassistant.components.proximity.*
homeassistant.components.prusalink.* homeassistant.components.prusalink.*
@@ -275,7 +277,6 @@ homeassistant.components.scene.*
homeassistant.components.schedule.* homeassistant.components.schedule.*
homeassistant.components.scrape.* homeassistant.components.scrape.*
homeassistant.components.select.* homeassistant.components.select.*
homeassistant.components.senseme.*
homeassistant.components.sensibo.* homeassistant.components.sensibo.*
homeassistant.components.sensirion_ble.* homeassistant.components.sensirion_ble.*
homeassistant.components.sensor.* homeassistant.components.sensor.*
@@ -308,6 +309,7 @@ homeassistant.components.tag.*
homeassistant.components.tailscale.* homeassistant.components.tailscale.*
homeassistant.components.tautulli.* homeassistant.components.tautulli.*
homeassistant.components.tcp.* homeassistant.components.tcp.*
homeassistant.components.text.*
homeassistant.components.threshold.* homeassistant.components.threshold.*
homeassistant.components.tibber.* homeassistant.components.tibber.*
homeassistant.components.tile.* homeassistant.components.tile.*

View File

@@ -275,6 +275,8 @@ build.json @home-assistant/supervisor
/homeassistant/components/discogs/ @thibmaek /homeassistant/components/discogs/ @thibmaek
/homeassistant/components/discord/ @tkdrob /homeassistant/components/discord/ @tkdrob
/tests/components/discord/ @tkdrob /tests/components/discord/ @tkdrob
/homeassistant/components/discovergy/ @jpbede
/tests/components/discovergy/ @jpbede
/homeassistant/components/discovery/ @home-assistant/core /homeassistant/components/discovery/ @home-assistant/core
/tests/components/discovery/ @home-assistant/core /tests/components/discovery/ @home-assistant/core
/homeassistant/components/dlink/ @tkdrob /homeassistant/components/dlink/ @tkdrob
@@ -289,6 +291,8 @@ build.json @home-assistant/supervisor
/tests/components/doorbird/ @oblogic7 @bdraco @flacjacket /tests/components/doorbird/ @oblogic7 @bdraco @flacjacket
/homeassistant/components/dormakaba_dkey/ @emontnemery /homeassistant/components/dormakaba_dkey/ @emontnemery
/tests/components/dormakaba_dkey/ @emontnemery /tests/components/dormakaba_dkey/ @emontnemery
/homeassistant/components/dremel_3d_printer/ @tkdrob
/tests/components/dremel_3d_printer/ @tkdrob
/homeassistant/components/dsmr/ @Robbie1221 @frenck /homeassistant/components/dsmr/ @Robbie1221 @frenck
/tests/components/dsmr/ @Robbie1221 @frenck /tests/components/dsmr/ @Robbie1221 @frenck
/homeassistant/components/dsmr_reader/ @depl0y @glodenox /homeassistant/components/dsmr_reader/ @depl0y @glodenox
@@ -352,8 +356,8 @@ build.json @home-assistant/supervisor
/homeassistant/components/eq3btsmart/ @rytilahti /homeassistant/components/eq3btsmart/ @rytilahti
/homeassistant/components/escea/ @lazdavila /homeassistant/components/escea/ @lazdavila
/tests/components/escea/ @lazdavila /tests/components/escea/ @lazdavila
/homeassistant/components/esphome/ @OttoWinter @jesserockz /homeassistant/components/esphome/ @OttoWinter @jesserockz @bdraco
/tests/components/esphome/ @OttoWinter @jesserockz /tests/components/esphome/ @OttoWinter @jesserockz @bdraco
/homeassistant/components/eufylife_ble/ @bdr99 /homeassistant/components/eufylife_ble/ @bdr99
/tests/components/eufylife_ble/ @bdr99 /tests/components/eufylife_ble/ @bdr99
/homeassistant/components/evil_genius_labs/ @balloob /homeassistant/components/evil_genius_labs/ @balloob
@@ -448,8 +452,8 @@ build.json @home-assistant/supervisor
/tests/components/glances/ @engrbm87 /tests/components/glances/ @engrbm87
/homeassistant/components/goalzero/ @tkdrob /homeassistant/components/goalzero/ @tkdrob
/tests/components/goalzero/ @tkdrob /tests/components/goalzero/ @tkdrob
/homeassistant/components/gogogate2/ @vangorra @bdraco /homeassistant/components/gogogate2/ @vangorra
/tests/components/gogogate2/ @vangorra @bdraco /tests/components/gogogate2/ @vangorra
/homeassistant/components/goodwe/ @mletenay @starkillerOG /homeassistant/components/goodwe/ @mletenay @starkillerOG
/tests/components/goodwe/ @mletenay @starkillerOG /tests/components/goodwe/ @mletenay @starkillerOG
/homeassistant/components/google/ @allenporter /homeassistant/components/google/ @allenporter
@@ -559,12 +563,14 @@ build.json @home-assistant/supervisor
/tests/components/icloud/ @Quentame @nzapponi /tests/components/icloud/ @Quentame @nzapponi
/homeassistant/components/ign_sismologia/ @exxamalte /homeassistant/components/ign_sismologia/ @exxamalte
/tests/components/ign_sismologia/ @exxamalte /tests/components/ign_sismologia/ @exxamalte
/homeassistant/components/image/ @home-assistant/core
/tests/components/image/ @home-assistant/core
/homeassistant/components/image_processing/ @home-assistant/core /homeassistant/components/image_processing/ @home-assistant/core
/tests/components/image_processing/ @home-assistant/core /tests/components/image_processing/ @home-assistant/core
/homeassistant/components/image_upload/ @home-assistant/core /homeassistant/components/image_upload/ @home-assistant/core
/tests/components/image_upload/ @home-assistant/core /tests/components/image_upload/ @home-assistant/core
/homeassistant/components/imap/ @engrbm87 @jbouwh /homeassistant/components/imap/ @jbouwh
/tests/components/imap/ @engrbm87 @jbouwh /tests/components/imap/ @jbouwh
/homeassistant/components/incomfort/ @zxdavb /homeassistant/components/incomfort/ @zxdavb
/homeassistant/components/influxdb/ @mdegat01 /homeassistant/components/influxdb/ @mdegat01
/tests/components/influxdb/ @mdegat01 /tests/components/influxdb/ @mdegat01
@@ -697,6 +703,8 @@ build.json @home-assistant/supervisor
/tests/components/logi_circle/ @evanjd /tests/components/logi_circle/ @evanjd
/homeassistant/components/lookin/ @ANMalko @bdraco /homeassistant/components/lookin/ @ANMalko @bdraco
/tests/components/lookin/ @ANMalko @bdraco /tests/components/lookin/ @ANMalko @bdraco
/homeassistant/components/loqed/ @mikewoudenberg
/tests/components/loqed/ @mikewoudenberg
/homeassistant/components/lovelace/ @home-assistant/frontend /homeassistant/components/lovelace/ @home-assistant/frontend
/tests/components/lovelace/ @home-assistant/frontend /tests/components/lovelace/ @home-assistant/frontend
/homeassistant/components/luci/ @mzdrale /homeassistant/components/luci/ @mzdrale
@@ -779,11 +787,12 @@ build.json @home-assistant/supervisor
/tests/components/mutesync/ @currentoor /tests/components/mutesync/ @currentoor
/homeassistant/components/my/ @home-assistant/core /homeassistant/components/my/ @home-assistant/core
/tests/components/my/ @home-assistant/core /tests/components/my/ @home-assistant/core
/homeassistant/components/myq/ @bdraco @ehendrix23 /homeassistant/components/myq/ @ehendrix23
/tests/components/myq/ @bdraco @ehendrix23 /tests/components/myq/ @ehendrix23
/homeassistant/components/mysensors/ @MartinHjelmare @functionpointer /homeassistant/components/mysensors/ @MartinHjelmare @functionpointer
/tests/components/mysensors/ @MartinHjelmare @functionpointer /tests/components/mysensors/ @MartinHjelmare @functionpointer
/homeassistant/components/mystrom/ @fabaff /homeassistant/components/mystrom/ @fabaff
/tests/components/mystrom/ @fabaff
/homeassistant/components/nam/ @bieniu /homeassistant/components/nam/ @bieniu
/tests/components/nam/ @bieniu /tests/components/nam/ @bieniu
/homeassistant/components/nanoleaf/ @milanmeu /homeassistant/components/nanoleaf/ @milanmeu
@@ -878,6 +887,7 @@ build.json @home-assistant/supervisor
/homeassistant/components/opengarage/ @danielhiversen /homeassistant/components/opengarage/ @danielhiversen
/tests/components/opengarage/ @danielhiversen /tests/components/opengarage/ @danielhiversen
/homeassistant/components/openhome/ @bazwilliams /homeassistant/components/openhome/ @bazwilliams
/tests/components/openhome/ @bazwilliams
/homeassistant/components/opensky/ @joostlek /homeassistant/components/opensky/ @joostlek
/homeassistant/components/opentherm_gw/ @mvn23 /homeassistant/components/opentherm_gw/ @mvn23
/tests/components/opentherm_gw/ @mvn23 /tests/components/opentherm_gw/ @mvn23
@@ -961,6 +971,8 @@ build.json @home-assistant/supervisor
/tests/components/qingping/ @bdraco @skgsergio /tests/components/qingping/ @bdraco @skgsergio
/homeassistant/components/qld_bushfire/ @exxamalte /homeassistant/components/qld_bushfire/ @exxamalte
/tests/components/qld_bushfire/ @exxamalte /tests/components/qld_bushfire/ @exxamalte
/homeassistant/components/qnap/ @disforw
/tests/components/qnap/ @disforw
/homeassistant/components/qnap_qsw/ @Noltari /homeassistant/components/qnap_qsw/ @Noltari
/tests/components/qnap_qsw/ @Noltari /tests/components/qnap_qsw/ @Noltari
/homeassistant/components/quantum_gateway/ @cisasteelersfan /homeassistant/components/quantum_gateway/ @cisasteelersfan
@@ -999,6 +1011,8 @@ 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/renson/ @jimmyd-be
/tests/components/renson/ @jimmyd-be
/homeassistant/components/reolink/ @starkillerOG /homeassistant/components/reolink/ @starkillerOG
/tests/components/reolink/ @starkillerOG /tests/components/reolink/ @starkillerOG
/homeassistant/components/repairs/ @home-assistant/core /homeassistant/components/repairs/ @home-assistant/core
@@ -1068,8 +1082,6 @@ build.json @home-assistant/supervisor
/tests/components/select/ @home-assistant/core /tests/components/select/ @home-assistant/core
/homeassistant/components/sense/ @kbickar /homeassistant/components/sense/ @kbickar
/tests/components/sense/ @kbickar /tests/components/sense/ @kbickar
/homeassistant/components/senseme/ @mikelawrence @bdraco
/tests/components/senseme/ @mikelawrence @bdraco
/homeassistant/components/sensibo/ @andrey-git @gjohansson-ST /homeassistant/components/sensibo/ @andrey-git @gjohansson-ST
/tests/components/sensibo/ @andrey-git @gjohansson-ST /tests/components/sensibo/ @andrey-git @gjohansson-ST
/homeassistant/components/sensirion_ble/ @akx /homeassistant/components/sensirion_ble/ @akx
@@ -1292,6 +1304,8 @@ build.json @home-assistant/supervisor
/tests/components/twentemilieu/ @frenck /tests/components/twentemilieu/ @frenck
/homeassistant/components/twinkly/ @dr1rrb @Robbie1221 /homeassistant/components/twinkly/ @dr1rrb @Robbie1221
/tests/components/twinkly/ @dr1rrb @Robbie1221 /tests/components/twinkly/ @dr1rrb @Robbie1221
/homeassistant/components/twitch/ @joostlek
/tests/components/twitch/ @joostlek
/homeassistant/components/ukraine_alarm/ @PaulAnnekov /homeassistant/components/ukraine_alarm/ @PaulAnnekov
/tests/components/ukraine_alarm/ @PaulAnnekov /tests/components/ukraine_alarm/ @PaulAnnekov
/homeassistant/components/unifi/ @Kane610 /homeassistant/components/unifi/ @Kane610

View File

@@ -1,4 +1,4 @@
FROM mcr.microsoft.com/vscode/devcontainers/python:0-3.10 FROM mcr.microsoft.com/vscode/devcontainers/python:0-3.11
SHELL ["/bin/bash", "-o", "pipefail", "-c"] SHELL ["/bin/bash", "-o", "pipefail", "-c"]

View File

@@ -21,7 +21,7 @@ If you run into issues while using Home Assistant or during development
of a component, check the `Home Assistant help section <https://home-assistant.io/help/>`__ of our website for further help and information. of a component, check the `Home Assistant help section <https://home-assistant.io/help/>`__ of our website for further help and information.
.. |Chat Status| image:: https://img.shields.io/discord/330944238910963714.svg .. |Chat Status| image:: https://img.shields.io/discord/330944238910963714.svg
:target: https://discord.gg/c5DvZ4e :target: https://www.home-assistant.io/join-chat/
.. |screenshot-states| image:: https://raw.githubusercontent.com/home-assistant/core/master/docs/screenshots.png .. |screenshot-states| image:: https://raw.githubusercontent.com/home-assistant/core/master/docs/screenshots.png
:target: https://demo.home-assistant.io :target: https://demo.home-assistant.io
.. |screenshot-integrations| image:: https://raw.githubusercontent.com/home-assistant/core/dev/docs/screenshot-integrations.png .. |screenshot-integrations| image:: https://raw.githubusercontent.com/home-assistant/core/dev/docs/screenshot-integrations.png

View File

@@ -1,14 +1,16 @@
image: homeassistant/{arch}-homeassistant image: ghcr.io/home-assistant/{arch}-homeassistant
shadow_repository: ghcr.io/home-assistant
build_from: build_from:
aarch64: ghcr.io/home-assistant/aarch64-homeassistant-base:2023.06.0 aarch64: ghcr.io/home-assistant/aarch64-homeassistant-base:2023.06.1
armhf: ghcr.io/home-assistant/armhf-homeassistant-base:2023.06.0 armhf: ghcr.io/home-assistant/armhf-homeassistant-base:2023.06.1
armv7: ghcr.io/home-assistant/armv7-homeassistant-base:2023.06.0 armv7: ghcr.io/home-assistant/armv7-homeassistant-base:2023.06.1
amd64: ghcr.io/home-assistant/amd64-homeassistant-base:2023.06.0 amd64: ghcr.io/home-assistant/amd64-homeassistant-base:2023.06.1
i386: ghcr.io/home-assistant/i386-homeassistant-base:2023.06.0 i386: ghcr.io/home-assistant/i386-homeassistant-base:2023.06.1
codenotary: codenotary:
signer: notary@home-assistant.io signer: notary@home-assistant.io
base_image: notary@home-assistant.io base_image: notary@home-assistant.io
cosign:
base_identity: https://github.com/home-assistant/docker/.*
identity: https://github.com/home-assistant/core/.*
labels: labels:
io.hass.type: core io.hass.type: core
org.opencontainers.image.title: Home Assistant org.opencontainers.image.title: Home Assistant

View File

@@ -13,8 +13,8 @@ from homeassistant.const import CONF_COMMAND
from homeassistant.data_entry_flow import FlowResult from homeassistant.data_entry_flow import FlowResult
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
from ..models import Credentials, UserMeta from ..models import Credentials, UserMeta
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
CONF_ARGS = "args" CONF_ARGS = "args"
CONF_META = "meta" CONF_META = "meta"

View File

@@ -16,8 +16,8 @@ from homeassistant.data_entry_flow import FlowResult
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.storage import Store from homeassistant.helpers.storage import Store
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
from ..models import Credentials, UserMeta from ..models import Credentials, UserMeta
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
STORAGE_VERSION = 1 STORAGE_VERSION = 1
STORAGE_KEY = "auth_provider.homeassistant" STORAGE_KEY = "auth_provider.homeassistant"

View File

@@ -11,8 +11,8 @@ from homeassistant.core import callback
from homeassistant.data_entry_flow import FlowResult from homeassistant.data_entry_flow import FlowResult
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
from ..models import Credentials, UserMeta from ..models import Credentials, UserMeta
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
USER_SCHEMA = vol.Schema( USER_SCHEMA = vol.Schema(
{ {

View File

@@ -15,8 +15,8 @@ from homeassistant.data_entry_flow import FlowResult
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
from ..models import Credentials, UserMeta from ..models import Credentials, UserMeta
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
AUTH_PROVIDER_TYPE = "legacy_api_password" AUTH_PROVIDER_TYPE = "legacy_api_password"
CONF_API_PASSWORD = "api_password" CONF_API_PASSWORD = "api_password"

View File

@@ -23,9 +23,9 @@ from homeassistant.data_entry_flow import FlowResult
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
from .. import InvalidAuthError from .. import InvalidAuthError
from ..models import Credentials, RefreshToken, UserMeta from ..models import Credentials, RefreshToken, UserMeta
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
IPAddress = IPv4Address | IPv6Address IPAddress = IPv4Address | IPv6Address
IPNetwork = IPv4Network | IPv6Network IPNetwork = IPv4Network | IPv6Network

View File

@@ -0,0 +1,72 @@
"""Functools backports from standard lib."""
from __future__ import annotations
from collections.abc import Callable
from types import GenericAlias
from typing import Any, Generic, TypeVar, overload
from typing_extensions import Self
_T = TypeVar("_T")
_R = TypeVar("_R")
class cached_property(Generic[_T, _R]): # pylint: disable=invalid-name
"""Backport of Python 3.12's cached_property.
Includes https://github.com/python/cpython/pull/101890/files
"""
def __init__(self, func: Callable[[_T], _R]) -> None:
"""Initialize."""
self.func = func
self.attrname: Any = None
self.__doc__ = func.__doc__
def __set_name__(self, owner: type[_T], name: str) -> None:
"""Set name."""
if self.attrname is None:
self.attrname = name
elif name != self.attrname:
raise TypeError(
"Cannot assign the same cached_property to two different names "
f"({self.attrname!r} and {name!r})."
)
@overload
def __get__(self, instance: None, owner: type[_T]) -> Self:
...
@overload
def __get__(self, instance: _T, owner: type[_T]) -> _R:
...
def __get__(self, instance: _T | None, owner: type[_T] | None = None) -> _R | Self:
"""Get."""
if instance is None:
return self
if self.attrname is None:
raise TypeError(
"Cannot use cached_property instance without calling __set_name__ on it."
)
try:
cache = instance.__dict__
# not all objects have __dict__ (e.g. class defines slots)
except AttributeError:
msg = (
f"No '__dict__' attribute on {type(instance).__name__!r} "
f"instance to cache {self.attrname!r} property."
)
raise TypeError(msg) from None
val = self.func(instance)
try:
cache[self.attrname] = val
except TypeError:
msg = (
f"The '__dict__' attribute on {type(instance).__name__!r} instance "
f"does not support item assignment for caching {self.attrname!r} property."
)
raise TypeError(msg) from None
return val
__class_getitem__ = classmethod(GenericAlias)

View File

@@ -34,6 +34,7 @@ class AbodeAlarm(AbodeDevice, alarm.AlarmControlPanelEntity):
"""An alarm_control_panel implementation for Abode.""" """An alarm_control_panel implementation for Abode."""
_attr_icon = ICON _attr_icon = ICON
_attr_name = None
_attr_code_arm_required = False _attr_code_arm_required = False
_attr_supported_features = ( _attr_supported_features = (
AlarmControlPanelEntityFeature.ARM_HOME AlarmControlPanelEntityFeature.ARM_HOME

View File

@@ -42,6 +42,7 @@ async def async_setup_entry(
class AbodeBinarySensor(AbodeDevice, BinarySensorEntity): class AbodeBinarySensor(AbodeDevice, BinarySensorEntity):
"""A binary sensor implementation for Abode device.""" """A binary sensor implementation for Abode device."""
_attr_name = None
_device: ABBinarySensor _device: ABBinarySensor
@property @property

View File

@@ -39,6 +39,7 @@ class AbodeCamera(AbodeDevice, Camera):
"""Representation of an Abode camera.""" """Representation of an Abode camera."""
_device: AbodeCam _device: AbodeCam
_attr_name = None
def __init__(self, data: AbodeSystem, device: AbodeDev, event: Event) -> None: def __init__(self, data: AbodeSystem, device: AbodeDev, event: Event) -> None:
"""Initialize the Abode device.""" """Initialize the Abode device."""

View File

@@ -29,6 +29,7 @@ class AbodeCover(AbodeDevice, CoverEntity):
"""Representation of an Abode cover.""" """Representation of an Abode cover."""
_device: AbodeCV _device: AbodeCV
_attr_name = None
@property @property
def is_closed(self) -> bool: def is_closed(self) -> bool:

View File

@@ -42,6 +42,7 @@ class AbodeLight(AbodeDevice, LightEntity):
"""Representation of an Abode light.""" """Representation of an Abode light."""
_device: AbodeLT _device: AbodeLT
_attr_name = None
def turn_on(self, **kwargs: Any) -> None: def turn_on(self, **kwargs: Any) -> None:
"""Turn on the light.""" """Turn on the light."""

View File

@@ -29,6 +29,7 @@ class AbodeLock(AbodeDevice, LockEntity):
"""Representation of an Abode lock.""" """Representation of an Abode lock."""
_device: AbodeLK _device: AbodeLK
_attr_name = None
def lock(self, **kwargs: Any) -> None: def lock(self, **kwargs: Any) -> None:
"""Lock the device.""" """Lock the device."""

View File

@@ -22,17 +22,14 @@ from .const import DOMAIN
SENSOR_TYPES: tuple[SensorEntityDescription, ...] = ( SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
SensorEntityDescription( SensorEntityDescription(
key=CONST.TEMP_STATUS_KEY, key=CONST.TEMP_STATUS_KEY,
name="Temperature",
device_class=SensorDeviceClass.TEMPERATURE, device_class=SensorDeviceClass.TEMPERATURE,
), ),
SensorEntityDescription( SensorEntityDescription(
key=CONST.HUMI_STATUS_KEY, key=CONST.HUMI_STATUS_KEY,
name="Humidity",
device_class=SensorDeviceClass.HUMIDITY, device_class=SensorDeviceClass.HUMIDITY,
), ),
SensorEntityDescription( SensorEntityDescription(
key=CONST.LUX_STATUS_KEY, key=CONST.LUX_STATUS_KEY,
name="Lux",
device_class=SensorDeviceClass.ILLUMINANCE, device_class=SensorDeviceClass.ILLUMINANCE,
), ),
) )

View File

@@ -44,6 +44,7 @@ class AbodeSwitch(AbodeDevice, SwitchEntity):
"""Representation of an Abode switch.""" """Representation of an Abode switch."""
_device: AbodeSW _device: AbodeSW
_attr_name = None
def turn_on(self, **kwargs: Any) -> None: def turn_on(self, **kwargs: Any) -> None:
"""Turn on the device.""" """Turn on the device."""

View File

@@ -4,10 +4,13 @@ from __future__ import annotations
from typing import cast from typing import cast
from homeassistant.components.weather import ( from homeassistant.components.weather import (
ATTR_FORECAST_CLOUD_COVERAGE,
ATTR_FORECAST_CONDITION, ATTR_FORECAST_CONDITION,
ATTR_FORECAST_NATIVE_APPARENT_TEMP,
ATTR_FORECAST_NATIVE_PRECIPITATION, ATTR_FORECAST_NATIVE_PRECIPITATION,
ATTR_FORECAST_NATIVE_TEMP, ATTR_FORECAST_NATIVE_TEMP,
ATTR_FORECAST_NATIVE_TEMP_LOW, ATTR_FORECAST_NATIVE_TEMP_LOW,
ATTR_FORECAST_NATIVE_WIND_GUST_SPEED,
ATTR_FORECAST_NATIVE_WIND_SPEED, ATTR_FORECAST_NATIVE_WIND_SPEED,
ATTR_FORECAST_PRECIPITATION_PROBABILITY, ATTR_FORECAST_PRECIPITATION_PROBABILITY,
ATTR_FORECAST_TIME, ATTR_FORECAST_TIME,
@@ -29,7 +32,16 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.util.dt import utc_from_timestamp from homeassistant.util.dt import utc_from_timestamp
from . import AccuWeatherDataUpdateCoordinator from . import AccuWeatherDataUpdateCoordinator
from .const import API_METRIC, ATTR_FORECAST, ATTRIBUTION, CONDITION_CLASSES, DOMAIN from .const import (
API_METRIC,
ATTR_DIRECTION,
ATTR_FORECAST,
ATTR_SPEED,
ATTR_VALUE,
ATTRIBUTION,
CONDITION_CLASSES,
DOMAIN,
)
PARALLEL_UPDATES = 1 PARALLEL_UPDATES = 1
@@ -50,6 +62,7 @@ class AccuWeatherEntity(
"""Define an AccuWeather entity.""" """Define an AccuWeather entity."""
_attr_has_entity_name = True _attr_has_entity_name = True
_attr_name = None
def __init__(self, coordinator: AccuWeatherDataUpdateCoordinator) -> None: def __init__(self, coordinator: AccuWeatherDataUpdateCoordinator) -> None:
"""Initialize.""" """Initialize."""
@@ -78,35 +91,61 @@ class AccuWeatherEntity(
except IndexError: except IndexError:
return None return None
@property
def cloud_coverage(self) -> float:
"""Return the Cloud coverage in %."""
return cast(float, self.coordinator.data["CloudCover"])
@property
def native_apparent_temperature(self) -> float:
"""Return the apparent temperature."""
return cast(
float, self.coordinator.data["ApparentTemperature"][API_METRIC][ATTR_VALUE]
)
@property @property
def native_temperature(self) -> float: def native_temperature(self) -> float:
"""Return the temperature.""" """Return the temperature."""
return cast(float, self.coordinator.data["Temperature"][API_METRIC]["Value"]) return cast(float, self.coordinator.data["Temperature"][API_METRIC][ATTR_VALUE])
@property @property
def native_pressure(self) -> float: def native_pressure(self) -> float:
"""Return the pressure.""" """Return the pressure."""
return cast(float, self.coordinator.data["Pressure"][API_METRIC]["Value"]) return cast(float, self.coordinator.data["Pressure"][API_METRIC][ATTR_VALUE])
@property
def native_dew_point(self) -> float:
"""Return the dew point."""
return cast(float, self.coordinator.data["DewPoint"][API_METRIC][ATTR_VALUE])
@property @property
def humidity(self) -> int: def humidity(self) -> int:
"""Return the humidity.""" """Return the humidity."""
return cast(int, self.coordinator.data["RelativeHumidity"]) return cast(int, self.coordinator.data["RelativeHumidity"])
@property
def native_wind_gust_speed(self) -> float:
"""Return the wind gust speed."""
return cast(
float, self.coordinator.data["WindGust"][ATTR_SPEED][API_METRIC][ATTR_VALUE]
)
@property @property
def native_wind_speed(self) -> float: def native_wind_speed(self) -> float:
"""Return the wind speed.""" """Return the wind speed."""
return cast(float, self.coordinator.data["Wind"]["Speed"][API_METRIC]["Value"]) return cast(
float, self.coordinator.data["Wind"][ATTR_SPEED][API_METRIC][ATTR_VALUE]
)
@property @property
def wind_bearing(self) -> int: def wind_bearing(self) -> int:
"""Return the wind bearing.""" """Return the wind bearing."""
return cast(int, self.coordinator.data["Wind"]["Direction"]["Degrees"]) return cast(int, self.coordinator.data["Wind"][ATTR_DIRECTION]["Degrees"])
@property @property
def native_visibility(self) -> float: def native_visibility(self) -> float:
"""Return the visibility.""" """Return the visibility."""
return cast(float, self.coordinator.data["Visibility"][API_METRIC]["Value"]) return cast(float, self.coordinator.data["Visibility"][API_METRIC][ATTR_VALUE])
@property @property
def forecast(self) -> list[Forecast] | None: def forecast(self) -> list[Forecast] | None:
@@ -117,14 +156,23 @@ class AccuWeatherEntity(
return [ return [
{ {
ATTR_FORECAST_TIME: utc_from_timestamp(item["EpochDate"]).isoformat(), ATTR_FORECAST_TIME: utc_from_timestamp(item["EpochDate"]).isoformat(),
ATTR_FORECAST_NATIVE_TEMP: item["TemperatureMax"]["Value"], ATTR_FORECAST_CLOUD_COVERAGE: item["CloudCoverDay"],
ATTR_FORECAST_NATIVE_TEMP_LOW: item["TemperatureMin"]["Value"], ATTR_FORECAST_NATIVE_TEMP: item["TemperatureMax"][ATTR_VALUE],
ATTR_FORECAST_NATIVE_PRECIPITATION: item["TotalLiquidDay"]["Value"], ATTR_FORECAST_NATIVE_TEMP_LOW: item["TemperatureMin"][ATTR_VALUE],
ATTR_FORECAST_NATIVE_APPARENT_TEMP: item["RealFeelTemperatureMax"][
ATTR_VALUE
],
ATTR_FORECAST_NATIVE_PRECIPITATION: item["TotalLiquidDay"][ATTR_VALUE],
ATTR_FORECAST_PRECIPITATION_PROBABILITY: item[ ATTR_FORECAST_PRECIPITATION_PROBABILITY: item[
"PrecipitationProbabilityDay" "PrecipitationProbabilityDay"
], ],
ATTR_FORECAST_NATIVE_WIND_SPEED: item["WindDay"]["Speed"]["Value"], ATTR_FORECAST_NATIVE_WIND_SPEED: item["WindDay"][ATTR_SPEED][
ATTR_FORECAST_WIND_BEARING: item["WindDay"]["Direction"]["Degrees"], ATTR_VALUE
],
ATTR_FORECAST_NATIVE_WIND_GUST_SPEED: item["WindGustDay"][ATTR_SPEED][
ATTR_VALUE
],
ATTR_FORECAST_WIND_BEARING: item["WindDay"][ATTR_DIRECTION]["Degrees"],
ATTR_FORECAST_CONDITION: [ ATTR_FORECAST_CONDITION: [
k for k, v in CONDITION_CLASSES.items() if item["IconDay"] in v k for k, v in CONDITION_CLASSES.items() if item["IconDay"] in v
][0], ][0],

View File

@@ -14,6 +14,7 @@ class AcmedaBase(entity.Entity):
"""Base representation of an Acmeda roller.""" """Base representation of an Acmeda roller."""
_attr_should_poll = False _attr_should_poll = False
_attr_has_entity_name = True
def __init__(self, roller: aiopulse.Roller) -> None: def __init__(self, roller: aiopulse.Roller) -> None:
"""Initialize the roller.""" """Initialize the roller."""
@@ -72,11 +73,6 @@ class AcmedaBase(entity.Entity):
"""Return the ID of this roller.""" """Return the ID of this roller."""
return self.roller.id return self.roller.id
@property
def name(self) -> str | None:
"""Return the name of roller."""
return self.roller.name
@property @property
def device_info(self) -> entity.DeviceInfo: def device_info(self) -> entity.DeviceInfo:
"""Return the device info.""" """Return the device info."""

View File

@@ -45,7 +45,9 @@ async def async_setup_entry(
class AcmedaCover(AcmedaBase, CoverEntity): class AcmedaCover(AcmedaBase, CoverEntity):
"""Representation of a Acmeda cover device.""" """Representation of an Acmeda cover device."""
_attr_name = None
@property @property
def current_cover_position(self) -> int | None: def current_cover_position(self) -> int | None:

View File

@@ -40,16 +40,11 @@ async def async_setup_entry(
class AcmedaBattery(AcmedaBase, SensorEntity): class AcmedaBattery(AcmedaBase, SensorEntity):
"""Representation of a Acmeda cover device.""" """Representation of an Acmeda cover sensor."""
_attr_device_class = SensorDeviceClass.BATTERY _attr_device_class = SensorDeviceClass.BATTERY
_attr_native_unit_of_measurement = PERCENTAGE _attr_native_unit_of_measurement = PERCENTAGE
@property
def name(self) -> str:
"""Return the name of roller."""
return f"{super().name} Battery"
@property @property
def native_value(self) -> float | int | None: def native_value(self) -> float | int | None:
"""Return the state of the device.""" """Return the state of the device."""

View File

@@ -1,7 +1,7 @@
"""Support for Adax wifi-enabled home heaters.""" """Support for Adax wifi-enabled home heaters."""
from __future__ import annotations from __future__ import annotations
from typing import Any from typing import Any, cast
from adax import Adax from adax import Adax
from adax_local import Adax as AdaxLocal from adax_local import Adax as AdaxLocal
@@ -79,7 +79,10 @@ class AdaxDevice(ClimateEntity):
self._attr_unique_id = f"{heater_data['homeId']}_{heater_data['id']}" self._attr_unique_id = f"{heater_data['homeId']}_{heater_data['id']}"
self._attr_device_info = DeviceInfo( self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, heater_data["id"])}, identifiers={(DOMAIN, heater_data["id"])},
name=self.name, # Instead of setting the device name to the entity name, adax
# should be updated to set has_entity_name = True, and set the entity
# name to None
name=cast(str | None, self.name),
manufacturer="Adax", manufacturer="Adax",
) )

View File

@@ -39,56 +39,56 @@ class AdGuardHomeEntityDescription(
SENSORS: tuple[AdGuardHomeEntityDescription, ...] = ( SENSORS: tuple[AdGuardHomeEntityDescription, ...] = (
AdGuardHomeEntityDescription( AdGuardHomeEntityDescription(
key="dns_queries", key="dns_queries",
name="DNS queries", translation_key="dns_queries",
icon="mdi:magnify", icon="mdi:magnify",
native_unit_of_measurement="queries", native_unit_of_measurement="queries",
value_fn=lambda adguard: adguard.stats.dns_queries(), value_fn=lambda adguard: adguard.stats.dns_queries(),
), ),
AdGuardHomeEntityDescription( AdGuardHomeEntityDescription(
key="blocked_filtering", key="blocked_filtering",
name="DNS queries blocked", translation_key="dns_queries_blocked",
icon="mdi:magnify-close", icon="mdi:magnify-close",
native_unit_of_measurement="queries", native_unit_of_measurement="queries",
value_fn=lambda adguard: adguard.stats.blocked_filtering(), value_fn=lambda adguard: adguard.stats.blocked_filtering(),
), ),
AdGuardHomeEntityDescription( AdGuardHomeEntityDescription(
key="blocked_percentage", key="blocked_percentage",
name="DNS queries blocked ratio", translation_key="dns_queries_blocked_ratio",
icon="mdi:magnify-close", icon="mdi:magnify-close",
native_unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
value_fn=lambda adguard: adguard.stats.blocked_percentage(), value_fn=lambda adguard: adguard.stats.blocked_percentage(),
), ),
AdGuardHomeEntityDescription( AdGuardHomeEntityDescription(
key="blocked_parental", key="blocked_parental",
name="Parental control blocked", translation_key="parental_control_blocked",
icon="mdi:human-male-girl", icon="mdi:human-male-girl",
native_unit_of_measurement="requests", native_unit_of_measurement="requests",
value_fn=lambda adguard: adguard.stats.replaced_parental(), value_fn=lambda adguard: adguard.stats.replaced_parental(),
), ),
AdGuardHomeEntityDescription( AdGuardHomeEntityDescription(
key="blocked_safebrowsing", key="blocked_safebrowsing",
name="Safe browsing blocked", translation_key="safe_browsing_blocked",
icon="mdi:shield-half-full", icon="mdi:shield-half-full",
native_unit_of_measurement="requests", native_unit_of_measurement="requests",
value_fn=lambda adguard: adguard.stats.replaced_safebrowsing(), value_fn=lambda adguard: adguard.stats.replaced_safebrowsing(),
), ),
AdGuardHomeEntityDescription( AdGuardHomeEntityDescription(
key="enforced_safesearch", key="enforced_safesearch",
name="Safe searches enforced", translation_key="safe_searches_enforced",
icon="mdi:shield-search", icon="mdi:shield-search",
native_unit_of_measurement="requests", native_unit_of_measurement="requests",
value_fn=lambda adguard: adguard.stats.replaced_safesearch(), value_fn=lambda adguard: adguard.stats.replaced_safesearch(),
), ),
AdGuardHomeEntityDescription( AdGuardHomeEntityDescription(
key="average_speed", key="average_speed",
name="Average processing speed", translation_key="average_processing_speed",
icon="mdi:speedometer", icon="mdi:speedometer",
native_unit_of_measurement=UnitOfTime.MILLISECONDS, native_unit_of_measurement=UnitOfTime.MILLISECONDS,
value_fn=lambda adguard: adguard.stats.avg_processing_time(), value_fn=lambda adguard: adguard.stats.avg_processing_time(),
), ),
AdGuardHomeEntityDescription( AdGuardHomeEntityDescription(
key="rules_count", key="rules_count",
name="Rules count", translation_key="rules_count",
icon="mdi:counter", icon="mdi:counter",
native_unit_of_measurement="rules", native_unit_of_measurement="rules",
value_fn=lambda adguard: adguard.filtering.rules_count(allowlist=False), value_fn=lambda adguard: adguard.filtering.rules_count(allowlist=False),

View File

@@ -24,5 +24,53 @@
"existing_instance_updated": "Updated existing configuration.", "existing_instance_updated": "Updated existing configuration.",
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]" "already_configured": "[%key:common::config_flow::abort::already_configured_service%]"
} }
},
"entity": {
"sensor": {
"dns_queries": {
"name": "DNS queries"
},
"dns_queries_blocked": {
"name": "DNS queries blocked"
},
"dns_queries_blocked_ratio": {
"name": "DNS queries blocked ratio"
},
"parental_control_blocked": {
"name": "Parental control blocked"
},
"safe_browsing_blocked": {
"name": "Safe browsing blocked"
},
"safe_searches_enforced": {
"name": "Safe searches enforced"
},
"average_processing_speed": {
"name": "Average processing speed"
},
"rules_count": {
"name": "Rules count"
}
},
"switch": {
"protection": {
"name": "Protection"
},
"parental": {
"name": "Parental control"
},
"safe_search": {
"name": "Safe search"
},
"safe_browsing": {
"name": "Safe browsing"
},
"filtering": {
"name": "Filtering"
},
"query_log": {
"name": "Query log"
}
}
} }
} }

View File

@@ -40,7 +40,7 @@ class AdGuardHomeSwitchEntityDescription(
SWITCHES: tuple[AdGuardHomeSwitchEntityDescription, ...] = ( SWITCHES: tuple[AdGuardHomeSwitchEntityDescription, ...] = (
AdGuardHomeSwitchEntityDescription( AdGuardHomeSwitchEntityDescription(
key="protection", key="protection",
name="Protection", translation_key="protection",
icon="mdi:shield-check", icon="mdi:shield-check",
is_on_fn=lambda adguard: adguard.protection_enabled, is_on_fn=lambda adguard: adguard.protection_enabled,
turn_on_fn=lambda adguard: adguard.enable_protection, turn_on_fn=lambda adguard: adguard.enable_protection,
@@ -48,7 +48,7 @@ SWITCHES: tuple[AdGuardHomeSwitchEntityDescription, ...] = (
), ),
AdGuardHomeSwitchEntityDescription( AdGuardHomeSwitchEntityDescription(
key="parental", key="parental",
name="Parental control", translation_key="parental",
icon="mdi:shield-check", icon="mdi:shield-check",
is_on_fn=lambda adguard: adguard.parental.enabled, is_on_fn=lambda adguard: adguard.parental.enabled,
turn_on_fn=lambda adguard: adguard.parental.enable, turn_on_fn=lambda adguard: adguard.parental.enable,
@@ -56,7 +56,7 @@ SWITCHES: tuple[AdGuardHomeSwitchEntityDescription, ...] = (
), ),
AdGuardHomeSwitchEntityDescription( AdGuardHomeSwitchEntityDescription(
key="safesearch", key="safesearch",
name="Safe search", translation_key="safe_search",
icon="mdi:shield-check", icon="mdi:shield-check",
is_on_fn=lambda adguard: adguard.safesearch.enabled, is_on_fn=lambda adguard: adguard.safesearch.enabled,
turn_on_fn=lambda adguard: adguard.safesearch.enable, turn_on_fn=lambda adguard: adguard.safesearch.enable,
@@ -64,7 +64,7 @@ SWITCHES: tuple[AdGuardHomeSwitchEntityDescription, ...] = (
), ),
AdGuardHomeSwitchEntityDescription( AdGuardHomeSwitchEntityDescription(
key="safebrowsing", key="safebrowsing",
name="Safe browsing", translation_key="safe_browsing",
icon="mdi:shield-check", icon="mdi:shield-check",
is_on_fn=lambda adguard: adguard.safebrowsing.enabled, is_on_fn=lambda adguard: adguard.safebrowsing.enabled,
turn_on_fn=lambda adguard: adguard.safebrowsing.enable, turn_on_fn=lambda adguard: adguard.safebrowsing.enable,
@@ -72,7 +72,7 @@ SWITCHES: tuple[AdGuardHomeSwitchEntityDescription, ...] = (
), ),
AdGuardHomeSwitchEntityDescription( AdGuardHomeSwitchEntityDescription(
key="filtering", key="filtering",
name="Filtering", translation_key="filtering",
icon="mdi:shield-check", icon="mdi:shield-check",
is_on_fn=lambda adguard: adguard.filtering.enabled, is_on_fn=lambda adguard: adguard.filtering.enabled,
turn_on_fn=lambda adguard: adguard.filtering.enable, turn_on_fn=lambda adguard: adguard.filtering.enable,
@@ -80,7 +80,7 @@ SWITCHES: tuple[AdGuardHomeSwitchEntityDescription, ...] = (
), ),
AdGuardHomeSwitchEntityDescription( AdGuardHomeSwitchEntityDescription(
key="querylog", key="querylog",
name="Query log", translation_key="query_log",
icon="mdi:shield-check", icon="mdi:shield-check",
is_on_fn=lambda adguard: adguard.querylog.enabled, is_on_fn=lambda adguard: adguard.querylog.enabled,
turn_on_fn=lambda adguard: adguard.querylog.enable, turn_on_fn=lambda adguard: adguard.querylog.enable,

View File

@@ -10,6 +10,7 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, StateType from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, StateType
from .. import ads
from . import ( from . import (
ADS_TYPEMAP, ADS_TYPEMAP,
CONF_ADS_FACTOR, CONF_ADS_FACTOR,
@@ -18,7 +19,6 @@ from . import (
STATE_KEY_STATE, STATE_KEY_STATE,
AdsEntity, AdsEntity,
) )
from .. import ads
DEFAULT_NAME = "ADS sensor" DEFAULT_NAME = "ADS sensor"
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(

View File

@@ -90,6 +90,7 @@ class AdvantageAirAC(AdvantageAirAcEntity, ClimateEntity):
_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
_attr_name = None
_attr_hvac_modes = [ _attr_hvac_modes = [
HVACMode.OFF, HVACMode.OFF,

View File

@@ -9,17 +9,30 @@ from homeassistant.core import HomeAssistant
from .const import DOMAIN as ADVANTAGE_AIR_DOMAIN from .const import DOMAIN as ADVANTAGE_AIR_DOMAIN
TO_REDACT = ["dealerPhoneNumber", "latitude", "logoPIN", "longitude", "postCode"] TO_REDACT = [
"dealerPhoneNumber",
"latitude",
"logoPIN",
"longitude",
"postCode",
"rid",
"deviceNames",
"deviceIds",
"deviceIdsV2",
"backupId",
]
async def async_get_config_entry_diagnostics( async def async_get_config_entry_diagnostics(
hass: HomeAssistant, config_entry: ConfigEntry hass: HomeAssistant, config_entry: ConfigEntry
) -> dict[str, Any]: ) -> dict[str, Any]:
"""Return diagnostics for a config entry.""" """Return diagnostics for a config entry."""
data = hass.data[ADVANTAGE_AIR_DOMAIN][config_entry.entry_id]["coordinator"].data data = hass.data[ADVANTAGE_AIR_DOMAIN][config_entry.entry_id].coordinator.data
# Return only the relevant children # Return only the relevant children
return { return {
"aircons": data["aircons"], "aircons": data.get("aircons"),
"myLights": data.get("myLights"),
"myThings": data.get("myThings"),
"system": async_redact_data(data["system"], TO_REDACT), "system": async_redact_data(data["system"], TO_REDACT),
} }

View File

@@ -84,6 +84,8 @@ class AdvantageAirZoneEntity(AdvantageAirAcEntity):
class AdvantageAirThingEntity(AdvantageAirEntity): class AdvantageAirThingEntity(AdvantageAirEntity):
"""Parent class for Advantage Air Things Entities.""" """Parent class for Advantage Air Things Entities."""
_attr_name = None
def __init__(self, instance: AdvantageAirData, thing: dict[str, Any]) -> None: def __init__(self, instance: AdvantageAirData, thing: dict[str, Any]) -> None:
"""Initialize common aspects of an Advantage Air Things entity.""" """Initialize common aspects of an Advantage Air Things entity."""
super().__init__(instance) super().__init__(instance)

View File

@@ -41,6 +41,7 @@ class AdvantageAirLight(AdvantageAirEntity, LightEntity):
"""Representation of Advantage Air Light.""" """Representation of Advantage Air Light."""
_attr_supported_color_modes = {ColorMode.ONOFF} _attr_supported_color_modes = {ColorMode.ONOFF}
_attr_name = None
def __init__(self, instance: AdvantageAirData, light: dict[str, Any]) -> None: def __init__(self, instance: AdvantageAirData, light: dict[str, Any]) -> None:
"""Initialize an Advantage Air Light.""" """Initialize an Advantage Air Light."""

View File

@@ -7,5 +7,5 @@
"iot_class": "local_polling", "iot_class": "local_polling",
"loggers": ["advantage_air"], "loggers": ["advantage_air"],
"quality_scale": "platinum", "quality_scale": "platinum",
"requirements": ["advantage_air==0.4.4"] "requirements": ["advantage-air==0.4.4"]
} }

View File

@@ -80,7 +80,6 @@ SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = (
AirlySensorEntityDescription( AirlySensorEntityDescription(
key=ATTR_API_PM1, key=ATTR_API_PM1,
device_class=SensorDeviceClass.PM1, device_class=SensorDeviceClass.PM1,
translation_key="pm1",
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,
suggested_display_precision=0, suggested_display_precision=0,
@@ -88,7 +87,6 @@ SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = (
AirlySensorEntityDescription( AirlySensorEntityDescription(
key=ATTR_API_PM25, key=ATTR_API_PM25,
device_class=SensorDeviceClass.PM25, device_class=SensorDeviceClass.PM25,
translation_key="pm25",
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,
suggested_display_precision=0, suggested_display_precision=0,
@@ -100,7 +98,6 @@ SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = (
AirlySensorEntityDescription( AirlySensorEntityDescription(
key=ATTR_API_PM10, key=ATTR_API_PM10,
device_class=SensorDeviceClass.PM10, device_class=SensorDeviceClass.PM10,
translation_key="pm10",
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,
suggested_display_precision=0, suggested_display_precision=0,
@@ -112,7 +109,6 @@ SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = (
AirlySensorEntityDescription( AirlySensorEntityDescription(
key=ATTR_API_HUMIDITY, key=ATTR_API_HUMIDITY,
device_class=SensorDeviceClass.HUMIDITY, device_class=SensorDeviceClass.HUMIDITY,
translation_key="humidity",
native_unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=1, suggested_display_precision=1,
@@ -120,7 +116,6 @@ SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = (
AirlySensorEntityDescription( AirlySensorEntityDescription(
key=ATTR_API_PRESSURE, key=ATTR_API_PRESSURE,
device_class=SensorDeviceClass.PRESSURE, device_class=SensorDeviceClass.PRESSURE,
translation_key="pressure",
native_unit_of_measurement=UnitOfPressure.HPA, native_unit_of_measurement=UnitOfPressure.HPA,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=0, suggested_display_precision=0,
@@ -128,7 +123,6 @@ SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = (
AirlySensorEntityDescription( AirlySensorEntityDescription(
key=ATTR_API_TEMPERATURE, key=ATTR_API_TEMPERATURE,
device_class=SensorDeviceClass.TEMPERATURE, device_class=SensorDeviceClass.TEMPERATURE,
translation_key="temperature",
native_unit_of_measurement=UnitOfTemperature.CELSIUS, native_unit_of_measurement=UnitOfTemperature.CELSIUS,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=1, suggested_display_precision=1,
@@ -147,7 +141,6 @@ SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = (
AirlySensorEntityDescription( AirlySensorEntityDescription(
key=ATTR_API_NO2, key=ATTR_API_NO2,
device_class=SensorDeviceClass.NITROGEN_DIOXIDE, device_class=SensorDeviceClass.NITROGEN_DIOXIDE,
translation_key="no2",
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,
suggested_display_precision=0, suggested_display_precision=0,
@@ -159,7 +152,6 @@ SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = (
AirlySensorEntityDescription( AirlySensorEntityDescription(
key=ATTR_API_SO2, key=ATTR_API_SO2,
device_class=SensorDeviceClass.SULPHUR_DIOXIDE, device_class=SensorDeviceClass.SULPHUR_DIOXIDE,
translation_key="so2",
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,
suggested_display_precision=0, suggested_display_precision=0,
@@ -171,7 +163,6 @@ SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = (
AirlySensorEntityDescription( AirlySensorEntityDescription(
key=ATTR_API_O3, key=ATTR_API_O3,
device_class=SensorDeviceClass.OZONE, device_class=SensorDeviceClass.OZONE,
translation_key="o3",
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,
suggested_display_precision=0, suggested_display_precision=0,

View File

@@ -32,35 +32,8 @@
"caqi": { "caqi": {
"name": "Common air quality index" "name": "Common air quality index"
}, },
"pm1": {
"name": "[%key:component::sensor::entity_component::pm1::name%]"
},
"pm25": {
"name": "[%key:component::sensor::entity_component::pm25::name%]"
},
"pm10": {
"name": "[%key:component::sensor::entity_component::pm10::name%]"
},
"humidity": {
"name": "[%key:component::sensor::entity_component::humidity::name%]"
},
"pressure": {
"name": "[%key:component::sensor::entity_component::pressure::name%]"
},
"temperature": {
"name": "[%key:component::sensor::entity_component::temperature::name%]"
},
"co": { "co": {
"name": "[%key:component::sensor::entity_component::carbon_monoxide::name%]" "name": "[%key:component::sensor::entity_component::carbon_monoxide::name%]"
},
"no2": {
"name": "[%key:component::sensor::entity_component::nitrogen_dioxide::name%]"
},
"so2": {
"name": "[%key:component::sensor::entity_component::sulphur_dioxide::name%]"
},
"o3": {
"name": "[%key:component::sensor::entity_component::ozone::name%]"
} }
} }
} }

View File

@@ -17,5 +17,3 @@ ATTR_API_STATION_LATITUDE = "Latitude"
ATTR_API_STATION_LONGITUDE = "Longitude" ATTR_API_STATION_LONGITUDE = "Longitude"
DEFAULT_NAME = "AirNow" DEFAULT_NAME = "AirNow"
DOMAIN = "airnow" DOMAIN = "airnow"
SENSOR_AQI_ATTR_DESCR = "description"
SENSOR_AQI_ATTR_LEVEL = "level"

View File

@@ -1,7 +1,12 @@
"""Support for the AirNow sensor service.""" """Support for the AirNow sensor service."""
from __future__ import annotations from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
from typing import Any
from homeassistant.components.sensor import ( from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity, SensorEntity,
SensorEntityDescription, SensorEntityDescription,
SensorStateClass, SensorStateClass,
@@ -12,7 +17,10 @@ from homeassistant.const import (
CONCENTRATION_PARTS_PER_MILLION, CONCENTRATION_PARTS_PER_MILLION,
) )
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.typing import StateType
from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.helpers.update_coordinator import CoordinatorEntity
from . import AirNowDataUpdateCoordinator from . import AirNowDataUpdateCoordinator
@@ -22,36 +30,60 @@ from .const import (
ATTR_API_AQI_LEVEL, ATTR_API_AQI_LEVEL,
ATTR_API_O3, ATTR_API_O3,
ATTR_API_PM25, ATTR_API_PM25,
DEFAULT_NAME,
DOMAIN, DOMAIN,
SENSOR_AQI_ATTR_DESCR,
SENSOR_AQI_ATTR_LEVEL,
) )
ATTRIBUTION = "Data provided by AirNow" ATTRIBUTION = "Data provided by AirNow"
PARALLEL_UPDATES = 1 PARALLEL_UPDATES = 1
SENSOR_TYPES: tuple[SensorEntityDescription, ...] = ( ATTR_DESCR = "description"
SensorEntityDescription( ATTR_LEVEL = "level"
@dataclass
class AirNowEntityDescriptionMixin:
"""Mixin for required keys."""
value_fn: Callable[[Any], StateType]
extra_state_attributes_fn: Callable[[Any], dict[str, str]] | None
@dataclass
class AirNowEntityDescription(SensorEntityDescription, AirNowEntityDescriptionMixin):
"""Describes Airnow sensor entity."""
SENSOR_TYPES: tuple[AirNowEntityDescription, ...] = (
AirNowEntityDescription(
key=ATTR_API_AQI, key=ATTR_API_AQI,
icon="mdi:blur", icon="mdi:blur",
name=ATTR_API_AQI,
native_unit_of_measurement="aqi",
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.AQI,
value_fn=lambda data: data.get(ATTR_API_AQI),
extra_state_attributes_fn=lambda data: {
ATTR_DESCR: data[ATTR_API_AQI_DESCRIPTION],
ATTR_LEVEL: data[ATTR_API_AQI_LEVEL],
},
), ),
SensorEntityDescription( AirNowEntityDescription(
key=ATTR_API_PM25, key=ATTR_API_PM25,
icon="mdi:blur", icon="mdi:blur",
name=ATTR_API_PM25,
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,
device_class=SensorDeviceClass.PM25,
value_fn=lambda data: data.get(ATTR_API_PM25),
extra_state_attributes_fn=None,
), ),
SensorEntityDescription( AirNowEntityDescription(
key=ATTR_API_O3, key=ATTR_API_O3,
translation_key="o3",
icon="mdi:blur", icon="mdi:blur",
name=ATTR_API_O3,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION, native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda data: data.get(ATTR_API_O3),
extra_state_attributes_fn=None,
), ),
) )
@@ -73,38 +105,38 @@ class AirNowSensor(CoordinatorEntity[AirNowDataUpdateCoordinator], SensorEntity)
"""Define an AirNow sensor.""" """Define an AirNow sensor."""
_attr_attribution = ATTRIBUTION _attr_attribution = ATTRIBUTION
_attr_has_entity_name = True
entity_description: AirNowEntityDescription
def __init__( def __init__(
self, self,
coordinator: AirNowDataUpdateCoordinator, coordinator: AirNowDataUpdateCoordinator,
description: SensorEntityDescription, description: AirNowEntityDescription,
) -> None: ) -> None:
"""Initialize.""" """Initialize."""
super().__init__(coordinator) super().__init__(coordinator)
self.entity_description = description self.entity_description = description
self._state = None
self._attrs: dict[str, str] = {}
self._attr_name = f"AirNow {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()}"
) )
self._attr_device_info = DeviceInfo(
entry_type=DeviceEntryType.SERVICE,
identifiers={(DOMAIN, self._attr_unique_id)},
manufacturer=DEFAULT_NAME,
name=DEFAULT_NAME,
)
@property @property
def native_value(self): def native_value(self) -> StateType:
"""Return the state.""" """Return the state."""
self._state = self.coordinator.data.get(self.entity_description.key) return self.entity_description.value_fn(self.coordinator.data)
return self._state
@property @property
def extra_state_attributes(self): def extra_state_attributes(self) -> dict[str, str] | None:
"""Return the state attributes.""" """Return the state attributes."""
if self.entity_description.key == ATTR_API_AQI: if self.entity_description.extra_state_attributes_fn:
self._attrs[SENSOR_AQI_ATTR_DESCR] = self.coordinator.data[ return self.entity_description.extra_state_attributes_fn(
ATTR_API_AQI_DESCRIPTION self.coordinator.data
] )
self._attrs[SENSOR_AQI_ATTR_LEVEL] = self.coordinator.data[ return None
ATTR_API_AQI_LEVEL
]
return self._attrs

View File

@@ -20,5 +20,12 @@
"abort": { "abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]" "already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
} }
},
"entity": {
"sensor": {
"o3": {
"name": "[%key:component::sensor::entity_component::ozone::name%]"
}
}
} }
} }

View File

@@ -53,63 +53,62 @@ class AirQEntityDescription(SensorEntityDescription, AirQEntityDescriptionMixin)
SENSOR_TYPES: list[AirQEntityDescription] = [ SENSOR_TYPES: list[AirQEntityDescription] = [
AirQEntityDescription( AirQEntityDescription(
key="c2h4o", key="c2h4o",
name="Acetaldehyde", translation_key="acetaldehyde",
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,
value=lambda data: data.get("c2h4o"), value=lambda data: data.get("c2h4o"),
), ),
AirQEntityDescription( AirQEntityDescription(
key="nh3_MR100", key="nh3_MR100",
name="Ammonia", translation_key="ammonia",
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,
value=lambda data: data.get("nh3_MR100"), value=lambda data: data.get("nh3_MR100"),
), ),
AirQEntityDescription( AirQEntityDescription(
key="ash3", key="ash3",
name="Arsine", translation_key="arsine",
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,
value=lambda data: data.get("ash3"), value=lambda data: data.get("ash3"),
), ),
AirQEntityDescription( AirQEntityDescription(
key="br2", key="br2",
name="Bromine", translation_key="bromine",
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,
value=lambda data: data.get("br2"), value=lambda data: data.get("br2"),
), ),
AirQEntityDescription( AirQEntityDescription(
key="ch4s", key="ch4s",
name="CH4S", translation_key="methanethiol",
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,
value=lambda data: data.get("ch4s"), value=lambda data: data.get("ch4s"),
), ),
AirQEntityDescription( AirQEntityDescription(
key="cl2_M20", key="cl2_M20",
name="Chlorine", translation_key="chlorine",
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,
value=lambda data: data.get("cl2_M20"), value=lambda data: data.get("cl2_M20"),
), ),
AirQEntityDescription( AirQEntityDescription(
key="clo2", key="clo2",
name="ClO2", translation_key="chlorine_dioxide",
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,
value=lambda data: data.get("clo2"), value=lambda data: data.get("clo2"),
), ),
AirQEntityDescription( AirQEntityDescription(
key="co", key="co",
name="CO", translation_key="carbon_monoxide",
native_unit_of_measurement=CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER, native_unit_of_measurement=CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("co"), value=lambda data: data.get("co"),
), ),
AirQEntityDescription( AirQEntityDescription(
key="co2", key="co2",
name="CO2",
device_class=SensorDeviceClass.CO2, device_class=SensorDeviceClass.CO2,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION, native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
@@ -117,14 +116,14 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
), ),
AirQEntityDescription( AirQEntityDescription(
key="cs2", key="cs2",
name="CS2", translation_key="carbon_disulfide",
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,
value=lambda data: data.get("cs2"), value=lambda data: data.get("cs2"),
), ),
AirQEntityDescription( AirQEntityDescription(
key="dewpt", key="dewpt",
name="Dew point", translation_key="dew_point",
native_unit_of_measurement=UnitOfTemperature.CELSIUS, native_unit_of_measurement=UnitOfTemperature.CELSIUS,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("dewpt"), value=lambda data: data.get("dewpt"),
@@ -132,63 +131,63 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
), ),
AirQEntityDescription( AirQEntityDescription(
key="ethanol", key="ethanol",
name="Ethanol", translation_key="ethanol",
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,
value=lambda data: data.get("ethanol"), value=lambda data: data.get("ethanol"),
), ),
AirQEntityDescription( AirQEntityDescription(
key="c2h4", key="c2h4",
name="Ethylene", translation_key="ethylene",
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,
value=lambda data: data.get("c2h4"), value=lambda data: data.get("c2h4"),
), ),
AirQEntityDescription( AirQEntityDescription(
key="ch2o_M10", key="ch2o_M10",
name="Formaldehyde", translation_key="formaldehyde",
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,
value=lambda data: data.get("ch2o_M10"), value=lambda data: data.get("ch2o_M10"),
), ),
AirQEntityDescription( AirQEntityDescription(
key="f2", key="f2",
name="Fluorine", translation_key="fluorine",
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,
value=lambda data: data.get("f2"), value=lambda data: data.get("f2"),
), ),
AirQEntityDescription( AirQEntityDescription(
key="h2s", key="h2s",
name="H2S", translation_key="hydrogen_sulfide",
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,
value=lambda data: data.get("h2s"), value=lambda data: data.get("h2s"),
), ),
AirQEntityDescription( AirQEntityDescription(
key="hcl", key="hcl",
name="HCl", translation_key="hydrochloric_acid",
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,
value=lambda data: data.get("hcl"), value=lambda data: data.get("hcl"),
), ),
AirQEntityDescription( AirQEntityDescription(
key="hcn", key="hcn",
name="HCN", translation_key="hydrogen_cyanide",
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,
value=lambda data: data.get("hcn"), value=lambda data: data.get("hcn"),
), ),
AirQEntityDescription( AirQEntityDescription(
key="hf", key="hf",
name="HF", translation_key="hydrogen_fluoride",
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,
value=lambda data: data.get("hf"), value=lambda data: data.get("hf"),
), ),
AirQEntityDescription( AirQEntityDescription(
key="health", key="health",
name="Health Index", translation_key="health_index",
native_unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
icon="mdi:heart-pulse", icon="mdi:heart-pulse",
@@ -196,7 +195,6 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
), ),
AirQEntityDescription( AirQEntityDescription(
key="humidity", key="humidity",
name="Humidity",
device_class=SensorDeviceClass.HUMIDITY, device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
@@ -204,7 +202,7 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
), ),
AirQEntityDescription( AirQEntityDescription(
key="humidity_abs", key="humidity_abs",
name="Absolute humidity", translation_key="absolute_humidity",
native_unit_of_measurement=CONCENTRATION_GRAMS_PER_CUBIC_METER, native_unit_of_measurement=CONCENTRATION_GRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("humidity_abs"), value=lambda data: data.get("humidity_abs"),
@@ -212,28 +210,27 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
), ),
AirQEntityDescription( AirQEntityDescription(
key="h2_M1000", key="h2_M1000",
name="Hydrogen", translation_key="hydrogen",
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,
value=lambda data: data.get("h2_M1000"), value=lambda data: data.get("h2_M1000"),
), ),
AirQEntityDescription( AirQEntityDescription(
key="h2o2", key="h2o2",
name="Hydrogen peroxide", translation_key="hydrogen_peroxide",
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,
value=lambda data: data.get("h2o2"), value=lambda data: data.get("h2o2"),
), ),
AirQEntityDescription( AirQEntityDescription(
key="ch4_MIPEX", key="ch4_MIPEX",
name="Methane", translation_key="methane",
native_unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("ch4_MIPEX"), value=lambda data: data.get("ch4_MIPEX"),
), ),
AirQEntityDescription( AirQEntityDescription(
key="n2o", key="n2o",
name="N2O",
device_class=SensorDeviceClass.NITROUS_OXIDE, device_class=SensorDeviceClass.NITROUS_OXIDE,
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,
@@ -241,7 +238,6 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
), ),
AirQEntityDescription( AirQEntityDescription(
key="no_M250", key="no_M250",
name="NO",
device_class=SensorDeviceClass.NITROGEN_MONOXIDE, device_class=SensorDeviceClass.NITROGEN_MONOXIDE,
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,
@@ -249,7 +245,6 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
), ),
AirQEntityDescription( AirQEntityDescription(
key="no2", key="no2",
name="NO2",
device_class=SensorDeviceClass.NITROGEN_DIOXIDE, device_class=SensorDeviceClass.NITROGEN_DIOXIDE,
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,
@@ -257,14 +252,14 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
), ),
AirQEntityDescription( AirQEntityDescription(
key="acid_M100", key="acid_M100",
name="Organic acid", translation_key="organic_acid",
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION, native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("acid_M100"), value=lambda data: data.get("acid_M100"),
), ),
AirQEntityDescription( AirQEntityDescription(
key="oxygen", key="oxygen",
name="Oxygen", translation_key="oxygen",
native_unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("oxygen"), value=lambda data: data.get("oxygen"),
@@ -272,7 +267,6 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
), ),
AirQEntityDescription( AirQEntityDescription(
key="o3", key="o3",
name="Ozone",
device_class=SensorDeviceClass.OZONE, device_class=SensorDeviceClass.OZONE,
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,
@@ -280,7 +274,7 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
), ),
AirQEntityDescription( AirQEntityDescription(
key="performance", key="performance",
name="Performance Index", translation_key="performance_index",
native_unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
icon="mdi:head-check", icon="mdi:head-check",
@@ -288,14 +282,13 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
), ),
AirQEntityDescription( AirQEntityDescription(
key="ph3", key="ph3",
name="PH3", translation_key="hydrogen_phosphide",
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,
value=lambda data: data.get("ph3"), value=lambda data: data.get("ph3"),
), ),
AirQEntityDescription( AirQEntityDescription(
key="pm1", key="pm1",
name="PM1",
device_class=SensorDeviceClass.PM1, device_class=SensorDeviceClass.PM1,
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,
@@ -304,7 +297,6 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
), ),
AirQEntityDescription( AirQEntityDescription(
key="pm2_5", key="pm2_5",
name="PM2.5",
device_class=SensorDeviceClass.PM25, device_class=SensorDeviceClass.PM25,
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,
@@ -313,7 +305,6 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
), ),
AirQEntityDescription( AirQEntityDescription(
key="pm10", key="pm10",
name="PM10",
device_class=SensorDeviceClass.PM10, device_class=SensorDeviceClass.PM10,
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,
@@ -322,7 +313,6 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
), ),
AirQEntityDescription( AirQEntityDescription(
key="pressure", key="pressure",
name="Pressure",
device_class=SensorDeviceClass.PRESSURE, device_class=SensorDeviceClass.PRESSURE,
native_unit_of_measurement=UnitOfPressure.HPA, native_unit_of_measurement=UnitOfPressure.HPA,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
@@ -330,7 +320,7 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
), ),
AirQEntityDescription( AirQEntityDescription(
key="pressure_rel", key="pressure_rel",
name="Relative pressure", translation_key="relative_pressure",
native_unit_of_measurement=UnitOfPressure.HPA, native_unit_of_measurement=UnitOfPressure.HPA,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("pressure_rel"), value=lambda data: data.get("pressure_rel"),
@@ -338,28 +328,27 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
), ),
AirQEntityDescription( AirQEntityDescription(
key="c3h8_MIPEX", key="c3h8_MIPEX",
name="Propane", translation_key="propane",
native_unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("c3h8_MIPEX"), value=lambda data: data.get("c3h8_MIPEX"),
), ),
AirQEntityDescription( AirQEntityDescription(
key="refigerant", key="refigerant",
name="Refrigerant", translation_key="refigerant",
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,
value=lambda data: data.get("refigerant"), value=lambda data: data.get("refigerant"),
), ),
AirQEntityDescription( AirQEntityDescription(
key="sih4", key="sih4",
name="SiH4", translation_key="silicon_hydride",
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,
value=lambda data: data.get("sih4"), value=lambda data: data.get("sih4"),
), ),
AirQEntityDescription( AirQEntityDescription(
key="so2", key="so2",
name="SO2",
device_class=SensorDeviceClass.SULPHUR_DIOXIDE, device_class=SensorDeviceClass.SULPHUR_DIOXIDE,
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,
@@ -367,7 +356,7 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
), ),
AirQEntityDescription( AirQEntityDescription(
key="sound", key="sound",
name="Noise", translation_key="noise",
native_unit_of_measurement=UnitOfSoundPressure.WEIGHTED_DECIBEL_A, native_unit_of_measurement=UnitOfSoundPressure.WEIGHTED_DECIBEL_A,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("sound"), value=lambda data: data.get("sound"),
@@ -375,7 +364,7 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
), ),
AirQEntityDescription( AirQEntityDescription(
key="sound_max", key="sound_max",
name="Noise (Maximum)", translation_key="maximum_noise",
native_unit_of_measurement=UnitOfSoundPressure.WEIGHTED_DECIBEL_A, native_unit_of_measurement=UnitOfSoundPressure.WEIGHTED_DECIBEL_A,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("sound_max"), value=lambda data: data.get("sound_max"),
@@ -383,7 +372,7 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
), ),
AirQEntityDescription( AirQEntityDescription(
key="radon", key="radon",
name="Radon", translation_key="radon",
native_unit_of_measurement=ACTIVITY_BECQUEREL_PER_CUBIC_METER, native_unit_of_measurement=ACTIVITY_BECQUEREL_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("radon"), value=lambda data: data.get("radon"),
@@ -391,7 +380,6 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
), ),
AirQEntityDescription( AirQEntityDescription(
key="temperature", key="temperature",
name="Temperature",
device_class=SensorDeviceClass.TEMPERATURE, device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=UnitOfTemperature.CELSIUS, native_unit_of_measurement=UnitOfTemperature.CELSIUS,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
@@ -399,21 +387,22 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
), ),
AirQEntityDescription( AirQEntityDescription(
key="tvoc", key="tvoc",
name="VOC", device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS_PARTS,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION, native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("tvoc"), value=lambda data: data.get("tvoc"),
), ),
AirQEntityDescription( AirQEntityDescription(
key="tvoc_ionsc", key="tvoc_ionsc",
name="VOC (Industrial)", translation_key="industrial_volatile_organic_compounds",
device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS_PARTS,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION, native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("tvoc_ionsc"), value=lambda data: data.get("tvoc_ionsc"),
), ),
AirQEntityDescription( AirQEntityDescription(
key="virus", key="virus",
name="Virus Index", translation_key="virus_index",
native_unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
icon="mdi:virus-off", icon="mdi:virus-off",

View File

@@ -18,5 +18,117 @@
"abort": { "abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]" "already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
} }
},
"entity": {
"sensor": {
"acetaldehyde": {
"name": "Acetaldehyde"
},
"ammonia": {
"name": "Ammonia"
},
"arsine": {
"name": "Arsine"
},
"bromine": {
"name": "Bromine"
},
"methanethiol": {
"name": "Methanethiol"
},
"chlorine": {
"name": "Chlorine"
},
"chlorine_dioxide": {
"name": "Chlorine dioxide"
},
"carbon_disulfide": {
"name": "Carbon disulfide"
},
"carbon_monoxide": {
"name": "[%key:component::sensor::entity_component::carbon_monoxide::name%]"
},
"dew_point": {
"name": "Dew point"
},
"ethanol": {
"name": "Ethanol"
},
"ethylene": {
"name": "Ethylene"
},
"formaldehyde": {
"name": "Formaldehyde"
},
"fluorine": {
"name": "Fluorine"
},
"hydrogen_sulfide": {
"name": "Hydrogen sulfide"
},
"hydrochloric_acid": {
"name": "Hydrochloric acid"
},
"hydrogen_cyanide": {
"name": "Hydrogen cyanide"
},
"hydrogen_fluoride": {
"name": "Hydrogen fluoride"
},
"health_index": {
"name": "Health Index"
},
"absolute_humidity": {
"name": "Absolute humidity"
},
"hydrogen": {
"name": "Hydrogen"
},
"hydrogen_peroxide": {
"name": "Hydrogen peroxide"
},
"methane": {
"name": "Methane"
},
"organic_acid": {
"name": "Organic acid"
},
"oxygen": {
"name": "Oxygen"
},
"performance_index": {
"name": "Performance Index"
},
"hydrogen_phosphide": {
"name": "Hydrogen Phosphide"
},
"relative_pressure": {
"name": "Relative pressure"
},
"propane": {
"name": "Propane"
},
"refigerant": {
"name": "Refrigerant"
},
"silicon_hydride": {
"name": "Silicon Hydride"
},
"noise": {
"name": "Noise"
},
"maximum_noise": {
"name": "Noise (Maximum)"
},
"radon": {
"name": "Radon"
},
"industrial_volatile_organic_compounds": {
"name": "VOCs (Industrial)"
},
"virus_index": {
"name": "Virus Index"
}
}
} }
} }

View File

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/airthings", "documentation": "https://www.home-assistant.io/integrations/airthings",
"iot_class": "cloud_polling", "iot_class": "cloud_polling",
"loggers": ["airthings"], "loggers": ["airthings"],
"requirements": ["airthings_cloud==0.1.0"] "requirements": ["airthings-cloud==0.1.0"]
} }

View File

@@ -35,62 +35,56 @@ SENSORS: dict[str, SensorEntityDescription] = {
"radonShortTermAvg": SensorEntityDescription( "radonShortTermAvg": SensorEntityDescription(
key="radonShortTermAvg", key="radonShortTermAvg",
native_unit_of_measurement="Bq/m³", native_unit_of_measurement="Bq/m³",
name="Radon", translation_key="radon",
), ),
"temp": SensorEntityDescription( "temp": SensorEntityDescription(
key="temp", key="temp",
device_class=SensorDeviceClass.TEMPERATURE, device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=UnitOfTemperature.CELSIUS, native_unit_of_measurement=UnitOfTemperature.CELSIUS,
name="Temperature",
), ),
"humidity": SensorEntityDescription( "humidity": SensorEntityDescription(
key="humidity", key="humidity",
device_class=SensorDeviceClass.HUMIDITY, device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
name="Humidity",
), ),
"pressure": SensorEntityDescription( "pressure": SensorEntityDescription(
key="pressure", key="pressure",
device_class=SensorDeviceClass.PRESSURE, device_class=SensorDeviceClass.PRESSURE,
native_unit_of_measurement=UnitOfPressure.MBAR, native_unit_of_measurement=UnitOfPressure.MBAR,
name="Pressure",
), ),
"battery": SensorEntityDescription( "battery": SensorEntityDescription(
key="battery", key="battery",
device_class=SensorDeviceClass.BATTERY, device_class=SensorDeviceClass.BATTERY,
native_unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
entity_category=EntityCategory.DIAGNOSTIC, entity_category=EntityCategory.DIAGNOSTIC,
name="Battery",
), ),
"co2": SensorEntityDescription( "co2": SensorEntityDescription(
key="co2", key="co2",
device_class=SensorDeviceClass.CO2, device_class=SensorDeviceClass.CO2,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION, native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
name="CO2",
), ),
"voc": SensorEntityDescription( "voc": SensorEntityDescription(
key="voc", key="voc",
device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS_PARTS,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION, native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
name="VOC",
), ),
"light": SensorEntityDescription( "light": SensorEntityDescription(
key="light", key="light",
native_unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
name="Light", translation_key="light",
), ),
"virusRisk": SensorEntityDescription( "virusRisk": SensorEntityDescription(
key="virusRisk", key="virusRisk",
name="Virus Risk", translation_key="virus_risk",
), ),
"mold": SensorEntityDescription( "mold": SensorEntityDescription(
key="mold", key="mold",
name="Mold", translation_key="mold",
), ),
"rssi": SensorEntityDescription( "rssi": SensorEntityDescription(
key="rssi", key="rssi",
native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS, native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS,
device_class=SensorDeviceClass.SIGNAL_STRENGTH, device_class=SensorDeviceClass.SIGNAL_STRENGTH,
name="RSSI",
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC, entity_category=EntityCategory.DIAGNOSTIC,
), ),
@@ -98,13 +92,11 @@ SENSORS: dict[str, SensorEntityDescription] = {
key="pm1", key="pm1",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
device_class=SensorDeviceClass.PM1, device_class=SensorDeviceClass.PM1,
name="PM1",
), ),
"pm25": SensorEntityDescription( "pm25": SensorEntityDescription(
key="pm25", key="pm25",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
device_class=SensorDeviceClass.PM25, device_class=SensorDeviceClass.PM25,
name="PM25",
), ),
} }
@@ -134,6 +126,7 @@ class AirthingsHeaterEnergySensor(CoordinatorEntity, SensorEntity):
"""Representation of a Airthings Sensor device.""" """Representation of a Airthings Sensor device."""
_attr_state_class = SensorStateClass.MEASUREMENT _attr_state_class = SensorStateClass.MEASUREMENT
_attr_has_entity_name = True
def __init__( def __init__(
self, self,
@@ -146,7 +139,6 @@ class AirthingsHeaterEnergySensor(CoordinatorEntity, SensorEntity):
self.entity_description = entity_description self.entity_description = entity_description
self._attr_name = f"{airthings_device.name} {entity_description.name}"
self._attr_unique_id = f"{airthings_device.device_id}_{entity_description.key}" self._attr_unique_id = f"{airthings_device.device_id}_{entity_description.key}"
self._id = airthings_device.device_id self._id = airthings_device.device_id
self._attr_device_info = DeviceInfo( self._attr_device_info = DeviceInfo(

View File

@@ -17,5 +17,21 @@
"abort": { "abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]" "already_configured": "[%key:common::config_flow::abort::already_configured_account%]"
} }
},
"entity": {
"sensor": {
"radon": {
"name": "Radon"
},
"light": {
"name": "Light"
},
"virus_risk": {
"name": "Virus Risk"
},
"mold": {
"name": "Mold"
}
}
} }
} }

View File

@@ -39,26 +39,26 @@ _LOGGER = logging.getLogger(__name__)
SENSORS_MAPPING_TEMPLATE: dict[str, SensorEntityDescription] = { SENSORS_MAPPING_TEMPLATE: dict[str, SensorEntityDescription] = {
"radon_1day_avg": SensorEntityDescription( "radon_1day_avg": SensorEntityDescription(
key="radon_1day_avg", key="radon_1day_avg",
translation_key="radon_1day_avg",
native_unit_of_measurement=VOLUME_BECQUEREL, native_unit_of_measurement=VOLUME_BECQUEREL,
name="Radon 1-day average",
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
icon="mdi:radioactive", icon="mdi:radioactive",
), ),
"radon_longterm_avg": SensorEntityDescription( "radon_longterm_avg": SensorEntityDescription(
key="radon_longterm_avg", key="radon_longterm_avg",
translation_key="radon_longterm_avg",
native_unit_of_measurement=VOLUME_BECQUEREL, native_unit_of_measurement=VOLUME_BECQUEREL,
name="Radon longterm average",
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
icon="mdi:radioactive", icon="mdi:radioactive",
), ),
"radon_1day_level": SensorEntityDescription( "radon_1day_level": SensorEntityDescription(
key="radon_1day_level", key="radon_1day_level",
name="Radon 1-day level", translation_key="radon_1day_level",
icon="mdi:radioactive", icon="mdi:radioactive",
), ),
"radon_longterm_level": SensorEntityDescription( "radon_longterm_level": SensorEntityDescription(
key="radon_longterm_level", key="radon_longterm_level",
name="Radon longterm level", translation_key="radon_longterm_level",
icon="mdi:radioactive", icon="mdi:radioactive",
), ),
"temperature": SensorEntityDescription( "temperature": SensorEntityDescription(
@@ -66,21 +66,18 @@ SENSORS_MAPPING_TEMPLATE: dict[str, SensorEntityDescription] = {
device_class=SensorDeviceClass.TEMPERATURE, device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=UnitOfTemperature.CELSIUS, native_unit_of_measurement=UnitOfTemperature.CELSIUS,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
name="Temperature",
), ),
"humidity": SensorEntityDescription( "humidity": SensorEntityDescription(
key="humidity", key="humidity",
device_class=SensorDeviceClass.HUMIDITY, device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
name="Humidity",
), ),
"pressure": SensorEntityDescription( "pressure": SensorEntityDescription(
key="pressure", key="pressure",
device_class=SensorDeviceClass.PRESSURE, device_class=SensorDeviceClass.PRESSURE,
native_unit_of_measurement=UnitOfPressure.MBAR, native_unit_of_measurement=UnitOfPressure.MBAR,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
name="Pressure",
), ),
"battery": SensorEntityDescription( "battery": SensorEntityDescription(
key="battery", key="battery",
@@ -88,20 +85,18 @@ SENSORS_MAPPING_TEMPLATE: dict[str, SensorEntityDescription] = {
native_unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC, entity_category=EntityCategory.DIAGNOSTIC,
name="Battery",
), ),
"co2": SensorEntityDescription( "co2": SensorEntityDescription(
key="co2", key="co2",
device_class=SensorDeviceClass.CO2, device_class=SensorDeviceClass.CO2,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION, native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
name="co2",
), ),
"voc": SensorEntityDescription( "voc": SensorEntityDescription(
key="voc", key="voc",
device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS_PARTS,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION, native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
name="VOC",
icon="mdi:cloud", icon="mdi:cloud",
), ),
"illuminance": SensorEntityDescription( "illuminance": SensorEntityDescription(
@@ -109,7 +104,6 @@ SENSORS_MAPPING_TEMPLATE: dict[str, SensorEntityDescription] = {
device_class=SensorDeviceClass.ILLUMINANCE, device_class=SensorDeviceClass.ILLUMINANCE,
native_unit_of_measurement=LIGHT_LUX, native_unit_of_measurement=LIGHT_LUX,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
name="Illuminance",
), ),
} }

View File

@@ -19,5 +19,21 @@
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"unknown": "[%key:common::config_flow::error::unknown%]" "unknown": "[%key:common::config_flow::error::unknown%]"
} }
},
"entity": {
"sensor": {
"radon_1day_avg": {
"name": "Radon 1-day average"
},
"radon_longterm_avg": {
"name": "Radon longterm average"
},
"radon_1day_level": {
"name": "Radon 1-day level"
},
"radon_longterm_level": {
"name": "Radon longterm level"
}
}
} }
} }

View File

@@ -17,7 +17,7 @@
"api_key": "[%key:common::config_flow::data::api_key%]", "api_key": "[%key:common::config_flow::data::api_key%]",
"city": "City", "city": "City",
"country": "Country", "country": "Country",
"state": "state" "state": "State"
} }
}, },
"reauth_confirm": { "reauth_confirm": {

View File

@@ -193,6 +193,8 @@ class AirzoneClimate(AirzoneZoneEntity, ClimateEntity):
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
"""Set hvac mode.""" """Set hvac mode."""
slave_raise = False
params = {} params = {}
if hvac_mode == HVACMode.OFF: if hvac_mode == HVACMode.OFF:
params[API_ON] = 0 params[API_ON] = 0
@@ -202,12 +204,13 @@ class AirzoneClimate(AirzoneZoneEntity, ClimateEntity):
if self.get_airzone_value(AZD_MASTER): if self.get_airzone_value(AZD_MASTER):
params[API_MODE] = mode params[API_MODE] = mode
else: else:
raise HomeAssistantError( slave_raise = True
f"Mode can't be changed on slave zone {self.name}"
)
params[API_ON] = 1 params[API_ON] = 1
await self._async_update_hvac_params(params) await self._async_update_hvac_params(params)
if slave_raise:
raise HomeAssistantError(f"Mode can't be changed on slave zone {self.name}")
async def async_set_temperature(self, **kwargs: Any) -> None: async def async_set_temperature(self, **kwargs: Any) -> None:
"""Set new target temperature.""" """Set new target temperature."""
params = {} params = {}

View File

@@ -12,7 +12,10 @@ from homeassistant.helpers import aiohttp_client
from .const import DOMAIN from .const import DOMAIN
from .coordinator import AirzoneUpdateCoordinator from .coordinator import AirzoneUpdateCoordinator
PLATFORMS: list[Platform] = [Platform.SENSOR] PLATFORMS: list[Platform] = [
Platform.BINARY_SENSOR,
Platform.SENSOR,
]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:

View File

@@ -0,0 +1,106 @@
"""Support for the Airzone Cloud binary sensors."""
from __future__ import annotations
from dataclasses import dataclass
from typing import Any, Final
from aioairzone_cloud.const import AZD_PROBLEMS, AZD_WARNINGS, AZD_ZONES
from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass,
BinarySensorEntity,
BinarySensorEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN
from .coordinator import AirzoneUpdateCoordinator
from .entity import AirzoneEntity, AirzoneZoneEntity
@dataclass
class AirzoneBinarySensorEntityDescription(BinarySensorEntityDescription):
"""A class that describes Airzone Cloud binary sensor entities."""
attributes: dict[str, str] | None = None
ZONE_BINARY_SENSOR_TYPES: Final[tuple[AirzoneBinarySensorEntityDescription, ...]] = (
AirzoneBinarySensorEntityDescription(
attributes={
"warnings": AZD_WARNINGS,
},
device_class=BinarySensorDeviceClass.PROBLEM,
entity_category=EntityCategory.DIAGNOSTIC,
key=AZD_PROBLEMS,
),
)
async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Add Airzone Cloud binary sensors from a config_entry."""
coordinator: AirzoneUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
binary_sensors: list[AirzoneBinarySensor] = []
for zone_id, zone_data in coordinator.data.get(AZD_ZONES, {}).items():
for description in ZONE_BINARY_SENSOR_TYPES:
if description.key in zone_data:
binary_sensors.append(
AirzoneZoneBinarySensor(
coordinator,
description,
zone_id,
zone_data,
)
)
async_add_entities(binary_sensors)
class AirzoneBinarySensor(AirzoneEntity, BinarySensorEntity):
"""Define an Airzone Cloud binary sensor."""
entity_description: AirzoneBinarySensorEntityDescription
@callback
def _handle_coordinator_update(self) -> None:
"""Update attributes when the coordinator updates."""
self._async_update_attrs()
super()._handle_coordinator_update()
@callback
def _async_update_attrs(self) -> None:
"""Update binary sensor attributes."""
self._attr_is_on = self.get_airzone_value(self.entity_description.key)
if self.entity_description.attributes:
self._attr_extra_state_attributes = {
key: self.get_airzone_value(val)
for key, val in self.entity_description.attributes.items()
}
class AirzoneZoneBinarySensor(AirzoneZoneEntity, AirzoneBinarySensor):
"""Define an Airzone Cloud Zone binary sensor."""
_attr_has_entity_name = True
def __init__(
self,
coordinator: AirzoneUpdateCoordinator,
description: AirzoneBinarySensorEntityDescription,
zone_id: str,
zone_data: dict[str, Any],
) -> None:
"""Initialize."""
super().__init__(coordinator, zone_id, zone_data)
self._attr_unique_id = f"{zone_id}_{description.key}"
self.entity_description = description
self._async_update_attrs()

View File

@@ -15,7 +15,6 @@ from aioairzone_cloud.const import (
AZD_ZONES, AZD_ZONES,
) )
from homeassistant.config_entries import ConfigEntry
from homeassistant.helpers import device_registry as dr from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.helpers.update_coordinator import CoordinatorEntity
@@ -43,7 +42,6 @@ class AirzoneAidooEntity(AirzoneEntity):
def __init__( def __init__(
self, self,
coordinator: AirzoneUpdateCoordinator, coordinator: AirzoneUpdateCoordinator,
entry: ConfigEntry,
aidoo_id: str, aidoo_id: str,
aidoo_data: dict[str, Any], aidoo_data: dict[str, Any],
) -> None: ) -> None:
@@ -73,7 +71,6 @@ class AirzoneWebServerEntity(AirzoneEntity):
def __init__( def __init__(
self, self,
coordinator: AirzoneUpdateCoordinator, coordinator: AirzoneUpdateCoordinator,
entry: ConfigEntry,
ws_id: str, ws_id: str,
ws_data: dict[str, Any], ws_data: dict[str, Any],
) -> None: ) -> None:
@@ -86,7 +83,7 @@ class AirzoneWebServerEntity(AirzoneEntity):
connections={(dr.CONNECTION_NETWORK_MAC, ws_id)}, connections={(dr.CONNECTION_NETWORK_MAC, ws_id)},
identifiers={(DOMAIN, ws_id)}, identifiers={(DOMAIN, ws_id)},
manufacturer=MANUFACTURER, manufacturer=MANUFACTURER,
name=f"WebServer {ws_id}", name=ws_data[AZD_NAME],
sw_version=ws_data[AZD_FIRMWARE], sw_version=ws_data[AZD_FIRMWARE],
) )
@@ -104,7 +101,6 @@ class AirzoneZoneEntity(AirzoneEntity):
def __init__( def __init__(
self, self,
coordinator: AirzoneUpdateCoordinator, coordinator: AirzoneUpdateCoordinator,
entry: ConfigEntry,
zone_id: str, zone_id: str,
zone_data: dict[str, Any], zone_data: dict[str, Any],
) -> None: ) -> None:

View File

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/airzone_cloud", "documentation": "https://www.home-assistant.io/integrations/airzone_cloud",
"iot_class": "cloud_polling", "iot_class": "cloud_polling",
"loggers": ["aioairzone_cloud"], "loggers": ["aioairzone_cloud"],
"requirements": ["aioairzone-cloud==0.1.8"] "requirements": ["aioairzone-cloud==0.2.0"]
} }

View File

@@ -6,7 +6,6 @@ from typing import Any, Final
from aioairzone_cloud.const import ( from aioairzone_cloud.const import (
AZD_AIDOOS, AZD_AIDOOS,
AZD_HUMIDITY, AZD_HUMIDITY,
AZD_NAME,
AZD_TEMP, AZD_TEMP,
AZD_WEBSERVERS, AZD_WEBSERVERS,
AZD_WIFI_RSSI, AZD_WIFI_RSSI,
@@ -42,7 +41,6 @@ AIDOO_SENSOR_TYPES: Final[tuple[SensorEntityDescription, ...]] = (
SensorEntityDescription( SensorEntityDescription(
device_class=SensorDeviceClass.TEMPERATURE, device_class=SensorDeviceClass.TEMPERATURE,
key=AZD_TEMP, key=AZD_TEMP,
name="Temperature",
native_unit_of_measurement=UnitOfTemperature.CELSIUS, native_unit_of_measurement=UnitOfTemperature.CELSIUS,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
), ),
@@ -53,9 +51,7 @@ WEBSERVER_SENSOR_TYPES: Final[tuple[SensorEntityDescription, ...]] = (
device_class=SensorDeviceClass.SIGNAL_STRENGTH, device_class=SensorDeviceClass.SIGNAL_STRENGTH,
entity_category=EntityCategory.DIAGNOSTIC, entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
has_entity_name=True,
key=AZD_WIFI_RSSI, key=AZD_WIFI_RSSI,
name="RSSI",
native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT, native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
), ),
@@ -65,14 +61,12 @@ ZONE_SENSOR_TYPES: Final[tuple[SensorEntityDescription, ...]] = (
SensorEntityDescription( SensorEntityDescription(
device_class=SensorDeviceClass.TEMPERATURE, device_class=SensorDeviceClass.TEMPERATURE,
key=AZD_TEMP, key=AZD_TEMP,
name="Temperature",
native_unit_of_measurement=UnitOfTemperature.CELSIUS, native_unit_of_measurement=UnitOfTemperature.CELSIUS,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
), ),
SensorEntityDescription( SensorEntityDescription(
device_class=SensorDeviceClass.HUMIDITY, device_class=SensorDeviceClass.HUMIDITY,
key=AZD_HUMIDITY, key=AZD_HUMIDITY,
name="Humidity",
native_unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
), ),
@@ -95,7 +89,6 @@ async def async_setup_entry(
AirzoneAidooSensor( AirzoneAidooSensor(
coordinator, coordinator,
description, description,
entry,
aidoo_id, aidoo_id,
aidoo_data, aidoo_data,
) )
@@ -109,7 +102,6 @@ async def async_setup_entry(
AirzoneWebServerSensor( AirzoneWebServerSensor(
coordinator, coordinator,
description, description,
entry,
ws_id, ws_id,
ws_data, ws_data,
) )
@@ -123,7 +115,6 @@ async def async_setup_entry(
AirzoneZoneSensor( AirzoneZoneSensor(
coordinator, coordinator,
description, description,
entry,
zone_id, zone_id,
zone_data, zone_data,
) )
@@ -154,14 +145,13 @@ class AirzoneAidooSensor(AirzoneAidooEntity, AirzoneSensor):
self, self,
coordinator: AirzoneUpdateCoordinator, coordinator: AirzoneUpdateCoordinator,
description: SensorEntityDescription, description: SensorEntityDescription,
entry: ConfigEntry,
aidoo_id: str, aidoo_id: str,
aidoo_data: dict[str, Any], aidoo_data: dict[str, Any],
) -> None: ) -> None:
"""Initialize.""" """Initialize."""
super().__init__(coordinator, entry, aidoo_id, aidoo_data) super().__init__(coordinator, aidoo_id, aidoo_data)
self._attr_name = f"{aidoo_data[AZD_NAME]} {description.name}" self._attr_has_entity_name = True
self._attr_unique_id = f"{aidoo_id}_{description.key}" self._attr_unique_id = f"{aidoo_id}_{description.key}"
self.entity_description = description self.entity_description = description
@@ -175,13 +165,13 @@ class AirzoneWebServerSensor(AirzoneWebServerEntity, AirzoneSensor):
self, self,
coordinator: AirzoneUpdateCoordinator, coordinator: AirzoneUpdateCoordinator,
description: SensorEntityDescription, description: SensorEntityDescription,
entry: ConfigEntry,
ws_id: str, ws_id: str,
ws_data: dict[str, Any], ws_data: dict[str, Any],
) -> None: ) -> None:
"""Initialize.""" """Initialize."""
super().__init__(coordinator, entry, ws_id, ws_data) super().__init__(coordinator, ws_id, ws_data)
self._attr_has_entity_name = True
self._attr_unique_id = f"{ws_id}_{description.key}" self._attr_unique_id = f"{ws_id}_{description.key}"
self.entity_description = description self.entity_description = description
@@ -195,14 +185,13 @@ class AirzoneZoneSensor(AirzoneZoneEntity, AirzoneSensor):
self, self,
coordinator: AirzoneUpdateCoordinator, coordinator: AirzoneUpdateCoordinator,
description: SensorEntityDescription, description: SensorEntityDescription,
entry: ConfigEntry,
zone_id: str, zone_id: str,
zone_data: dict[str, Any], zone_data: dict[str, Any],
) -> None: ) -> None:
"""Initialize.""" """Initialize."""
super().__init__(coordinator, entry, zone_id, zone_data) super().__init__(coordinator, zone_id, zone_data)
self._attr_name = f"{zone_data[AZD_NAME]} {description.name}" self._attr_has_entity_name = True
self._attr_unique_id = f"{zone_id}_{description.key}" self._attr_unique_id = f"{zone_id}_{description.key}"
self.entity_description = description self.entity_description = description

View File

@@ -40,26 +40,24 @@ class AladdinDevice(CoverEntity):
_attr_device_class = CoverDeviceClass.GARAGE _attr_device_class = CoverDeviceClass.GARAGE
_attr_supported_features = SUPPORTED_FEATURES _attr_supported_features = SUPPORTED_FEATURES
_attr_has_entity_name = True
_attr_name = None
def __init__( def __init__(
self, acc: AladdinConnectClient, device: DoorDevice, entry: ConfigEntry self, acc: AladdinConnectClient, device: DoorDevice, entry: ConfigEntry
) -> None: ) -> None:
"""Initialize the Aladdin Connect cover.""" """Initialize the Aladdin Connect cover."""
self._acc = acc self._acc = acc
self._entry_id = entry.entry_id
self._device_id = device["device_id"] self._device_id = device["device_id"]
self._number = device["door_number"] self._number = device["door_number"]
self._name = device["name"]
self._serial = device["serial"] self._serial = device["serial"]
self._model = device["model"]
self._attr_device_info = DeviceInfo( self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, f"{self._device_id}-{self._number}")}, identifiers={(DOMAIN, f"{self._device_id}-{self._number}")},
name=self._name, name=device["name"],
manufacturer="Overhead Door", manufacturer="Overhead Door",
model=self._model, model=device["model"],
) )
self._attr_has_entity_name = True
self._attr_unique_id = f"{self._device_id}-{self._number}" self._attr_unique_id = f"{self._device_id}-{self._number}"
async def async_added_to_hass(self) -> None: async def async_added_to_hass(self) -> None:

View File

@@ -40,7 +40,6 @@ class AccSensorEntityDescription(
SENSORS: tuple[AccSensorEntityDescription, ...] = ( SENSORS: tuple[AccSensorEntityDescription, ...] = (
AccSensorEntityDescription( AccSensorEntityDescription(
key="battery_level", key="battery_level",
name="Battery level",
device_class=SensorDeviceClass.BATTERY, device_class=SensorDeviceClass.BATTERY,
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
native_unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
@@ -49,7 +48,7 @@ SENSORS: tuple[AccSensorEntityDescription, ...] = (
), ),
AccSensorEntityDescription( AccSensorEntityDescription(
key="rssi", key="rssi",
name="Wi-Fi RSSI", translation_key="wifi_strength",
device_class=SensorDeviceClass.SIGNAL_STRENGTH, device_class=SensorDeviceClass.SIGNAL_STRENGTH,
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS, native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS,
@@ -58,7 +57,7 @@ SENSORS: tuple[AccSensorEntityDescription, ...] = (
), ),
AccSensorEntityDescription( AccSensorEntityDescription(
key="ble_strength", key="ble_strength",
name="BLE Strength", translation_key="ble_strength",
device_class=SensorDeviceClass.SIGNAL_STRENGTH, device_class=SensorDeviceClass.SIGNAL_STRENGTH,
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS, native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS,
@@ -89,8 +88,8 @@ async def async_setup_entry(
class AladdinConnectSensor(SensorEntity): class AladdinConnectSensor(SensorEntity):
"""A sensor implementation for Aladdin Connect devices.""" """A sensor implementation for Aladdin Connect devices."""
_device: AladdinConnectSensor
entity_description: AccSensorEntityDescription entity_description: AccSensorEntityDescription
_attr_has_entity_name = True
def __init__( def __init__(
self, self,
@@ -101,24 +100,20 @@ class AladdinConnectSensor(SensorEntity):
"""Initialize a sensor for an Aladdin Connect device.""" """Initialize a sensor for an Aladdin Connect device."""
self._device_id = device["device_id"] self._device_id = device["device_id"]
self._number = device["door_number"] self._number = device["door_number"]
self._name = device["name"]
self._model = device["model"]
self._acc = acc self._acc = acc
self.entity_description = description self.entity_description = description
self._attr_unique_id = f"{self._device_id}-{self._number}-{description.key}" self._attr_unique_id = f"{self._device_id}-{self._number}-{description.key}"
self._attr_has_entity_name = True self._attr_device_info = DeviceInfo(
if self._model == "01" and description.key in ("battery_level", "ble_strength"):
self._attr_entity_registry_enabled_default = True
@property
def device_info(self) -> DeviceInfo | None:
"""Device information for Aladdin Connect sensors."""
return DeviceInfo(
identifiers={(DOMAIN, f"{self._device_id}-{self._number}")}, identifiers={(DOMAIN, f"{self._device_id}-{self._number}")},
name=self._name, name=device["name"],
manufacturer="Overhead Door", manufacturer="Overhead Door",
model=self._model, model=device["model"],
) )
if device["model"] == "01" and description.key in (
"battery_level",
"ble_strength",
):
self._attr_entity_registry_enabled_default = True
@property @property
def native_value(self) -> float | None: def native_value(self) -> float | None:

View File

@@ -25,5 +25,15 @@
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]", "already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]" "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
} }
},
"entity": {
"sensor": {
"wifi_strength": {
"name": "Wi-Fi RSSI"
},
"ble_strength": {
"name": "BLE Strength"
}
}
} }
} }

View File

@@ -5,6 +5,7 @@ from typing import Final
import voluptuous as vol import voluptuous as vol
from homeassistant.components.device_automation import async_validate_entity_schema
from homeassistant.const import ( from homeassistant.const import (
ATTR_CODE, ATTR_CODE,
ATTR_ENTITY_ID, ATTR_ENTITY_ID,
@@ -44,15 +45,22 @@ ACTION_TYPES: Final[set[str]] = {
"trigger", "trigger",
} }
ACTION_SCHEMA: Final = cv.DEVICE_ACTION_BASE_SCHEMA.extend( _ACTION_SCHEMA: Final = cv.DEVICE_ACTION_BASE_SCHEMA.extend(
{ {
vol.Required(CONF_TYPE): vol.In(ACTION_TYPES), vol.Required(CONF_TYPE): vol.In(ACTION_TYPES),
vol.Required(CONF_ENTITY_ID): cv.entity_domain(DOMAIN), vol.Required(CONF_ENTITY_ID): cv.entity_id_or_uuid,
vol.Optional(CONF_CODE): cv.string, vol.Optional(CONF_CODE): cv.string,
} }
) )
async def async_validate_action_config(
hass: HomeAssistant, config: ConfigType
) -> ConfigType:
"""Validate config."""
return async_validate_entity_schema(hass, config, _ACTION_SCHEMA)
async def async_get_actions( async def async_get_actions(
hass: HomeAssistant, device_id: str hass: HomeAssistant, device_id: str
) -> list[dict[str, str]]: ) -> list[dict[str, str]]:
@@ -70,7 +78,7 @@ async def async_get_actions(
base_action: dict = { base_action: dict = {
CONF_DEVICE_ID: device_id, CONF_DEVICE_ID: device_id,
CONF_DOMAIN: DOMAIN, CONF_DOMAIN: DOMAIN,
CONF_ENTITY_ID: entry.entity_id, CONF_ENTITY_ID: entry.id,
} }
# Add actions for each entity that belongs to this integration # Add actions for each entity that belongs to this integration
@@ -124,7 +132,9 @@ async def async_get_action_capabilities(
"""List action capabilities.""" """List action capabilities."""
# We need to refer to the state directly because ATTR_CODE_ARM_REQUIRED is not a # We need to refer to the state directly because ATTR_CODE_ARM_REQUIRED is not a
# capability attribute # capability attribute
state = hass.states.get(config[CONF_ENTITY_ID]) registry = er.async_get(hass)
entity_id = er.async_resolve_entity_id(registry, config[CONF_ENTITY_ID])
state = hass.states.get(entity_id) if entity_id else None
code_required = state.attributes.get(ATTR_CODE_ARM_REQUIRED) if state else False code_required = state.attributes.get(ATTR_CODE_ARM_REQUIRED) if state else False
if config[CONF_TYPE] == "trigger" or ( if config[CONF_TYPE] == "trigger" or (

View File

@@ -58,7 +58,7 @@ CONDITION_TYPES: Final[set[str]] = {
CONDITION_SCHEMA: Final = DEVICE_CONDITION_BASE_SCHEMA.extend( CONDITION_SCHEMA: Final = DEVICE_CONDITION_BASE_SCHEMA.extend(
{ {
vol.Required(CONF_ENTITY_ID): cv.entity_id, vol.Required(CONF_ENTITY_ID): cv.entity_id_or_uuid,
vol.Required(CONF_TYPE): vol.In(CONDITION_TYPES), vol.Required(CONF_TYPE): vol.In(CONDITION_TYPES),
} }
) )
@@ -83,7 +83,7 @@ async def async_get_conditions(
CONF_CONDITION: "device", CONF_CONDITION: "device",
CONF_DEVICE_ID: device_id, CONF_DEVICE_ID: device_id,
CONF_DOMAIN: DOMAIN, CONF_DOMAIN: DOMAIN,
CONF_ENTITY_ID: entry.entity_id, CONF_ENTITY_ID: entry.id,
} }
conditions += [ conditions += [
@@ -126,8 +126,11 @@ def async_condition_from_config(
elif config[CONF_TYPE] == CONDITION_ARMED_CUSTOM_BYPASS: elif config[CONF_TYPE] == CONDITION_ARMED_CUSTOM_BYPASS:
state = STATE_ALARM_ARMED_CUSTOM_BYPASS state = STATE_ALARM_ARMED_CUSTOM_BYPASS
registry = er.async_get(hass)
entity_id = er.async_resolve_entity_id(registry, config[ATTR_ENTITY_ID])
def test_is_state(hass: HomeAssistant, variables: TemplateVarsType) -> bool: def test_is_state(hass: HomeAssistant, variables: TemplateVarsType) -> bool:
"""Test if an entity is a certain state.""" """Test if an entity is a certain state."""
return condition.state(hass, config[ATTR_ENTITY_ID], state) return condition.state(hass, entity_id, state)
return test_is_state return test_is_state

View File

@@ -46,7 +46,7 @@ TRIGGER_TYPES: Final[set[str]] = BASIC_TRIGGER_TYPES | {
TRIGGER_SCHEMA: Final = DEVICE_TRIGGER_BASE_SCHEMA.extend( TRIGGER_SCHEMA: Final = DEVICE_TRIGGER_BASE_SCHEMA.extend(
{ {
vol.Required(CONF_ENTITY_ID): cv.entity_id, vol.Required(CONF_ENTITY_ID): cv.entity_id_or_uuid,
vol.Required(CONF_TYPE): vol.In(TRIGGER_TYPES), vol.Required(CONF_TYPE): vol.In(TRIGGER_TYPES),
vol.Optional(CONF_FOR): cv.positive_time_period_dict, vol.Optional(CONF_FOR): cv.positive_time_period_dict,
} }
@@ -72,7 +72,7 @@ async def async_get_triggers(
CONF_PLATFORM: "device", CONF_PLATFORM: "device",
CONF_DEVICE_ID: device_id, CONF_DEVICE_ID: device_id,
CONF_DOMAIN: DOMAIN, CONF_DOMAIN: DOMAIN,
CONF_ENTITY_ID: entry.entity_id, CONF_ENTITY_ID: entry.id,
} }
triggers += [ triggers += [

View File

@@ -5,5 +5,5 @@
"documentation": "https://www.home-assistant.io/integrations/alpha_vantage", "documentation": "https://www.home-assistant.io/integrations/alpha_vantage",
"iot_class": "cloud_polling", "iot_class": "cloud_polling",
"loggers": ["alpha_vantage"], "loggers": ["alpha_vantage"],
"requirements": ["alpha_vantage==2.3.1"] "requirements": ["alpha-vantage==2.3.1"]
} }

View File

@@ -7,5 +7,5 @@
"documentation": "https://www.home-assistant.io/integrations/ambiclimate", "documentation": "https://www.home-assistant.io/integrations/ambiclimate",
"iot_class": "cloud_polling", "iot_class": "cloud_polling",
"loggers": ["ambiclimate"], "loggers": ["ambiclimate"],
"requirements": ["ambiclimate==0.2.1"] "requirements": ["Ambiclimate==0.2.1"]
} }

View File

@@ -3,6 +3,8 @@ from __future__ import annotations
import logging import logging
from homeassistant.helpers.typing import UndefinedType
from .const import DOMAIN from .const import DOMAIN
@@ -14,7 +16,7 @@ def service_signal(service: str, *args: str) -> str:
def log_update_error( def log_update_error(
logger: logging.Logger, logger: logging.Logger,
action: str, action: str,
name: str | None, name: str | UndefinedType | None,
entity_type: str, entity_type: str,
error: Exception, error: Exception,
level: int = logging.ERROR, level: int = logging.ERROR,

View File

@@ -21,12 +21,22 @@ from homeassistant.components.recorder import (
DOMAIN as RECORDER_DOMAIN, DOMAIN as RECORDER_DOMAIN,
get_instance as get_recorder_instance, get_instance as get_recorder_instance,
) )
import homeassistant.config as conf_util
from homeassistant.config_entries import (
SOURCE_IGNORE,
)
from homeassistant.const import ATTR_DOMAIN, __version__ as HA_VERSION from homeassistant.const import ATTR_DOMAIN, __version__ as HA_VERSION
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.entity_registry as er
from homeassistant.helpers.storage import Store from homeassistant.helpers.storage import Store
from homeassistant.helpers.system_info import async_get_system_info from homeassistant.helpers.system_info import async_get_system_info
from homeassistant.loader import IntegrationNotFound, async_get_integrations from homeassistant.loader import (
Integration,
IntegrationNotFound,
async_get_integrations,
)
from homeassistant.setup import async_get_loaded_integrations from homeassistant.setup import async_get_loaded_integrations
from .const import ( from .const import (
@@ -206,8 +216,25 @@ class Analytics:
if self.preferences.get(ATTR_USAGE, False) or self.preferences.get( if self.preferences.get(ATTR_USAGE, False) or self.preferences.get(
ATTR_STATISTICS, False ATTR_STATISTICS, False
): ):
ent_reg = er.async_get(self.hass)
try:
yaml_configuration = await conf_util.async_hass_config_yaml(self.hass)
except HomeAssistantError as err:
LOGGER.error(err)
return
configuration_set = set(yaml_configuration)
er_platforms = {
entity.platform
for entity in ent_reg.entities.values()
if not entity.disabled
}
domains = async_get_loaded_integrations(self.hass) domains = async_get_loaded_integrations(self.hass)
configured_integrations = await async_get_integrations(self.hass, domains) configured_integrations = await async_get_integrations(self.hass, domains)
enabled_domains = set(configured_integrations)
for integration in configured_integrations.values(): for integration in configured_integrations.values():
if isinstance(integration, IntegrationNotFound): if isinstance(integration, IntegrationNotFound):
continue continue
@@ -215,7 +242,11 @@ class Analytics:
if isinstance(integration, BaseException): if isinstance(integration, BaseException):
raise integration raise integration
if integration.disabled: if not self._async_should_report_integration(
integration=integration,
yaml_domains=configuration_set,
entity_registry_platforms=er_platforms,
):
continue continue
if not integration.is_built_in: if not integration.is_built_in:
@@ -253,12 +284,12 @@ class Analytics:
if supervisor_info is not None: if supervisor_info is not None:
payload[ATTR_ADDONS] = addons payload[ATTR_ADDONS] = addons
if ENERGY_DOMAIN in integrations: if ENERGY_DOMAIN in enabled_domains:
payload[ATTR_ENERGY] = { payload[ATTR_ENERGY] = {
ATTR_CONFIGURED: await energy_is_configured(self.hass) ATTR_CONFIGURED: await energy_is_configured(self.hass)
} }
if RECORDER_DOMAIN in integrations: if RECORDER_DOMAIN in enabled_domains:
instance = get_recorder_instance(self.hass) instance = get_recorder_instance(self.hass)
engine = instance.database_engine engine = instance.database_engine
if engine and engine.version is not None: if engine and engine.version is not None:
@@ -306,3 +337,34 @@ class Analytics:
LOGGER.error( LOGGER.error(
"Error sending analytics to %s: %r", ANALYTICS_ENDPOINT_URL, err "Error sending analytics to %s: %r", ANALYTICS_ENDPOINT_URL, err
) )
@callback
def _async_should_report_integration(
self,
integration: Integration,
yaml_domains: set[str],
entity_registry_platforms: set[str],
) -> bool:
"""Return a bool to indicate if this integration should be reported."""
if integration.disabled:
return False
# Check if the integration is defined in YAML or in the entity registry
if (
integration.domain in yaml_domains
or integration.domain in entity_registry_platforms
):
return True
# Check if the integration provide a config flow
if not integration.config_flow:
return False
entries = self.hass.config_entries.async_entries(integration.domain)
# Filter out ignored and disabled entries
return any(
entry
for entry in entries
if entry.source != SOURCE_IGNORE and entry.disabled_by is None
)

View File

@@ -296,7 +296,6 @@ class ADBDevice(MediaPlayerEntity):
self._process_config, self._process_config,
) )
) )
return
@property @property
def media_image_hash(self) -> str | None: def media_image_hash(self) -> str | None:

View File

@@ -16,6 +16,7 @@ from .const import DOMAIN
class AndroidTVRemoteBaseEntity(Entity): class AndroidTVRemoteBaseEntity(Entity):
"""Android TV Remote Base Entity.""" """Android TV Remote Base Entity."""
_attr_name = None
_attr_has_entity_name = True _attr_has_entity_name = True
_attr_should_poll = False _attr_should_poll = False

View File

@@ -5,5 +5,5 @@
"documentation": "https://www.home-assistant.io/integrations/anel_pwrctrl", "documentation": "https://www.home-assistant.io/integrations/anel_pwrctrl",
"iot_class": "local_polling", "iot_class": "local_polling",
"loggers": ["anel_pwrctrl"], "loggers": ["anel_pwrctrl"],
"requirements": ["anel_pwrctrl-homeassistant==0.0.1.dev2"] "requirements": ["anel-pwrctrl-homeassistant==0.0.1.dev2"]
} }

View File

@@ -80,6 +80,7 @@ class AnthemAVR(MediaPlayerEntity):
self._attr_name = f"zone {zone_number}" self._attr_name = f"zone {zone_number}"
self._attr_unique_id = f"{mac_address}_{zone_number}" self._attr_unique_id = f"{mac_address}_{zone_number}"
else: else:
self._attr_name = None
self._attr_unique_id = mac_address self._attr_unique_id = mac_address
self._attr_device_info = DeviceInfo( self._attr_device_info = DeviceInfo(

View File

@@ -7,7 +7,7 @@
"documentation": "https://www.home-assistant.io/integrations/apple_tv", "documentation": "https://www.home-assistant.io/integrations/apple_tv",
"iot_class": "local_push", "iot_class": "local_push",
"loggers": ["pyatv", "srptools"], "loggers": ["pyatv", "srptools"],
"requirements": ["pyatv==0.12.0"], "requirements": ["pyatv==0.13.2"],
"zeroconf": [ "zeroconf": [
"_mediaremotetv._tcp.local.", "_mediaremotetv._tcp.local.",
"_companion-link._tcp.local.", "_companion-link._tcp.local.",

View File

@@ -282,7 +282,7 @@ class AppleTvMediaPlayer(AppleTVEntity, MediaPlayerEntity):
"""Send the play_media command to the media player.""" """Send the play_media command to the media player."""
# If input (file) has a file format supported by pyatv, then stream it with # If input (file) has a file format supported by pyatv, then stream it with
# RAOP. Otherwise try to play it with regular AirPlay. # RAOP. Otherwise try to play it with regular AirPlay.
if media_type == MediaType.APP: if media_type in {MediaType.APP, MediaType.URL}:
await self.atv.apps.launch_app(media_id) await self.atv.apps.launch_app(media_id)
return return

View File

@@ -49,6 +49,7 @@
}, },
"abort": { "abort": {
"ipv6_not_supported": "IPv6 is not supported.", "ipv6_not_supported": "IPv6 is not supported.",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]", "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]",
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]", "already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"device_did_not_pair": "No attempt to finish pairing process was made from the device.", "device_did_not_pair": "No attempt to finish pairing process was made from the device.",

View File

@@ -5,5 +5,5 @@
"documentation": "https://www.home-assistant.io/integrations/aquostv", "documentation": "https://www.home-assistant.io/integrations/aquostv",
"iot_class": "local_polling", "iot_class": "local_polling",
"loggers": ["sharp_aquos_rc"], "loggers": ["sharp_aquos_rc"],
"requirements": ["sharp_aquos_rc==0.3.2"] "requirements": ["sharp-aquos-rc==0.3.2"]
} }

View File

@@ -1,6 +1,8 @@
"""Support for Aranet sensors.""" """Support for Aranet sensors."""
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass
from aranet4.client import Aranet4Advertisement from aranet4.client import Aranet4Advertisement
from bleak.backends.device import BLEDevice from bleak.backends.device import BLEDevice
@@ -23,6 +25,7 @@ from homeassistant.const import (
ATTR_SW_VERSION, ATTR_SW_VERSION,
CONCENTRATION_PARTS_PER_MILLION, CONCENTRATION_PARTS_PER_MILLION,
PERCENTAGE, PERCENTAGE,
EntityCategory,
UnitOfPressure, UnitOfPressure,
UnitOfTemperature, UnitOfTemperature,
UnitOfTime, UnitOfTime,
@@ -33,43 +36,55 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN from .const import DOMAIN
@dataclass
class AranetSensorEntityDescription(SensorEntityDescription):
"""Class to describe an Aranet sensor entity."""
# PassiveBluetoothDataUpdate does not support UNDEFINED
# Restrict the type to satisfy the type checker and catch attempts
# to use UNDEFINED in the entity descriptions.
name: str | None = None
SENSOR_DESCRIPTIONS = { SENSOR_DESCRIPTIONS = {
"temperature": SensorEntityDescription( "temperature": AranetSensorEntityDescription(
key="temperature", key="temperature",
name="Temperature", name="Temperature",
device_class=SensorDeviceClass.TEMPERATURE, device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=UnitOfTemperature.CELSIUS, native_unit_of_measurement=UnitOfTemperature.CELSIUS,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
), ),
"humidity": SensorEntityDescription( "humidity": AranetSensorEntityDescription(
key="humidity", key="humidity",
name="Humidity", name="Humidity",
device_class=SensorDeviceClass.HUMIDITY, device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
), ),
"pressure": SensorEntityDescription( "pressure": AranetSensorEntityDescription(
key="pressure", key="pressure",
name="Pressure", name="Pressure",
device_class=SensorDeviceClass.PRESSURE, device_class=SensorDeviceClass.PRESSURE,
native_unit_of_measurement=UnitOfPressure.HPA, native_unit_of_measurement=UnitOfPressure.HPA,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
), ),
"co2": SensorEntityDescription( "co2": AranetSensorEntityDescription(
key="co2", key="co2",
name="Carbon Dioxide", name="Carbon Dioxide",
device_class=SensorDeviceClass.CO2, device_class=SensorDeviceClass.CO2,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION, native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
), ),
"battery": SensorEntityDescription( "battery": AranetSensorEntityDescription(
key="battery", key="battery",
name="Battery", name="Battery",
device_class=SensorDeviceClass.BATTERY, device_class=SensorDeviceClass.BATTERY,
native_unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC,
), ),
"interval": SensorEntityDescription( "interval": AranetSensorEntityDescription(
key="update_interval", key="update_interval",
name="Update Interval", name="Update Interval",
device_class=SensorDeviceClass.DURATION, device_class=SensorDeviceClass.DURATION,
@@ -77,6 +92,7 @@ SENSOR_DESCRIPTIONS = {
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
# The interval setting is not a generally useful entity for most users. # The interval setting is not a generally useful entity for most users.
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC,
), ),
} }

View File

@@ -3,7 +3,9 @@ from __future__ import annotations
import voluptuous as vol import voluptuous as vol
from homeassistant.components.device_automation import DEVICE_TRIGGER_BASE_SCHEMA from homeassistant.components.device_automation import (
DEVICE_TRIGGER_BASE_SCHEMA,
)
from homeassistant.const import ( from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_ENTITY_ID,
CONF_DEVICE_ID, CONF_DEVICE_ID,
@@ -22,7 +24,7 @@ from .const import DOMAIN, EVENT_TURN_ON
TRIGGER_TYPES = {"turn_on"} TRIGGER_TYPES = {"turn_on"}
TRIGGER_SCHEMA = DEVICE_TRIGGER_BASE_SCHEMA.extend( TRIGGER_SCHEMA = DEVICE_TRIGGER_BASE_SCHEMA.extend(
{ {
vol.Required(CONF_ENTITY_ID): cv.entity_id, vol.Required(CONF_ENTITY_ID): cv.entity_id_or_uuid,
vol.Required(CONF_TYPE): vol.In(TRIGGER_TYPES), vol.Required(CONF_TYPE): vol.In(TRIGGER_TYPES),
} }
) )
@@ -43,7 +45,7 @@ async def async_get_triggers(
CONF_PLATFORM: "device", CONF_PLATFORM: "device",
CONF_DEVICE_ID: device_id, CONF_DEVICE_ID: device_id,
CONF_DOMAIN: DOMAIN, CONF_DOMAIN: DOMAIN,
CONF_ENTITY_ID: entry.entity_id, CONF_ENTITY_ID: entry.id,
CONF_TYPE: "turn_on", CONF_TYPE: "turn_on",
} }
) )
@@ -62,7 +64,8 @@ async def async_attach_trigger(
job = HassJob(action) job = HassJob(action)
if config[CONF_TYPE] == "turn_on": if config[CONF_TYPE] == "turn_on":
entity_id = config[CONF_ENTITY_ID] registry = er.async_get(hass)
entity_id = er.async_resolve_entity_id(registry, config[ATTR_ENTITY_ID])
@callback @callback
def _handle_event(event: Event) -> None: def _handle_event(event: Event) -> None:
@@ -71,9 +74,10 @@ async def async_attach_trigger(
job, job,
{ {
"trigger": { "trigger": {
**trigger_data, # type: ignore[arg-type] # https://github.com/python/mypy/issues/9117 **trigger_data,
**config, **config,
"description": f"{DOMAIN} - {entity_id}", "description": f"{DOMAIN} - {entity_id}",
"entity_id": entity_id,
} }
}, },
event.context, event.context,

View File

@@ -6,7 +6,7 @@
"documentation": "https://www.home-assistant.io/integrations/arcam_fmj", "documentation": "https://www.home-assistant.io/integrations/arcam_fmj",
"iot_class": "local_polling", "iot_class": "local_polling",
"loggers": ["arcam"], "loggers": ["arcam"],
"requirements": ["arcam-fmj==1.3.0"], "requirements": ["arcam-fmj==1.4.0"],
"ssdp": [ "ssdp": [
{ {
"deviceType": "urn:schemas-upnp-org:device:MediaRenderer:1", "deviceType": "urn:schemas-upnp-org:device:MediaRenderer:1",

View File

@@ -57,6 +57,7 @@ async def async_pipeline_from_audio_stream(
pipeline_id: str | None = None, pipeline_id: str | None = None,
conversation_id: str | None = None, conversation_id: str | None = None,
tts_audio_output: str | None = None, tts_audio_output: str | None = None,
device_id: str | None = None,
) -> None: ) -> None:
"""Create an audio pipeline from an audio stream. """Create an audio pipeline from an audio stream.
@@ -64,6 +65,7 @@ async def async_pipeline_from_audio_stream(
""" """
pipeline_input = PipelineInput( pipeline_input = PipelineInput(
conversation_id=conversation_id, conversation_id=conversation_id,
device_id=device_id,
stt_metadata=stt_metadata, stt_metadata=stt_metadata,
stt_stream=stt_stream, stt_stream=stt_stream,
run=PipelineRun( run=PipelineRun(

View File

@@ -499,7 +499,7 @@ class PipelineRun:
self.intent_agent = agent_info.id self.intent_agent = agent_info.id
async def recognize_intent( async def recognize_intent(
self, intent_input: str, conversation_id: str | None self, intent_input: str, conversation_id: str | None, device_id: str | None
) -> str: ) -> str:
"""Run intent recognition portion of pipeline. Returns text to speak.""" """Run intent recognition portion of pipeline. Returns text to speak."""
if self.intent_agent is None: if self.intent_agent is None:
@@ -512,6 +512,8 @@ class PipelineRun:
"engine": self.intent_agent, "engine": self.intent_agent,
"language": self.pipeline.conversation_language, "language": self.pipeline.conversation_language,
"intent_input": intent_input, "intent_input": intent_input,
"conversation_id": conversation_id,
"device_id": device_id,
}, },
) )
) )
@@ -521,6 +523,7 @@ class PipelineRun:
hass=self.hass, hass=self.hass,
text=intent_input, text=intent_input,
conversation_id=conversation_id, conversation_id=conversation_id,
device_id=device_id,
context=self.context, context=self.context,
language=self.pipeline.conversation_language, language=self.pipeline.conversation_language,
agent_id=self.intent_agent, agent_id=self.intent_agent,
@@ -655,6 +658,8 @@ class PipelineInput:
conversation_id: str | None = None conversation_id: str | None = None
device_id: str | None = None
async def execute(self) -> None: async def execute(self) -> None:
"""Run pipeline.""" """Run pipeline."""
self.run.start() self.run.start()
@@ -678,7 +683,9 @@ class PipelineInput:
if current_stage == PipelineStage.INTENT: if current_stage == PipelineStage.INTENT:
assert intent_input is not None assert intent_input is not None
tts_input = await self.run.recognize_intent( tts_input = await self.run.recognize_intent(
intent_input, self.conversation_id intent_input,
self.conversation_id,
self.device_id,
) )
current_stage = PipelineStage.TTS current_stage = PipelineStage.TTS
@@ -730,17 +737,30 @@ class PipelineInput:
) )
start_stage_index = PIPELINE_STAGE_ORDER.index(self.run.start_stage) start_stage_index = PIPELINE_STAGE_ORDER.index(self.run.start_stage)
end_stage_index = PIPELINE_STAGE_ORDER.index(self.run.end_stage)
prepare_tasks = [] prepare_tasks = []
if start_stage_index <= PIPELINE_STAGE_ORDER.index(PipelineStage.STT): if (
start_stage_index
<= PIPELINE_STAGE_ORDER.index(PipelineStage.STT)
<= end_stage_index
):
# self.stt_metadata can't be None or we'd raise above # self.stt_metadata can't be None or we'd raise above
prepare_tasks.append(self.run.prepare_speech_to_text(self.stt_metadata)) # type: ignore[arg-type] prepare_tasks.append(self.run.prepare_speech_to_text(self.stt_metadata)) # type: ignore[arg-type]
if start_stage_index <= PIPELINE_STAGE_ORDER.index(PipelineStage.INTENT): if (
start_stage_index
<= PIPELINE_STAGE_ORDER.index(PipelineStage.INTENT)
<= end_stage_index
):
prepare_tasks.append(self.run.prepare_recognize_intent()) prepare_tasks.append(self.run.prepare_recognize_intent())
if start_stage_index <= PIPELINE_STAGE_ORDER.index(PipelineStage.TTS): if (
start_stage_index
<= PIPELINE_STAGE_ORDER.index(PipelineStage.TTS)
<= end_stage_index
):
prepare_tasks.append(self.run.prepare_text_to_speech()) prepare_tasks.append(self.run.prepare_text_to_speech())
if prepare_tasks: if prepare_tasks:
@@ -944,6 +964,7 @@ class PipelineData:
pipeline_runs: dict[str, LimitedSizeDict[str, PipelineRunDebug]] pipeline_runs: dict[str, LimitedSizeDict[str, PipelineRunDebug]]
pipeline_store: PipelineStorageCollection pipeline_store: PipelineStorageCollection
pipeline_devices: set[str] = field(default_factory=set, init=False)
@dataclass @dataclass

View File

@@ -10,7 +10,8 @@ from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import collection, entity_registry as er, restore_state from homeassistant.helpers import collection, entity_registry as er, restore_state
from .const import DOMAIN from .const import DOMAIN
from .pipeline import PipelineStorageCollection from .pipeline import PipelineData, PipelineStorageCollection
from .vad import VadSensitivity
OPTION_PREFERRED = "preferred" OPTION_PREFERRED = "preferred"
@@ -38,6 +39,25 @@ def get_chosen_pipeline(
) )
@callback
def get_vad_sensitivity(
hass: HomeAssistant, domain: str, unique_id_prefix: str
) -> VadSensitivity:
"""Get the chosen vad sensitivity for a domain."""
ent_reg = er.async_get(hass)
sensitivity_entity_id = ent_reg.async_get_entity_id(
Platform.SELECT, domain, f"{unique_id_prefix}-vad_sensitivity"
)
if sensitivity_entity_id is None:
return VadSensitivity.DEFAULT
state = hass.states.get(sensitivity_entity_id)
if state is None:
return VadSensitivity.DEFAULT
return VadSensitivity(state.state)
class AssistPipelineSelect(SelectEntity, restore_state.RestoreEntity): class AssistPipelineSelect(SelectEntity, restore_state.RestoreEntity):
"""Entity to represent a pipeline selector.""" """Entity to represent a pipeline selector."""
@@ -60,15 +80,24 @@ class AssistPipelineSelect(SelectEntity, restore_state.RestoreEntity):
"""When entity is added to Home Assistant.""" """When entity is added to Home Assistant."""
await super().async_added_to_hass() await super().async_added_to_hass()
pipeline_store: PipelineStorageCollection = self.hass.data[ pipeline_data: PipelineData = self.hass.data[DOMAIN]
DOMAIN pipeline_store = pipeline_data.pipeline_store
].pipeline_store self.async_on_remove(
pipeline_store.async_add_change_set_listener(self._pipelines_updated) pipeline_store.async_add_change_set_listener(self._pipelines_updated)
)
state = await self.async_get_last_state() state = await self.async_get_last_state()
if state is not None and state.state in self.options: if state is not None and state.state in self.options:
self._attr_current_option = state.state self._attr_current_option = state.state
if self.registry_entry and (device_id := self.registry_entry.device_id):
pipeline_data.pipeline_devices.add(device_id)
self.async_on_remove(
lambda: pipeline_data.pipeline_devices.discard(
device_id # type: ignore[arg-type]
)
)
async def async_select_option(self, option: str) -> None: async def async_select_option(self, option: str) -> None:
"""Select an option.""" """Select an option."""
self._attr_current_option = option self._attr_current_option = option
@@ -93,3 +122,34 @@ class AssistPipelineSelect(SelectEntity, restore_state.RestoreEntity):
if self._attr_current_option not in options: if self._attr_current_option not in options:
self._attr_current_option = OPTION_PREFERRED self._attr_current_option = OPTION_PREFERRED
class VadSensitivitySelect(SelectEntity, restore_state.RestoreEntity):
"""Entity to represent VAD sensitivity."""
entity_description = SelectEntityDescription(
key="vad_sensitivity",
translation_key="vad_sensitivity",
entity_category=EntityCategory.CONFIG,
)
_attr_should_poll = False
_attr_current_option = VadSensitivity.DEFAULT.value
_attr_options = [vs.value for vs in VadSensitivity]
def __init__(self, hass: HomeAssistant, unique_id_prefix: str) -> None:
"""Initialize a pipeline selector."""
self._attr_unique_id = f"{unique_id_prefix}-vad_sensitivity"
self.hass = hass
async def async_added_to_hass(self) -> None:
"""When entity is added to Home Assistant."""
await super().async_added_to_hass()
state = await self.async_get_last_state()
if state is not None and state.state in self.options:
self._attr_current_option = state.state
async def async_select_option(self, option: str) -> None:
"""Select an option."""
self._attr_current_option = option
self.async_write_ha_state()

View File

@@ -11,6 +11,14 @@
"state": { "state": {
"preferred": "Preferred" "preferred": "Preferred"
} }
},
"vad_sensitivity": {
"name": "Finished speaking detection",
"state": {
"default": "Default",
"aggressive": "Aggressive",
"relaxed": "Relaxed"
}
} }
} }
} }

View File

@@ -1,11 +1,35 @@
"""Voice activity detection.""" """Voice activity detection."""
from __future__ import annotations
from dataclasses import dataclass, field from dataclasses import dataclass, field
import webrtcvad import webrtcvad
from homeassistant.backports.enum import StrEnum
_SAMPLE_RATE = 16000 _SAMPLE_RATE = 16000
class VadSensitivity(StrEnum):
"""How quickly the end of a voice command is detected."""
DEFAULT = "default"
RELAXED = "relaxed"
AGGRESSIVE = "aggressive"
@staticmethod
def to_seconds(sensitivity: VadSensitivity | str) -> float:
"""Return seconds of silence for sensitivity level."""
sensitivity = VadSensitivity(sensitivity)
if sensitivity == VadSensitivity.RELAXED:
return 2.0
if sensitivity == VadSensitivity.AGGRESSIVE:
return 0.5
return 1.0
@dataclass @dataclass
class VoiceCommandSegmenter: class VoiceCommandSegmenter:
"""Segments an audio stream into voice commands using webrtcvad.""" """Segments an audio stream into voice commands using webrtcvad."""
@@ -113,16 +137,15 @@ class VoiceCommandSegmenter:
self._reset_seconds_left -= self._seconds_per_chunk self._reset_seconds_left -= self._seconds_per_chunk
if self._reset_seconds_left <= 0: if self._reset_seconds_left <= 0:
self._speech_seconds_left = self.speech_seconds self._speech_seconds_left = self.speech_seconds
elif not is_speech:
self._reset_seconds_left = self.reset_seconds
self._silence_seconds_left -= self._seconds_per_chunk
if self._silence_seconds_left <= 0:
return False
else: else:
if not is_speech: # Reset if enough speech
self._reset_seconds_left = self.reset_seconds self._reset_seconds_left -= self._seconds_per_chunk
self._silence_seconds_left -= self._seconds_per_chunk if self._reset_seconds_left <= 0:
if self._silence_seconds_left <= 0: self._silence_seconds_left = self.silence_seconds
return False
else:
# Reset if enough speech
self._reset_seconds_left -= self._seconds_per_chunk
if self._reset_seconds_left <= 0:
self._silence_seconds_left = self.silence_seconds
return True return True

View File

@@ -56,6 +56,7 @@ def async_register_websocket_api(hass: HomeAssistant) -> None:
vol.Optional("input"): dict, vol.Optional("input"): dict,
vol.Optional("pipeline"): str, vol.Optional("pipeline"): str,
vol.Optional("conversation_id"): vol.Any(str, None), vol.Optional("conversation_id"): vol.Any(str, None),
vol.Optional("device_id"): vol.Any(str, None),
vol.Optional("timeout"): vol.Any(float, int), vol.Optional("timeout"): vol.Any(float, int),
}, },
), ),
@@ -105,6 +106,7 @@ async def websocket_run(
# Arguments to PipelineInput # Arguments to PipelineInput
input_args: dict[str, Any] = { input_args: dict[str, Any] = {
"conversation_id": msg.get("conversation_id"), "conversation_id": msg.get("conversation_id"),
"device_id": msg.get("device_id"),
} }
if start_stage == PipelineStage.STT: if start_stage == PipelineStage.STT:
@@ -280,7 +282,6 @@ def websocket_get_run(
) )
@callback
@websocket_api.websocket_command( @websocket_api.websocket_command(
{ {
vol.Required("type"): "assist_pipeline/language/list", vol.Required("type"): "assist_pipeline/language/list",

View File

@@ -5,5 +5,5 @@
"documentation": "https://www.home-assistant.io/integrations/asterisk_mbox", "documentation": "https://www.home-assistant.io/integrations/asterisk_mbox",
"iot_class": "local_push", "iot_class": "local_push",
"loggers": ["asterisk_mbox"], "loggers": ["asterisk_mbox"],
"requirements": ["asterisk_mbox==0.5.0"] "requirements": ["asterisk-mbox==0.5.0"]
} }

View File

@@ -15,6 +15,7 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_TEMPERATURE, Platform from homeassistant.const import ATTR_TEMPERATURE, Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util.enum import try_parse_enum
from . import DOMAIN, AtagEntity from . import DOMAIN, AtagEntity
@@ -52,14 +53,12 @@ class AtagThermostat(AtagEntity, ClimateEntity):
self._attr_temperature_unit = coordinator.data.climate.temp_unit self._attr_temperature_unit = coordinator.data.climate.temp_unit
@property @property
def hvac_mode(self) -> str | None: def hvac_mode(self) -> HVACMode | None:
"""Return hvac operation ie. heat, cool mode.""" """Return hvac operation ie. heat, cool mode."""
if self.coordinator.data.climate.hvac_mode in HVAC_MODES: return try_parse_enum(HVACMode, self.coordinator.data.climate.hvac_mode)
return self.coordinator.data.climate.hvac_mode
return None
@property @property
def hvac_action(self) -> str | None: def hvac_action(self) -> HVACAction | None:
"""Return the current running hvac operation.""" """Return the current running hvac operation."""
is_active = self.coordinator.data.climate.status is_active = self.coordinator.data.climate.status
return HVACAction.HEATING if is_active else HVACAction.IDLE return HVACAction.HEATING if is_active else HVACAction.IDLE

View File

@@ -5,5 +5,5 @@
"documentation": "https://www.home-assistant.io/integrations/atome", "documentation": "https://www.home-assistant.io/integrations/atome",
"iot_class": "cloud_polling", "iot_class": "cloud_polling",
"loggers": ["pyatome"], "loggers": ["pyatome"],
"requirements": ["pyatome==0.1.1"] "requirements": ["pyAtome==0.1.1"]
} }

View File

@@ -5,7 +5,6 @@ from collections.abc import Callable
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime, timedelta from datetime import datetime, timedelta
import logging import logging
from typing import cast
from yalexs.activity import ( from yalexs.activity import (
ACTION_DOORBELL_CALL_MISSED, ACTION_DOORBELL_CALL_MISSED,
@@ -104,7 +103,16 @@ def _native_datetime() -> datetime:
@dataclass @dataclass
class AugustRequiredKeysMixin: class AugustBinarySensorEntityDescription(BinarySensorEntityDescription):
"""Describes August binary_sensor entity."""
# AugustBinarySensor does not support UNDEFINED or None,
# restrict the type to str.
name: str = ""
@dataclass
class AugustDoorbellRequiredKeysMixin:
"""Mixin for required keys.""" """Mixin for required keys."""
value_fn: Callable[[AugustData, DoorbellDetail], bool] value_fn: Callable[[AugustData, DoorbellDetail], bool]
@@ -112,41 +120,45 @@ class AugustRequiredKeysMixin:
@dataclass @dataclass
class AugustBinarySensorEntityDescription( class AugustDoorbellBinarySensorEntityDescription(
BinarySensorEntityDescription, AugustRequiredKeysMixin BinarySensorEntityDescription, AugustDoorbellRequiredKeysMixin
): ):
"""Describes August binary_sensor entity.""" """Describes August binary_sensor entity."""
# AugustDoorbellBinarySensor does not support UNDEFINED or None,
# restrict the type to str.
name: str = ""
SENSOR_TYPE_DOOR = BinarySensorEntityDescription(
SENSOR_TYPE_DOOR = AugustBinarySensorEntityDescription(
key="door_open", key="door_open",
name="Open", name="Open",
) )
SENSOR_TYPES_DOORBELL: tuple[AugustBinarySensorEntityDescription, ...] = ( SENSOR_TYPES_DOORBELL: tuple[AugustDoorbellBinarySensorEntityDescription, ...] = (
AugustBinarySensorEntityDescription( AugustDoorbellBinarySensorEntityDescription(
key="doorbell_ding", key="doorbell_ding",
name="Ding", name="Ding",
device_class=BinarySensorDeviceClass.OCCUPANCY, device_class=BinarySensorDeviceClass.OCCUPANCY,
value_fn=_retrieve_ding_state, value_fn=_retrieve_ding_state,
is_time_based=True, is_time_based=True,
), ),
AugustBinarySensorEntityDescription( AugustDoorbellBinarySensorEntityDescription(
key="doorbell_motion", key="doorbell_motion",
name="Motion", name="Motion",
device_class=BinarySensorDeviceClass.MOTION, device_class=BinarySensorDeviceClass.MOTION,
value_fn=_retrieve_motion_state, value_fn=_retrieve_motion_state,
is_time_based=True, is_time_based=True,
), ),
AugustBinarySensorEntityDescription( AugustDoorbellBinarySensorEntityDescription(
key="doorbell_image_capture", key="doorbell_image_capture",
name="Image Capture", name="Image Capture",
icon="mdi:file-image", icon="mdi:file-image",
value_fn=_retrieve_image_capture_state, value_fn=_retrieve_image_capture_state,
is_time_based=True, is_time_based=True,
), ),
AugustBinarySensorEntityDescription( AugustDoorbellBinarySensorEntityDescription(
key="doorbell_online", key="doorbell_online",
name="Online", name="Online",
device_class=BinarySensorDeviceClass.CONNECTIVITY, device_class=BinarySensorDeviceClass.CONNECTIVITY,
@@ -199,7 +211,10 @@ class AugustDoorBinarySensor(AugustEntityMixin, BinarySensorEntity):
_attr_device_class = BinarySensorDeviceClass.DOOR _attr_device_class = BinarySensorDeviceClass.DOOR
def __init__( def __init__(
self, data: AugustData, device: Lock, description: BinarySensorEntityDescription self,
data: AugustData,
device: Lock,
description: AugustBinarySensorEntityDescription,
) -> None: ) -> None:
"""Initialize the sensor.""" """Initialize the sensor."""
super().__init__(data, device) super().__init__(data, device)
@@ -207,9 +222,7 @@ class AugustDoorBinarySensor(AugustEntityMixin, BinarySensorEntity):
self._data = data self._data = data
self._device = device self._device = device
self._attr_name = f"{device.device_name} {description.name}" self._attr_name = f"{device.device_name} {description.name}"
self._attr_unique_id = ( self._attr_unique_id = f"{self._device_id}_{description.name.lower()}"
f"{self._device_id}_{cast(str, description.name).lower()}"
)
@callback @callback
def _update_from_data(self): def _update_from_data(self):
@@ -243,13 +256,13 @@ class AugustDoorBinarySensor(AugustEntityMixin, BinarySensorEntity):
class AugustDoorbellBinarySensor(AugustEntityMixin, BinarySensorEntity): class AugustDoorbellBinarySensor(AugustEntityMixin, BinarySensorEntity):
"""Representation of an August binary sensor.""" """Representation of an August binary sensor."""
entity_description: AugustBinarySensorEntityDescription entity_description: AugustDoorbellBinarySensorEntityDescription
def __init__( def __init__(
self, self,
data: AugustData, data: AugustData,
device: Doorbell, device: Doorbell,
description: AugustBinarySensorEntityDescription, description: AugustDoorbellBinarySensorEntityDescription,
) -> None: ) -> None:
"""Initialize the sensor.""" """Initialize the sensor."""
super().__init__(data, device) super().__init__(data, device)
@@ -257,9 +270,7 @@ class AugustDoorbellBinarySensor(AugustEntityMixin, BinarySensorEntity):
self._check_for_off_update_listener = None self._check_for_off_update_listener = None
self._data = data self._data = data
self._attr_name = f"{device.device_name} {description.name}" self._attr_name = f"{device.device_name} {description.name}"
self._attr_unique_id = ( self._attr_unique_id = f"{self._device_id}_{description.name.lower()}"
f"{self._device_id}_{cast(str, description.name).lower()}"
)
@callback @callback
def _update_from_data(self): def _update_from_data(self):

View File

@@ -1,25 +1,15 @@
"""The aurora component.""" """The aurora component."""
from datetime import timedelta
import logging import logging
from aiohttp import ClientError
from auroranoaa import AuroraForecast from auroranoaa import AuroraForecast
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, Platform from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers import aiohttp_client from homeassistant.helpers import aiohttp_client
from homeassistant.helpers.device_registry import DeviceEntryType
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
DataUpdateCoordinator,
UpdateFailed,
)
from .const import ( from .const import (
ATTRIBUTION,
AURORA_API, AURORA_API,
CONF_THRESHOLD, CONF_THRESHOLD,
COORDINATOR, COORDINATOR,
@@ -27,6 +17,7 @@ from .const import (
DEFAULT_THRESHOLD, DEFAULT_THRESHOLD,
DOMAIN, DOMAIN,
) )
from .coordinator import AuroraDataUpdateCoordinator
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -79,71 +70,3 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
hass.data[DOMAIN].pop(entry.entry_id) hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok return unload_ok
class AuroraDataUpdateCoordinator(DataUpdateCoordinator):
"""Class to manage fetching data from the NOAA Aurora API."""
def __init__(
self,
hass: HomeAssistant,
name: str,
polling_interval: int,
api: str,
latitude: float,
longitude: float,
threshold: float,
) -> None:
"""Initialize the data updater."""
super().__init__(
hass=hass,
logger=_LOGGER,
name=name,
update_interval=timedelta(minutes=polling_interval),
)
self.api = api
self.name = name
self.latitude = int(latitude)
self.longitude = int(longitude)
self.threshold = int(threshold)
async def _async_update_data(self):
"""Fetch the data from the NOAA Aurora Forecast."""
try:
return await self.api.get_forecast_data(self.longitude, self.latitude)
except ClientError as error:
raise UpdateFailed(f"Error updating from NOAA: {error}") from error
class AuroraEntity(CoordinatorEntity[AuroraDataUpdateCoordinator]):
"""Implementation of the base Aurora Entity."""
_attr_attribution = ATTRIBUTION
def __init__(
self,
coordinator: AuroraDataUpdateCoordinator,
name: str,
icon: str,
) -> None:
"""Initialize the Aurora Entity."""
super().__init__(coordinator=coordinator)
self._attr_name = name
self._attr_unique_id = f"{coordinator.latitude}_{coordinator.longitude}"
self._attr_icon = icon
@property
def device_info(self) -> DeviceInfo:
"""Define the device based on name."""
return DeviceInfo(
entry_type=DeviceEntryType.SERVICE,
identifiers={(DOMAIN, str(self.unique_id))},
manufacturer="NOAA",
model="Aurora Visibility Sensor",
name=self.coordinator.name,
)

View File

@@ -4,8 +4,8 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import AuroraEntity
from .const import COORDINATOR, DOMAIN from .const import COORDINATOR, DOMAIN
from .entity import AuroraEntity
async def async_setup_entry( async def async_setup_entry(

View File

@@ -0,0 +1,52 @@
"""The aurora component."""
from datetime import timedelta
import logging
from aiohttp import ClientError
from auroranoaa import AuroraForecast
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import (
DataUpdateCoordinator,
UpdateFailed,
)
_LOGGER = logging.getLogger(__name__)
class AuroraDataUpdateCoordinator(DataUpdateCoordinator):
"""Class to manage fetching data from the NOAA Aurora API."""
def __init__(
self,
hass: HomeAssistant,
name: str,
polling_interval: int,
api: AuroraForecast,
latitude: float,
longitude: float,
threshold: float,
) -> None:
"""Initialize the data updater."""
super().__init__(
hass=hass,
logger=_LOGGER,
name=name,
update_interval=timedelta(minutes=polling_interval),
)
self.api = api
self.name = name
self.latitude = int(latitude)
self.longitude = int(longitude)
self.threshold = int(threshold)
async def _async_update_data(self):
"""Fetch the data from the NOAA Aurora Forecast."""
try:
return await self.api.get_forecast_data(self.longitude, self.latitude)
except ClientError as error:
raise UpdateFailed(f"Error updating from NOAA: {error}") from error

View File

@@ -0,0 +1,48 @@
"""The aurora component."""
import logging
from homeassistant.helpers.device_registry import DeviceEntryType
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
)
from .const import (
ATTRIBUTION,
DOMAIN,
)
from .coordinator import AuroraDataUpdateCoordinator
_LOGGER = logging.getLogger(__name__)
class AuroraEntity(CoordinatorEntity[AuroraDataUpdateCoordinator]):
"""Implementation of the base Aurora Entity."""
_attr_attribution = ATTRIBUTION
def __init__(
self,
coordinator: AuroraDataUpdateCoordinator,
name: str,
icon: str,
) -> None:
"""Initialize the Aurora Entity."""
super().__init__(coordinator=coordinator)
self._attr_name = name
self._attr_unique_id = f"{coordinator.latitude}_{coordinator.longitude}"
self._attr_icon = icon
@property
def device_info(self) -> DeviceInfo:
"""Define the device based on name."""
return DeviceInfo(
entry_type=DeviceEntryType.SERVICE,
identifiers={(DOMAIN, str(self.unique_id))},
manufacturer="NOAA",
model="Aurora Visibility Sensor",
name=self.coordinator.name,
)

View File

@@ -5,8 +5,8 @@ from homeassistant.const import PERCENTAGE
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import AuroraEntity
from .const import COORDINATOR, DOMAIN from .const import COORDINATOR, DOMAIN
from .entity import AuroraEntity
async def async_setup_entry( async def async_setup_entry(

View File

@@ -47,10 +47,10 @@ class AuroraEntity(Entity):
@property @property
def device_info(self) -> DeviceInfo: def device_info(self) -> DeviceInfo:
"""Return device specific attributes.""" """Return device specific attributes."""
return { return DeviceInfo(
"identifiers": {(DOMAIN, self._data[ATTR_SERIAL_NUMBER])}, identifiers={(DOMAIN, self._data[ATTR_SERIAL_NUMBER])},
"manufacturer": MANUFACTURER, manufacturer=MANUFACTURER,
"model": self._data[ATTR_MODEL], model=self._data[ATTR_MODEL],
"name": self._data.get(ATTR_DEVICE_NAME, DEFAULT_DEVICE_NAME), name=self._data.get(ATTR_DEVICE_NAME, DEFAULT_DEVICE_NAME),
"sw_version": self._data[ATTR_FIRMWARE], sw_version=self._data[ATTR_FIRMWARE],
} )

View File

@@ -34,7 +34,7 @@ SENSOR_TYPES = [
device_class=SensorDeviceClass.POWER, device_class=SensorDeviceClass.POWER,
native_unit_of_measurement=UnitOfPower.WATT, native_unit_of_measurement=UnitOfPower.WATT,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
name="Power Output", translation_key="power_output",
), ),
SensorEntityDescription( SensorEntityDescription(
key="temp", key="temp",
@@ -42,14 +42,13 @@ SENSOR_TYPES = [
entity_category=EntityCategory.DIAGNOSTIC, entity_category=EntityCategory.DIAGNOSTIC,
native_unit_of_measurement=UnitOfTemperature.CELSIUS, native_unit_of_measurement=UnitOfTemperature.CELSIUS,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
name="Temperature",
), ),
SensorEntityDescription( SensorEntityDescription(
key="totalenergy", key="totalenergy",
device_class=SensorDeviceClass.ENERGY, device_class=SensorDeviceClass.ENERGY,
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
state_class=SensorStateClass.TOTAL_INCREASING, state_class=SensorStateClass.TOTAL_INCREASING,
name="Total Energy", translation_key="total_energy",
), ),
] ]
@@ -75,6 +74,8 @@ async def async_setup_entry(
class AuroraSensor(AuroraEntity, SensorEntity): class AuroraSensor(AuroraEntity, SensorEntity):
"""Representation of a Sensor on a Aurora ABB PowerOne Solar inverter.""" """Representation of a Sensor on a Aurora ABB PowerOne Solar inverter."""
_attr_has_entity_name = True
def __init__( def __init__(
self, self,
client: AuroraSerialClient, client: AuroraSerialClient,

View File

@@ -18,5 +18,15 @@
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]", "already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"no_serial_ports": "No com ports found. Need a valid RS485 device to communicate." "no_serial_ports": "No com ports found. Need a valid RS485 device to communicate."
} }
},
"entity": {
"sensor": {
"power_output": {
"name": "Power Output"
},
"total_energy": {
"name": "Total Energy"
}
}
} }
} }

View File

@@ -35,7 +35,7 @@ SENSOR_DESCRIPTIONS: tuple[SensorValueEntityDescription, ...] = (
# Internet Services sensors # Internet Services sensors
SensorValueEntityDescription( SensorValueEntityDescription(
key="usedMb", key="usedMb",
name="Data used", translation_key="data_used",
state_class=SensorStateClass.TOTAL_INCREASING, state_class=SensorStateClass.TOTAL_INCREASING,
native_unit_of_measurement=UnitOfInformation.MEGABYTES, native_unit_of_measurement=UnitOfInformation.MEGABYTES,
device_class=SensorDeviceClass.DATA_SIZE, device_class=SensorDeviceClass.DATA_SIZE,
@@ -43,7 +43,7 @@ SENSOR_DESCRIPTIONS: tuple[SensorValueEntityDescription, ...] = (
), ),
SensorValueEntityDescription( SensorValueEntityDescription(
key="downloadedMb", key="downloadedMb",
name="Downloaded", translation_key="downloaded",
state_class=SensorStateClass.TOTAL_INCREASING, state_class=SensorStateClass.TOTAL_INCREASING,
native_unit_of_measurement=UnitOfInformation.MEGABYTES, native_unit_of_measurement=UnitOfInformation.MEGABYTES,
device_class=SensorDeviceClass.DATA_SIZE, device_class=SensorDeviceClass.DATA_SIZE,
@@ -51,7 +51,7 @@ SENSOR_DESCRIPTIONS: tuple[SensorValueEntityDescription, ...] = (
), ),
SensorValueEntityDescription( SensorValueEntityDescription(
key="uploadedMb", key="uploadedMb",
name="Uploaded", translation_key="uploaded",
state_class=SensorStateClass.TOTAL_INCREASING, state_class=SensorStateClass.TOTAL_INCREASING,
native_unit_of_measurement=UnitOfInformation.MEGABYTES, native_unit_of_measurement=UnitOfInformation.MEGABYTES,
device_class=SensorDeviceClass.DATA_SIZE, device_class=SensorDeviceClass.DATA_SIZE,
@@ -60,21 +60,21 @@ SENSOR_DESCRIPTIONS: tuple[SensorValueEntityDescription, ...] = (
# Mobile Phone Services sensors # Mobile Phone Services sensors
SensorValueEntityDescription( SensorValueEntityDescription(
key="national", key="national",
name="National calls", translation_key="national_calls",
state_class=SensorStateClass.TOTAL_INCREASING, state_class=SensorStateClass.TOTAL_INCREASING,
icon="mdi:phone", icon="mdi:phone",
value=lambda x: x.get("calls"), value=lambda x: x.get("calls"),
), ),
SensorValueEntityDescription( SensorValueEntityDescription(
key="mobile", key="mobile",
name="Mobile calls", translation_key="mobile_calls",
state_class=SensorStateClass.TOTAL_INCREASING, state_class=SensorStateClass.TOTAL_INCREASING,
icon="mdi:phone", icon="mdi:phone",
value=lambda x: x.get("calls"), value=lambda x: x.get("calls"),
), ),
SensorValueEntityDescription( SensorValueEntityDescription(
key="international", key="international",
name="International calls", translation_key="international_calls",
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
state_class=SensorStateClass.TOTAL_INCREASING, state_class=SensorStateClass.TOTAL_INCREASING,
icon="mdi:phone-plus", icon="mdi:phone-plus",
@@ -82,14 +82,14 @@ SENSOR_DESCRIPTIONS: tuple[SensorValueEntityDescription, ...] = (
), ),
SensorValueEntityDescription( SensorValueEntityDescription(
key="sms", key="sms",
name="SMS sent", translation_key="sms_sent",
state_class=SensorStateClass.TOTAL_INCREASING, state_class=SensorStateClass.TOTAL_INCREASING,
icon="mdi:message-processing", icon="mdi:message-processing",
value=lambda x: x.get("calls"), value=lambda x: x.get("calls"),
), ),
SensorValueEntityDescription( SensorValueEntityDescription(
key="internet", key="internet",
name="Data used", translation_key="data_used",
state_class=SensorStateClass.TOTAL_INCREASING, state_class=SensorStateClass.TOTAL_INCREASING,
native_unit_of_measurement=UnitOfInformation.KILOBYTES, native_unit_of_measurement=UnitOfInformation.KILOBYTES,
device_class=SensorDeviceClass.DATA_SIZE, device_class=SensorDeviceClass.DATA_SIZE,
@@ -98,7 +98,7 @@ SENSOR_DESCRIPTIONS: tuple[SensorValueEntityDescription, ...] = (
), ),
SensorValueEntityDescription( SensorValueEntityDescription(
key="voicemail", key="voicemail",
name="Voicemail calls", translation_key="voicemail_calls",
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
state_class=SensorStateClass.TOTAL_INCREASING, state_class=SensorStateClass.TOTAL_INCREASING,
icon="mdi:phone", icon="mdi:phone",
@@ -106,7 +106,7 @@ SENSOR_DESCRIPTIONS: tuple[SensorValueEntityDescription, ...] = (
), ),
SensorValueEntityDescription( SensorValueEntityDescription(
key="other", key="other",
name="Other calls", translation_key="other_calls",
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
state_class=SensorStateClass.TOTAL_INCREASING, state_class=SensorStateClass.TOTAL_INCREASING,
icon="mdi:phone", icon="mdi:phone",
@@ -115,13 +115,13 @@ SENSOR_DESCRIPTIONS: tuple[SensorValueEntityDescription, ...] = (
# Generic sensors # Generic sensors
SensorValueEntityDescription( SensorValueEntityDescription(
key="daysTotal", key="daysTotal",
name="Billing cycle length", translation_key="billing_cycle_length",
native_unit_of_measurement=UnitOfTime.DAYS, native_unit_of_measurement=UnitOfTime.DAYS,
icon="mdi:calendar-range", icon="mdi:calendar-range",
), ),
SensorValueEntityDescription( SensorValueEntityDescription(
key="daysRemaining", key="daysRemaining",
name="Billing cycle remaining", translation_key="billing_cycle_remaining",
native_unit_of_measurement=UnitOfTime.DAYS, native_unit_of_measurement=UnitOfTime.DAYS,
icon="mdi:calendar-clock", icon="mdi:calendar-clock",
), ),

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