mirror of
https://github.com/home-assistant/core.git
synced 2025-08-07 06:35:10 +02:00
2023.7.0 (#95908)
This commit is contained in:
@@ -27,6 +27,7 @@ base_platforms: &base_platforms
|
||||
- homeassistant/components/fan/**
|
||||
- homeassistant/components/geo_location/**
|
||||
- homeassistant/components/humidifier/**
|
||||
- homeassistant/components/image/**
|
||||
- homeassistant/components/image_processing/**
|
||||
- homeassistant/components/light/**
|
||||
- homeassistant/components/lock/**
|
||||
|
35
.coveragerc
35
.coveragerc
@@ -90,6 +90,8 @@ omit =
|
||||
homeassistant/components/atome/*
|
||||
homeassistant/components/aurora/__init__.py
|
||||
homeassistant/components/aurora/binary_sensor.py
|
||||
homeassistant/components/aurora/coordinator.py
|
||||
homeassistant/components/aurora/entity.py
|
||||
homeassistant/components/aurora/sensor.py
|
||||
homeassistant/components/avea/light.py
|
||||
homeassistant/components/avion/light.py
|
||||
@@ -122,7 +124,6 @@ omit =
|
||||
homeassistant/components/bluetooth_tracker/*
|
||||
homeassistant/components/bmw_connected_drive/__init__.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/lock.py
|
||||
homeassistant/components/bmw_connected_drive/notify.py
|
||||
@@ -202,6 +203,9 @@ omit =
|
||||
homeassistant/components/discogs/sensor.py
|
||||
homeassistant/components/discord/__init__.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_identify/image_processing.py
|
||||
homeassistant/components/dlink/data.py
|
||||
@@ -302,22 +306,10 @@ omit =
|
||||
homeassistant/components/escea/climate.py
|
||||
homeassistant/components/escea/discovery.py
|
||||
homeassistant/components/esphome/__init__.py
|
||||
homeassistant/components/esphome/binary_sensor.py
|
||||
homeassistant/components/esphome/bluetooth/*
|
||||
homeassistant/components/esphome/button.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/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/eufy/*
|
||||
homeassistant/components/eufylife_ble/__init__.py
|
||||
@@ -327,6 +319,7 @@ omit =
|
||||
homeassistant/components/ezviz/__init__.py
|
||||
homeassistant/components/ezviz/binary_sensor.py
|
||||
homeassistant/components/ezviz/camera.py
|
||||
homeassistant/components/ezviz/light.py
|
||||
homeassistant/components/ezviz/coordinator.py
|
||||
homeassistant/components/ezviz/number.py
|
||||
homeassistant/components/ezviz/entity.py
|
||||
@@ -362,10 +355,13 @@ omit =
|
||||
homeassistant/components/fitbit/*
|
||||
homeassistant/components/fivem/__init__.py
|
||||
homeassistant/components/fivem/binary_sensor.py
|
||||
homeassistant/components/fivem/coordinator.py
|
||||
homeassistant/components/fivem/entity.py
|
||||
homeassistant/components/fivem/sensor.py
|
||||
homeassistant/components/fixer/sensor.py
|
||||
homeassistant/components/fjaraskupan/__init__.py
|
||||
homeassistant/components/fjaraskupan/binary_sensor.py
|
||||
homeassistant/components/fjaraskupan/coordinator.py
|
||||
homeassistant/components/fjaraskupan/fan.py
|
||||
homeassistant/components/fjaraskupan/light.py
|
||||
homeassistant/components/fjaraskupan/number.py
|
||||
@@ -615,7 +611,6 @@ omit =
|
||||
homeassistant/components/kwb/sensor.py
|
||||
homeassistant/components/lacrosse/sensor.py
|
||||
homeassistant/components/lannouncer/notify.py
|
||||
homeassistant/components/lastfm/sensor.py
|
||||
homeassistant/components/launch_library/__init__.py
|
||||
homeassistant/components/launch_library/sensor.py
|
||||
homeassistant/components/lcn/climate.py
|
||||
@@ -946,6 +941,8 @@ omit =
|
||||
homeassistant/components/pyload/sensor.py
|
||||
homeassistant/components/qbittorrent/__init__.py
|
||||
homeassistant/components/qbittorrent/sensor.py
|
||||
homeassistant/components/qnap/__init__.py
|
||||
homeassistant/components/qnap/coordinator.py
|
||||
homeassistant/components/qnap/sensor.py
|
||||
homeassistant/components/qrcode/image_processing.py
|
||||
homeassistant/components/quantum_gateway/device_tracker.py
|
||||
@@ -973,6 +970,10 @@ omit =
|
||||
homeassistant/components/rainmachine/switch.py
|
||||
homeassistant/components/rainmachine/update.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/recollect_waste/sensor.py
|
||||
homeassistant/components/recorder/repack.py
|
||||
@@ -1046,12 +1047,6 @@ omit =
|
||||
homeassistant/components/sense/__init__.py
|
||||
homeassistant/components/sense/binary_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/api.py
|
||||
homeassistant/components/senz/climate.py
|
||||
|
171
.github/workflows/builder.yml
vendored
171
.github/workflows/builder.yml
vendored
@@ -10,7 +10,7 @@ on:
|
||||
|
||||
env:
|
||||
BUILD_TYPE: core
|
||||
DEFAULT_PYTHON: "3.10"
|
||||
DEFAULT_PYTHON: "3.11"
|
||||
|
||||
jobs:
|
||||
init:
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
publish: ${{ steps.version.outputs.publish }}
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v3.5.2
|
||||
uses: actions/checkout@v3.5.3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -48,18 +48,6 @@ jobs:
|
||||
with:
|
||||
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:
|
||||
name: Build PyPi package
|
||||
environment: ${{ needs.init.outputs.channel }}
|
||||
@@ -68,7 +56,7 @@ jobs:
|
||||
if: github.repository_owner == 'home-assistant' && needs.init.outputs.publish == 'true'
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v3.5.2
|
||||
uses: actions/checkout@v3.5.3
|
||||
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
uses: actions/setup-python@v4.6.1
|
||||
@@ -101,12 +89,16 @@ jobs:
|
||||
if: github.repository_owner == 'home-assistant'
|
||||
needs: init
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
id-token: write
|
||||
strategy:
|
||||
matrix:
|
||||
arch: ${{ fromJson(needs.init.outputs.architectures) }}
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v3.5.2
|
||||
uses: actions/checkout@v3.5.3
|
||||
|
||||
- name: Download nightly wheels of frontend
|
||||
if: needs.init.outputs.channel == 'dev'
|
||||
@@ -182,7 +174,7 @@ jobs:
|
||||
# will drop the platform in the near future (they consider it
|
||||
# "flimsy" on 386). The following packages depend on pandas,
|
||||
# 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|pyezviz|# pyezviz|g" requirements_all.txt
|
||||
sed -i "s|pykrakenapi|# pykrakenapi|g" requirements_all.txt
|
||||
@@ -197,25 +189,20 @@ jobs:
|
||||
run: |
|
||||
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
|
||||
uses: docker/login-action@v2.1.0
|
||||
uses: docker/login-action@v2.2.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build base image
|
||||
uses: home-assistant/builder@2023.03.0
|
||||
uses: home-assistant/builder@2023.06.1
|
||||
with:
|
||||
args: |
|
||||
$BUILD_ARGS \
|
||||
--${{ matrix.arch }} \
|
||||
--cosign \
|
||||
--target /data \
|
||||
--generic ${{ needs.init.outputs.version }}
|
||||
env:
|
||||
@@ -237,6 +224,10 @@ jobs:
|
||||
if: github.repository_owner == 'home-assistant'
|
||||
needs: ["init", "build_base"]
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
id-token: write
|
||||
strategy:
|
||||
matrix:
|
||||
machine:
|
||||
@@ -262,7 +253,7 @@ jobs:
|
||||
- yellow
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v3.5.2
|
||||
uses: actions/checkout@v3.5.3
|
||||
|
||||
- name: Set build additional args
|
||||
run: |
|
||||
@@ -275,25 +266,20 @@ jobs:
|
||||
echo "BUILD_ARGS=--additional-tag stable" >> $GITHUB_ENV
|
||||
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
|
||||
uses: docker/login-action@v2.1.0
|
||||
uses: docker/login-action@v2.2.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build base image
|
||||
uses: home-assistant/builder@2023.03.0
|
||||
uses: home-assistant/builder@2023.06.1
|
||||
with:
|
||||
args: |
|
||||
$BUILD_ARGS \
|
||||
--target /data/machine \
|
||||
--cosign \
|
||||
--machine "${{ needs.init.outputs.version }}=${{ matrix.machine }}"
|
||||
env:
|
||||
CAS_API_KEY: ${{ secrets.CAS_TOKEN }}
|
||||
@@ -306,7 +292,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v3.5.2
|
||||
uses: actions/checkout@v3.5.3
|
||||
|
||||
- name: Initialize git
|
||||
uses: home-assistant/actions/helpers/git-init@master
|
||||
@@ -338,34 +324,32 @@ jobs:
|
||||
if: github.repository_owner == 'home-assistant'
|
||||
needs: ["init", "build_base"]
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
registry:
|
||||
- "ghcr.io/home-assistant"
|
||||
- "homeassistant"
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
id-token: write
|
||||
steps:
|
||||
- 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
|
||||
if: matrix.registry == 'homeassistant'
|
||||
uses: docker/login-action@v2.1.0
|
||||
uses: docker/login-action@v2.2.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
if: matrix.registry == 'ghcr.io/home-assistant'
|
||||
uses: docker/login-action@v2.1.0
|
||||
uses: docker/login-action@v2.2.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install CAS tools
|
||||
uses: home-assistant/actions/helpers/cas@master
|
||||
|
||||
- name: Build Meta Image
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -375,55 +359,78 @@ jobs:
|
||||
local tag_l=${1}
|
||||
local tag_r=${2}
|
||||
|
||||
docker manifest create "${{ matrix.registry }}/home-assistant:${tag_l}" \
|
||||
"${{ matrix.registry }}/amd64-homeassistant:${tag_r}" \
|
||||
"${{ matrix.registry }}/i386-homeassistant:${tag_r}" \
|
||||
"${{ matrix.registry }}/armhf-homeassistant:${tag_r}" \
|
||||
"${{ matrix.registry }}/armv7-homeassistant:${tag_r}" \
|
||||
"${{ matrix.registry }}/aarch64-homeassistant:${tag_r}"
|
||||
for registry in "ghcr.io/home-assistant" "docker.io/homeassistant"
|
||||
do
|
||||
|
||||
docker manifest annotate "${{ matrix.registry }}/home-assistant:${tag_l}" \
|
||||
"${{ matrix.registry }}/amd64-homeassistant:${tag_r}" \
|
||||
--os linux --arch amd64
|
||||
docker manifest create "${registry}/home-assistant:${tag_l}" \
|
||||
"${registry}/amd64-homeassistant:${tag_r}" \
|
||||
"${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}" \
|
||||
"${{ matrix.registry }}/i386-homeassistant:${tag_r}" \
|
||||
--os linux --arch 386
|
||||
docker manifest annotate "${registry}/home-assistant:${tag_l}" \
|
||||
"${registry}/amd64-homeassistant:${tag_r}" \
|
||||
--os linux --arch amd64
|
||||
|
||||
docker manifest annotate "${{ matrix.registry }}/home-assistant:${tag_l}" \
|
||||
"${{ matrix.registry }}/armhf-homeassistant:${tag_r}" \
|
||||
--os linux --arch arm --variant=v6
|
||||
docker manifest annotate "${registry}/home-assistant:${tag_l}" \
|
||||
"${registry}/i386-homeassistant:${tag_r}" \
|
||||
--os linux --arch 386
|
||||
|
||||
docker manifest annotate "${{ matrix.registry }}/home-assistant:${tag_l}" \
|
||||
"${{ matrix.registry }}/armv7-homeassistant:${tag_r}" \
|
||||
--os linux --arch arm --variant=v7
|
||||
docker manifest annotate "${registry}/home-assistant:${tag_l}" \
|
||||
"${registry}/armhf-homeassistant:${tag_r}" \
|
||||
--os linux --arch arm --variant=v6
|
||||
|
||||
docker manifest annotate "${{ matrix.registry }}/home-assistant:${tag_l}" \
|
||||
"${{ matrix.registry }}/aarch64-homeassistant:${tag_r}" \
|
||||
--os linux --arch arm64 --variant=v8
|
||||
docker manifest annotate "${registry}/home-assistant:${tag_l}" \
|
||||
"${registry}/armv7-homeassistant:${tag_r}" \
|
||||
--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() {
|
||||
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!"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
docker pull "${{ matrix.registry }}/amd64-homeassistant:${{ needs.init.outputs.version }}"
|
||||
docker pull "${{ matrix.registry }}/i386-homeassistant:${{ needs.init.outputs.version }}"
|
||||
docker pull "${{ matrix.registry }}/armhf-homeassistant:${{ needs.init.outputs.version }}"
|
||||
docker pull "${{ matrix.registry }}/armv7-homeassistant:${{ needs.init.outputs.version }}"
|
||||
docker pull "${{ matrix.registry }}/aarch64-homeassistant:${{ needs.init.outputs.version }}"
|
||||
function push_dockerhub() {
|
||||
local image=${1}
|
||||
local tag=${2}
|
||||
|
||||
validate_image "${{ matrix.registry }}/amd64-homeassistant:${{ needs.init.outputs.version }}"
|
||||
validate_image "${{ matrix.registry }}/i386-homeassistant:${{ needs.init.outputs.version }}"
|
||||
validate_image "${{ matrix.registry }}/armhf-homeassistant:${{ needs.init.outputs.version }}"
|
||||
validate_image "${{ matrix.registry }}/armv7-homeassistant:${{ needs.init.outputs.version }}"
|
||||
validate_image "${{ matrix.registry }}/aarch64-homeassistant:${{ needs.init.outputs.version }}"
|
||||
docker tag "ghcr.io/home-assistant/${image}:${tag}" "docker.io/homeassistant/${image}:${tag}"
|
||||
docker push "docker.io/homeassistant/${image}:${tag}"
|
||||
cosign sign --yes "docker.io/homeassistant/${image}:${tag}"
|
||||
}
|
||||
|
||||
# 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_manifest "${{ needs.init.outputs.version }}" "${{ needs.init.outputs.version }}"
|
||||
|
92
.github/workflows/ci.yaml
vendored
92
.github/workflows/ci.yaml
vendored
@@ -32,7 +32,7 @@ env:
|
||||
CACHE_VERSION: 5
|
||||
PIP_CACHE_VERSION: 4
|
||||
MYPY_CACHE_VERSION: 4
|
||||
HA_SHORT_VERSION: 2023.6
|
||||
HA_SHORT_VERSION: 2023.7
|
||||
DEFAULT_PYTHON: "3.10"
|
||||
ALL_PYTHON_VERSIONS: "['3.10', '3.11']"
|
||||
# 10.3 is the oldest supported version
|
||||
@@ -82,7 +82,7 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- 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
|
||||
id: generate_python_cache_key
|
||||
run: >-
|
||||
@@ -206,7 +206,7 @@ jobs:
|
||||
- info
|
||||
steps:
|
||||
- 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 }}
|
||||
id: python
|
||||
uses: actions/setup-python@v4.6.1
|
||||
@@ -251,7 +251,7 @@ jobs:
|
||||
- pre-commit
|
||||
steps:
|
||||
- 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 }}
|
||||
uses: actions/setup-python@v4.6.1
|
||||
id: python
|
||||
@@ -297,7 +297,7 @@ jobs:
|
||||
- pre-commit
|
||||
steps:
|
||||
- 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 }}
|
||||
uses: actions/setup-python@v4.6.1
|
||||
id: python
|
||||
@@ -338,44 +338,6 @@ jobs:
|
||||
shopt -s globstar
|
||||
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:
|
||||
name: Check other linters
|
||||
runs-on: ubuntu-22.04
|
||||
@@ -384,7 +346,7 @@ jobs:
|
||||
- pre-commit
|
||||
steps:
|
||||
- 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 }}
|
||||
uses: actions/setup-python@v4.6.1
|
||||
id: python
|
||||
@@ -468,19 +430,6 @@ jobs:
|
||||
with:
|
||||
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:
|
||||
name: Prepare dependencies
|
||||
runs-on: ubuntu-22.04
|
||||
@@ -491,7 +440,7 @@ jobs:
|
||||
python-version: ${{ fromJSON(needs.info.outputs.python_versions) }}
|
||||
steps:
|
||||
- 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 }}
|
||||
id: python
|
||||
uses: actions/setup-python@v4.6.1
|
||||
@@ -543,10 +492,10 @@ jobs:
|
||||
python -m venv venv
|
||||
. venv/bin/activate
|
||||
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_test.txt
|
||||
pip install -e .
|
||||
pip install -e . --config-settings editable_mode=compat
|
||||
|
||||
hassfest:
|
||||
name: Check hassfest
|
||||
@@ -559,7 +508,7 @@ jobs:
|
||||
- base
|
||||
steps:
|
||||
- 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 }}
|
||||
id: python
|
||||
uses: actions/setup-python@v4.6.1
|
||||
@@ -591,7 +540,7 @@ jobs:
|
||||
- base
|
||||
steps:
|
||||
- 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 }}
|
||||
id: python
|
||||
uses: actions/setup-python@v4.6.1
|
||||
@@ -624,7 +573,7 @@ jobs:
|
||||
- base
|
||||
steps:
|
||||
- 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 }}
|
||||
id: python
|
||||
uses: actions/setup-python@v4.6.1
|
||||
@@ -668,7 +617,7 @@ jobs:
|
||||
- base
|
||||
steps:
|
||||
- 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 }}
|
||||
id: python
|
||||
uses: actions/setup-python@v4.6.1
|
||||
@@ -732,7 +681,6 @@ jobs:
|
||||
- base
|
||||
- gen-requirements-all
|
||||
- hassfest
|
||||
- lint-isort
|
||||
- lint-other
|
||||
- lint-ruff
|
||||
- mypy
|
||||
@@ -751,7 +699,7 @@ jobs:
|
||||
bluez \
|
||||
ffmpeg
|
||||
- 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 }}
|
||||
id: python
|
||||
uses: actions/setup-python@v4.6.1
|
||||
@@ -857,7 +805,6 @@ jobs:
|
||||
- base
|
||||
- gen-requirements-all
|
||||
- hassfest
|
||||
- lint-isort
|
||||
- lint-other
|
||||
- lint-ruff
|
||||
- mypy
|
||||
@@ -877,7 +824,7 @@ jobs:
|
||||
ffmpeg \
|
||||
libmariadb-dev-compat
|
||||
- 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 }}
|
||||
id: python
|
||||
uses: actions/setup-python@v4.6.1
|
||||
@@ -965,7 +912,6 @@ jobs:
|
||||
- base
|
||||
- gen-requirements-all
|
||||
- hassfest
|
||||
- lint-isort
|
||||
- lint-other
|
||||
- lint-ruff
|
||||
- mypy
|
||||
@@ -985,7 +931,7 @@ jobs:
|
||||
ffmpeg \
|
||||
postgresql-server-dev-14
|
||||
- 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 }}
|
||||
id: python
|
||||
uses: actions/setup-python@v4.6.1
|
||||
@@ -1062,12 +1008,12 @@ jobs:
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v3.5.2
|
||||
uses: actions/checkout@v3.5.3
|
||||
- name: Download all coverage artifacts
|
||||
uses: actions/download-artifact@v3
|
||||
- name: Upload coverage to Codecov (full coverage)
|
||||
if: needs.info.outputs.test_full_suite == 'true'
|
||||
uses: Wandalen/wretry.action@v1.0.36
|
||||
uses: Wandalen/wretry.action@v1.3.0
|
||||
with:
|
||||
action: codecov/codecov-action@v3.1.3
|
||||
with: |
|
||||
@@ -1077,7 +1023,7 @@ jobs:
|
||||
attempt_delay: 30000
|
||||
- name: Upload coverage to Codecov (partial coverage)
|
||||
if: needs.info.outputs.test_full_suite == 'false'
|
||||
uses: Wandalen/wretry.action@v1.0.36
|
||||
uses: Wandalen/wretry.action@v1.3.0
|
||||
with:
|
||||
action: codecov/codecov-action@v3.1.3
|
||||
with: |
|
||||
|
2
.github/workflows/lock.yml
vendored
2
.github/workflows/lock.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
if: github.repository_owner == 'home-assistant'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v4.0.0
|
||||
- uses: dessant/lock-threads@v4.0.1
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
issue-inactive-days: "30"
|
||||
|
4
.github/workflows/translations.yml
vendored
4
.github/workflows/translations.yml
vendored
@@ -10,7 +10,7 @@ on:
|
||||
- "**strings.json"
|
||||
|
||||
env:
|
||||
DEFAULT_PYTHON: "3.10"
|
||||
DEFAULT_PYTHON: "3.11"
|
||||
|
||||
jobs:
|
||||
upload:
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v3.5.2
|
||||
uses: actions/checkout@v3.5.3
|
||||
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
uses: actions/setup-python@v4.6.1
|
||||
|
176
.github/workflows/wheels.yml
vendored
176
.github/workflows/wheels.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
architectures: ${{ steps.info.outputs.architectures }}
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v3.5.2
|
||||
uses: actions/checkout@v3.5.3
|
||||
|
||||
- name: Get information
|
||||
id: info
|
||||
@@ -47,10 +47,7 @@ jobs:
|
||||
echo "GRPC_PYTHON_BUILD_SYSTEM_OPENSSL=true"
|
||||
echo "GRPC_PYTHON_BUILD_WITH_CYTHON=true"
|
||||
echo "GRPC_PYTHON_DISABLE_LIBC_COMPATIBILITY=true"
|
||||
# GRPC on armv7 needs -lexecinfo (issue #56669) since home assistant installs
|
||||
# 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"
|
||||
echo "GRPC_PYTHON_LDFLAGS=-lpthread -Wl,-wrap,memcpy -static-libgcc"
|
||||
|
||||
# Fix out of memory issues with rust
|
||||
echo "CARGO_NET_GIT_FETCH_WITH_CLI=true"
|
||||
@@ -83,11 +80,11 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
abi: ["cp310", "cp311"]
|
||||
abi: ["cp311"]
|
||||
arch: ${{ fromJson(needs.init.outputs.architectures) }}
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v3.5.2
|
||||
uses: actions/checkout@v3.5.3
|
||||
|
||||
- name: Download env_file
|
||||
uses: actions/download-artifact@v3
|
||||
@@ -113,100 +110,6 @@ jobs:
|
||||
requirements-diff: "requirements_diff.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:
|
||||
name: Build wheels ${{ matrix.abi }} for ${{ matrix.arch }}
|
||||
if: github.repository_owner == 'home-assistant'
|
||||
@@ -219,31 +122,12 @@ jobs:
|
||||
arch: ${{ fromJson(needs.init.outputs.architectures) }}
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v3.5.2
|
||||
uses: actions/checkout@v3.5.3
|
||||
|
||||
- name: Write alternative env-file for cp311
|
||||
run: |
|
||||
(
|
||||
echo "GRPC_BUILD_WITH_BORING_SSL_ASM=false"
|
||||
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 env_file
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: env_file
|
||||
|
||||
- name: Download requirements_diff
|
||||
uses: actions/download-artifact@v3
|
||||
@@ -254,27 +138,12 @@ jobs:
|
||||
run: |
|
||||
requirement_files="requirements_all.txt requirements_diff.txt"
|
||||
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|# 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|# decora-wifi|decora-wifi|g" ${requirement_file}
|
||||
sed -i "s|# python-gammu|python-gammu|g" ${requirement_file}
|
||||
|
||||
# Some packages are not buildable on armhf anymore
|
||||
@@ -284,7 +153,7 @@ jobs:
|
||||
# will drop the platform in the near future (they consider it
|
||||
# "flimsy" on 386). The following packages depend on pandas,
|
||||
# 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|pyezviz|# pyezviz|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
|
||||
# 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
|
||||
run: |
|
||||
@@ -305,13 +174,6 @@ jobs:
|
||||
echo "NPY_DISABLE_SVML=1" >> .env_file
|
||||
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
|
||||
sed -i "/numpy/d" homeassistant/package_constraints.txt
|
||||
|
||||
@@ -342,3 +204,17 @@ jobs:
|
||||
constraints: "homeassistant/package_constraints.txt"
|
||||
requirements-diff: "requirements_diff.txt"
|
||||
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"
|
||||
|
@@ -1,6 +1,6 @@
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.0.262
|
||||
rev: v0.0.272
|
||||
hooks:
|
||||
- id: ruff
|
||||
args:
|
||||
@@ -22,19 +22,6 @@ repos:
|
||||
- --quiet-level=2
|
||||
exclude_types: [csv, json]
|
||||
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
|
||||
rev: v4.4.0
|
||||
hooks:
|
||||
|
@@ -172,6 +172,7 @@ homeassistant.components.huawei_lte.*
|
||||
homeassistant.components.hydrawise.*
|
||||
homeassistant.components.hyperion.*
|
||||
homeassistant.components.ibeacon.*
|
||||
homeassistant.components.image.*
|
||||
homeassistant.components.image_processing.*
|
||||
homeassistant.components.image_upload.*
|
||||
homeassistant.components.imap.*
|
||||
@@ -243,6 +244,7 @@ homeassistant.components.overkiz.*
|
||||
homeassistant.components.peco.*
|
||||
homeassistant.components.persistent_notification.*
|
||||
homeassistant.components.pi_hole.*
|
||||
homeassistant.components.ping.*
|
||||
homeassistant.components.powerwall.*
|
||||
homeassistant.components.proximity.*
|
||||
homeassistant.components.prusalink.*
|
||||
@@ -275,7 +277,6 @@ homeassistant.components.scene.*
|
||||
homeassistant.components.schedule.*
|
||||
homeassistant.components.scrape.*
|
||||
homeassistant.components.select.*
|
||||
homeassistant.components.senseme.*
|
||||
homeassistant.components.sensibo.*
|
||||
homeassistant.components.sensirion_ble.*
|
||||
homeassistant.components.sensor.*
|
||||
@@ -308,6 +309,7 @@ homeassistant.components.tag.*
|
||||
homeassistant.components.tailscale.*
|
||||
homeassistant.components.tautulli.*
|
||||
homeassistant.components.tcp.*
|
||||
homeassistant.components.text.*
|
||||
homeassistant.components.threshold.*
|
||||
homeassistant.components.tibber.*
|
||||
homeassistant.components.tile.*
|
||||
|
34
CODEOWNERS
34
CODEOWNERS
@@ -275,6 +275,8 @@ build.json @home-assistant/supervisor
|
||||
/homeassistant/components/discogs/ @thibmaek
|
||||
/homeassistant/components/discord/ @tkdrob
|
||||
/tests/components/discord/ @tkdrob
|
||||
/homeassistant/components/discovergy/ @jpbede
|
||||
/tests/components/discovergy/ @jpbede
|
||||
/homeassistant/components/discovery/ @home-assistant/core
|
||||
/tests/components/discovery/ @home-assistant/core
|
||||
/homeassistant/components/dlink/ @tkdrob
|
||||
@@ -289,6 +291,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/doorbird/ @oblogic7 @bdraco @flacjacket
|
||||
/homeassistant/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
|
||||
/tests/components/dsmr/ @Robbie1221 @frenck
|
||||
/homeassistant/components/dsmr_reader/ @depl0y @glodenox
|
||||
@@ -352,8 +356,8 @@ build.json @home-assistant/supervisor
|
||||
/homeassistant/components/eq3btsmart/ @rytilahti
|
||||
/homeassistant/components/escea/ @lazdavila
|
||||
/tests/components/escea/ @lazdavila
|
||||
/homeassistant/components/esphome/ @OttoWinter @jesserockz
|
||||
/tests/components/esphome/ @OttoWinter @jesserockz
|
||||
/homeassistant/components/esphome/ @OttoWinter @jesserockz @bdraco
|
||||
/tests/components/esphome/ @OttoWinter @jesserockz @bdraco
|
||||
/homeassistant/components/eufylife_ble/ @bdr99
|
||||
/tests/components/eufylife_ble/ @bdr99
|
||||
/homeassistant/components/evil_genius_labs/ @balloob
|
||||
@@ -448,8 +452,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/glances/ @engrbm87
|
||||
/homeassistant/components/goalzero/ @tkdrob
|
||||
/tests/components/goalzero/ @tkdrob
|
||||
/homeassistant/components/gogogate2/ @vangorra @bdraco
|
||||
/tests/components/gogogate2/ @vangorra @bdraco
|
||||
/homeassistant/components/gogogate2/ @vangorra
|
||||
/tests/components/gogogate2/ @vangorra
|
||||
/homeassistant/components/goodwe/ @mletenay @starkillerOG
|
||||
/tests/components/goodwe/ @mletenay @starkillerOG
|
||||
/homeassistant/components/google/ @allenporter
|
||||
@@ -559,12 +563,14 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/icloud/ @Quentame @nzapponi
|
||||
/homeassistant/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
|
||||
/tests/components/image_processing/ @home-assistant/core
|
||||
/homeassistant/components/image_upload/ @home-assistant/core
|
||||
/tests/components/image_upload/ @home-assistant/core
|
||||
/homeassistant/components/imap/ @engrbm87 @jbouwh
|
||||
/tests/components/imap/ @engrbm87 @jbouwh
|
||||
/homeassistant/components/imap/ @jbouwh
|
||||
/tests/components/imap/ @jbouwh
|
||||
/homeassistant/components/incomfort/ @zxdavb
|
||||
/homeassistant/components/influxdb/ @mdegat01
|
||||
/tests/components/influxdb/ @mdegat01
|
||||
@@ -697,6 +703,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/logi_circle/ @evanjd
|
||||
/homeassistant/components/lookin/ @ANMalko @bdraco
|
||||
/tests/components/lookin/ @ANMalko @bdraco
|
||||
/homeassistant/components/loqed/ @mikewoudenberg
|
||||
/tests/components/loqed/ @mikewoudenberg
|
||||
/homeassistant/components/lovelace/ @home-assistant/frontend
|
||||
/tests/components/lovelace/ @home-assistant/frontend
|
||||
/homeassistant/components/luci/ @mzdrale
|
||||
@@ -779,11 +787,12 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/mutesync/ @currentoor
|
||||
/homeassistant/components/my/ @home-assistant/core
|
||||
/tests/components/my/ @home-assistant/core
|
||||
/homeassistant/components/myq/ @bdraco @ehendrix23
|
||||
/tests/components/myq/ @bdraco @ehendrix23
|
||||
/homeassistant/components/myq/ @ehendrix23
|
||||
/tests/components/myq/ @ehendrix23
|
||||
/homeassistant/components/mysensors/ @MartinHjelmare @functionpointer
|
||||
/tests/components/mysensors/ @MartinHjelmare @functionpointer
|
||||
/homeassistant/components/mystrom/ @fabaff
|
||||
/tests/components/mystrom/ @fabaff
|
||||
/homeassistant/components/nam/ @bieniu
|
||||
/tests/components/nam/ @bieniu
|
||||
/homeassistant/components/nanoleaf/ @milanmeu
|
||||
@@ -878,6 +887,7 @@ build.json @home-assistant/supervisor
|
||||
/homeassistant/components/opengarage/ @danielhiversen
|
||||
/tests/components/opengarage/ @danielhiversen
|
||||
/homeassistant/components/openhome/ @bazwilliams
|
||||
/tests/components/openhome/ @bazwilliams
|
||||
/homeassistant/components/opensky/ @joostlek
|
||||
/homeassistant/components/opentherm_gw/ @mvn23
|
||||
/tests/components/opentherm_gw/ @mvn23
|
||||
@@ -961,6 +971,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/qingping/ @bdraco @skgsergio
|
||||
/homeassistant/components/qld_bushfire/ @exxamalte
|
||||
/tests/components/qld_bushfire/ @exxamalte
|
||||
/homeassistant/components/qnap/ @disforw
|
||||
/tests/components/qnap/ @disforw
|
||||
/homeassistant/components/qnap_qsw/ @Noltari
|
||||
/tests/components/qnap_qsw/ @Noltari
|
||||
/homeassistant/components/quantum_gateway/ @cisasteelersfan
|
||||
@@ -999,6 +1011,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/remote/ @home-assistant/core
|
||||
/homeassistant/components/renault/ @epenet
|
||||
/tests/components/renault/ @epenet
|
||||
/homeassistant/components/renson/ @jimmyd-be
|
||||
/tests/components/renson/ @jimmyd-be
|
||||
/homeassistant/components/reolink/ @starkillerOG
|
||||
/tests/components/reolink/ @starkillerOG
|
||||
/homeassistant/components/repairs/ @home-assistant/core
|
||||
@@ -1068,8 +1082,6 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/select/ @home-assistant/core
|
||||
/homeassistant/components/sense/ @kbickar
|
||||
/tests/components/sense/ @kbickar
|
||||
/homeassistant/components/senseme/ @mikelawrence @bdraco
|
||||
/tests/components/senseme/ @mikelawrence @bdraco
|
||||
/homeassistant/components/sensibo/ @andrey-git @gjohansson-ST
|
||||
/tests/components/sensibo/ @andrey-git @gjohansson-ST
|
||||
/homeassistant/components/sensirion_ble/ @akx
|
||||
@@ -1292,6 +1304,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/twentemilieu/ @frenck
|
||||
/homeassistant/components/twinkly/ @dr1rrb @Robbie1221
|
||||
/tests/components/twinkly/ @dr1rrb @Robbie1221
|
||||
/homeassistant/components/twitch/ @joostlek
|
||||
/tests/components/twitch/ @joostlek
|
||||
/homeassistant/components/ukraine_alarm/ @PaulAnnekov
|
||||
/tests/components/ukraine_alarm/ @PaulAnnekov
|
||||
/homeassistant/components/unifi/ @Kane610
|
||||
|
@@ -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"]
|
||||
|
||||
|
@@ -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.
|
||||
|
||||
.. |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
|
||||
:target: https://demo.home-assistant.io
|
||||
.. |screenshot-integrations| image:: https://raw.githubusercontent.com/home-assistant/core/dev/docs/screenshot-integrations.png
|
||||
|
16
build.yaml
16
build.yaml
@@ -1,14 +1,16 @@
|
||||
image: homeassistant/{arch}-homeassistant
|
||||
shadow_repository: ghcr.io/home-assistant
|
||||
image: ghcr.io/home-assistant/{arch}-homeassistant
|
||||
build_from:
|
||||
aarch64: ghcr.io/home-assistant/aarch64-homeassistant-base:2023.06.0
|
||||
armhf: ghcr.io/home-assistant/armhf-homeassistant-base:2023.06.0
|
||||
armv7: ghcr.io/home-assistant/armv7-homeassistant-base:2023.06.0
|
||||
amd64: ghcr.io/home-assistant/amd64-homeassistant-base:2023.06.0
|
||||
i386: ghcr.io/home-assistant/i386-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.1
|
||||
armv7: ghcr.io/home-assistant/armv7-homeassistant-base:2023.06.1
|
||||
amd64: ghcr.io/home-assistant/amd64-homeassistant-base:2023.06.1
|
||||
i386: ghcr.io/home-assistant/i386-homeassistant-base:2023.06.1
|
||||
codenotary:
|
||||
signer: 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:
|
||||
io.hass.type: core
|
||||
org.opencontainers.image.title: Home Assistant
|
||||
|
@@ -13,8 +13,8 @@ from homeassistant.const import CONF_COMMAND
|
||||
from homeassistant.data_entry_flow import FlowResult
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
|
||||
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
|
||||
from ..models import Credentials, UserMeta
|
||||
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
|
||||
|
||||
CONF_ARGS = "args"
|
||||
CONF_META = "meta"
|
||||
|
@@ -16,8 +16,8 @@ from homeassistant.data_entry_flow import FlowResult
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.storage import Store
|
||||
|
||||
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
|
||||
from ..models import Credentials, UserMeta
|
||||
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
|
||||
|
||||
STORAGE_VERSION = 1
|
||||
STORAGE_KEY = "auth_provider.homeassistant"
|
||||
|
@@ -11,8 +11,8 @@ from homeassistant.core import callback
|
||||
from homeassistant.data_entry_flow import FlowResult
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
|
||||
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
|
||||
from ..models import Credentials, UserMeta
|
||||
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
|
||||
|
||||
USER_SCHEMA = vol.Schema(
|
||||
{
|
||||
|
@@ -15,8 +15,8 @@ from homeassistant.data_entry_flow import FlowResult
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
|
||||
from ..models import Credentials, UserMeta
|
||||
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
|
||||
|
||||
AUTH_PROVIDER_TYPE = "legacy_api_password"
|
||||
CONF_API_PASSWORD = "api_password"
|
||||
|
@@ -23,9 +23,9 @@ from homeassistant.data_entry_flow import FlowResult
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
|
||||
from .. import InvalidAuthError
|
||||
from ..models import Credentials, RefreshToken, UserMeta
|
||||
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
|
||||
|
||||
IPAddress = IPv4Address | IPv6Address
|
||||
IPNetwork = IPv4Network | IPv6Network
|
||||
|
72
homeassistant/backports/functools.py
Normal file
72
homeassistant/backports/functools.py
Normal 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)
|
@@ -34,6 +34,7 @@ class AbodeAlarm(AbodeDevice, alarm.AlarmControlPanelEntity):
|
||||
"""An alarm_control_panel implementation for Abode."""
|
||||
|
||||
_attr_icon = ICON
|
||||
_attr_name = None
|
||||
_attr_code_arm_required = False
|
||||
_attr_supported_features = (
|
||||
AlarmControlPanelEntityFeature.ARM_HOME
|
||||
|
@@ -42,6 +42,7 @@ async def async_setup_entry(
|
||||
class AbodeBinarySensor(AbodeDevice, BinarySensorEntity):
|
||||
"""A binary sensor implementation for Abode device."""
|
||||
|
||||
_attr_name = None
|
||||
_device: ABBinarySensor
|
||||
|
||||
@property
|
||||
|
@@ -39,6 +39,7 @@ class AbodeCamera(AbodeDevice, Camera):
|
||||
"""Representation of an Abode camera."""
|
||||
|
||||
_device: AbodeCam
|
||||
_attr_name = None
|
||||
|
||||
def __init__(self, data: AbodeSystem, device: AbodeDev, event: Event) -> None:
|
||||
"""Initialize the Abode device."""
|
||||
|
@@ -29,6 +29,7 @@ class AbodeCover(AbodeDevice, CoverEntity):
|
||||
"""Representation of an Abode cover."""
|
||||
|
||||
_device: AbodeCV
|
||||
_attr_name = None
|
||||
|
||||
@property
|
||||
def is_closed(self) -> bool:
|
||||
|
@@ -42,6 +42,7 @@ class AbodeLight(AbodeDevice, LightEntity):
|
||||
"""Representation of an Abode light."""
|
||||
|
||||
_device: AbodeLT
|
||||
_attr_name = None
|
||||
|
||||
def turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn on the light."""
|
||||
|
@@ -29,6 +29,7 @@ class AbodeLock(AbodeDevice, LockEntity):
|
||||
"""Representation of an Abode lock."""
|
||||
|
||||
_device: AbodeLK
|
||||
_attr_name = None
|
||||
|
||||
def lock(self, **kwargs: Any) -> None:
|
||||
"""Lock the device."""
|
||||
|
@@ -22,17 +22,14 @@ from .const import DOMAIN
|
||||
SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
|
||||
SensorEntityDescription(
|
||||
key=CONST.TEMP_STATUS_KEY,
|
||||
name="Temperature",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=CONST.HUMI_STATUS_KEY,
|
||||
name="Humidity",
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=CONST.LUX_STATUS_KEY,
|
||||
name="Lux",
|
||||
device_class=SensorDeviceClass.ILLUMINANCE,
|
||||
),
|
||||
)
|
||||
|
@@ -44,6 +44,7 @@ class AbodeSwitch(AbodeDevice, SwitchEntity):
|
||||
"""Representation of an Abode switch."""
|
||||
|
||||
_device: AbodeSW
|
||||
_attr_name = None
|
||||
|
||||
def turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn on the device."""
|
||||
|
@@ -4,10 +4,13 @@ from __future__ import annotations
|
||||
from typing import cast
|
||||
|
||||
from homeassistant.components.weather import (
|
||||
ATTR_FORECAST_CLOUD_COVERAGE,
|
||||
ATTR_FORECAST_CONDITION,
|
||||
ATTR_FORECAST_NATIVE_APPARENT_TEMP,
|
||||
ATTR_FORECAST_NATIVE_PRECIPITATION,
|
||||
ATTR_FORECAST_NATIVE_TEMP,
|
||||
ATTR_FORECAST_NATIVE_TEMP_LOW,
|
||||
ATTR_FORECAST_NATIVE_WIND_GUST_SPEED,
|
||||
ATTR_FORECAST_NATIVE_WIND_SPEED,
|
||||
ATTR_FORECAST_PRECIPITATION_PROBABILITY,
|
||||
ATTR_FORECAST_TIME,
|
||||
@@ -29,7 +32,16 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
from homeassistant.util.dt import utc_from_timestamp
|
||||
|
||||
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
|
||||
|
||||
@@ -50,6 +62,7 @@ class AccuWeatherEntity(
|
||||
"""Define an AccuWeather entity."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
_attr_name = None
|
||||
|
||||
def __init__(self, coordinator: AccuWeatherDataUpdateCoordinator) -> None:
|
||||
"""Initialize."""
|
||||
@@ -78,35 +91,61 @@ class AccuWeatherEntity(
|
||||
except IndexError:
|
||||
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
|
||||
def native_temperature(self) -> float:
|
||||
"""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
|
||||
def native_pressure(self) -> float:
|
||||
"""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
|
||||
def humidity(self) -> int:
|
||||
"""Return the humidity."""
|
||||
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
|
||||
def native_wind_speed(self) -> float:
|
||||
"""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
|
||||
def wind_bearing(self) -> int:
|
||||
"""Return the wind bearing."""
|
||||
return cast(int, self.coordinator.data["Wind"]["Direction"]["Degrees"])
|
||||
return cast(int, self.coordinator.data["Wind"][ATTR_DIRECTION]["Degrees"])
|
||||
|
||||
@property
|
||||
def native_visibility(self) -> float:
|
||||
"""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
|
||||
def forecast(self) -> list[Forecast] | None:
|
||||
@@ -117,14 +156,23 @@ class AccuWeatherEntity(
|
||||
return [
|
||||
{
|
||||
ATTR_FORECAST_TIME: utc_from_timestamp(item["EpochDate"]).isoformat(),
|
||||
ATTR_FORECAST_NATIVE_TEMP: item["TemperatureMax"]["Value"],
|
||||
ATTR_FORECAST_NATIVE_TEMP_LOW: item["TemperatureMin"]["Value"],
|
||||
ATTR_FORECAST_NATIVE_PRECIPITATION: item["TotalLiquidDay"]["Value"],
|
||||
ATTR_FORECAST_CLOUD_COVERAGE: item["CloudCoverDay"],
|
||||
ATTR_FORECAST_NATIVE_TEMP: item["TemperatureMax"][ATTR_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[
|
||||
"PrecipitationProbabilityDay"
|
||||
],
|
||||
ATTR_FORECAST_NATIVE_WIND_SPEED: item["WindDay"]["Speed"]["Value"],
|
||||
ATTR_FORECAST_WIND_BEARING: item["WindDay"]["Direction"]["Degrees"],
|
||||
ATTR_FORECAST_NATIVE_WIND_SPEED: item["WindDay"][ATTR_SPEED][
|
||||
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: [
|
||||
k for k, v in CONDITION_CLASSES.items() if item["IconDay"] in v
|
||||
][0],
|
||||
|
@@ -14,6 +14,7 @@ class AcmedaBase(entity.Entity):
|
||||
"""Base representation of an Acmeda roller."""
|
||||
|
||||
_attr_should_poll = False
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(self, roller: aiopulse.Roller) -> None:
|
||||
"""Initialize the roller."""
|
||||
@@ -72,11 +73,6 @@ class AcmedaBase(entity.Entity):
|
||||
"""Return the ID of this roller."""
|
||||
return self.roller.id
|
||||
|
||||
@property
|
||||
def name(self) -> str | None:
|
||||
"""Return the name of roller."""
|
||||
return self.roller.name
|
||||
|
||||
@property
|
||||
def device_info(self) -> entity.DeviceInfo:
|
||||
"""Return the device info."""
|
||||
|
@@ -45,7 +45,9 @@ async def async_setup_entry(
|
||||
|
||||
|
||||
class AcmedaCover(AcmedaBase, CoverEntity):
|
||||
"""Representation of a Acmeda cover device."""
|
||||
"""Representation of an Acmeda cover device."""
|
||||
|
||||
_attr_name = None
|
||||
|
||||
@property
|
||||
def current_cover_position(self) -> int | None:
|
||||
|
@@ -40,16 +40,11 @@ async def async_setup_entry(
|
||||
|
||||
|
||||
class AcmedaBattery(AcmedaBase, SensorEntity):
|
||||
"""Representation of a Acmeda cover device."""
|
||||
"""Representation of an Acmeda cover sensor."""
|
||||
|
||||
_attr_device_class = SensorDeviceClass.BATTERY
|
||||
_attr_native_unit_of_measurement = PERCENTAGE
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""Return the name of roller."""
|
||||
return f"{super().name} Battery"
|
||||
|
||||
@property
|
||||
def native_value(self) -> float | int | None:
|
||||
"""Return the state of the device."""
|
||||
|
@@ -1,7 +1,7 @@
|
||||
"""Support for Adax wifi-enabled home heaters."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
from typing import Any, cast
|
||||
|
||||
from adax import Adax
|
||||
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_device_info = DeviceInfo(
|
||||
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",
|
||||
)
|
||||
|
||||
|
@@ -39,56 +39,56 @@ class AdGuardHomeEntityDescription(
|
||||
SENSORS: tuple[AdGuardHomeEntityDescription, ...] = (
|
||||
AdGuardHomeEntityDescription(
|
||||
key="dns_queries",
|
||||
name="DNS queries",
|
||||
translation_key="dns_queries",
|
||||
icon="mdi:magnify",
|
||||
native_unit_of_measurement="queries",
|
||||
value_fn=lambda adguard: adguard.stats.dns_queries(),
|
||||
),
|
||||
AdGuardHomeEntityDescription(
|
||||
key="blocked_filtering",
|
||||
name="DNS queries blocked",
|
||||
translation_key="dns_queries_blocked",
|
||||
icon="mdi:magnify-close",
|
||||
native_unit_of_measurement="queries",
|
||||
value_fn=lambda adguard: adguard.stats.blocked_filtering(),
|
||||
),
|
||||
AdGuardHomeEntityDescription(
|
||||
key="blocked_percentage",
|
||||
name="DNS queries blocked ratio",
|
||||
translation_key="dns_queries_blocked_ratio",
|
||||
icon="mdi:magnify-close",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
value_fn=lambda adguard: adguard.stats.blocked_percentage(),
|
||||
),
|
||||
AdGuardHomeEntityDescription(
|
||||
key="blocked_parental",
|
||||
name="Parental control blocked",
|
||||
translation_key="parental_control_blocked",
|
||||
icon="mdi:human-male-girl",
|
||||
native_unit_of_measurement="requests",
|
||||
value_fn=lambda adguard: adguard.stats.replaced_parental(),
|
||||
),
|
||||
AdGuardHomeEntityDescription(
|
||||
key="blocked_safebrowsing",
|
||||
name="Safe browsing blocked",
|
||||
translation_key="safe_browsing_blocked",
|
||||
icon="mdi:shield-half-full",
|
||||
native_unit_of_measurement="requests",
|
||||
value_fn=lambda adguard: adguard.stats.replaced_safebrowsing(),
|
||||
),
|
||||
AdGuardHomeEntityDescription(
|
||||
key="enforced_safesearch",
|
||||
name="Safe searches enforced",
|
||||
translation_key="safe_searches_enforced",
|
||||
icon="mdi:shield-search",
|
||||
native_unit_of_measurement="requests",
|
||||
value_fn=lambda adguard: adguard.stats.replaced_safesearch(),
|
||||
),
|
||||
AdGuardHomeEntityDescription(
|
||||
key="average_speed",
|
||||
name="Average processing speed",
|
||||
translation_key="average_processing_speed",
|
||||
icon="mdi:speedometer",
|
||||
native_unit_of_measurement=UnitOfTime.MILLISECONDS,
|
||||
value_fn=lambda adguard: adguard.stats.avg_processing_time(),
|
||||
),
|
||||
AdGuardHomeEntityDescription(
|
||||
key="rules_count",
|
||||
name="Rules count",
|
||||
translation_key="rules_count",
|
||||
icon="mdi:counter",
|
||||
native_unit_of_measurement="rules",
|
||||
value_fn=lambda adguard: adguard.filtering.rules_count(allowlist=False),
|
||||
|
@@ -24,5 +24,53 @@
|
||||
"existing_instance_updated": "Updated existing configuration.",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -40,7 +40,7 @@ class AdGuardHomeSwitchEntityDescription(
|
||||
SWITCHES: tuple[AdGuardHomeSwitchEntityDescription, ...] = (
|
||||
AdGuardHomeSwitchEntityDescription(
|
||||
key="protection",
|
||||
name="Protection",
|
||||
translation_key="protection",
|
||||
icon="mdi:shield-check",
|
||||
is_on_fn=lambda adguard: adguard.protection_enabled,
|
||||
turn_on_fn=lambda adguard: adguard.enable_protection,
|
||||
@@ -48,7 +48,7 @@ SWITCHES: tuple[AdGuardHomeSwitchEntityDescription, ...] = (
|
||||
),
|
||||
AdGuardHomeSwitchEntityDescription(
|
||||
key="parental",
|
||||
name="Parental control",
|
||||
translation_key="parental",
|
||||
icon="mdi:shield-check",
|
||||
is_on_fn=lambda adguard: adguard.parental.enabled,
|
||||
turn_on_fn=lambda adguard: adguard.parental.enable,
|
||||
@@ -56,7 +56,7 @@ SWITCHES: tuple[AdGuardHomeSwitchEntityDescription, ...] = (
|
||||
),
|
||||
AdGuardHomeSwitchEntityDescription(
|
||||
key="safesearch",
|
||||
name="Safe search",
|
||||
translation_key="safe_search",
|
||||
icon="mdi:shield-check",
|
||||
is_on_fn=lambda adguard: adguard.safesearch.enabled,
|
||||
turn_on_fn=lambda adguard: adguard.safesearch.enable,
|
||||
@@ -64,7 +64,7 @@ SWITCHES: tuple[AdGuardHomeSwitchEntityDescription, ...] = (
|
||||
),
|
||||
AdGuardHomeSwitchEntityDescription(
|
||||
key="safebrowsing",
|
||||
name="Safe browsing",
|
||||
translation_key="safe_browsing",
|
||||
icon="mdi:shield-check",
|
||||
is_on_fn=lambda adguard: adguard.safebrowsing.enabled,
|
||||
turn_on_fn=lambda adguard: adguard.safebrowsing.enable,
|
||||
@@ -72,7 +72,7 @@ SWITCHES: tuple[AdGuardHomeSwitchEntityDescription, ...] = (
|
||||
),
|
||||
AdGuardHomeSwitchEntityDescription(
|
||||
key="filtering",
|
||||
name="Filtering",
|
||||
translation_key="filtering",
|
||||
icon="mdi:shield-check",
|
||||
is_on_fn=lambda adguard: adguard.filtering.enabled,
|
||||
turn_on_fn=lambda adguard: adguard.filtering.enable,
|
||||
@@ -80,7 +80,7 @@ SWITCHES: tuple[AdGuardHomeSwitchEntityDescription, ...] = (
|
||||
),
|
||||
AdGuardHomeSwitchEntityDescription(
|
||||
key="querylog",
|
||||
name="Query log",
|
||||
translation_key="query_log",
|
||||
icon="mdi:shield-check",
|
||||
is_on_fn=lambda adguard: adguard.querylog.enabled,
|
||||
turn_on_fn=lambda adguard: adguard.querylog.enable,
|
||||
|
@@ -10,6 +10,7 @@ import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, StateType
|
||||
|
||||
from .. import ads
|
||||
from . import (
|
||||
ADS_TYPEMAP,
|
||||
CONF_ADS_FACTOR,
|
||||
@@ -18,7 +19,6 @@ from . import (
|
||||
STATE_KEY_STATE,
|
||||
AdsEntity,
|
||||
)
|
||||
from .. import ads
|
||||
|
||||
DEFAULT_NAME = "ADS sensor"
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||
|
@@ -90,6 +90,7 @@ class AdvantageAirAC(AdvantageAirAcEntity, ClimateEntity):
|
||||
_attr_target_temperature_step = PRECISION_WHOLE
|
||||
_attr_max_temp = 32
|
||||
_attr_min_temp = 16
|
||||
_attr_name = None
|
||||
|
||||
_attr_hvac_modes = [
|
||||
HVACMode.OFF,
|
||||
|
@@ -9,17 +9,30 @@ from homeassistant.core import HomeAssistant
|
||||
|
||||
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(
|
||||
hass: HomeAssistant, config_entry: ConfigEntry
|
||||
) -> dict[str, Any]:
|
||||
"""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 {
|
||||
"aircons": data["aircons"],
|
||||
"aircons": data.get("aircons"),
|
||||
"myLights": data.get("myLights"),
|
||||
"myThings": data.get("myThings"),
|
||||
"system": async_redact_data(data["system"], TO_REDACT),
|
||||
}
|
||||
|
@@ -84,6 +84,8 @@ class AdvantageAirZoneEntity(AdvantageAirAcEntity):
|
||||
class AdvantageAirThingEntity(AdvantageAirEntity):
|
||||
"""Parent class for Advantage Air Things Entities."""
|
||||
|
||||
_attr_name = None
|
||||
|
||||
def __init__(self, instance: AdvantageAirData, thing: dict[str, Any]) -> None:
|
||||
"""Initialize common aspects of an Advantage Air Things entity."""
|
||||
super().__init__(instance)
|
||||
|
@@ -41,6 +41,7 @@ class AdvantageAirLight(AdvantageAirEntity, LightEntity):
|
||||
"""Representation of Advantage Air Light."""
|
||||
|
||||
_attr_supported_color_modes = {ColorMode.ONOFF}
|
||||
_attr_name = None
|
||||
|
||||
def __init__(self, instance: AdvantageAirData, light: dict[str, Any]) -> None:
|
||||
"""Initialize an Advantage Air Light."""
|
||||
|
@@ -7,5 +7,5 @@
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["advantage_air"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["advantage_air==0.4.4"]
|
||||
"requirements": ["advantage-air==0.4.4"]
|
||||
}
|
||||
|
@@ -80,7 +80,6 @@ SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = (
|
||||
AirlySensorEntityDescription(
|
||||
key=ATTR_API_PM1,
|
||||
device_class=SensorDeviceClass.PM1,
|
||||
translation_key="pm1",
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=0,
|
||||
@@ -88,7 +87,6 @@ SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = (
|
||||
AirlySensorEntityDescription(
|
||||
key=ATTR_API_PM25,
|
||||
device_class=SensorDeviceClass.PM25,
|
||||
translation_key="pm25",
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=0,
|
||||
@@ -100,7 +98,6 @@ SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = (
|
||||
AirlySensorEntityDescription(
|
||||
key=ATTR_API_PM10,
|
||||
device_class=SensorDeviceClass.PM10,
|
||||
translation_key="pm10",
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=0,
|
||||
@@ -112,7 +109,6 @@ SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = (
|
||||
AirlySensorEntityDescription(
|
||||
key=ATTR_API_HUMIDITY,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
translation_key="humidity",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=1,
|
||||
@@ -120,7 +116,6 @@ SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = (
|
||||
AirlySensorEntityDescription(
|
||||
key=ATTR_API_PRESSURE,
|
||||
device_class=SensorDeviceClass.PRESSURE,
|
||||
translation_key="pressure",
|
||||
native_unit_of_measurement=UnitOfPressure.HPA,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=0,
|
||||
@@ -128,7 +123,6 @@ SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = (
|
||||
AirlySensorEntityDescription(
|
||||
key=ATTR_API_TEMPERATURE,
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
translation_key="temperature",
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=1,
|
||||
@@ -147,7 +141,6 @@ SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = (
|
||||
AirlySensorEntityDescription(
|
||||
key=ATTR_API_NO2,
|
||||
device_class=SensorDeviceClass.NITROGEN_DIOXIDE,
|
||||
translation_key="no2",
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=0,
|
||||
@@ -159,7 +152,6 @@ SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = (
|
||||
AirlySensorEntityDescription(
|
||||
key=ATTR_API_SO2,
|
||||
device_class=SensorDeviceClass.SULPHUR_DIOXIDE,
|
||||
translation_key="so2",
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=0,
|
||||
@@ -171,7 +163,6 @@ SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = (
|
||||
AirlySensorEntityDescription(
|
||||
key=ATTR_API_O3,
|
||||
device_class=SensorDeviceClass.OZONE,
|
||||
translation_key="o3",
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=0,
|
||||
|
@@ -32,35 +32,8 @@
|
||||
"caqi": {
|
||||
"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": {
|
||||
"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%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -17,5 +17,3 @@ ATTR_API_STATION_LATITUDE = "Latitude"
|
||||
ATTR_API_STATION_LONGITUDE = "Longitude"
|
||||
DEFAULT_NAME = "AirNow"
|
||||
DOMAIN = "airnow"
|
||||
SENSOR_AQI_ATTR_DESCR = "description"
|
||||
SENSOR_AQI_ATTR_LEVEL = "level"
|
||||
|
@@ -1,7 +1,12 @@
|
||||
"""Support for the AirNow sensor service."""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
SensorEntity,
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
@@ -12,7 +17,10 @@ from homeassistant.const import (
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
)
|
||||
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.typing import StateType
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from . import AirNowDataUpdateCoordinator
|
||||
@@ -22,36 +30,60 @@ from .const import (
|
||||
ATTR_API_AQI_LEVEL,
|
||||
ATTR_API_O3,
|
||||
ATTR_API_PM25,
|
||||
DEFAULT_NAME,
|
||||
DOMAIN,
|
||||
SENSOR_AQI_ATTR_DESCR,
|
||||
SENSOR_AQI_ATTR_LEVEL,
|
||||
)
|
||||
|
||||
ATTRIBUTION = "Data provided by AirNow"
|
||||
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
|
||||
SensorEntityDescription(
|
||||
ATTR_DESCR = "description"
|
||||
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,
|
||||
icon="mdi:blur",
|
||||
name=ATTR_API_AQI,
|
||||
native_unit_of_measurement="aqi",
|
||||
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,
|
||||
icon="mdi:blur",
|
||||
name=ATTR_API_PM25,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
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,
|
||||
translation_key="o3",
|
||||
icon="mdi:blur",
|
||||
name=ATTR_API_O3,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
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."""
|
||||
|
||||
_attr_attribution = ATTRIBUTION
|
||||
_attr_has_entity_name = True
|
||||
|
||||
entity_description: AirNowEntityDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: AirNowDataUpdateCoordinator,
|
||||
description: SensorEntityDescription,
|
||||
description: AirNowEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize."""
|
||||
super().__init__(coordinator)
|
||||
self.entity_description = description
|
||||
self._state = None
|
||||
self._attrs: dict[str, str] = {}
|
||||
self._attr_name = f"AirNow {description.name}"
|
||||
self._attr_unique_id = (
|
||||
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
|
||||
def native_value(self):
|
||||
def native_value(self) -> StateType:
|
||||
"""Return the state."""
|
||||
self._state = self.coordinator.data.get(self.entity_description.key)
|
||||
|
||||
return self._state
|
||||
return self.entity_description.value_fn(self.coordinator.data)
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self):
|
||||
def extra_state_attributes(self) -> dict[str, str] | None:
|
||||
"""Return the state attributes."""
|
||||
if self.entity_description.key == ATTR_API_AQI:
|
||||
self._attrs[SENSOR_AQI_ATTR_DESCR] = self.coordinator.data[
|
||||
ATTR_API_AQI_DESCRIPTION
|
||||
]
|
||||
self._attrs[SENSOR_AQI_ATTR_LEVEL] = self.coordinator.data[
|
||||
ATTR_API_AQI_LEVEL
|
||||
]
|
||||
|
||||
return self._attrs
|
||||
if self.entity_description.extra_state_attributes_fn:
|
||||
return self.entity_description.extra_state_attributes_fn(
|
||||
self.coordinator.data
|
||||
)
|
||||
return None
|
||||
|
@@ -20,5 +20,12 @@
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"sensor": {
|
||||
"o3": {
|
||||
"name": "[%key:component::sensor::entity_component::ozone::name%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -53,63 +53,62 @@ class AirQEntityDescription(SensorEntityDescription, AirQEntityDescriptionMixin)
|
||||
SENSOR_TYPES: list[AirQEntityDescription] = [
|
||||
AirQEntityDescription(
|
||||
key="c2h4o",
|
||||
name="Acetaldehyde",
|
||||
translation_key="acetaldehyde",
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("c2h4o"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="nh3_MR100",
|
||||
name="Ammonia",
|
||||
translation_key="ammonia",
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("nh3_MR100"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="ash3",
|
||||
name="Arsine",
|
||||
translation_key="arsine",
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("ash3"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="br2",
|
||||
name="Bromine",
|
||||
translation_key="bromine",
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("br2"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="ch4s",
|
||||
name="CH4S",
|
||||
translation_key="methanethiol",
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("ch4s"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="cl2_M20",
|
||||
name="Chlorine",
|
||||
translation_key="chlorine",
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("cl2_M20"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="clo2",
|
||||
name="ClO2",
|
||||
translation_key="chlorine_dioxide",
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("clo2"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="co",
|
||||
name="CO",
|
||||
translation_key="carbon_monoxide",
|
||||
native_unit_of_measurement=CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("co"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="co2",
|
||||
name="CO2",
|
||||
device_class=SensorDeviceClass.CO2,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
@@ -117,14 +116,14 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="cs2",
|
||||
name="CS2",
|
||||
translation_key="carbon_disulfide",
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("cs2"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="dewpt",
|
||||
name="Dew point",
|
||||
translation_key="dew_point",
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("dewpt"),
|
||||
@@ -132,63 +131,63 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="ethanol",
|
||||
name="Ethanol",
|
||||
translation_key="ethanol",
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("ethanol"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="c2h4",
|
||||
name="Ethylene",
|
||||
translation_key="ethylene",
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("c2h4"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="ch2o_M10",
|
||||
name="Formaldehyde",
|
||||
translation_key="formaldehyde",
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("ch2o_M10"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="f2",
|
||||
name="Fluorine",
|
||||
translation_key="fluorine",
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("f2"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="h2s",
|
||||
name="H2S",
|
||||
translation_key="hydrogen_sulfide",
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("h2s"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="hcl",
|
||||
name="HCl",
|
||||
translation_key="hydrochloric_acid",
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("hcl"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="hcn",
|
||||
name="HCN",
|
||||
translation_key="hydrogen_cyanide",
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("hcn"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="hf",
|
||||
name="HF",
|
||||
translation_key="hydrogen_fluoride",
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("hf"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="health",
|
||||
name="Health Index",
|
||||
translation_key="health_index",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
icon="mdi:heart-pulse",
|
||||
@@ -196,7 +195,6 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="humidity",
|
||||
name="Humidity",
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
@@ -204,7 +202,7 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="humidity_abs",
|
||||
name="Absolute humidity",
|
||||
translation_key="absolute_humidity",
|
||||
native_unit_of_measurement=CONCENTRATION_GRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("humidity_abs"),
|
||||
@@ -212,28 +210,27 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="h2_M1000",
|
||||
name="Hydrogen",
|
||||
translation_key="hydrogen",
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("h2_M1000"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="h2o2",
|
||||
name="Hydrogen peroxide",
|
||||
translation_key="hydrogen_peroxide",
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("h2o2"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="ch4_MIPEX",
|
||||
name="Methane",
|
||||
translation_key="methane",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("ch4_MIPEX"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="n2o",
|
||||
name="N2O",
|
||||
device_class=SensorDeviceClass.NITROUS_OXIDE,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
@@ -241,7 +238,6 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="no_M250",
|
||||
name="NO",
|
||||
device_class=SensorDeviceClass.NITROGEN_MONOXIDE,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
@@ -249,7 +245,6 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="no2",
|
||||
name="NO2",
|
||||
device_class=SensorDeviceClass.NITROGEN_DIOXIDE,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
@@ -257,14 +252,14 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="acid_M100",
|
||||
name="Organic acid",
|
||||
translation_key="organic_acid",
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("acid_M100"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="oxygen",
|
||||
name="Oxygen",
|
||||
translation_key="oxygen",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("oxygen"),
|
||||
@@ -272,7 +267,6 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="o3",
|
||||
name="Ozone",
|
||||
device_class=SensorDeviceClass.OZONE,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
@@ -280,7 +274,7 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="performance",
|
||||
name="Performance Index",
|
||||
translation_key="performance_index",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
icon="mdi:head-check",
|
||||
@@ -288,14 +282,13 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="ph3",
|
||||
name="PH3",
|
||||
translation_key="hydrogen_phosphide",
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("ph3"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="pm1",
|
||||
name="PM1",
|
||||
device_class=SensorDeviceClass.PM1,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
@@ -304,7 +297,6 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="pm2_5",
|
||||
name="PM2.5",
|
||||
device_class=SensorDeviceClass.PM25,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
@@ -313,7 +305,6 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="pm10",
|
||||
name="PM10",
|
||||
device_class=SensorDeviceClass.PM10,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
@@ -322,7 +313,6 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="pressure",
|
||||
name="Pressure",
|
||||
device_class=SensorDeviceClass.PRESSURE,
|
||||
native_unit_of_measurement=UnitOfPressure.HPA,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
@@ -330,7 +320,7 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="pressure_rel",
|
||||
name="Relative pressure",
|
||||
translation_key="relative_pressure",
|
||||
native_unit_of_measurement=UnitOfPressure.HPA,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("pressure_rel"),
|
||||
@@ -338,28 +328,27 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="c3h8_MIPEX",
|
||||
name="Propane",
|
||||
translation_key="propane",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("c3h8_MIPEX"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="refigerant",
|
||||
name="Refrigerant",
|
||||
translation_key="refigerant",
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("refigerant"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="sih4",
|
||||
name="SiH4",
|
||||
translation_key="silicon_hydride",
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("sih4"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="so2",
|
||||
name="SO2",
|
||||
device_class=SensorDeviceClass.SULPHUR_DIOXIDE,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
@@ -367,7 +356,7 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="sound",
|
||||
name="Noise",
|
||||
translation_key="noise",
|
||||
native_unit_of_measurement=UnitOfSoundPressure.WEIGHTED_DECIBEL_A,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("sound"),
|
||||
@@ -375,7 +364,7 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="sound_max",
|
||||
name="Noise (Maximum)",
|
||||
translation_key="maximum_noise",
|
||||
native_unit_of_measurement=UnitOfSoundPressure.WEIGHTED_DECIBEL_A,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("sound_max"),
|
||||
@@ -383,7 +372,7 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="radon",
|
||||
name="Radon",
|
||||
translation_key="radon",
|
||||
native_unit_of_measurement=ACTIVITY_BECQUEREL_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("radon"),
|
||||
@@ -391,7 +380,6 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="temperature",
|
||||
name="Temperature",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
@@ -399,21 +387,22 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="tvoc",
|
||||
name="VOC",
|
||||
device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS_PARTS,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("tvoc"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
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,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("tvoc_ionsc"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="virus",
|
||||
name="Virus Index",
|
||||
translation_key="virus_index",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
icon="mdi:virus-off",
|
||||
|
@@ -18,5 +18,117 @@
|
||||
"abort": {
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/airthings",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["airthings"],
|
||||
"requirements": ["airthings_cloud==0.1.0"]
|
||||
"requirements": ["airthings-cloud==0.1.0"]
|
||||
}
|
||||
|
@@ -35,62 +35,56 @@ SENSORS: dict[str, SensorEntityDescription] = {
|
||||
"radonShortTermAvg": SensorEntityDescription(
|
||||
key="radonShortTermAvg",
|
||||
native_unit_of_measurement="Bq/m³",
|
||||
name="Radon",
|
||||
translation_key="radon",
|
||||
),
|
||||
"temp": SensorEntityDescription(
|
||||
key="temp",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
name="Temperature",
|
||||
),
|
||||
"humidity": SensorEntityDescription(
|
||||
key="humidity",
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
name="Humidity",
|
||||
),
|
||||
"pressure": SensorEntityDescription(
|
||||
key="pressure",
|
||||
device_class=SensorDeviceClass.PRESSURE,
|
||||
native_unit_of_measurement=UnitOfPressure.MBAR,
|
||||
name="Pressure",
|
||||
),
|
||||
"battery": SensorEntityDescription(
|
||||
key="battery",
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
name="Battery",
|
||||
),
|
||||
"co2": SensorEntityDescription(
|
||||
key="co2",
|
||||
device_class=SensorDeviceClass.CO2,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
name="CO2",
|
||||
),
|
||||
"voc": SensorEntityDescription(
|
||||
key="voc",
|
||||
device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS_PARTS,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
|
||||
name="VOC",
|
||||
),
|
||||
"light": SensorEntityDescription(
|
||||
key="light",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
name="Light",
|
||||
translation_key="light",
|
||||
),
|
||||
"virusRisk": SensorEntityDescription(
|
||||
key="virusRisk",
|
||||
name="Virus Risk",
|
||||
translation_key="virus_risk",
|
||||
),
|
||||
"mold": SensorEntityDescription(
|
||||
key="mold",
|
||||
name="Mold",
|
||||
translation_key="mold",
|
||||
),
|
||||
"rssi": SensorEntityDescription(
|
||||
key="rssi",
|
||||
native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS,
|
||||
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
|
||||
name="RSSI",
|
||||
entity_registry_enabled_default=False,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
@@ -98,13 +92,11 @@ SENSORS: dict[str, SensorEntityDescription] = {
|
||||
key="pm1",
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
device_class=SensorDeviceClass.PM1,
|
||||
name="PM1",
|
||||
),
|
||||
"pm25": SensorEntityDescription(
|
||||
key="pm25",
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
device_class=SensorDeviceClass.PM25,
|
||||
name="PM25",
|
||||
),
|
||||
}
|
||||
|
||||
@@ -134,6 +126,7 @@ class AirthingsHeaterEnergySensor(CoordinatorEntity, SensorEntity):
|
||||
"""Representation of a Airthings Sensor device."""
|
||||
|
||||
_attr_state_class = SensorStateClass.MEASUREMENT
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -146,7 +139,6 @@ class AirthingsHeaterEnergySensor(CoordinatorEntity, SensorEntity):
|
||||
|
||||
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._id = airthings_device.device_id
|
||||
self._attr_device_info = DeviceInfo(
|
||||
|
@@ -17,5 +17,21 @@
|
||||
"abort": {
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -39,26 +39,26 @@ _LOGGER = logging.getLogger(__name__)
|
||||
SENSORS_MAPPING_TEMPLATE: dict[str, SensorEntityDescription] = {
|
||||
"radon_1day_avg": SensorEntityDescription(
|
||||
key="radon_1day_avg",
|
||||
translation_key="radon_1day_avg",
|
||||
native_unit_of_measurement=VOLUME_BECQUEREL,
|
||||
name="Radon 1-day average",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
icon="mdi:radioactive",
|
||||
),
|
||||
"radon_longterm_avg": SensorEntityDescription(
|
||||
key="radon_longterm_avg",
|
||||
translation_key="radon_longterm_avg",
|
||||
native_unit_of_measurement=VOLUME_BECQUEREL,
|
||||
name="Radon longterm average",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
icon="mdi:radioactive",
|
||||
),
|
||||
"radon_1day_level": SensorEntityDescription(
|
||||
key="radon_1day_level",
|
||||
name="Radon 1-day level",
|
||||
translation_key="radon_1day_level",
|
||||
icon="mdi:radioactive",
|
||||
),
|
||||
"radon_longterm_level": SensorEntityDescription(
|
||||
key="radon_longterm_level",
|
||||
name="Radon longterm level",
|
||||
translation_key="radon_longterm_level",
|
||||
icon="mdi:radioactive",
|
||||
),
|
||||
"temperature": SensorEntityDescription(
|
||||
@@ -66,21 +66,18 @@ SENSORS_MAPPING_TEMPLATE: dict[str, SensorEntityDescription] = {
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
name="Temperature",
|
||||
),
|
||||
"humidity": SensorEntityDescription(
|
||||
key="humidity",
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
name="Humidity",
|
||||
),
|
||||
"pressure": SensorEntityDescription(
|
||||
key="pressure",
|
||||
device_class=SensorDeviceClass.PRESSURE,
|
||||
native_unit_of_measurement=UnitOfPressure.MBAR,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
name="Pressure",
|
||||
),
|
||||
"battery": SensorEntityDescription(
|
||||
key="battery",
|
||||
@@ -88,20 +85,18 @@ SENSORS_MAPPING_TEMPLATE: dict[str, SensorEntityDescription] = {
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
name="Battery",
|
||||
),
|
||||
"co2": SensorEntityDescription(
|
||||
key="co2",
|
||||
device_class=SensorDeviceClass.CO2,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
name="co2",
|
||||
),
|
||||
"voc": SensorEntityDescription(
|
||||
key="voc",
|
||||
device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS_PARTS,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
name="VOC",
|
||||
icon="mdi:cloud",
|
||||
),
|
||||
"illuminance": SensorEntityDescription(
|
||||
@@ -109,7 +104,6 @@ SENSORS_MAPPING_TEMPLATE: dict[str, SensorEntityDescription] = {
|
||||
device_class=SensorDeviceClass.ILLUMINANCE,
|
||||
native_unit_of_measurement=LIGHT_LUX,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
name="Illuminance",
|
||||
),
|
||||
}
|
||||
|
||||
|
@@ -19,5 +19,21 @@
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -17,7 +17,7 @@
|
||||
"api_key": "[%key:common::config_flow::data::api_key%]",
|
||||
"city": "City",
|
||||
"country": "Country",
|
||||
"state": "state"
|
||||
"state": "State"
|
||||
}
|
||||
},
|
||||
"reauth_confirm": {
|
||||
|
@@ -193,6 +193,8 @@ class AirzoneClimate(AirzoneZoneEntity, ClimateEntity):
|
||||
|
||||
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
|
||||
"""Set hvac mode."""
|
||||
slave_raise = False
|
||||
|
||||
params = {}
|
||||
if hvac_mode == HVACMode.OFF:
|
||||
params[API_ON] = 0
|
||||
@@ -202,12 +204,13 @@ class AirzoneClimate(AirzoneZoneEntity, ClimateEntity):
|
||||
if self.get_airzone_value(AZD_MASTER):
|
||||
params[API_MODE] = mode
|
||||
else:
|
||||
raise HomeAssistantError(
|
||||
f"Mode can't be changed on slave zone {self.name}"
|
||||
)
|
||||
slave_raise = True
|
||||
params[API_ON] = 1
|
||||
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:
|
||||
"""Set new target temperature."""
|
||||
params = {}
|
||||
|
@@ -12,7 +12,10 @@ from homeassistant.helpers import aiohttp_client
|
||||
from .const import DOMAIN
|
||||
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:
|
||||
|
106
homeassistant/components/airzone_cloud/binary_sensor.py
Normal file
106
homeassistant/components/airzone_cloud/binary_sensor.py
Normal 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()
|
@@ -15,7 +15,6 @@ from aioairzone_cloud.const import (
|
||||
AZD_ZONES,
|
||||
)
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
@@ -43,7 +42,6 @@ class AirzoneAidooEntity(AirzoneEntity):
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: AirzoneUpdateCoordinator,
|
||||
entry: ConfigEntry,
|
||||
aidoo_id: str,
|
||||
aidoo_data: dict[str, Any],
|
||||
) -> None:
|
||||
@@ -73,7 +71,6 @@ class AirzoneWebServerEntity(AirzoneEntity):
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: AirzoneUpdateCoordinator,
|
||||
entry: ConfigEntry,
|
||||
ws_id: str,
|
||||
ws_data: dict[str, Any],
|
||||
) -> None:
|
||||
@@ -86,7 +83,7 @@ class AirzoneWebServerEntity(AirzoneEntity):
|
||||
connections={(dr.CONNECTION_NETWORK_MAC, ws_id)},
|
||||
identifiers={(DOMAIN, ws_id)},
|
||||
manufacturer=MANUFACTURER,
|
||||
name=f"WebServer {ws_id}",
|
||||
name=ws_data[AZD_NAME],
|
||||
sw_version=ws_data[AZD_FIRMWARE],
|
||||
)
|
||||
|
||||
@@ -104,7 +101,6 @@ class AirzoneZoneEntity(AirzoneEntity):
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: AirzoneUpdateCoordinator,
|
||||
entry: ConfigEntry,
|
||||
zone_id: str,
|
||||
zone_data: dict[str, Any],
|
||||
) -> None:
|
||||
|
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/airzone_cloud",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["aioairzone_cloud"],
|
||||
"requirements": ["aioairzone-cloud==0.1.8"]
|
||||
"requirements": ["aioairzone-cloud==0.2.0"]
|
||||
}
|
||||
|
@@ -6,7 +6,6 @@ from typing import Any, Final
|
||||
from aioairzone_cloud.const import (
|
||||
AZD_AIDOOS,
|
||||
AZD_HUMIDITY,
|
||||
AZD_NAME,
|
||||
AZD_TEMP,
|
||||
AZD_WEBSERVERS,
|
||||
AZD_WIFI_RSSI,
|
||||
@@ -42,7 +41,6 @@ AIDOO_SENSOR_TYPES: Final[tuple[SensorEntityDescription, ...]] = (
|
||||
SensorEntityDescription(
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
key=AZD_TEMP,
|
||||
name="Temperature",
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
@@ -53,9 +51,7 @@ WEBSERVER_SENSOR_TYPES: Final[tuple[SensorEntityDescription, ...]] = (
|
||||
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
has_entity_name=True,
|
||||
key=AZD_WIFI_RSSI,
|
||||
name="RSSI",
|
||||
native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
@@ -65,14 +61,12 @@ ZONE_SENSOR_TYPES: Final[tuple[SensorEntityDescription, ...]] = (
|
||||
SensorEntityDescription(
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
key=AZD_TEMP,
|
||||
name="Temperature",
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
key=AZD_HUMIDITY,
|
||||
name="Humidity",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
@@ -95,7 +89,6 @@ async def async_setup_entry(
|
||||
AirzoneAidooSensor(
|
||||
coordinator,
|
||||
description,
|
||||
entry,
|
||||
aidoo_id,
|
||||
aidoo_data,
|
||||
)
|
||||
@@ -109,7 +102,6 @@ async def async_setup_entry(
|
||||
AirzoneWebServerSensor(
|
||||
coordinator,
|
||||
description,
|
||||
entry,
|
||||
ws_id,
|
||||
ws_data,
|
||||
)
|
||||
@@ -123,7 +115,6 @@ async def async_setup_entry(
|
||||
AirzoneZoneSensor(
|
||||
coordinator,
|
||||
description,
|
||||
entry,
|
||||
zone_id,
|
||||
zone_data,
|
||||
)
|
||||
@@ -154,14 +145,13 @@ class AirzoneAidooSensor(AirzoneAidooEntity, AirzoneSensor):
|
||||
self,
|
||||
coordinator: AirzoneUpdateCoordinator,
|
||||
description: SensorEntityDescription,
|
||||
entry: ConfigEntry,
|
||||
aidoo_id: str,
|
||||
aidoo_data: dict[str, Any],
|
||||
) -> None:
|
||||
"""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.entity_description = description
|
||||
|
||||
@@ -175,13 +165,13 @@ class AirzoneWebServerSensor(AirzoneWebServerEntity, AirzoneSensor):
|
||||
self,
|
||||
coordinator: AirzoneUpdateCoordinator,
|
||||
description: SensorEntityDescription,
|
||||
entry: ConfigEntry,
|
||||
ws_id: str,
|
||||
ws_data: dict[str, Any],
|
||||
) -> None:
|
||||
"""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.entity_description = description
|
||||
|
||||
@@ -195,14 +185,13 @@ class AirzoneZoneSensor(AirzoneZoneEntity, AirzoneSensor):
|
||||
self,
|
||||
coordinator: AirzoneUpdateCoordinator,
|
||||
description: SensorEntityDescription,
|
||||
entry: ConfigEntry,
|
||||
zone_id: str,
|
||||
zone_data: dict[str, Any],
|
||||
) -> None:
|
||||
"""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.entity_description = description
|
||||
|
||||
|
@@ -40,26 +40,24 @@ class AladdinDevice(CoverEntity):
|
||||
|
||||
_attr_device_class = CoverDeviceClass.GARAGE
|
||||
_attr_supported_features = SUPPORTED_FEATURES
|
||||
_attr_has_entity_name = True
|
||||
_attr_name = None
|
||||
|
||||
def __init__(
|
||||
self, acc: AladdinConnectClient, device: DoorDevice, entry: ConfigEntry
|
||||
) -> None:
|
||||
"""Initialize the Aladdin Connect cover."""
|
||||
self._acc = acc
|
||||
self._entry_id = entry.entry_id
|
||||
self._device_id = device["device_id"]
|
||||
self._number = device["door_number"]
|
||||
self._name = device["name"]
|
||||
self._serial = device["serial"]
|
||||
self._model = device["model"]
|
||||
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, f"{self._device_id}-{self._number}")},
|
||||
name=self._name,
|
||||
name=device["name"],
|
||||
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}"
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
|
@@ -40,7 +40,6 @@ class AccSensorEntityDescription(
|
||||
SENSORS: tuple[AccSensorEntityDescription, ...] = (
|
||||
AccSensorEntityDescription(
|
||||
key="battery_level",
|
||||
name="Battery level",
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
entity_registry_enabled_default=False,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
@@ -49,7 +48,7 @@ SENSORS: tuple[AccSensorEntityDescription, ...] = (
|
||||
),
|
||||
AccSensorEntityDescription(
|
||||
key="rssi",
|
||||
name="Wi-Fi RSSI",
|
||||
translation_key="wifi_strength",
|
||||
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
|
||||
entity_registry_enabled_default=False,
|
||||
native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS,
|
||||
@@ -58,7 +57,7 @@ SENSORS: tuple[AccSensorEntityDescription, ...] = (
|
||||
),
|
||||
AccSensorEntityDescription(
|
||||
key="ble_strength",
|
||||
name="BLE Strength",
|
||||
translation_key="ble_strength",
|
||||
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
|
||||
entity_registry_enabled_default=False,
|
||||
native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS,
|
||||
@@ -89,8 +88,8 @@ async def async_setup_entry(
|
||||
class AladdinConnectSensor(SensorEntity):
|
||||
"""A sensor implementation for Aladdin Connect devices."""
|
||||
|
||||
_device: AladdinConnectSensor
|
||||
entity_description: AccSensorEntityDescription
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -101,24 +100,20 @@ class AladdinConnectSensor(SensorEntity):
|
||||
"""Initialize a sensor for an Aladdin Connect device."""
|
||||
self._device_id = device["device_id"]
|
||||
self._number = device["door_number"]
|
||||
self._name = device["name"]
|
||||
self._model = device["model"]
|
||||
self._acc = acc
|
||||
self.entity_description = description
|
||||
self._attr_unique_id = f"{self._device_id}-{self._number}-{description.key}"
|
||||
self._attr_has_entity_name = True
|
||||
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(
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, f"{self._device_id}-{self._number}")},
|
||||
name=self._name,
|
||||
name=device["name"],
|
||||
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
|
||||
def native_value(self) -> float | None:
|
||||
|
@@ -25,5 +25,15 @@
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"sensor": {
|
||||
"wifi_strength": {
|
||||
"name": "Wi-Fi RSSI"
|
||||
},
|
||||
"ble_strength": {
|
||||
"name": "BLE Strength"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -5,6 +5,7 @@ from typing import Final
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.device_automation import async_validate_entity_schema
|
||||
from homeassistant.const import (
|
||||
ATTR_CODE,
|
||||
ATTR_ENTITY_ID,
|
||||
@@ -44,15 +45,22 @@ ACTION_TYPES: Final[set[str]] = {
|
||||
"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_ENTITY_ID): cv.entity_domain(DOMAIN),
|
||||
vol.Required(CONF_ENTITY_ID): cv.entity_id_or_uuid,
|
||||
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(
|
||||
hass: HomeAssistant, device_id: str
|
||||
) -> list[dict[str, str]]:
|
||||
@@ -70,7 +78,7 @@ async def async_get_actions(
|
||||
base_action: dict = {
|
||||
CONF_DEVICE_ID: device_id,
|
||||
CONF_DOMAIN: DOMAIN,
|
||||
CONF_ENTITY_ID: entry.entity_id,
|
||||
CONF_ENTITY_ID: entry.id,
|
||||
}
|
||||
|
||||
# Add actions for each entity that belongs to this integration
|
||||
@@ -124,7 +132,9 @@ async def async_get_action_capabilities(
|
||||
"""List action capabilities."""
|
||||
# We need to refer to the state directly because ATTR_CODE_ARM_REQUIRED is not a
|
||||
# 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
|
||||
|
||||
if config[CONF_TYPE] == "trigger" or (
|
||||
|
@@ -58,7 +58,7 @@ CONDITION_TYPES: Final[set[str]] = {
|
||||
|
||||
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),
|
||||
}
|
||||
)
|
||||
@@ -83,7 +83,7 @@ async def async_get_conditions(
|
||||
CONF_CONDITION: "device",
|
||||
CONF_DEVICE_ID: device_id,
|
||||
CONF_DOMAIN: DOMAIN,
|
||||
CONF_ENTITY_ID: entry.entity_id,
|
||||
CONF_ENTITY_ID: entry.id,
|
||||
}
|
||||
|
||||
conditions += [
|
||||
@@ -126,8 +126,11 @@ def async_condition_from_config(
|
||||
elif config[CONF_TYPE] == CONDITION_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:
|
||||
"""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
|
||||
|
@@ -46,7 +46,7 @@ TRIGGER_TYPES: Final[set[str]] = BASIC_TRIGGER_TYPES | {
|
||||
|
||||
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.Optional(CONF_FOR): cv.positive_time_period_dict,
|
||||
}
|
||||
@@ -72,7 +72,7 @@ async def async_get_triggers(
|
||||
CONF_PLATFORM: "device",
|
||||
CONF_DEVICE_ID: device_id,
|
||||
CONF_DOMAIN: DOMAIN,
|
||||
CONF_ENTITY_ID: entry.entity_id,
|
||||
CONF_ENTITY_ID: entry.id,
|
||||
}
|
||||
|
||||
triggers += [
|
||||
|
@@ -5,5 +5,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/alpha_vantage",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["alpha_vantage"],
|
||||
"requirements": ["alpha_vantage==2.3.1"]
|
||||
"requirements": ["alpha-vantage==2.3.1"]
|
||||
}
|
||||
|
@@ -7,5 +7,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/ambiclimate",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["ambiclimate"],
|
||||
"requirements": ["ambiclimate==0.2.1"]
|
||||
"requirements": ["Ambiclimate==0.2.1"]
|
||||
}
|
||||
|
@@ -3,6 +3,8 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from homeassistant.helpers.typing import UndefinedType
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
|
||||
@@ -14,7 +16,7 @@ def service_signal(service: str, *args: str) -> str:
|
||||
def log_update_error(
|
||||
logger: logging.Logger,
|
||||
action: str,
|
||||
name: str | None,
|
||||
name: str | UndefinedType | None,
|
||||
entity_type: str,
|
||||
error: Exception,
|
||||
level: int = logging.ERROR,
|
||||
|
@@ -21,12 +21,22 @@ from homeassistant.components.recorder import (
|
||||
DOMAIN as RECORDER_DOMAIN,
|
||||
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.core import HomeAssistant
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
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.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 .const import (
|
||||
@@ -206,8 +216,25 @@ class Analytics:
|
||||
if self.preferences.get(ATTR_USAGE, False) or self.preferences.get(
|
||||
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)
|
||||
configured_integrations = await async_get_integrations(self.hass, domains)
|
||||
enabled_domains = set(configured_integrations)
|
||||
|
||||
for integration in configured_integrations.values():
|
||||
if isinstance(integration, IntegrationNotFound):
|
||||
continue
|
||||
@@ -215,7 +242,11 @@ class Analytics:
|
||||
if isinstance(integration, BaseException):
|
||||
raise integration
|
||||
|
||||
if integration.disabled:
|
||||
if not self._async_should_report_integration(
|
||||
integration=integration,
|
||||
yaml_domains=configuration_set,
|
||||
entity_registry_platforms=er_platforms,
|
||||
):
|
||||
continue
|
||||
|
||||
if not integration.is_built_in:
|
||||
@@ -253,12 +284,12 @@ class Analytics:
|
||||
if supervisor_info is not None:
|
||||
payload[ATTR_ADDONS] = addons
|
||||
|
||||
if ENERGY_DOMAIN in integrations:
|
||||
if ENERGY_DOMAIN in enabled_domains:
|
||||
payload[ATTR_ENERGY] = {
|
||||
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)
|
||||
engine = instance.database_engine
|
||||
if engine and engine.version is not None:
|
||||
@@ -306,3 +337,34 @@ class Analytics:
|
||||
LOGGER.error(
|
||||
"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
|
||||
)
|
||||
|
@@ -296,7 +296,6 @@ class ADBDevice(MediaPlayerEntity):
|
||||
self._process_config,
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
@property
|
||||
def media_image_hash(self) -> str | None:
|
||||
|
@@ -16,6 +16,7 @@ from .const import DOMAIN
|
||||
class AndroidTVRemoteBaseEntity(Entity):
|
||||
"""Android TV Remote Base Entity."""
|
||||
|
||||
_attr_name = None
|
||||
_attr_has_entity_name = True
|
||||
_attr_should_poll = False
|
||||
|
||||
|
@@ -5,5 +5,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/anel_pwrctrl",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["anel_pwrctrl"],
|
||||
"requirements": ["anel_pwrctrl-homeassistant==0.0.1.dev2"]
|
||||
"requirements": ["anel-pwrctrl-homeassistant==0.0.1.dev2"]
|
||||
}
|
||||
|
@@ -80,6 +80,7 @@ class AnthemAVR(MediaPlayerEntity):
|
||||
self._attr_name = f"zone {zone_number}"
|
||||
self._attr_unique_id = f"{mac_address}_{zone_number}"
|
||||
else:
|
||||
self._attr_name = None
|
||||
self._attr_unique_id = mac_address
|
||||
|
||||
self._attr_device_info = DeviceInfo(
|
||||
|
@@ -7,7 +7,7 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/apple_tv",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["pyatv", "srptools"],
|
||||
"requirements": ["pyatv==0.12.0"],
|
||||
"requirements": ["pyatv==0.13.2"],
|
||||
"zeroconf": [
|
||||
"_mediaremotetv._tcp.local.",
|
||||
"_companion-link._tcp.local.",
|
||||
|
@@ -282,7 +282,7 @@ class AppleTvMediaPlayer(AppleTVEntity, MediaPlayerEntity):
|
||||
"""Send the play_media command to the media player."""
|
||||
# If input (file) has a file format supported by pyatv, then stream it with
|
||||
# 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)
|
||||
return
|
||||
|
||||
|
@@ -49,6 +49,7 @@
|
||||
},
|
||||
"abort": {
|
||||
"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%]",
|
||||
"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.",
|
||||
|
@@ -5,5 +5,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/aquostv",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["sharp_aquos_rc"],
|
||||
"requirements": ["sharp_aquos_rc==0.3.2"]
|
||||
"requirements": ["sharp-aquos-rc==0.3.2"]
|
||||
}
|
||||
|
@@ -1,6 +1,8 @@
|
||||
"""Support for Aranet sensors."""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from aranet4.client import Aranet4Advertisement
|
||||
from bleak.backends.device import BLEDevice
|
||||
|
||||
@@ -23,6 +25,7 @@ from homeassistant.const import (
|
||||
ATTR_SW_VERSION,
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
PERCENTAGE,
|
||||
EntityCategory,
|
||||
UnitOfPressure,
|
||||
UnitOfTemperature,
|
||||
UnitOfTime,
|
||||
@@ -33,43 +36,55 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
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 = {
|
||||
"temperature": SensorEntityDescription(
|
||||
"temperature": AranetSensorEntityDescription(
|
||||
key="temperature",
|
||||
name="Temperature",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
"humidity": SensorEntityDescription(
|
||||
"humidity": AranetSensorEntityDescription(
|
||||
key="humidity",
|
||||
name="Humidity",
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
"pressure": SensorEntityDescription(
|
||||
"pressure": AranetSensorEntityDescription(
|
||||
key="pressure",
|
||||
name="Pressure",
|
||||
device_class=SensorDeviceClass.PRESSURE,
|
||||
native_unit_of_measurement=UnitOfPressure.HPA,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
"co2": SensorEntityDescription(
|
||||
"co2": AranetSensorEntityDescription(
|
||||
key="co2",
|
||||
name="Carbon Dioxide",
|
||||
device_class=SensorDeviceClass.CO2,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
"battery": SensorEntityDescription(
|
||||
"battery": AranetSensorEntityDescription(
|
||||
key="battery",
|
||||
name="Battery",
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
"interval": SensorEntityDescription(
|
||||
"interval": AranetSensorEntityDescription(
|
||||
key="update_interval",
|
||||
name="Update Interval",
|
||||
device_class=SensorDeviceClass.DURATION,
|
||||
@@ -77,6 +92,7 @@ SENSOR_DESCRIPTIONS = {
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
# The interval setting is not a generally useful entity for most users.
|
||||
entity_registry_enabled_default=False,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
}
|
||||
|
||||
|
@@ -3,7 +3,9 @@ from __future__ import annotations
|
||||
|
||||
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 (
|
||||
ATTR_ENTITY_ID,
|
||||
CONF_DEVICE_ID,
|
||||
@@ -22,7 +24,7 @@ from .const import DOMAIN, EVENT_TURN_ON
|
||||
TRIGGER_TYPES = {"turn_on"}
|
||||
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),
|
||||
}
|
||||
)
|
||||
@@ -43,7 +45,7 @@ async def async_get_triggers(
|
||||
CONF_PLATFORM: "device",
|
||||
CONF_DEVICE_ID: device_id,
|
||||
CONF_DOMAIN: DOMAIN,
|
||||
CONF_ENTITY_ID: entry.entity_id,
|
||||
CONF_ENTITY_ID: entry.id,
|
||||
CONF_TYPE: "turn_on",
|
||||
}
|
||||
)
|
||||
@@ -62,7 +64,8 @@ async def async_attach_trigger(
|
||||
job = HassJob(action)
|
||||
|
||||
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
|
||||
def _handle_event(event: Event) -> None:
|
||||
@@ -71,9 +74,10 @@ async def async_attach_trigger(
|
||||
job,
|
||||
{
|
||||
"trigger": {
|
||||
**trigger_data, # type: ignore[arg-type] # https://github.com/python/mypy/issues/9117
|
||||
**trigger_data,
|
||||
**config,
|
||||
"description": f"{DOMAIN} - {entity_id}",
|
||||
"entity_id": entity_id,
|
||||
}
|
||||
},
|
||||
event.context,
|
||||
|
@@ -6,7 +6,7 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/arcam_fmj",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["arcam"],
|
||||
"requirements": ["arcam-fmj==1.3.0"],
|
||||
"requirements": ["arcam-fmj==1.4.0"],
|
||||
"ssdp": [
|
||||
{
|
||||
"deviceType": "urn:schemas-upnp-org:device:MediaRenderer:1",
|
||||
|
@@ -57,6 +57,7 @@ async def async_pipeline_from_audio_stream(
|
||||
pipeline_id: str | None = None,
|
||||
conversation_id: str | None = None,
|
||||
tts_audio_output: str | None = None,
|
||||
device_id: str | None = None,
|
||||
) -> None:
|
||||
"""Create an audio pipeline from an audio stream.
|
||||
|
||||
@@ -64,6 +65,7 @@ async def async_pipeline_from_audio_stream(
|
||||
"""
|
||||
pipeline_input = PipelineInput(
|
||||
conversation_id=conversation_id,
|
||||
device_id=device_id,
|
||||
stt_metadata=stt_metadata,
|
||||
stt_stream=stt_stream,
|
||||
run=PipelineRun(
|
||||
|
@@ -499,7 +499,7 @@ class PipelineRun:
|
||||
self.intent_agent = agent_info.id
|
||||
|
||||
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:
|
||||
"""Run intent recognition portion of pipeline. Returns text to speak."""
|
||||
if self.intent_agent is None:
|
||||
@@ -512,6 +512,8 @@ class PipelineRun:
|
||||
"engine": self.intent_agent,
|
||||
"language": self.pipeline.conversation_language,
|
||||
"intent_input": intent_input,
|
||||
"conversation_id": conversation_id,
|
||||
"device_id": device_id,
|
||||
},
|
||||
)
|
||||
)
|
||||
@@ -521,6 +523,7 @@ class PipelineRun:
|
||||
hass=self.hass,
|
||||
text=intent_input,
|
||||
conversation_id=conversation_id,
|
||||
device_id=device_id,
|
||||
context=self.context,
|
||||
language=self.pipeline.conversation_language,
|
||||
agent_id=self.intent_agent,
|
||||
@@ -655,6 +658,8 @@ class PipelineInput:
|
||||
|
||||
conversation_id: str | None = None
|
||||
|
||||
device_id: str | None = None
|
||||
|
||||
async def execute(self) -> None:
|
||||
"""Run pipeline."""
|
||||
self.run.start()
|
||||
@@ -678,7 +683,9 @@ class PipelineInput:
|
||||
if current_stage == PipelineStage.INTENT:
|
||||
assert intent_input is not None
|
||||
tts_input = await self.run.recognize_intent(
|
||||
intent_input, self.conversation_id
|
||||
intent_input,
|
||||
self.conversation_id,
|
||||
self.device_id,
|
||||
)
|
||||
current_stage = PipelineStage.TTS
|
||||
|
||||
@@ -730,17 +737,30 @@ class PipelineInput:
|
||||
)
|
||||
|
||||
start_stage_index = PIPELINE_STAGE_ORDER.index(self.run.start_stage)
|
||||
end_stage_index = PIPELINE_STAGE_ORDER.index(self.run.end_stage)
|
||||
|
||||
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
|
||||
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())
|
||||
|
||||
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())
|
||||
|
||||
if prepare_tasks:
|
||||
@@ -944,6 +964,7 @@ class PipelineData:
|
||||
|
||||
pipeline_runs: dict[str, LimitedSizeDict[str, PipelineRunDebug]]
|
||||
pipeline_store: PipelineStorageCollection
|
||||
pipeline_devices: set[str] = field(default_factory=set, init=False)
|
||||
|
||||
|
||||
@dataclass
|
||||
|
@@ -10,7 +10,8 @@ from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import collection, entity_registry as er, restore_state
|
||||
|
||||
from .const import DOMAIN
|
||||
from .pipeline import PipelineStorageCollection
|
||||
from .pipeline import PipelineData, PipelineStorageCollection
|
||||
from .vad import VadSensitivity
|
||||
|
||||
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):
|
||||
"""Entity to represent a pipeline selector."""
|
||||
|
||||
@@ -60,15 +80,24 @@ class AssistPipelineSelect(SelectEntity, restore_state.RestoreEntity):
|
||||
"""When entity is added to Home Assistant."""
|
||||
await super().async_added_to_hass()
|
||||
|
||||
pipeline_store: PipelineStorageCollection = self.hass.data[
|
||||
DOMAIN
|
||||
].pipeline_store
|
||||
pipeline_store.async_add_change_set_listener(self._pipelines_updated)
|
||||
pipeline_data: PipelineData = self.hass.data[DOMAIN]
|
||||
pipeline_store = pipeline_data.pipeline_store
|
||||
self.async_on_remove(
|
||||
pipeline_store.async_add_change_set_listener(self._pipelines_updated)
|
||||
)
|
||||
|
||||
state = await self.async_get_last_state()
|
||||
if state is not None and state.state in self.options:
|
||||
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:
|
||||
"""Select an option."""
|
||||
self._attr_current_option = option
|
||||
@@ -93,3 +122,34 @@ class AssistPipelineSelect(SelectEntity, restore_state.RestoreEntity):
|
||||
|
||||
if self._attr_current_option not in options:
|
||||
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()
|
||||
|
@@ -11,6 +11,14 @@
|
||||
"state": {
|
||||
"preferred": "Preferred"
|
||||
}
|
||||
},
|
||||
"vad_sensitivity": {
|
||||
"name": "Finished speaking detection",
|
||||
"state": {
|
||||
"default": "Default",
|
||||
"aggressive": "Aggressive",
|
||||
"relaxed": "Relaxed"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,11 +1,35 @@
|
||||
"""Voice activity detection."""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
import webrtcvad
|
||||
|
||||
from homeassistant.backports.enum import StrEnum
|
||||
|
||||
_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
|
||||
class VoiceCommandSegmenter:
|
||||
"""Segments an audio stream into voice commands using webrtcvad."""
|
||||
@@ -113,16 +137,15 @@ class VoiceCommandSegmenter:
|
||||
self._reset_seconds_left -= self._seconds_per_chunk
|
||||
if self._reset_seconds_left <= 0:
|
||||
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:
|
||||
if 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:
|
||||
# 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
|
||||
# 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
|
||||
|
@@ -56,6 +56,7 @@ def async_register_websocket_api(hass: HomeAssistant) -> None:
|
||||
vol.Optional("input"): dict,
|
||||
vol.Optional("pipeline"): str,
|
||||
vol.Optional("conversation_id"): vol.Any(str, None),
|
||||
vol.Optional("device_id"): vol.Any(str, None),
|
||||
vol.Optional("timeout"): vol.Any(float, int),
|
||||
},
|
||||
),
|
||||
@@ -105,6 +106,7 @@ async def websocket_run(
|
||||
# Arguments to PipelineInput
|
||||
input_args: dict[str, Any] = {
|
||||
"conversation_id": msg.get("conversation_id"),
|
||||
"device_id": msg.get("device_id"),
|
||||
}
|
||||
|
||||
if start_stage == PipelineStage.STT:
|
||||
@@ -280,7 +282,6 @@ def websocket_get_run(
|
||||
)
|
||||
|
||||
|
||||
@callback
|
||||
@websocket_api.websocket_command(
|
||||
{
|
||||
vol.Required("type"): "assist_pipeline/language/list",
|
||||
|
@@ -5,5 +5,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/asterisk_mbox",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["asterisk_mbox"],
|
||||
"requirements": ["asterisk_mbox==0.5.0"]
|
||||
"requirements": ["asterisk-mbox==0.5.0"]
|
||||
}
|
||||
|
@@ -15,6 +15,7 @@ from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import ATTR_TEMPERATURE, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.util.enum import try_parse_enum
|
||||
|
||||
from . import DOMAIN, AtagEntity
|
||||
|
||||
@@ -52,14 +53,12 @@ class AtagThermostat(AtagEntity, ClimateEntity):
|
||||
self._attr_temperature_unit = coordinator.data.climate.temp_unit
|
||||
|
||||
@property
|
||||
def hvac_mode(self) -> str | None:
|
||||
def hvac_mode(self) -> HVACMode | None:
|
||||
"""Return hvac operation ie. heat, cool mode."""
|
||||
if self.coordinator.data.climate.hvac_mode in HVAC_MODES:
|
||||
return self.coordinator.data.climate.hvac_mode
|
||||
return None
|
||||
return try_parse_enum(HVACMode, self.coordinator.data.climate.hvac_mode)
|
||||
|
||||
@property
|
||||
def hvac_action(self) -> str | None:
|
||||
def hvac_action(self) -> HVACAction | None:
|
||||
"""Return the current running hvac operation."""
|
||||
is_active = self.coordinator.data.climate.status
|
||||
return HVACAction.HEATING if is_active else HVACAction.IDLE
|
||||
|
@@ -5,5 +5,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/atome",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["pyatome"],
|
||||
"requirements": ["pyatome==0.1.1"]
|
||||
"requirements": ["pyAtome==0.1.1"]
|
||||
}
|
||||
|
@@ -5,7 +5,6 @@ from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
from typing import cast
|
||||
|
||||
from yalexs.activity import (
|
||||
ACTION_DOORBELL_CALL_MISSED,
|
||||
@@ -104,7 +103,16 @@ def _native_datetime() -> datetime:
|
||||
|
||||
|
||||
@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."""
|
||||
|
||||
value_fn: Callable[[AugustData, DoorbellDetail], bool]
|
||||
@@ -112,41 +120,45 @@ class AugustRequiredKeysMixin:
|
||||
|
||||
|
||||
@dataclass
|
||||
class AugustBinarySensorEntityDescription(
|
||||
BinarySensorEntityDescription, AugustRequiredKeysMixin
|
||||
class AugustDoorbellBinarySensorEntityDescription(
|
||||
BinarySensorEntityDescription, AugustDoorbellRequiredKeysMixin
|
||||
):
|
||||
"""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",
|
||||
name="Open",
|
||||
)
|
||||
|
||||
|
||||
SENSOR_TYPES_DOORBELL: tuple[AugustBinarySensorEntityDescription, ...] = (
|
||||
AugustBinarySensorEntityDescription(
|
||||
SENSOR_TYPES_DOORBELL: tuple[AugustDoorbellBinarySensorEntityDescription, ...] = (
|
||||
AugustDoorbellBinarySensorEntityDescription(
|
||||
key="doorbell_ding",
|
||||
name="Ding",
|
||||
device_class=BinarySensorDeviceClass.OCCUPANCY,
|
||||
value_fn=_retrieve_ding_state,
|
||||
is_time_based=True,
|
||||
),
|
||||
AugustBinarySensorEntityDescription(
|
||||
AugustDoorbellBinarySensorEntityDescription(
|
||||
key="doorbell_motion",
|
||||
name="Motion",
|
||||
device_class=BinarySensorDeviceClass.MOTION,
|
||||
value_fn=_retrieve_motion_state,
|
||||
is_time_based=True,
|
||||
),
|
||||
AugustBinarySensorEntityDescription(
|
||||
AugustDoorbellBinarySensorEntityDescription(
|
||||
key="doorbell_image_capture",
|
||||
name="Image Capture",
|
||||
icon="mdi:file-image",
|
||||
value_fn=_retrieve_image_capture_state,
|
||||
is_time_based=True,
|
||||
),
|
||||
AugustBinarySensorEntityDescription(
|
||||
AugustDoorbellBinarySensorEntityDescription(
|
||||
key="doorbell_online",
|
||||
name="Online",
|
||||
device_class=BinarySensorDeviceClass.CONNECTIVITY,
|
||||
@@ -199,7 +211,10 @@ class AugustDoorBinarySensor(AugustEntityMixin, BinarySensorEntity):
|
||||
_attr_device_class = BinarySensorDeviceClass.DOOR
|
||||
|
||||
def __init__(
|
||||
self, data: AugustData, device: Lock, description: BinarySensorEntityDescription
|
||||
self,
|
||||
data: AugustData,
|
||||
device: Lock,
|
||||
description: AugustBinarySensorEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize the sensor."""
|
||||
super().__init__(data, device)
|
||||
@@ -207,9 +222,7 @@ class AugustDoorBinarySensor(AugustEntityMixin, BinarySensorEntity):
|
||||
self._data = data
|
||||
self._device = device
|
||||
self._attr_name = f"{device.device_name} {description.name}"
|
||||
self._attr_unique_id = (
|
||||
f"{self._device_id}_{cast(str, description.name).lower()}"
|
||||
)
|
||||
self._attr_unique_id = f"{self._device_id}_{description.name.lower()}"
|
||||
|
||||
@callback
|
||||
def _update_from_data(self):
|
||||
@@ -243,13 +256,13 @@ class AugustDoorBinarySensor(AugustEntityMixin, BinarySensorEntity):
|
||||
class AugustDoorbellBinarySensor(AugustEntityMixin, BinarySensorEntity):
|
||||
"""Representation of an August binary sensor."""
|
||||
|
||||
entity_description: AugustBinarySensorEntityDescription
|
||||
entity_description: AugustDoorbellBinarySensorEntityDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
data: AugustData,
|
||||
device: Doorbell,
|
||||
description: AugustBinarySensorEntityDescription,
|
||||
description: AugustDoorbellBinarySensorEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize the sensor."""
|
||||
super().__init__(data, device)
|
||||
@@ -257,9 +270,7 @@ class AugustDoorbellBinarySensor(AugustEntityMixin, BinarySensorEntity):
|
||||
self._check_for_off_update_listener = None
|
||||
self._data = data
|
||||
self._attr_name = f"{device.device_name} {description.name}"
|
||||
self._attr_unique_id = (
|
||||
f"{self._device_id}_{cast(str, description.name).lower()}"
|
||||
)
|
||||
self._attr_unique_id = f"{self._device_id}_{description.name.lower()}"
|
||||
|
||||
@callback
|
||||
def _update_from_data(self):
|
||||
|
@@ -1,25 +1,15 @@
|
||||
"""The aurora component."""
|
||||
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
from aiohttp import ClientError
|
||||
from auroranoaa import AuroraForecast
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
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 (
|
||||
ATTRIBUTION,
|
||||
AURORA_API,
|
||||
CONF_THRESHOLD,
|
||||
COORDINATOR,
|
||||
@@ -27,6 +17,7 @@ from .const import (
|
||||
DEFAULT_THRESHOLD,
|
||||
DOMAIN,
|
||||
)
|
||||
from .coordinator import AuroraDataUpdateCoordinator
|
||||
|
||||
_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)
|
||||
|
||||
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,
|
||||
)
|
||||
|
@@ -4,8 +4,8 @@ from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import AuroraEntity
|
||||
from .const import COORDINATOR, DOMAIN
|
||||
from .entity import AuroraEntity
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
|
52
homeassistant/components/aurora/coordinator.py
Normal file
52
homeassistant/components/aurora/coordinator.py
Normal 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
|
48
homeassistant/components/aurora/entity.py
Normal file
48
homeassistant/components/aurora/entity.py
Normal 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,
|
||||
)
|
@@ -5,8 +5,8 @@ from homeassistant.const import PERCENTAGE
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import AuroraEntity
|
||||
from .const import COORDINATOR, DOMAIN
|
||||
from .entity import AuroraEntity
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
|
@@ -47,10 +47,10 @@ class AuroraEntity(Entity):
|
||||
@property
|
||||
def device_info(self) -> DeviceInfo:
|
||||
"""Return device specific attributes."""
|
||||
return {
|
||||
"identifiers": {(DOMAIN, self._data[ATTR_SERIAL_NUMBER])},
|
||||
"manufacturer": MANUFACTURER,
|
||||
"model": self._data[ATTR_MODEL],
|
||||
"name": self._data.get(ATTR_DEVICE_NAME, DEFAULT_DEVICE_NAME),
|
||||
"sw_version": self._data[ATTR_FIRMWARE],
|
||||
}
|
||||
return DeviceInfo(
|
||||
identifiers={(DOMAIN, self._data[ATTR_SERIAL_NUMBER])},
|
||||
manufacturer=MANUFACTURER,
|
||||
model=self._data[ATTR_MODEL],
|
||||
name=self._data.get(ATTR_DEVICE_NAME, DEFAULT_DEVICE_NAME),
|
||||
sw_version=self._data[ATTR_FIRMWARE],
|
||||
)
|
||||
|
@@ -34,7 +34,7 @@ SENSOR_TYPES = [
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
name="Power Output",
|
||||
translation_key="power_output",
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="temp",
|
||||
@@ -42,14 +42,13 @@ SENSOR_TYPES = [
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
name="Temperature",
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="totalenergy",
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
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):
|
||||
"""Representation of a Sensor on a Aurora ABB PowerOne Solar inverter."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
client: AuroraSerialClient,
|
||||
|
@@ -18,5 +18,15 @@
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -35,7 +35,7 @@ SENSOR_DESCRIPTIONS: tuple[SensorValueEntityDescription, ...] = (
|
||||
# Internet Services sensors
|
||||
SensorValueEntityDescription(
|
||||
key="usedMb",
|
||||
name="Data used",
|
||||
translation_key="data_used",
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
native_unit_of_measurement=UnitOfInformation.MEGABYTES,
|
||||
device_class=SensorDeviceClass.DATA_SIZE,
|
||||
@@ -43,7 +43,7 @@ SENSOR_DESCRIPTIONS: tuple[SensorValueEntityDescription, ...] = (
|
||||
),
|
||||
SensorValueEntityDescription(
|
||||
key="downloadedMb",
|
||||
name="Downloaded",
|
||||
translation_key="downloaded",
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
native_unit_of_measurement=UnitOfInformation.MEGABYTES,
|
||||
device_class=SensorDeviceClass.DATA_SIZE,
|
||||
@@ -51,7 +51,7 @@ SENSOR_DESCRIPTIONS: tuple[SensorValueEntityDescription, ...] = (
|
||||
),
|
||||
SensorValueEntityDescription(
|
||||
key="uploadedMb",
|
||||
name="Uploaded",
|
||||
translation_key="uploaded",
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
native_unit_of_measurement=UnitOfInformation.MEGABYTES,
|
||||
device_class=SensorDeviceClass.DATA_SIZE,
|
||||
@@ -60,21 +60,21 @@ SENSOR_DESCRIPTIONS: tuple[SensorValueEntityDescription, ...] = (
|
||||
# Mobile Phone Services sensors
|
||||
SensorValueEntityDescription(
|
||||
key="national",
|
||||
name="National calls",
|
||||
translation_key="national_calls",
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
icon="mdi:phone",
|
||||
value=lambda x: x.get("calls"),
|
||||
),
|
||||
SensorValueEntityDescription(
|
||||
key="mobile",
|
||||
name="Mobile calls",
|
||||
translation_key="mobile_calls",
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
icon="mdi:phone",
|
||||
value=lambda x: x.get("calls"),
|
||||
),
|
||||
SensorValueEntityDescription(
|
||||
key="international",
|
||||
name="International calls",
|
||||
translation_key="international_calls",
|
||||
entity_registry_enabled_default=False,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
icon="mdi:phone-plus",
|
||||
@@ -82,14 +82,14 @@ SENSOR_DESCRIPTIONS: tuple[SensorValueEntityDescription, ...] = (
|
||||
),
|
||||
SensorValueEntityDescription(
|
||||
key="sms",
|
||||
name="SMS sent",
|
||||
translation_key="sms_sent",
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
icon="mdi:message-processing",
|
||||
value=lambda x: x.get("calls"),
|
||||
),
|
||||
SensorValueEntityDescription(
|
||||
key="internet",
|
||||
name="Data used",
|
||||
translation_key="data_used",
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
native_unit_of_measurement=UnitOfInformation.KILOBYTES,
|
||||
device_class=SensorDeviceClass.DATA_SIZE,
|
||||
@@ -98,7 +98,7 @@ SENSOR_DESCRIPTIONS: tuple[SensorValueEntityDescription, ...] = (
|
||||
),
|
||||
SensorValueEntityDescription(
|
||||
key="voicemail",
|
||||
name="Voicemail calls",
|
||||
translation_key="voicemail_calls",
|
||||
entity_registry_enabled_default=False,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
icon="mdi:phone",
|
||||
@@ -106,7 +106,7 @@ SENSOR_DESCRIPTIONS: tuple[SensorValueEntityDescription, ...] = (
|
||||
),
|
||||
SensorValueEntityDescription(
|
||||
key="other",
|
||||
name="Other calls",
|
||||
translation_key="other_calls",
|
||||
entity_registry_enabled_default=False,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
icon="mdi:phone",
|
||||
@@ -115,13 +115,13 @@ SENSOR_DESCRIPTIONS: tuple[SensorValueEntityDescription, ...] = (
|
||||
# Generic sensors
|
||||
SensorValueEntityDescription(
|
||||
key="daysTotal",
|
||||
name="Billing cycle length",
|
||||
translation_key="billing_cycle_length",
|
||||
native_unit_of_measurement=UnitOfTime.DAYS,
|
||||
icon="mdi:calendar-range",
|
||||
),
|
||||
SensorValueEntityDescription(
|
||||
key="daysRemaining",
|
||||
name="Billing cycle remaining",
|
||||
translation_key="billing_cycle_remaining",
|
||||
native_unit_of_measurement=UnitOfTime.DAYS,
|
||||
icon="mdi:calendar-clock",
|
||||
),
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user