mirror of
https://github.com/home-assistant/core.git
synced 2025-08-10 08:05:06 +02:00
2023.7.0 (#95908)
This commit is contained in:
@@ -27,6 +27,7 @@ base_platforms: &base_platforms
|
|||||||
- homeassistant/components/fan/**
|
- homeassistant/components/fan/**
|
||||||
- homeassistant/components/geo_location/**
|
- homeassistant/components/geo_location/**
|
||||||
- homeassistant/components/humidifier/**
|
- homeassistant/components/humidifier/**
|
||||||
|
- homeassistant/components/image/**
|
||||||
- homeassistant/components/image_processing/**
|
- homeassistant/components/image_processing/**
|
||||||
- homeassistant/components/light/**
|
- homeassistant/components/light/**
|
||||||
- homeassistant/components/lock/**
|
- homeassistant/components/lock/**
|
||||||
|
35
.coveragerc
35
.coveragerc
@@ -90,6 +90,8 @@ omit =
|
|||||||
homeassistant/components/atome/*
|
homeassistant/components/atome/*
|
||||||
homeassistant/components/aurora/__init__.py
|
homeassistant/components/aurora/__init__.py
|
||||||
homeassistant/components/aurora/binary_sensor.py
|
homeassistant/components/aurora/binary_sensor.py
|
||||||
|
homeassistant/components/aurora/coordinator.py
|
||||||
|
homeassistant/components/aurora/entity.py
|
||||||
homeassistant/components/aurora/sensor.py
|
homeassistant/components/aurora/sensor.py
|
||||||
homeassistant/components/avea/light.py
|
homeassistant/components/avea/light.py
|
||||||
homeassistant/components/avion/light.py
|
homeassistant/components/avion/light.py
|
||||||
@@ -122,7 +124,6 @@ omit =
|
|||||||
homeassistant/components/bluetooth_tracker/*
|
homeassistant/components/bluetooth_tracker/*
|
||||||
homeassistant/components/bmw_connected_drive/__init__.py
|
homeassistant/components/bmw_connected_drive/__init__.py
|
||||||
homeassistant/components/bmw_connected_drive/binary_sensor.py
|
homeassistant/components/bmw_connected_drive/binary_sensor.py
|
||||||
homeassistant/components/bmw_connected_drive/button.py
|
|
||||||
homeassistant/components/bmw_connected_drive/coordinator.py
|
homeassistant/components/bmw_connected_drive/coordinator.py
|
||||||
homeassistant/components/bmw_connected_drive/lock.py
|
homeassistant/components/bmw_connected_drive/lock.py
|
||||||
homeassistant/components/bmw_connected_drive/notify.py
|
homeassistant/components/bmw_connected_drive/notify.py
|
||||||
@@ -202,6 +203,9 @@ omit =
|
|||||||
homeassistant/components/discogs/sensor.py
|
homeassistant/components/discogs/sensor.py
|
||||||
homeassistant/components/discord/__init__.py
|
homeassistant/components/discord/__init__.py
|
||||||
homeassistant/components/discord/notify.py
|
homeassistant/components/discord/notify.py
|
||||||
|
homeassistant/components/discovergy/__init__.py
|
||||||
|
homeassistant/components/discovergy/sensor.py
|
||||||
|
homeassistant/components/discovergy/coordinator.py
|
||||||
homeassistant/components/dlib_face_detect/image_processing.py
|
homeassistant/components/dlib_face_detect/image_processing.py
|
||||||
homeassistant/components/dlib_face_identify/image_processing.py
|
homeassistant/components/dlib_face_identify/image_processing.py
|
||||||
homeassistant/components/dlink/data.py
|
homeassistant/components/dlink/data.py
|
||||||
@@ -302,22 +306,10 @@ omit =
|
|||||||
homeassistant/components/escea/climate.py
|
homeassistant/components/escea/climate.py
|
||||||
homeassistant/components/escea/discovery.py
|
homeassistant/components/escea/discovery.py
|
||||||
homeassistant/components/esphome/__init__.py
|
homeassistant/components/esphome/__init__.py
|
||||||
homeassistant/components/esphome/binary_sensor.py
|
|
||||||
homeassistant/components/esphome/bluetooth/*
|
homeassistant/components/esphome/bluetooth/*
|
||||||
homeassistant/components/esphome/button.py
|
|
||||||
homeassistant/components/esphome/camera.py
|
homeassistant/components/esphome/camera.py
|
||||||
homeassistant/components/esphome/climate.py
|
|
||||||
homeassistant/components/esphome/cover.py
|
|
||||||
homeassistant/components/esphome/domain_data.py
|
homeassistant/components/esphome/domain_data.py
|
||||||
homeassistant/components/esphome/entry_data.py
|
homeassistant/components/esphome/entry_data.py
|
||||||
homeassistant/components/esphome/fan.py
|
|
||||||
homeassistant/components/esphome/light.py
|
|
||||||
homeassistant/components/esphome/lock.py
|
|
||||||
homeassistant/components/esphome/media_player.py
|
|
||||||
homeassistant/components/esphome/number.py
|
|
||||||
homeassistant/components/esphome/select.py
|
|
||||||
homeassistant/components/esphome/sensor.py
|
|
||||||
homeassistant/components/esphome/switch.py
|
|
||||||
homeassistant/components/etherscan/sensor.py
|
homeassistant/components/etherscan/sensor.py
|
||||||
homeassistant/components/eufy/*
|
homeassistant/components/eufy/*
|
||||||
homeassistant/components/eufylife_ble/__init__.py
|
homeassistant/components/eufylife_ble/__init__.py
|
||||||
@@ -327,6 +319,7 @@ omit =
|
|||||||
homeassistant/components/ezviz/__init__.py
|
homeassistant/components/ezviz/__init__.py
|
||||||
homeassistant/components/ezviz/binary_sensor.py
|
homeassistant/components/ezviz/binary_sensor.py
|
||||||
homeassistant/components/ezviz/camera.py
|
homeassistant/components/ezviz/camera.py
|
||||||
|
homeassistant/components/ezviz/light.py
|
||||||
homeassistant/components/ezviz/coordinator.py
|
homeassistant/components/ezviz/coordinator.py
|
||||||
homeassistant/components/ezviz/number.py
|
homeassistant/components/ezviz/number.py
|
||||||
homeassistant/components/ezviz/entity.py
|
homeassistant/components/ezviz/entity.py
|
||||||
@@ -362,10 +355,13 @@ omit =
|
|||||||
homeassistant/components/fitbit/*
|
homeassistant/components/fitbit/*
|
||||||
homeassistant/components/fivem/__init__.py
|
homeassistant/components/fivem/__init__.py
|
||||||
homeassistant/components/fivem/binary_sensor.py
|
homeassistant/components/fivem/binary_sensor.py
|
||||||
|
homeassistant/components/fivem/coordinator.py
|
||||||
|
homeassistant/components/fivem/entity.py
|
||||||
homeassistant/components/fivem/sensor.py
|
homeassistant/components/fivem/sensor.py
|
||||||
homeassistant/components/fixer/sensor.py
|
homeassistant/components/fixer/sensor.py
|
||||||
homeassistant/components/fjaraskupan/__init__.py
|
homeassistant/components/fjaraskupan/__init__.py
|
||||||
homeassistant/components/fjaraskupan/binary_sensor.py
|
homeassistant/components/fjaraskupan/binary_sensor.py
|
||||||
|
homeassistant/components/fjaraskupan/coordinator.py
|
||||||
homeassistant/components/fjaraskupan/fan.py
|
homeassistant/components/fjaraskupan/fan.py
|
||||||
homeassistant/components/fjaraskupan/light.py
|
homeassistant/components/fjaraskupan/light.py
|
||||||
homeassistant/components/fjaraskupan/number.py
|
homeassistant/components/fjaraskupan/number.py
|
||||||
@@ -615,7 +611,6 @@ omit =
|
|||||||
homeassistant/components/kwb/sensor.py
|
homeassistant/components/kwb/sensor.py
|
||||||
homeassistant/components/lacrosse/sensor.py
|
homeassistant/components/lacrosse/sensor.py
|
||||||
homeassistant/components/lannouncer/notify.py
|
homeassistant/components/lannouncer/notify.py
|
||||||
homeassistant/components/lastfm/sensor.py
|
|
||||||
homeassistant/components/launch_library/__init__.py
|
homeassistant/components/launch_library/__init__.py
|
||||||
homeassistant/components/launch_library/sensor.py
|
homeassistant/components/launch_library/sensor.py
|
||||||
homeassistant/components/lcn/climate.py
|
homeassistant/components/lcn/climate.py
|
||||||
@@ -946,6 +941,8 @@ omit =
|
|||||||
homeassistant/components/pyload/sensor.py
|
homeassistant/components/pyload/sensor.py
|
||||||
homeassistant/components/qbittorrent/__init__.py
|
homeassistant/components/qbittorrent/__init__.py
|
||||||
homeassistant/components/qbittorrent/sensor.py
|
homeassistant/components/qbittorrent/sensor.py
|
||||||
|
homeassistant/components/qnap/__init__.py
|
||||||
|
homeassistant/components/qnap/coordinator.py
|
||||||
homeassistant/components/qnap/sensor.py
|
homeassistant/components/qnap/sensor.py
|
||||||
homeassistant/components/qrcode/image_processing.py
|
homeassistant/components/qrcode/image_processing.py
|
||||||
homeassistant/components/quantum_gateway/device_tracker.py
|
homeassistant/components/quantum_gateway/device_tracker.py
|
||||||
@@ -973,6 +970,10 @@ omit =
|
|||||||
homeassistant/components/rainmachine/switch.py
|
homeassistant/components/rainmachine/switch.py
|
||||||
homeassistant/components/rainmachine/update.py
|
homeassistant/components/rainmachine/update.py
|
||||||
homeassistant/components/rainmachine/util.py
|
homeassistant/components/rainmachine/util.py
|
||||||
|
homeassistant/components/renson/__init__.py
|
||||||
|
homeassistant/components/renson/const.py
|
||||||
|
homeassistant/components/renson/entity.py
|
||||||
|
homeassistant/components/renson/sensor.py
|
||||||
homeassistant/components/raspyrfm/*
|
homeassistant/components/raspyrfm/*
|
||||||
homeassistant/components/recollect_waste/sensor.py
|
homeassistant/components/recollect_waste/sensor.py
|
||||||
homeassistant/components/recorder/repack.py
|
homeassistant/components/recorder/repack.py
|
||||||
@@ -1046,12 +1047,6 @@ omit =
|
|||||||
homeassistant/components/sense/__init__.py
|
homeassistant/components/sense/__init__.py
|
||||||
homeassistant/components/sense/binary_sensor.py
|
homeassistant/components/sense/binary_sensor.py
|
||||||
homeassistant/components/sense/sensor.py
|
homeassistant/components/sense/sensor.py
|
||||||
homeassistant/components/senseme/__init__.py
|
|
||||||
homeassistant/components/senseme/discovery.py
|
|
||||||
homeassistant/components/senseme/entity.py
|
|
||||||
homeassistant/components/senseme/fan.py
|
|
||||||
homeassistant/components/senseme/light.py
|
|
||||||
homeassistant/components/senseme/switch.py
|
|
||||||
homeassistant/components/senz/__init__.py
|
homeassistant/components/senz/__init__.py
|
||||||
homeassistant/components/senz/api.py
|
homeassistant/components/senz/api.py
|
||||||
homeassistant/components/senz/climate.py
|
homeassistant/components/senz/climate.py
|
||||||
|
171
.github/workflows/builder.yml
vendored
171
.github/workflows/builder.yml
vendored
@@ -10,7 +10,7 @@ on:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
BUILD_TYPE: core
|
BUILD_TYPE: core
|
||||||
DEFAULT_PYTHON: "3.10"
|
DEFAULT_PYTHON: "3.11"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
init:
|
init:
|
||||||
@@ -24,7 +24,7 @@ jobs:
|
|||||||
publish: ${{ steps.version.outputs.publish }}
|
publish: ${{ steps.version.outputs.publish }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@v3.5.2
|
uses: actions/checkout@v3.5.3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
@@ -48,18 +48,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
ignore-dev: true
|
ignore-dev: true
|
||||||
|
|
||||||
- name: Generate meta info
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
echo "${{ github.sha }};${{ github.ref }};${{ github.event_name }};${{ github.actor }}" > OFFICIAL_IMAGE
|
|
||||||
|
|
||||||
- name: Signing meta info file
|
|
||||||
uses: home-assistant/actions/helpers/codenotary@master
|
|
||||||
with:
|
|
||||||
source: file://${{ github.workspace }}/OFFICIAL_IMAGE
|
|
||||||
asset: OFFICIAL_IMAGE-${{ steps.version.outputs.version }}
|
|
||||||
token: ${{ secrets.CAS_TOKEN }}
|
|
||||||
|
|
||||||
build_python:
|
build_python:
|
||||||
name: Build PyPi package
|
name: Build PyPi package
|
||||||
environment: ${{ needs.init.outputs.channel }}
|
environment: ${{ needs.init.outputs.channel }}
|
||||||
@@ -68,7 +56,7 @@ jobs:
|
|||||||
if: github.repository_owner == 'home-assistant' && needs.init.outputs.publish == 'true'
|
if: github.repository_owner == 'home-assistant' && needs.init.outputs.publish == 'true'
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@v3.5.2
|
uses: actions/checkout@v3.5.3
|
||||||
|
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
uses: actions/setup-python@v4.6.1
|
uses: actions/setup-python@v4.6.1
|
||||||
@@ -101,12 +89,16 @@ jobs:
|
|||||||
if: github.repository_owner == 'home-assistant'
|
if: github.repository_owner == 'home-assistant'
|
||||||
needs: init
|
needs: init
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
id-token: write
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
arch: ${{ fromJson(needs.init.outputs.architectures) }}
|
arch: ${{ fromJson(needs.init.outputs.architectures) }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@v3.5.2
|
uses: actions/checkout@v3.5.3
|
||||||
|
|
||||||
- name: Download nightly wheels of frontend
|
- name: Download nightly wheels of frontend
|
||||||
if: needs.init.outputs.channel == 'dev'
|
if: needs.init.outputs.channel == 'dev'
|
||||||
@@ -182,7 +174,7 @@ jobs:
|
|||||||
# will drop the platform in the near future (they consider it
|
# will drop the platform in the near future (they consider it
|
||||||
# "flimsy" on 386). The following packages depend on pandas,
|
# "flimsy" on 386). The following packages depend on pandas,
|
||||||
# so we comment them out.
|
# so we comment them out.
|
||||||
sed -i "s|env_canada|# env_canada|g" requirements_all.txt
|
sed -i "s|env-canada|# env-canada|g" requirements_all.txt
|
||||||
sed -i "s|noaa-coops|# noaa-coops|g" requirements_all.txt
|
sed -i "s|noaa-coops|# noaa-coops|g" requirements_all.txt
|
||||||
sed -i "s|pyezviz|# pyezviz|g" requirements_all.txt
|
sed -i "s|pyezviz|# pyezviz|g" requirements_all.txt
|
||||||
sed -i "s|pykrakenapi|# pykrakenapi|g" requirements_all.txt
|
sed -i "s|pykrakenapi|# pykrakenapi|g" requirements_all.txt
|
||||||
@@ -197,25 +189,20 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
echo "${{ github.sha }};${{ github.ref }};${{ github.event_name }};${{ github.actor }}" > rootfs/OFFICIAL_IMAGE
|
echo "${{ github.sha }};${{ github.ref }};${{ github.event_name }};${{ github.actor }}" > rootfs/OFFICIAL_IMAGE
|
||||||
|
|
||||||
- name: Login to DockerHub
|
|
||||||
uses: docker/login-action@v2.1.0
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@v2.1.0
|
uses: docker/login-action@v2.2.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Build base image
|
- name: Build base image
|
||||||
uses: home-assistant/builder@2023.03.0
|
uses: home-assistant/builder@2023.06.1
|
||||||
with:
|
with:
|
||||||
args: |
|
args: |
|
||||||
$BUILD_ARGS \
|
$BUILD_ARGS \
|
||||||
--${{ matrix.arch }} \
|
--${{ matrix.arch }} \
|
||||||
|
--cosign \
|
||||||
--target /data \
|
--target /data \
|
||||||
--generic ${{ needs.init.outputs.version }}
|
--generic ${{ needs.init.outputs.version }}
|
||||||
env:
|
env:
|
||||||
@@ -237,6 +224,10 @@ jobs:
|
|||||||
if: github.repository_owner == 'home-assistant'
|
if: github.repository_owner == 'home-assistant'
|
||||||
needs: ["init", "build_base"]
|
needs: ["init", "build_base"]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
id-token: write
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
machine:
|
machine:
|
||||||
@@ -262,7 +253,7 @@ jobs:
|
|||||||
- yellow
|
- yellow
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@v3.5.2
|
uses: actions/checkout@v3.5.3
|
||||||
|
|
||||||
- name: Set build additional args
|
- name: Set build additional args
|
||||||
run: |
|
run: |
|
||||||
@@ -275,25 +266,20 @@ jobs:
|
|||||||
echo "BUILD_ARGS=--additional-tag stable" >> $GITHUB_ENV
|
echo "BUILD_ARGS=--additional-tag stable" >> $GITHUB_ENV
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Login to DockerHub
|
|
||||||
uses: docker/login-action@v2.1.0
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@v2.1.0
|
uses: docker/login-action@v2.2.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Build base image
|
- name: Build base image
|
||||||
uses: home-assistant/builder@2023.03.0
|
uses: home-assistant/builder@2023.06.1
|
||||||
with:
|
with:
|
||||||
args: |
|
args: |
|
||||||
$BUILD_ARGS \
|
$BUILD_ARGS \
|
||||||
--target /data/machine \
|
--target /data/machine \
|
||||||
|
--cosign \
|
||||||
--machine "${{ needs.init.outputs.version }}=${{ matrix.machine }}"
|
--machine "${{ needs.init.outputs.version }}=${{ matrix.machine }}"
|
||||||
env:
|
env:
|
||||||
CAS_API_KEY: ${{ secrets.CAS_TOKEN }}
|
CAS_API_KEY: ${{ secrets.CAS_TOKEN }}
|
||||||
@@ -306,7 +292,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@v3.5.2
|
uses: actions/checkout@v3.5.3
|
||||||
|
|
||||||
- name: Initialize git
|
- name: Initialize git
|
||||||
uses: home-assistant/actions/helpers/git-init@master
|
uses: home-assistant/actions/helpers/git-init@master
|
||||||
@@ -338,34 +324,32 @@ jobs:
|
|||||||
if: github.repository_owner == 'home-assistant'
|
if: github.repository_owner == 'home-assistant'
|
||||||
needs: ["init", "build_base"]
|
needs: ["init", "build_base"]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
permissions:
|
||||||
fail-fast: false
|
contents: read
|
||||||
matrix:
|
packages: write
|
||||||
registry:
|
id-token: write
|
||||||
- "ghcr.io/home-assistant"
|
|
||||||
- "homeassistant"
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@v3.5.2
|
uses: actions/checkout@v3.5.3
|
||||||
|
|
||||||
|
- name: Install Cosign
|
||||||
|
uses: sigstore/cosign-installer@v3.1.1
|
||||||
|
with:
|
||||||
|
cosign-release: "v2.0.2"
|
||||||
|
|
||||||
- name: Login to DockerHub
|
- name: Login to DockerHub
|
||||||
if: matrix.registry == 'homeassistant'
|
uses: docker/login-action@v2.2.0
|
||||||
uses: docker/login-action@v2.1.0
|
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
if: matrix.registry == 'ghcr.io/home-assistant'
|
uses: docker/login-action@v2.2.0
|
||||||
uses: docker/login-action@v2.1.0
|
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Install CAS tools
|
|
||||||
uses: home-assistant/actions/helpers/cas@master
|
|
||||||
|
|
||||||
- name: Build Meta Image
|
- name: Build Meta Image
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
@@ -375,55 +359,78 @@ jobs:
|
|||||||
local tag_l=${1}
|
local tag_l=${1}
|
||||||
local tag_r=${2}
|
local tag_r=${2}
|
||||||
|
|
||||||
docker manifest create "${{ matrix.registry }}/home-assistant:${tag_l}" \
|
for registry in "ghcr.io/home-assistant" "docker.io/homeassistant"
|
||||||
"${{ matrix.registry }}/amd64-homeassistant:${tag_r}" \
|
do
|
||||||
"${{ matrix.registry }}/i386-homeassistant:${tag_r}" \
|
|
||||||
"${{ matrix.registry }}/armhf-homeassistant:${tag_r}" \
|
|
||||||
"${{ matrix.registry }}/armv7-homeassistant:${tag_r}" \
|
|
||||||
"${{ matrix.registry }}/aarch64-homeassistant:${tag_r}"
|
|
||||||
|
|
||||||
docker manifest annotate "${{ matrix.registry }}/home-assistant:${tag_l}" \
|
docker manifest create "${registry}/home-assistant:${tag_l}" \
|
||||||
"${{ matrix.registry }}/amd64-homeassistant:${tag_r}" \
|
"${registry}/amd64-homeassistant:${tag_r}" \
|
||||||
--os linux --arch amd64
|
"${registry}/i386-homeassistant:${tag_r}" \
|
||||||
|
"${registry}/armhf-homeassistant:${tag_r}" \
|
||||||
|
"${registry}/armv7-homeassistant:${tag_r}" \
|
||||||
|
"${registry}/aarch64-homeassistant:${tag_r}"
|
||||||
|
|
||||||
docker manifest annotate "${{ matrix.registry }}/home-assistant:${tag_l}" \
|
docker manifest annotate "${registry}/home-assistant:${tag_l}" \
|
||||||
"${{ matrix.registry }}/i386-homeassistant:${tag_r}" \
|
"${registry}/amd64-homeassistant:${tag_r}" \
|
||||||
--os linux --arch 386
|
--os linux --arch amd64
|
||||||
|
|
||||||
docker manifest annotate "${{ matrix.registry }}/home-assistant:${tag_l}" \
|
docker manifest annotate "${registry}/home-assistant:${tag_l}" \
|
||||||
"${{ matrix.registry }}/armhf-homeassistant:${tag_r}" \
|
"${registry}/i386-homeassistant:${tag_r}" \
|
||||||
--os linux --arch arm --variant=v6
|
--os linux --arch 386
|
||||||
|
|
||||||
docker manifest annotate "${{ matrix.registry }}/home-assistant:${tag_l}" \
|
docker manifest annotate "${registry}/home-assistant:${tag_l}" \
|
||||||
"${{ matrix.registry }}/armv7-homeassistant:${tag_r}" \
|
"${registry}/armhf-homeassistant:${tag_r}" \
|
||||||
--os linux --arch arm --variant=v7
|
--os linux --arch arm --variant=v6
|
||||||
|
|
||||||
docker manifest annotate "${{ matrix.registry }}/home-assistant:${tag_l}" \
|
docker manifest annotate "${registry}/home-assistant:${tag_l}" \
|
||||||
"${{ matrix.registry }}/aarch64-homeassistant:${tag_r}" \
|
"${registry}/armv7-homeassistant:${tag_r}" \
|
||||||
--os linux --arch arm64 --variant=v8
|
--os linux --arch arm --variant=v7
|
||||||
|
|
||||||
docker manifest push --purge "${{ matrix.registry }}/home-assistant:${tag_l}"
|
docker manifest annotate "${registry}/home-assistant:${tag_l}" \
|
||||||
|
"${registry}/aarch64-homeassistant:${tag_r}" \
|
||||||
|
--os linux --arch arm64 --variant=v8
|
||||||
|
|
||||||
|
docker manifest push --purge "${registry}/home-assistant:${tag_l}"
|
||||||
|
cosign sign --yes "${registry}/home-assistant:${tag_l}"
|
||||||
|
|
||||||
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
function validate_image() {
|
function validate_image() {
|
||||||
local image=${1}
|
local image=${1}
|
||||||
if ! cas authenticate --signerID notary@home-assistant.io "docker://${image}"; then
|
if ! cosign verify --certificate-oidc-issuer https://token.actions.githubusercontent.com --certificate-identity-regexp https://github.com/home-assistant/core/.* "${image}"; then
|
||||||
echo "Invalid signature!"
|
echo "Invalid signature!"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
docker pull "${{ matrix.registry }}/amd64-homeassistant:${{ needs.init.outputs.version }}"
|
function push_dockerhub() {
|
||||||
docker pull "${{ matrix.registry }}/i386-homeassistant:${{ needs.init.outputs.version }}"
|
local image=${1}
|
||||||
docker pull "${{ matrix.registry }}/armhf-homeassistant:${{ needs.init.outputs.version }}"
|
local tag=${2}
|
||||||
docker pull "${{ matrix.registry }}/armv7-homeassistant:${{ needs.init.outputs.version }}"
|
|
||||||
docker pull "${{ matrix.registry }}/aarch64-homeassistant:${{ needs.init.outputs.version }}"
|
|
||||||
|
|
||||||
validate_image "${{ matrix.registry }}/amd64-homeassistant:${{ needs.init.outputs.version }}"
|
docker tag "ghcr.io/home-assistant/${image}:${tag}" "docker.io/homeassistant/${image}:${tag}"
|
||||||
validate_image "${{ matrix.registry }}/i386-homeassistant:${{ needs.init.outputs.version }}"
|
docker push "docker.io/homeassistant/${image}:${tag}"
|
||||||
validate_image "${{ matrix.registry }}/armhf-homeassistant:${{ needs.init.outputs.version }}"
|
cosign sign --yes "docker.io/homeassistant/${image}:${tag}"
|
||||||
validate_image "${{ matrix.registry }}/armv7-homeassistant:${{ needs.init.outputs.version }}"
|
}
|
||||||
validate_image "${{ matrix.registry }}/aarch64-homeassistant:${{ needs.init.outputs.version }}"
|
|
||||||
|
# Pull images from github container registry and verify signature
|
||||||
|
docker pull "ghcr.io/home-assistant/amd64-homeassistant:${{ needs.init.outputs.version }}"
|
||||||
|
docker pull "ghcr.io/home-assistant/i386-homeassistant:${{ needs.init.outputs.version }}"
|
||||||
|
docker pull "ghcr.io/home-assistant/armhf-homeassistant:${{ needs.init.outputs.version }}"
|
||||||
|
docker pull "ghcr.io/home-assistant/armv7-homeassistant:${{ needs.init.outputs.version }}"
|
||||||
|
docker pull "ghcr.io/home-assistant/aarch64-homeassistant:${{ needs.init.outputs.version }}"
|
||||||
|
|
||||||
|
validate_image "ghcr.io/home-assistant/amd64-homeassistant:${{ needs.init.outputs.version }}"
|
||||||
|
validate_image "ghcr.io/home-assistant/i386-homeassistant:${{ needs.init.outputs.version }}"
|
||||||
|
validate_image "ghcr.io/home-assistant/armhf-homeassistant:${{ needs.init.outputs.version }}"
|
||||||
|
validate_image "ghcr.io/home-assistant/armv7-homeassistant:${{ needs.init.outputs.version }}"
|
||||||
|
validate_image "ghcr.io/home-assistant/aarch64-homeassistant:${{ needs.init.outputs.version }}"
|
||||||
|
|
||||||
|
# Upload images to dockerhub
|
||||||
|
push_dockerhub "amd64-homeassistant" "${{ needs.init.outputs.version }}"
|
||||||
|
push_dockerhub "i386-homeassistant" "${{ needs.init.outputs.version }}"
|
||||||
|
push_dockerhub "armhf-homeassistant" "${{ needs.init.outputs.version }}"
|
||||||
|
push_dockerhub "armv7-homeassistant" "${{ needs.init.outputs.version }}"
|
||||||
|
push_dockerhub "aarch64-homeassistant" "${{ needs.init.outputs.version }}"
|
||||||
|
|
||||||
# Create version tag
|
# Create version tag
|
||||||
create_manifest "${{ needs.init.outputs.version }}" "${{ needs.init.outputs.version }}"
|
create_manifest "${{ needs.init.outputs.version }}" "${{ needs.init.outputs.version }}"
|
||||||
|
92
.github/workflows/ci.yaml
vendored
92
.github/workflows/ci.yaml
vendored
@@ -32,7 +32,7 @@ env:
|
|||||||
CACHE_VERSION: 5
|
CACHE_VERSION: 5
|
||||||
PIP_CACHE_VERSION: 4
|
PIP_CACHE_VERSION: 4
|
||||||
MYPY_CACHE_VERSION: 4
|
MYPY_CACHE_VERSION: 4
|
||||||
HA_SHORT_VERSION: 2023.6
|
HA_SHORT_VERSION: 2023.7
|
||||||
DEFAULT_PYTHON: "3.10"
|
DEFAULT_PYTHON: "3.10"
|
||||||
ALL_PYTHON_VERSIONS: "['3.10', '3.11']"
|
ALL_PYTHON_VERSIONS: "['3.10', '3.11']"
|
||||||
# 10.3 is the oldest supported version
|
# 10.3 is the oldest supported version
|
||||||
@@ -82,7 +82,7 @@ jobs:
|
|||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v3.5.2
|
uses: actions/checkout@v3.5.3
|
||||||
- name: Generate partial Python venv restore key
|
- name: Generate partial Python venv restore key
|
||||||
id: generate_python_cache_key
|
id: generate_python_cache_key
|
||||||
run: >-
|
run: >-
|
||||||
@@ -206,7 +206,7 @@ jobs:
|
|||||||
- info
|
- info
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v3.5.2
|
uses: actions/checkout@v3.5.3
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
id: python
|
id: python
|
||||||
uses: actions/setup-python@v4.6.1
|
uses: actions/setup-python@v4.6.1
|
||||||
@@ -251,7 +251,7 @@ jobs:
|
|||||||
- pre-commit
|
- pre-commit
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v3.5.2
|
uses: actions/checkout@v3.5.3
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
uses: actions/setup-python@v4.6.1
|
uses: actions/setup-python@v4.6.1
|
||||||
id: python
|
id: python
|
||||||
@@ -297,7 +297,7 @@ jobs:
|
|||||||
- pre-commit
|
- pre-commit
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v3.5.2
|
uses: actions/checkout@v3.5.3
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
uses: actions/setup-python@v4.6.1
|
uses: actions/setup-python@v4.6.1
|
||||||
id: python
|
id: python
|
||||||
@@ -338,44 +338,6 @@ jobs:
|
|||||||
shopt -s globstar
|
shopt -s globstar
|
||||||
pre-commit run --hook-stage manual ruff --files {homeassistant,tests}/components/${{ needs.info.outputs.integrations_glob }}/{*,**/*} --show-diff-on-failure
|
pre-commit run --hook-stage manual ruff --files {homeassistant,tests}/components/${{ needs.info.outputs.integrations_glob }}/{*,**/*} --show-diff-on-failure
|
||||||
|
|
||||||
lint-isort:
|
|
||||||
name: Check isort
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
needs:
|
|
||||||
- info
|
|
||||||
- pre-commit
|
|
||||||
steps:
|
|
||||||
- name: Check out code from GitHub
|
|
||||||
uses: actions/checkout@v3.5.2
|
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
|
||||||
uses: actions/setup-python@v4.6.1
|
|
||||||
id: python
|
|
||||||
with:
|
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
|
||||||
check-latest: true
|
|
||||||
- name: Restore base Python virtual environment
|
|
||||||
id: cache-venv
|
|
||||||
uses: actions/cache/restore@v3.3.1
|
|
||||||
with:
|
|
||||||
path: venv
|
|
||||||
fail-on-cache-miss: true
|
|
||||||
key: >-
|
|
||||||
${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{
|
|
||||||
needs.info.outputs.pre-commit_cache_key }}
|
|
||||||
- name: Restore pre-commit environment from cache
|
|
||||||
id: cache-precommit
|
|
||||||
uses: actions/cache/restore@v3.3.1
|
|
||||||
with:
|
|
||||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
|
||||||
fail-on-cache-miss: true
|
|
||||||
key: >-
|
|
||||||
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
|
||||||
needs.info.outputs.pre-commit_cache_key }}
|
|
||||||
- name: Run isort
|
|
||||||
run: |
|
|
||||||
. venv/bin/activate
|
|
||||||
pre-commit run --hook-stage manual isort --all-files --show-diff-on-failure
|
|
||||||
|
|
||||||
lint-other:
|
lint-other:
|
||||||
name: Check other linters
|
name: Check other linters
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
@@ -384,7 +346,7 @@ jobs:
|
|||||||
- pre-commit
|
- pre-commit
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v3.5.2
|
uses: actions/checkout@v3.5.3
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
uses: actions/setup-python@v4.6.1
|
uses: actions/setup-python@v4.6.1
|
||||||
id: python
|
id: python
|
||||||
@@ -468,19 +430,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
args: hadolint Dockerfile.dev
|
args: hadolint Dockerfile.dev
|
||||||
|
|
||||||
- name: Run bandit (fully)
|
|
||||||
if: needs.info.outputs.test_full_suite == 'true'
|
|
||||||
run: |
|
|
||||||
. venv/bin/activate
|
|
||||||
pre-commit run --hook-stage manual bandit --all-files --show-diff-on-failure
|
|
||||||
- name: Run bandit (partially)
|
|
||||||
if: needs.info.outputs.test_full_suite == 'false'
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
. venv/bin/activate
|
|
||||||
shopt -s globstar
|
|
||||||
pre-commit run --hook-stage manual bandit --files {homeassistant,tests}/components/${{ needs.info.outputs.integrations_glob }}/{*,**/*} --show-diff-on-failure
|
|
||||||
|
|
||||||
base:
|
base:
|
||||||
name: Prepare dependencies
|
name: Prepare dependencies
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
@@ -491,7 +440,7 @@ jobs:
|
|||||||
python-version: ${{ fromJSON(needs.info.outputs.python_versions) }}
|
python-version: ${{ fromJSON(needs.info.outputs.python_versions) }}
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v3.5.2
|
uses: actions/checkout@v3.5.3
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
id: python
|
id: python
|
||||||
uses: actions/setup-python@v4.6.1
|
uses: actions/setup-python@v4.6.1
|
||||||
@@ -543,10 +492,10 @@ jobs:
|
|||||||
python -m venv venv
|
python -m venv venv
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
python --version
|
python --version
|
||||||
pip install --cache-dir=$PIP_CACHE -U "pip>=21.0,<23.2" setuptools wheel
|
pip install --cache-dir=$PIP_CACHE -U "pip>=21.3.1,<23.2" setuptools wheel
|
||||||
pip install --cache-dir=$PIP_CACHE -r requirements_all.txt
|
pip install --cache-dir=$PIP_CACHE -r requirements_all.txt
|
||||||
pip install --cache-dir=$PIP_CACHE -r requirements_test.txt
|
pip install --cache-dir=$PIP_CACHE -r requirements_test.txt
|
||||||
pip install -e .
|
pip install -e . --config-settings editable_mode=compat
|
||||||
|
|
||||||
hassfest:
|
hassfest:
|
||||||
name: Check hassfest
|
name: Check hassfest
|
||||||
@@ -559,7 +508,7 @@ jobs:
|
|||||||
- base
|
- base
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v3.5.2
|
uses: actions/checkout@v3.5.3
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
id: python
|
id: python
|
||||||
uses: actions/setup-python@v4.6.1
|
uses: actions/setup-python@v4.6.1
|
||||||
@@ -591,7 +540,7 @@ jobs:
|
|||||||
- base
|
- base
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v3.5.2
|
uses: actions/checkout@v3.5.3
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
id: python
|
id: python
|
||||||
uses: actions/setup-python@v4.6.1
|
uses: actions/setup-python@v4.6.1
|
||||||
@@ -624,7 +573,7 @@ jobs:
|
|||||||
- base
|
- base
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v3.5.2
|
uses: actions/checkout@v3.5.3
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
id: python
|
id: python
|
||||||
uses: actions/setup-python@v4.6.1
|
uses: actions/setup-python@v4.6.1
|
||||||
@@ -668,7 +617,7 @@ jobs:
|
|||||||
- base
|
- base
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v3.5.2
|
uses: actions/checkout@v3.5.3
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
id: python
|
id: python
|
||||||
uses: actions/setup-python@v4.6.1
|
uses: actions/setup-python@v4.6.1
|
||||||
@@ -732,7 +681,6 @@ jobs:
|
|||||||
- base
|
- base
|
||||||
- gen-requirements-all
|
- gen-requirements-all
|
||||||
- hassfest
|
- hassfest
|
||||||
- lint-isort
|
|
||||||
- lint-other
|
- lint-other
|
||||||
- lint-ruff
|
- lint-ruff
|
||||||
- mypy
|
- mypy
|
||||||
@@ -751,7 +699,7 @@ jobs:
|
|||||||
bluez \
|
bluez \
|
||||||
ffmpeg
|
ffmpeg
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v3.5.2
|
uses: actions/checkout@v3.5.3
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
id: python
|
id: python
|
||||||
uses: actions/setup-python@v4.6.1
|
uses: actions/setup-python@v4.6.1
|
||||||
@@ -857,7 +805,6 @@ jobs:
|
|||||||
- base
|
- base
|
||||||
- gen-requirements-all
|
- gen-requirements-all
|
||||||
- hassfest
|
- hassfest
|
||||||
- lint-isort
|
|
||||||
- lint-other
|
- lint-other
|
||||||
- lint-ruff
|
- lint-ruff
|
||||||
- mypy
|
- mypy
|
||||||
@@ -877,7 +824,7 @@ jobs:
|
|||||||
ffmpeg \
|
ffmpeg \
|
||||||
libmariadb-dev-compat
|
libmariadb-dev-compat
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v3.5.2
|
uses: actions/checkout@v3.5.3
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
id: python
|
id: python
|
||||||
uses: actions/setup-python@v4.6.1
|
uses: actions/setup-python@v4.6.1
|
||||||
@@ -965,7 +912,6 @@ jobs:
|
|||||||
- base
|
- base
|
||||||
- gen-requirements-all
|
- gen-requirements-all
|
||||||
- hassfest
|
- hassfest
|
||||||
- lint-isort
|
|
||||||
- lint-other
|
- lint-other
|
||||||
- lint-ruff
|
- lint-ruff
|
||||||
- mypy
|
- mypy
|
||||||
@@ -985,7 +931,7 @@ jobs:
|
|||||||
ffmpeg \
|
ffmpeg \
|
||||||
postgresql-server-dev-14
|
postgresql-server-dev-14
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v3.5.2
|
uses: actions/checkout@v3.5.3
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
id: python
|
id: python
|
||||||
uses: actions/setup-python@v4.6.1
|
uses: actions/setup-python@v4.6.1
|
||||||
@@ -1062,12 +1008,12 @@ jobs:
|
|||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v3.5.2
|
uses: actions/checkout@v3.5.3
|
||||||
- name: Download all coverage artifacts
|
- name: Download all coverage artifacts
|
||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@v3
|
||||||
- name: Upload coverage to Codecov (full coverage)
|
- name: Upload coverage to Codecov (full coverage)
|
||||||
if: needs.info.outputs.test_full_suite == 'true'
|
if: needs.info.outputs.test_full_suite == 'true'
|
||||||
uses: Wandalen/wretry.action@v1.0.36
|
uses: Wandalen/wretry.action@v1.3.0
|
||||||
with:
|
with:
|
||||||
action: codecov/codecov-action@v3.1.3
|
action: codecov/codecov-action@v3.1.3
|
||||||
with: |
|
with: |
|
||||||
@@ -1077,7 +1023,7 @@ jobs:
|
|||||||
attempt_delay: 30000
|
attempt_delay: 30000
|
||||||
- name: Upload coverage to Codecov (partial coverage)
|
- name: Upload coverage to Codecov (partial coverage)
|
||||||
if: needs.info.outputs.test_full_suite == 'false'
|
if: needs.info.outputs.test_full_suite == 'false'
|
||||||
uses: Wandalen/wretry.action@v1.0.36
|
uses: Wandalen/wretry.action@v1.3.0
|
||||||
with:
|
with:
|
||||||
action: codecov/codecov-action@v3.1.3
|
action: codecov/codecov-action@v3.1.3
|
||||||
with: |
|
with: |
|
||||||
|
2
.github/workflows/lock.yml
vendored
2
.github/workflows/lock.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
|||||||
if: github.repository_owner == 'home-assistant'
|
if: github.repository_owner == 'home-assistant'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: dessant/lock-threads@v4.0.0
|
- uses: dessant/lock-threads@v4.0.1
|
||||||
with:
|
with:
|
||||||
github-token: ${{ github.token }}
|
github-token: ${{ github.token }}
|
||||||
issue-inactive-days: "30"
|
issue-inactive-days: "30"
|
||||||
|
4
.github/workflows/translations.yml
vendored
4
.github/workflows/translations.yml
vendored
@@ -10,7 +10,7 @@ on:
|
|||||||
- "**strings.json"
|
- "**strings.json"
|
||||||
|
|
||||||
env:
|
env:
|
||||||
DEFAULT_PYTHON: "3.10"
|
DEFAULT_PYTHON: "3.11"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
upload:
|
upload:
|
||||||
@@ -19,7 +19,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@v3.5.2
|
uses: actions/checkout@v3.5.3
|
||||||
|
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
uses: actions/setup-python@v4.6.1
|
uses: actions/setup-python@v4.6.1
|
||||||
|
176
.github/workflows/wheels.yml
vendored
176
.github/workflows/wheels.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
|||||||
architectures: ${{ steps.info.outputs.architectures }}
|
architectures: ${{ steps.info.outputs.architectures }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@v3.5.2
|
uses: actions/checkout@v3.5.3
|
||||||
|
|
||||||
- name: Get information
|
- name: Get information
|
||||||
id: info
|
id: info
|
||||||
@@ -47,10 +47,7 @@ jobs:
|
|||||||
echo "GRPC_PYTHON_BUILD_SYSTEM_OPENSSL=true"
|
echo "GRPC_PYTHON_BUILD_SYSTEM_OPENSSL=true"
|
||||||
echo "GRPC_PYTHON_BUILD_WITH_CYTHON=true"
|
echo "GRPC_PYTHON_BUILD_WITH_CYTHON=true"
|
||||||
echo "GRPC_PYTHON_DISABLE_LIBC_COMPATIBILITY=true"
|
echo "GRPC_PYTHON_DISABLE_LIBC_COMPATIBILITY=true"
|
||||||
# GRPC on armv7 needs -lexecinfo (issue #56669) since home assistant installs
|
echo "GRPC_PYTHON_LDFLAGS=-lpthread -Wl,-wrap,memcpy -static-libgcc"
|
||||||
# execinfo-dev when building wheels. The setuptools build setup does not have an option for
|
|
||||||
# adding a single LDFLAG so copy all relevant linux flags here (as of 1.43.0)
|
|
||||||
echo "GRPC_PYTHON_LDFLAGS=-lpthread -Wl,-wrap,memcpy -static-libgcc -lexecinfo"
|
|
||||||
|
|
||||||
# Fix out of memory issues with rust
|
# Fix out of memory issues with rust
|
||||||
echo "CARGO_NET_GIT_FETCH_WITH_CLI=true"
|
echo "CARGO_NET_GIT_FETCH_WITH_CLI=true"
|
||||||
@@ -83,11 +80,11 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
abi: ["cp310", "cp311"]
|
abi: ["cp311"]
|
||||||
arch: ${{ fromJson(needs.init.outputs.architectures) }}
|
arch: ${{ fromJson(needs.init.outputs.architectures) }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@v3.5.2
|
uses: actions/checkout@v3.5.3
|
||||||
|
|
||||||
- name: Download env_file
|
- name: Download env_file
|
||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@v3
|
||||||
@@ -113,100 +110,6 @@ jobs:
|
|||||||
requirements-diff: "requirements_diff.txt"
|
requirements-diff: "requirements_diff.txt"
|
||||||
requirements: "requirements.txt"
|
requirements: "requirements.txt"
|
||||||
|
|
||||||
integrations_cp310:
|
|
||||||
name: Build wheels ${{ matrix.abi }} for ${{ matrix.arch }}
|
|
||||||
if: github.repository_owner == 'home-assistant'
|
|
||||||
needs: init
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
abi: ["cp310"]
|
|
||||||
arch: ${{ fromJson(needs.init.outputs.architectures) }}
|
|
||||||
steps:
|
|
||||||
- name: Checkout the repository
|
|
||||||
uses: actions/checkout@v3.5.2
|
|
||||||
|
|
||||||
- name: Download env_file
|
|
||||||
uses: actions/download-artifact@v3
|
|
||||||
with:
|
|
||||||
name: env_file
|
|
||||||
|
|
||||||
- name: Download requirements_diff
|
|
||||||
uses: actions/download-artifact@v3
|
|
||||||
with:
|
|
||||||
name: requirements_diff
|
|
||||||
|
|
||||||
- name: Uncomment packages
|
|
||||||
run: |
|
|
||||||
requirement_files="requirements_all.txt requirements_diff.txt"
|
|
||||||
for requirement_file in ${requirement_files}; do
|
|
||||||
sed -i "s|# pybluez|pybluez|g" ${requirement_file}
|
|
||||||
sed -i "s|# beacontools|beacontools|g" ${requirement_file}
|
|
||||||
sed -i "s|# fritzconnection|fritzconnection|g" ${requirement_file}
|
|
||||||
sed -i "s|# pyuserinput|pyuserinput|g" ${requirement_file}
|
|
||||||
sed -i "s|# evdev|evdev|g" ${requirement_file}
|
|
||||||
sed -i "s|# pycups|pycups|g" ${requirement_file}
|
|
||||||
sed -i "s|# homekit|homekit|g" ${requirement_file}
|
|
||||||
sed -i "s|# decora_wifi|decora_wifi|g" ${requirement_file}
|
|
||||||
sed -i "s|# python-gammu|python-gammu|g" ${requirement_file}
|
|
||||||
sed -i "s|# opencv-python-headless|opencv-python-headless|g" ${requirement_file}
|
|
||||||
done
|
|
||||||
|
|
||||||
- name: Split requirements all
|
|
||||||
run: |
|
|
||||||
# We split requirements all into two different files.
|
|
||||||
# This is to prevent the build from running out of memory when
|
|
||||||
# resolving packages on 32-bits systems (like armhf, armv7).
|
|
||||||
|
|
||||||
split -l $(expr $(expr $(cat requirements_all.txt | wc -l) + 1) / 2) requirements_all.txt requirements_all.txt
|
|
||||||
|
|
||||||
- name: Adjust build env
|
|
||||||
run: |
|
|
||||||
if [ "${{ matrix.arch }}" = "i386" ]; then
|
|
||||||
echo "NPY_DISABLE_SVML=1" >> .env_file
|
|
||||||
fi
|
|
||||||
|
|
||||||
(
|
|
||||||
# cmake > 3.22.2 have issue on arm
|
|
||||||
# Tested until 3.22.5
|
|
||||||
echo "cmake==3.22.2"
|
|
||||||
) >> homeassistant/package_constraints.txt
|
|
||||||
|
|
||||||
# Do not pin numpy in wheels building
|
|
||||||
sed -i "/numpy/d" homeassistant/package_constraints.txt
|
|
||||||
|
|
||||||
- name: Build wheels (part 1)
|
|
||||||
uses: home-assistant/wheels@2023.04.0
|
|
||||||
with:
|
|
||||||
abi: ${{ matrix.abi }}
|
|
||||||
tag: musllinux_1_2
|
|
||||||
arch: ${{ matrix.arch }}
|
|
||||||
wheels-key: ${{ secrets.WHEELS_KEY }}
|
|
||||||
env-file: true
|
|
||||||
apk: "bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;cups-dev;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev;gammu-dev;yaml-dev;openblas-dev;fftw-dev;lapack-dev;gfortran;blas-dev;eigen-dev;freetype-dev;glew-dev;harfbuzz-dev;hdf5-dev;libdc1394-dev;libtbb-dev;mesa-dev;openexr-dev;openjpeg-dev;uchardet-dev"
|
|
||||||
skip-binary: aiohttp;grpcio;sqlalchemy;protobuf
|
|
||||||
constraints: "homeassistant/package_constraints.txt"
|
|
||||||
requirements-diff: "requirements_diff.txt"
|
|
||||||
requirements: "requirements_all.txtaa"
|
|
||||||
|
|
||||||
- name: Build wheels (part 2)
|
|
||||||
uses: home-assistant/wheels@2023.04.0
|
|
||||||
with:
|
|
||||||
abi: ${{ matrix.abi }}
|
|
||||||
tag: musllinux_1_2
|
|
||||||
arch: ${{ matrix.arch }}
|
|
||||||
wheels-key: ${{ secrets.WHEELS_KEY }}
|
|
||||||
env-file: true
|
|
||||||
apk: "bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;cups-dev;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev;gammu-dev;yaml-dev;openblas-dev;fftw-dev;lapack-dev;gfortran;blas-dev;eigen-dev;freetype-dev;glew-dev;harfbuzz-dev;hdf5-dev;libdc1394-dev;libtbb-dev;mesa-dev;openexr-dev;openjpeg-dev;uchardet-dev"
|
|
||||||
skip-binary: aiohttp;grpcio;sqlalchemy;protobuf
|
|
||||||
constraints: "homeassistant/package_constraints.txt"
|
|
||||||
requirements-diff: "requirements_diff.txt"
|
|
||||||
requirements: "requirements_all.txtab"
|
|
||||||
|
|
||||||
# Wheels building for the cp311 ABI is currently split
|
|
||||||
# This is mainly until we have figured out to get all wheels built.
|
|
||||||
# Without harming our current workflow.
|
|
||||||
integrations_cp311:
|
integrations_cp311:
|
||||||
name: Build wheels ${{ matrix.abi }} for ${{ matrix.arch }}
|
name: Build wheels ${{ matrix.abi }} for ${{ matrix.arch }}
|
||||||
if: github.repository_owner == 'home-assistant'
|
if: github.repository_owner == 'home-assistant'
|
||||||
@@ -219,31 +122,12 @@ jobs:
|
|||||||
arch: ${{ fromJson(needs.init.outputs.architectures) }}
|
arch: ${{ fromJson(needs.init.outputs.architectures) }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@v3.5.2
|
uses: actions/checkout@v3.5.3
|
||||||
|
|
||||||
- name: Write alternative env-file for cp311
|
- name: Download env_file
|
||||||
run: |
|
uses: actions/download-artifact@v3
|
||||||
(
|
with:
|
||||||
echo "GRPC_BUILD_WITH_BORING_SSL_ASM=false"
|
name: env_file
|
||||||
echo "GRPC_PYTHON_BUILD_SYSTEM_OPENSSL=true"
|
|
||||||
echo "GRPC_PYTHON_BUILD_WITH_CYTHON=true"
|
|
||||||
echo "GRPC_PYTHON_DISABLE_LIBC_COMPATIBILITY=true"
|
|
||||||
|
|
||||||
# GRPC on armv7 needed -lexecinfo (issue #56669) since home assistant installed
|
|
||||||
# execinfo-dev when building wheels. However, this package is no longer available
|
|
||||||
# Alpine 3.17, which we use for the cp311 ABI, so the flag should no longer be needed.
|
|
||||||
echo "GRPC_PYTHON_LDFLAGS=-lpthread -Wl,-wrap,memcpy -static-libgcc" # -lexecinfo
|
|
||||||
|
|
||||||
# Fix out of memory issues with rust
|
|
||||||
echo "CARGO_NET_GIT_FETCH_WITH_CLI=true"
|
|
||||||
|
|
||||||
# OpenCV headless installation
|
|
||||||
echo "CI_BUILD=1"
|
|
||||||
echo "ENABLE_HEADLESS=1"
|
|
||||||
|
|
||||||
# Use C-Extension for sqlalchemy
|
|
||||||
echo "REQUIRE_SQLALCHEMY_CEXT=1"
|
|
||||||
) > .env_file
|
|
||||||
|
|
||||||
- name: Download requirements_diff
|
- name: Download requirements_diff
|
||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@v3
|
||||||
@@ -254,27 +138,12 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
requirement_files="requirements_all.txt requirements_diff.txt"
|
requirement_files="requirements_all.txt requirements_diff.txt"
|
||||||
for requirement_file in ${requirement_files}; do
|
for requirement_file in ${requirement_files}; do
|
||||||
|
|
||||||
# PyBluez no longer compiles. Commented it out for now.
|
|
||||||
# It need further cleanup down the line, as all machine images
|
|
||||||
# try to install it.
|
|
||||||
# sed -i "s|# pybluez|pybluez|g" ${requirement_file}
|
|
||||||
|
|
||||||
# beacontools requires PyBluez.
|
|
||||||
# sed -i "s|# beacontools|beacontools|g" ${requirement_file}
|
|
||||||
|
|
||||||
# It doesn't build for some reason, so we skip it for now.
|
|
||||||
# Bumping to the latest version (4.7.0.72) supporting Python 3.11
|
|
||||||
# doesn't help. Reverted bump in #91871. There are 8 registered
|
|
||||||
# instances using this integration according to analytics.
|
|
||||||
# sed -i "s|# opencv-python-headless|opencv-python-headless|g" ${requirement_file}
|
|
||||||
|
|
||||||
sed -i "s|# fritzconnection|fritzconnection|g" ${requirement_file}
|
sed -i "s|# fritzconnection|fritzconnection|g" ${requirement_file}
|
||||||
sed -i "s|# pyuserinput|pyuserinput|g" ${requirement_file}
|
sed -i "s|# pyuserinput|pyuserinput|g" ${requirement_file}
|
||||||
sed -i "s|# evdev|evdev|g" ${requirement_file}
|
sed -i "s|# evdev|evdev|g" ${requirement_file}
|
||||||
sed -i "s|# pycups|pycups|g" ${requirement_file}
|
sed -i "s|# pycups|pycups|g" ${requirement_file}
|
||||||
sed -i "s|# homekit|homekit|g" ${requirement_file}
|
sed -i "s|# homekit|homekit|g" ${requirement_file}
|
||||||
sed -i "s|# decora_wifi|decora_wifi|g" ${requirement_file}
|
sed -i "s|# decora-wifi|decora-wifi|g" ${requirement_file}
|
||||||
sed -i "s|# python-gammu|python-gammu|g" ${requirement_file}
|
sed -i "s|# python-gammu|python-gammu|g" ${requirement_file}
|
||||||
|
|
||||||
# Some packages are not buildable on armhf anymore
|
# Some packages are not buildable on armhf anymore
|
||||||
@@ -284,7 +153,7 @@ jobs:
|
|||||||
# will drop the platform in the near future (they consider it
|
# will drop the platform in the near future (they consider it
|
||||||
# "flimsy" on 386). The following packages depend on pandas,
|
# "flimsy" on 386). The following packages depend on pandas,
|
||||||
# so we comment them out.
|
# so we comment them out.
|
||||||
sed -i "s|env_canada|# env_canada|g" ${requirement_file}
|
sed -i "s|env-canada|# env-canada|g" ${requirement_file}
|
||||||
sed -i "s|noaa-coops|# noaa-coops|g" ${requirement_file}
|
sed -i "s|noaa-coops|# noaa-coops|g" ${requirement_file}
|
||||||
sed -i "s|pyezviz|# pyezviz|g" ${requirement_file}
|
sed -i "s|pyezviz|# pyezviz|g" ${requirement_file}
|
||||||
sed -i "s|pykrakenapi|# pykrakenapi|g" ${requirement_file}
|
sed -i "s|pykrakenapi|# pykrakenapi|g" ${requirement_file}
|
||||||
@@ -297,7 +166,7 @@ jobs:
|
|||||||
# This is to prevent the build from running out of memory when
|
# This is to prevent the build from running out of memory when
|
||||||
# resolving packages on 32-bits systems (like armhf, armv7).
|
# resolving packages on 32-bits systems (like armhf, armv7).
|
||||||
|
|
||||||
split -l $(expr $(expr $(cat requirements_all.txt | wc -l) + 1) / 2) requirements_all.txt requirements_all.txt
|
split -l $(expr $(expr $(cat requirements_all.txt | wc -l) + 1) / 3) requirements_all.txt requirements_all.txt
|
||||||
|
|
||||||
- name: Adjust build env
|
- name: Adjust build env
|
||||||
run: |
|
run: |
|
||||||
@@ -305,13 +174,6 @@ jobs:
|
|||||||
echo "NPY_DISABLE_SVML=1" >> .env_file
|
echo "NPY_DISABLE_SVML=1" >> .env_file
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Probably not an issue anymore. Removing for now.
|
|
||||||
# (
|
|
||||||
# # cmake > 3.22.2 have issue on arm
|
|
||||||
# # Tested until 3.22.5
|
|
||||||
# echo "cmake==3.22.2"
|
|
||||||
# ) >> homeassistant/package_constraints.txt
|
|
||||||
|
|
||||||
# Do not pin numpy in wheels building
|
# Do not pin numpy in wheels building
|
||||||
sed -i "/numpy/d" homeassistant/package_constraints.txt
|
sed -i "/numpy/d" homeassistant/package_constraints.txt
|
||||||
|
|
||||||
@@ -342,3 +204,17 @@ jobs:
|
|||||||
constraints: "homeassistant/package_constraints.txt"
|
constraints: "homeassistant/package_constraints.txt"
|
||||||
requirements-diff: "requirements_diff.txt"
|
requirements-diff: "requirements_diff.txt"
|
||||||
requirements: "requirements_all.txtab"
|
requirements: "requirements_all.txtab"
|
||||||
|
|
||||||
|
- name: Build wheels (part 3)
|
||||||
|
uses: home-assistant/wheels@2023.04.0
|
||||||
|
with:
|
||||||
|
abi: ${{ matrix.abi }}
|
||||||
|
tag: musllinux_1_2
|
||||||
|
arch: ${{ matrix.arch }}
|
||||||
|
wheels-key: ${{ secrets.WHEELS_KEY }}
|
||||||
|
env-file: true
|
||||||
|
apk: "bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;cups-dev;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev;gammu-dev;yaml-dev;openblas-dev;fftw-dev;lapack-dev;gfortran;blas-dev;eigen-dev;freetype-dev;glew-dev;harfbuzz-dev;hdf5-dev;libdc1394-dev;libtbb-dev;mesa-dev;openexr-dev;openjpeg-dev;uchardet-dev"
|
||||||
|
skip-binary: aiohttp;grpcio;sqlalchemy;protobuf
|
||||||
|
constraints: "homeassistant/package_constraints.txt"
|
||||||
|
requirements-diff: "requirements_diff.txt"
|
||||||
|
requirements: "requirements_all.txtac"
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||||
rev: v0.0.262
|
rev: v0.0.272
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff
|
- id: ruff
|
||||||
args:
|
args:
|
||||||
@@ -22,19 +22,6 @@ repos:
|
|||||||
- --quiet-level=2
|
- --quiet-level=2
|
||||||
exclude_types: [csv, json]
|
exclude_types: [csv, json]
|
||||||
exclude: ^tests/fixtures/|homeassistant/generated/
|
exclude: ^tests/fixtures/|homeassistant/generated/
|
||||||
- repo: https://github.com/PyCQA/bandit
|
|
||||||
rev: 1.7.4
|
|
||||||
hooks:
|
|
||||||
- id: bandit
|
|
||||||
args:
|
|
||||||
- --quiet
|
|
||||||
- --format=custom
|
|
||||||
- --configfile=tests/bandit.yaml
|
|
||||||
files: ^(homeassistant|script|tests)/.+\.py$
|
|
||||||
- repo: https://github.com/PyCQA/isort
|
|
||||||
rev: 5.12.0
|
|
||||||
hooks:
|
|
||||||
- id: isort
|
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v4.4.0
|
rev: v4.4.0
|
||||||
hooks:
|
hooks:
|
||||||
|
@@ -172,6 +172,7 @@ homeassistant.components.huawei_lte.*
|
|||||||
homeassistant.components.hydrawise.*
|
homeassistant.components.hydrawise.*
|
||||||
homeassistant.components.hyperion.*
|
homeassistant.components.hyperion.*
|
||||||
homeassistant.components.ibeacon.*
|
homeassistant.components.ibeacon.*
|
||||||
|
homeassistant.components.image.*
|
||||||
homeassistant.components.image_processing.*
|
homeassistant.components.image_processing.*
|
||||||
homeassistant.components.image_upload.*
|
homeassistant.components.image_upload.*
|
||||||
homeassistant.components.imap.*
|
homeassistant.components.imap.*
|
||||||
@@ -243,6 +244,7 @@ homeassistant.components.overkiz.*
|
|||||||
homeassistant.components.peco.*
|
homeassistant.components.peco.*
|
||||||
homeassistant.components.persistent_notification.*
|
homeassistant.components.persistent_notification.*
|
||||||
homeassistant.components.pi_hole.*
|
homeassistant.components.pi_hole.*
|
||||||
|
homeassistant.components.ping.*
|
||||||
homeassistant.components.powerwall.*
|
homeassistant.components.powerwall.*
|
||||||
homeassistant.components.proximity.*
|
homeassistant.components.proximity.*
|
||||||
homeassistant.components.prusalink.*
|
homeassistant.components.prusalink.*
|
||||||
@@ -275,7 +277,6 @@ homeassistant.components.scene.*
|
|||||||
homeassistant.components.schedule.*
|
homeassistant.components.schedule.*
|
||||||
homeassistant.components.scrape.*
|
homeassistant.components.scrape.*
|
||||||
homeassistant.components.select.*
|
homeassistant.components.select.*
|
||||||
homeassistant.components.senseme.*
|
|
||||||
homeassistant.components.sensibo.*
|
homeassistant.components.sensibo.*
|
||||||
homeassistant.components.sensirion_ble.*
|
homeassistant.components.sensirion_ble.*
|
||||||
homeassistant.components.sensor.*
|
homeassistant.components.sensor.*
|
||||||
@@ -308,6 +309,7 @@ homeassistant.components.tag.*
|
|||||||
homeassistant.components.tailscale.*
|
homeassistant.components.tailscale.*
|
||||||
homeassistant.components.tautulli.*
|
homeassistant.components.tautulli.*
|
||||||
homeassistant.components.tcp.*
|
homeassistant.components.tcp.*
|
||||||
|
homeassistant.components.text.*
|
||||||
homeassistant.components.threshold.*
|
homeassistant.components.threshold.*
|
||||||
homeassistant.components.tibber.*
|
homeassistant.components.tibber.*
|
||||||
homeassistant.components.tile.*
|
homeassistant.components.tile.*
|
||||||
|
34
CODEOWNERS
34
CODEOWNERS
@@ -275,6 +275,8 @@ build.json @home-assistant/supervisor
|
|||||||
/homeassistant/components/discogs/ @thibmaek
|
/homeassistant/components/discogs/ @thibmaek
|
||||||
/homeassistant/components/discord/ @tkdrob
|
/homeassistant/components/discord/ @tkdrob
|
||||||
/tests/components/discord/ @tkdrob
|
/tests/components/discord/ @tkdrob
|
||||||
|
/homeassistant/components/discovergy/ @jpbede
|
||||||
|
/tests/components/discovergy/ @jpbede
|
||||||
/homeassistant/components/discovery/ @home-assistant/core
|
/homeassistant/components/discovery/ @home-assistant/core
|
||||||
/tests/components/discovery/ @home-assistant/core
|
/tests/components/discovery/ @home-assistant/core
|
||||||
/homeassistant/components/dlink/ @tkdrob
|
/homeassistant/components/dlink/ @tkdrob
|
||||||
@@ -289,6 +291,8 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/doorbird/ @oblogic7 @bdraco @flacjacket
|
/tests/components/doorbird/ @oblogic7 @bdraco @flacjacket
|
||||||
/homeassistant/components/dormakaba_dkey/ @emontnemery
|
/homeassistant/components/dormakaba_dkey/ @emontnemery
|
||||||
/tests/components/dormakaba_dkey/ @emontnemery
|
/tests/components/dormakaba_dkey/ @emontnemery
|
||||||
|
/homeassistant/components/dremel_3d_printer/ @tkdrob
|
||||||
|
/tests/components/dremel_3d_printer/ @tkdrob
|
||||||
/homeassistant/components/dsmr/ @Robbie1221 @frenck
|
/homeassistant/components/dsmr/ @Robbie1221 @frenck
|
||||||
/tests/components/dsmr/ @Robbie1221 @frenck
|
/tests/components/dsmr/ @Robbie1221 @frenck
|
||||||
/homeassistant/components/dsmr_reader/ @depl0y @glodenox
|
/homeassistant/components/dsmr_reader/ @depl0y @glodenox
|
||||||
@@ -352,8 +356,8 @@ build.json @home-assistant/supervisor
|
|||||||
/homeassistant/components/eq3btsmart/ @rytilahti
|
/homeassistant/components/eq3btsmart/ @rytilahti
|
||||||
/homeassistant/components/escea/ @lazdavila
|
/homeassistant/components/escea/ @lazdavila
|
||||||
/tests/components/escea/ @lazdavila
|
/tests/components/escea/ @lazdavila
|
||||||
/homeassistant/components/esphome/ @OttoWinter @jesserockz
|
/homeassistant/components/esphome/ @OttoWinter @jesserockz @bdraco
|
||||||
/tests/components/esphome/ @OttoWinter @jesserockz
|
/tests/components/esphome/ @OttoWinter @jesserockz @bdraco
|
||||||
/homeassistant/components/eufylife_ble/ @bdr99
|
/homeassistant/components/eufylife_ble/ @bdr99
|
||||||
/tests/components/eufylife_ble/ @bdr99
|
/tests/components/eufylife_ble/ @bdr99
|
||||||
/homeassistant/components/evil_genius_labs/ @balloob
|
/homeassistant/components/evil_genius_labs/ @balloob
|
||||||
@@ -448,8 +452,8 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/glances/ @engrbm87
|
/tests/components/glances/ @engrbm87
|
||||||
/homeassistant/components/goalzero/ @tkdrob
|
/homeassistant/components/goalzero/ @tkdrob
|
||||||
/tests/components/goalzero/ @tkdrob
|
/tests/components/goalzero/ @tkdrob
|
||||||
/homeassistant/components/gogogate2/ @vangorra @bdraco
|
/homeassistant/components/gogogate2/ @vangorra
|
||||||
/tests/components/gogogate2/ @vangorra @bdraco
|
/tests/components/gogogate2/ @vangorra
|
||||||
/homeassistant/components/goodwe/ @mletenay @starkillerOG
|
/homeassistant/components/goodwe/ @mletenay @starkillerOG
|
||||||
/tests/components/goodwe/ @mletenay @starkillerOG
|
/tests/components/goodwe/ @mletenay @starkillerOG
|
||||||
/homeassistant/components/google/ @allenporter
|
/homeassistant/components/google/ @allenporter
|
||||||
@@ -559,12 +563,14 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/icloud/ @Quentame @nzapponi
|
/tests/components/icloud/ @Quentame @nzapponi
|
||||||
/homeassistant/components/ign_sismologia/ @exxamalte
|
/homeassistant/components/ign_sismologia/ @exxamalte
|
||||||
/tests/components/ign_sismologia/ @exxamalte
|
/tests/components/ign_sismologia/ @exxamalte
|
||||||
|
/homeassistant/components/image/ @home-assistant/core
|
||||||
|
/tests/components/image/ @home-assistant/core
|
||||||
/homeassistant/components/image_processing/ @home-assistant/core
|
/homeassistant/components/image_processing/ @home-assistant/core
|
||||||
/tests/components/image_processing/ @home-assistant/core
|
/tests/components/image_processing/ @home-assistant/core
|
||||||
/homeassistant/components/image_upload/ @home-assistant/core
|
/homeassistant/components/image_upload/ @home-assistant/core
|
||||||
/tests/components/image_upload/ @home-assistant/core
|
/tests/components/image_upload/ @home-assistant/core
|
||||||
/homeassistant/components/imap/ @engrbm87 @jbouwh
|
/homeassistant/components/imap/ @jbouwh
|
||||||
/tests/components/imap/ @engrbm87 @jbouwh
|
/tests/components/imap/ @jbouwh
|
||||||
/homeassistant/components/incomfort/ @zxdavb
|
/homeassistant/components/incomfort/ @zxdavb
|
||||||
/homeassistant/components/influxdb/ @mdegat01
|
/homeassistant/components/influxdb/ @mdegat01
|
||||||
/tests/components/influxdb/ @mdegat01
|
/tests/components/influxdb/ @mdegat01
|
||||||
@@ -697,6 +703,8 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/logi_circle/ @evanjd
|
/tests/components/logi_circle/ @evanjd
|
||||||
/homeassistant/components/lookin/ @ANMalko @bdraco
|
/homeassistant/components/lookin/ @ANMalko @bdraco
|
||||||
/tests/components/lookin/ @ANMalko @bdraco
|
/tests/components/lookin/ @ANMalko @bdraco
|
||||||
|
/homeassistant/components/loqed/ @mikewoudenberg
|
||||||
|
/tests/components/loqed/ @mikewoudenberg
|
||||||
/homeassistant/components/lovelace/ @home-assistant/frontend
|
/homeassistant/components/lovelace/ @home-assistant/frontend
|
||||||
/tests/components/lovelace/ @home-assistant/frontend
|
/tests/components/lovelace/ @home-assistant/frontend
|
||||||
/homeassistant/components/luci/ @mzdrale
|
/homeassistant/components/luci/ @mzdrale
|
||||||
@@ -779,11 +787,12 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/mutesync/ @currentoor
|
/tests/components/mutesync/ @currentoor
|
||||||
/homeassistant/components/my/ @home-assistant/core
|
/homeassistant/components/my/ @home-assistant/core
|
||||||
/tests/components/my/ @home-assistant/core
|
/tests/components/my/ @home-assistant/core
|
||||||
/homeassistant/components/myq/ @bdraco @ehendrix23
|
/homeassistant/components/myq/ @ehendrix23
|
||||||
/tests/components/myq/ @bdraco @ehendrix23
|
/tests/components/myq/ @ehendrix23
|
||||||
/homeassistant/components/mysensors/ @MartinHjelmare @functionpointer
|
/homeassistant/components/mysensors/ @MartinHjelmare @functionpointer
|
||||||
/tests/components/mysensors/ @MartinHjelmare @functionpointer
|
/tests/components/mysensors/ @MartinHjelmare @functionpointer
|
||||||
/homeassistant/components/mystrom/ @fabaff
|
/homeassistant/components/mystrom/ @fabaff
|
||||||
|
/tests/components/mystrom/ @fabaff
|
||||||
/homeassistant/components/nam/ @bieniu
|
/homeassistant/components/nam/ @bieniu
|
||||||
/tests/components/nam/ @bieniu
|
/tests/components/nam/ @bieniu
|
||||||
/homeassistant/components/nanoleaf/ @milanmeu
|
/homeassistant/components/nanoleaf/ @milanmeu
|
||||||
@@ -878,6 +887,7 @@ build.json @home-assistant/supervisor
|
|||||||
/homeassistant/components/opengarage/ @danielhiversen
|
/homeassistant/components/opengarage/ @danielhiversen
|
||||||
/tests/components/opengarage/ @danielhiversen
|
/tests/components/opengarage/ @danielhiversen
|
||||||
/homeassistant/components/openhome/ @bazwilliams
|
/homeassistant/components/openhome/ @bazwilliams
|
||||||
|
/tests/components/openhome/ @bazwilliams
|
||||||
/homeassistant/components/opensky/ @joostlek
|
/homeassistant/components/opensky/ @joostlek
|
||||||
/homeassistant/components/opentherm_gw/ @mvn23
|
/homeassistant/components/opentherm_gw/ @mvn23
|
||||||
/tests/components/opentherm_gw/ @mvn23
|
/tests/components/opentherm_gw/ @mvn23
|
||||||
@@ -961,6 +971,8 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/qingping/ @bdraco @skgsergio
|
/tests/components/qingping/ @bdraco @skgsergio
|
||||||
/homeassistant/components/qld_bushfire/ @exxamalte
|
/homeassistant/components/qld_bushfire/ @exxamalte
|
||||||
/tests/components/qld_bushfire/ @exxamalte
|
/tests/components/qld_bushfire/ @exxamalte
|
||||||
|
/homeassistant/components/qnap/ @disforw
|
||||||
|
/tests/components/qnap/ @disforw
|
||||||
/homeassistant/components/qnap_qsw/ @Noltari
|
/homeassistant/components/qnap_qsw/ @Noltari
|
||||||
/tests/components/qnap_qsw/ @Noltari
|
/tests/components/qnap_qsw/ @Noltari
|
||||||
/homeassistant/components/quantum_gateway/ @cisasteelersfan
|
/homeassistant/components/quantum_gateway/ @cisasteelersfan
|
||||||
@@ -999,6 +1011,8 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/remote/ @home-assistant/core
|
/tests/components/remote/ @home-assistant/core
|
||||||
/homeassistant/components/renault/ @epenet
|
/homeassistant/components/renault/ @epenet
|
||||||
/tests/components/renault/ @epenet
|
/tests/components/renault/ @epenet
|
||||||
|
/homeassistant/components/renson/ @jimmyd-be
|
||||||
|
/tests/components/renson/ @jimmyd-be
|
||||||
/homeassistant/components/reolink/ @starkillerOG
|
/homeassistant/components/reolink/ @starkillerOG
|
||||||
/tests/components/reolink/ @starkillerOG
|
/tests/components/reolink/ @starkillerOG
|
||||||
/homeassistant/components/repairs/ @home-assistant/core
|
/homeassistant/components/repairs/ @home-assistant/core
|
||||||
@@ -1068,8 +1082,6 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/select/ @home-assistant/core
|
/tests/components/select/ @home-assistant/core
|
||||||
/homeassistant/components/sense/ @kbickar
|
/homeassistant/components/sense/ @kbickar
|
||||||
/tests/components/sense/ @kbickar
|
/tests/components/sense/ @kbickar
|
||||||
/homeassistant/components/senseme/ @mikelawrence @bdraco
|
|
||||||
/tests/components/senseme/ @mikelawrence @bdraco
|
|
||||||
/homeassistant/components/sensibo/ @andrey-git @gjohansson-ST
|
/homeassistant/components/sensibo/ @andrey-git @gjohansson-ST
|
||||||
/tests/components/sensibo/ @andrey-git @gjohansson-ST
|
/tests/components/sensibo/ @andrey-git @gjohansson-ST
|
||||||
/homeassistant/components/sensirion_ble/ @akx
|
/homeassistant/components/sensirion_ble/ @akx
|
||||||
@@ -1292,6 +1304,8 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/twentemilieu/ @frenck
|
/tests/components/twentemilieu/ @frenck
|
||||||
/homeassistant/components/twinkly/ @dr1rrb @Robbie1221
|
/homeassistant/components/twinkly/ @dr1rrb @Robbie1221
|
||||||
/tests/components/twinkly/ @dr1rrb @Robbie1221
|
/tests/components/twinkly/ @dr1rrb @Robbie1221
|
||||||
|
/homeassistant/components/twitch/ @joostlek
|
||||||
|
/tests/components/twitch/ @joostlek
|
||||||
/homeassistant/components/ukraine_alarm/ @PaulAnnekov
|
/homeassistant/components/ukraine_alarm/ @PaulAnnekov
|
||||||
/tests/components/ukraine_alarm/ @PaulAnnekov
|
/tests/components/ukraine_alarm/ @PaulAnnekov
|
||||||
/homeassistant/components/unifi/ @Kane610
|
/homeassistant/components/unifi/ @Kane610
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
FROM mcr.microsoft.com/vscode/devcontainers/python:0-3.10
|
FROM mcr.microsoft.com/vscode/devcontainers/python:0-3.11
|
||||||
|
|
||||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||||
|
|
||||||
|
@@ -21,7 +21,7 @@ If you run into issues while using Home Assistant or during development
|
|||||||
of a component, check the `Home Assistant help section <https://home-assistant.io/help/>`__ of our website for further help and information.
|
of a component, check the `Home Assistant help section <https://home-assistant.io/help/>`__ of our website for further help and information.
|
||||||
|
|
||||||
.. |Chat Status| image:: https://img.shields.io/discord/330944238910963714.svg
|
.. |Chat Status| image:: https://img.shields.io/discord/330944238910963714.svg
|
||||||
:target: https://discord.gg/c5DvZ4e
|
:target: https://www.home-assistant.io/join-chat/
|
||||||
.. |screenshot-states| image:: https://raw.githubusercontent.com/home-assistant/core/master/docs/screenshots.png
|
.. |screenshot-states| image:: https://raw.githubusercontent.com/home-assistant/core/master/docs/screenshots.png
|
||||||
:target: https://demo.home-assistant.io
|
:target: https://demo.home-assistant.io
|
||||||
.. |screenshot-integrations| image:: https://raw.githubusercontent.com/home-assistant/core/dev/docs/screenshot-integrations.png
|
.. |screenshot-integrations| image:: https://raw.githubusercontent.com/home-assistant/core/dev/docs/screenshot-integrations.png
|
||||||
|
16
build.yaml
16
build.yaml
@@ -1,14 +1,16 @@
|
|||||||
image: homeassistant/{arch}-homeassistant
|
image: ghcr.io/home-assistant/{arch}-homeassistant
|
||||||
shadow_repository: ghcr.io/home-assistant
|
|
||||||
build_from:
|
build_from:
|
||||||
aarch64: ghcr.io/home-assistant/aarch64-homeassistant-base:2023.06.0
|
aarch64: ghcr.io/home-assistant/aarch64-homeassistant-base:2023.06.1
|
||||||
armhf: ghcr.io/home-assistant/armhf-homeassistant-base:2023.06.0
|
armhf: ghcr.io/home-assistant/armhf-homeassistant-base:2023.06.1
|
||||||
armv7: ghcr.io/home-assistant/armv7-homeassistant-base:2023.06.0
|
armv7: ghcr.io/home-assistant/armv7-homeassistant-base:2023.06.1
|
||||||
amd64: ghcr.io/home-assistant/amd64-homeassistant-base:2023.06.0
|
amd64: ghcr.io/home-assistant/amd64-homeassistant-base:2023.06.1
|
||||||
i386: ghcr.io/home-assistant/i386-homeassistant-base:2023.06.0
|
i386: ghcr.io/home-assistant/i386-homeassistant-base:2023.06.1
|
||||||
codenotary:
|
codenotary:
|
||||||
signer: notary@home-assistant.io
|
signer: notary@home-assistant.io
|
||||||
base_image: notary@home-assistant.io
|
base_image: notary@home-assistant.io
|
||||||
|
cosign:
|
||||||
|
base_identity: https://github.com/home-assistant/docker/.*
|
||||||
|
identity: https://github.com/home-assistant/core/.*
|
||||||
labels:
|
labels:
|
||||||
io.hass.type: core
|
io.hass.type: core
|
||||||
org.opencontainers.image.title: Home Assistant
|
org.opencontainers.image.title: Home Assistant
|
||||||
|
@@ -13,8 +13,8 @@ from homeassistant.const import CONF_COMMAND
|
|||||||
from homeassistant.data_entry_flow import FlowResult
|
from homeassistant.data_entry_flow import FlowResult
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
|
|
||||||
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
|
|
||||||
from ..models import Credentials, UserMeta
|
from ..models import Credentials, UserMeta
|
||||||
|
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
|
||||||
|
|
||||||
CONF_ARGS = "args"
|
CONF_ARGS = "args"
|
||||||
CONF_META = "meta"
|
CONF_META = "meta"
|
||||||
|
@@ -16,8 +16,8 @@ from homeassistant.data_entry_flow import FlowResult
|
|||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers.storage import Store
|
from homeassistant.helpers.storage import Store
|
||||||
|
|
||||||
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
|
|
||||||
from ..models import Credentials, UserMeta
|
from ..models import Credentials, UserMeta
|
||||||
|
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
|
||||||
|
|
||||||
STORAGE_VERSION = 1
|
STORAGE_VERSION = 1
|
||||||
STORAGE_KEY = "auth_provider.homeassistant"
|
STORAGE_KEY = "auth_provider.homeassistant"
|
||||||
|
@@ -11,8 +11,8 @@ from homeassistant.core import callback
|
|||||||
from homeassistant.data_entry_flow import FlowResult
|
from homeassistant.data_entry_flow import FlowResult
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
|
|
||||||
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
|
|
||||||
from ..models import Credentials, UserMeta
|
from ..models import Credentials, UserMeta
|
||||||
|
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
|
||||||
|
|
||||||
USER_SCHEMA = vol.Schema(
|
USER_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
|
@@ -15,8 +15,8 @@ from homeassistant.data_entry_flow import FlowResult
|
|||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
|
|
||||||
from ..models import Credentials, UserMeta
|
from ..models import Credentials, UserMeta
|
||||||
|
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
|
||||||
|
|
||||||
AUTH_PROVIDER_TYPE = "legacy_api_password"
|
AUTH_PROVIDER_TYPE = "legacy_api_password"
|
||||||
CONF_API_PASSWORD = "api_password"
|
CONF_API_PASSWORD = "api_password"
|
||||||
|
@@ -23,9 +23,9 @@ from homeassistant.data_entry_flow import FlowResult
|
|||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
|
|
||||||
from .. import InvalidAuthError
|
from .. import InvalidAuthError
|
||||||
from ..models import Credentials, RefreshToken, UserMeta
|
from ..models import Credentials, RefreshToken, UserMeta
|
||||||
|
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
|
||||||
|
|
||||||
IPAddress = IPv4Address | IPv6Address
|
IPAddress = IPv4Address | IPv6Address
|
||||||
IPNetwork = IPv4Network | IPv6Network
|
IPNetwork = IPv4Network | IPv6Network
|
||||||
|
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."""
|
"""An alarm_control_panel implementation for Abode."""
|
||||||
|
|
||||||
_attr_icon = ICON
|
_attr_icon = ICON
|
||||||
|
_attr_name = None
|
||||||
_attr_code_arm_required = False
|
_attr_code_arm_required = False
|
||||||
_attr_supported_features = (
|
_attr_supported_features = (
|
||||||
AlarmControlPanelEntityFeature.ARM_HOME
|
AlarmControlPanelEntityFeature.ARM_HOME
|
||||||
|
@@ -42,6 +42,7 @@ async def async_setup_entry(
|
|||||||
class AbodeBinarySensor(AbodeDevice, BinarySensorEntity):
|
class AbodeBinarySensor(AbodeDevice, BinarySensorEntity):
|
||||||
"""A binary sensor implementation for Abode device."""
|
"""A binary sensor implementation for Abode device."""
|
||||||
|
|
||||||
|
_attr_name = None
|
||||||
_device: ABBinarySensor
|
_device: ABBinarySensor
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@@ -39,6 +39,7 @@ class AbodeCamera(AbodeDevice, Camera):
|
|||||||
"""Representation of an Abode camera."""
|
"""Representation of an Abode camera."""
|
||||||
|
|
||||||
_device: AbodeCam
|
_device: AbodeCam
|
||||||
|
_attr_name = None
|
||||||
|
|
||||||
def __init__(self, data: AbodeSystem, device: AbodeDev, event: Event) -> None:
|
def __init__(self, data: AbodeSystem, device: AbodeDev, event: Event) -> None:
|
||||||
"""Initialize the Abode device."""
|
"""Initialize the Abode device."""
|
||||||
|
@@ -29,6 +29,7 @@ class AbodeCover(AbodeDevice, CoverEntity):
|
|||||||
"""Representation of an Abode cover."""
|
"""Representation of an Abode cover."""
|
||||||
|
|
||||||
_device: AbodeCV
|
_device: AbodeCV
|
||||||
|
_attr_name = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_closed(self) -> bool:
|
def is_closed(self) -> bool:
|
||||||
|
@@ -42,6 +42,7 @@ class AbodeLight(AbodeDevice, LightEntity):
|
|||||||
"""Representation of an Abode light."""
|
"""Representation of an Abode light."""
|
||||||
|
|
||||||
_device: AbodeLT
|
_device: AbodeLT
|
||||||
|
_attr_name = None
|
||||||
|
|
||||||
def turn_on(self, **kwargs: Any) -> None:
|
def turn_on(self, **kwargs: Any) -> None:
|
||||||
"""Turn on the light."""
|
"""Turn on the light."""
|
||||||
|
@@ -29,6 +29,7 @@ class AbodeLock(AbodeDevice, LockEntity):
|
|||||||
"""Representation of an Abode lock."""
|
"""Representation of an Abode lock."""
|
||||||
|
|
||||||
_device: AbodeLK
|
_device: AbodeLK
|
||||||
|
_attr_name = None
|
||||||
|
|
||||||
def lock(self, **kwargs: Any) -> None:
|
def lock(self, **kwargs: Any) -> None:
|
||||||
"""Lock the device."""
|
"""Lock the device."""
|
||||||
|
@@ -22,17 +22,14 @@ from .const import DOMAIN
|
|||||||
SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
|
SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key=CONST.TEMP_STATUS_KEY,
|
key=CONST.TEMP_STATUS_KEY,
|
||||||
name="Temperature",
|
|
||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key=CONST.HUMI_STATUS_KEY,
|
key=CONST.HUMI_STATUS_KEY,
|
||||||
name="Humidity",
|
|
||||||
device_class=SensorDeviceClass.HUMIDITY,
|
device_class=SensorDeviceClass.HUMIDITY,
|
||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key=CONST.LUX_STATUS_KEY,
|
key=CONST.LUX_STATUS_KEY,
|
||||||
name="Lux",
|
|
||||||
device_class=SensorDeviceClass.ILLUMINANCE,
|
device_class=SensorDeviceClass.ILLUMINANCE,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@@ -44,6 +44,7 @@ class AbodeSwitch(AbodeDevice, SwitchEntity):
|
|||||||
"""Representation of an Abode switch."""
|
"""Representation of an Abode switch."""
|
||||||
|
|
||||||
_device: AbodeSW
|
_device: AbodeSW
|
||||||
|
_attr_name = None
|
||||||
|
|
||||||
def turn_on(self, **kwargs: Any) -> None:
|
def turn_on(self, **kwargs: Any) -> None:
|
||||||
"""Turn on the device."""
|
"""Turn on the device."""
|
||||||
|
@@ -4,10 +4,13 @@ from __future__ import annotations
|
|||||||
from typing import cast
|
from typing import cast
|
||||||
|
|
||||||
from homeassistant.components.weather import (
|
from homeassistant.components.weather import (
|
||||||
|
ATTR_FORECAST_CLOUD_COVERAGE,
|
||||||
ATTR_FORECAST_CONDITION,
|
ATTR_FORECAST_CONDITION,
|
||||||
|
ATTR_FORECAST_NATIVE_APPARENT_TEMP,
|
||||||
ATTR_FORECAST_NATIVE_PRECIPITATION,
|
ATTR_FORECAST_NATIVE_PRECIPITATION,
|
||||||
ATTR_FORECAST_NATIVE_TEMP,
|
ATTR_FORECAST_NATIVE_TEMP,
|
||||||
ATTR_FORECAST_NATIVE_TEMP_LOW,
|
ATTR_FORECAST_NATIVE_TEMP_LOW,
|
||||||
|
ATTR_FORECAST_NATIVE_WIND_GUST_SPEED,
|
||||||
ATTR_FORECAST_NATIVE_WIND_SPEED,
|
ATTR_FORECAST_NATIVE_WIND_SPEED,
|
||||||
ATTR_FORECAST_PRECIPITATION_PROBABILITY,
|
ATTR_FORECAST_PRECIPITATION_PROBABILITY,
|
||||||
ATTR_FORECAST_TIME,
|
ATTR_FORECAST_TIME,
|
||||||
@@ -29,7 +32,16 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
|||||||
from homeassistant.util.dt import utc_from_timestamp
|
from homeassistant.util.dt import utc_from_timestamp
|
||||||
|
|
||||||
from . import AccuWeatherDataUpdateCoordinator
|
from . import AccuWeatherDataUpdateCoordinator
|
||||||
from .const import API_METRIC, ATTR_FORECAST, ATTRIBUTION, CONDITION_CLASSES, DOMAIN
|
from .const import (
|
||||||
|
API_METRIC,
|
||||||
|
ATTR_DIRECTION,
|
||||||
|
ATTR_FORECAST,
|
||||||
|
ATTR_SPEED,
|
||||||
|
ATTR_VALUE,
|
||||||
|
ATTRIBUTION,
|
||||||
|
CONDITION_CLASSES,
|
||||||
|
DOMAIN,
|
||||||
|
)
|
||||||
|
|
||||||
PARALLEL_UPDATES = 1
|
PARALLEL_UPDATES = 1
|
||||||
|
|
||||||
@@ -50,6 +62,7 @@ class AccuWeatherEntity(
|
|||||||
"""Define an AccuWeather entity."""
|
"""Define an AccuWeather entity."""
|
||||||
|
|
||||||
_attr_has_entity_name = True
|
_attr_has_entity_name = True
|
||||||
|
_attr_name = None
|
||||||
|
|
||||||
def __init__(self, coordinator: AccuWeatherDataUpdateCoordinator) -> None:
|
def __init__(self, coordinator: AccuWeatherDataUpdateCoordinator) -> None:
|
||||||
"""Initialize."""
|
"""Initialize."""
|
||||||
@@ -78,35 +91,61 @@ class AccuWeatherEntity(
|
|||||||
except IndexError:
|
except IndexError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def cloud_coverage(self) -> float:
|
||||||
|
"""Return the Cloud coverage in %."""
|
||||||
|
return cast(float, self.coordinator.data["CloudCover"])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def native_apparent_temperature(self) -> float:
|
||||||
|
"""Return the apparent temperature."""
|
||||||
|
return cast(
|
||||||
|
float, self.coordinator.data["ApparentTemperature"][API_METRIC][ATTR_VALUE]
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def native_temperature(self) -> float:
|
def native_temperature(self) -> float:
|
||||||
"""Return the temperature."""
|
"""Return the temperature."""
|
||||||
return cast(float, self.coordinator.data["Temperature"][API_METRIC]["Value"])
|
return cast(float, self.coordinator.data["Temperature"][API_METRIC][ATTR_VALUE])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def native_pressure(self) -> float:
|
def native_pressure(self) -> float:
|
||||||
"""Return the pressure."""
|
"""Return the pressure."""
|
||||||
return cast(float, self.coordinator.data["Pressure"][API_METRIC]["Value"])
|
return cast(float, self.coordinator.data["Pressure"][API_METRIC][ATTR_VALUE])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def native_dew_point(self) -> float:
|
||||||
|
"""Return the dew point."""
|
||||||
|
return cast(float, self.coordinator.data["DewPoint"][API_METRIC][ATTR_VALUE])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def humidity(self) -> int:
|
def humidity(self) -> int:
|
||||||
"""Return the humidity."""
|
"""Return the humidity."""
|
||||||
return cast(int, self.coordinator.data["RelativeHumidity"])
|
return cast(int, self.coordinator.data["RelativeHumidity"])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def native_wind_gust_speed(self) -> float:
|
||||||
|
"""Return the wind gust speed."""
|
||||||
|
return cast(
|
||||||
|
float, self.coordinator.data["WindGust"][ATTR_SPEED][API_METRIC][ATTR_VALUE]
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def native_wind_speed(self) -> float:
|
def native_wind_speed(self) -> float:
|
||||||
"""Return the wind speed."""
|
"""Return the wind speed."""
|
||||||
return cast(float, self.coordinator.data["Wind"]["Speed"][API_METRIC]["Value"])
|
return cast(
|
||||||
|
float, self.coordinator.data["Wind"][ATTR_SPEED][API_METRIC][ATTR_VALUE]
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def wind_bearing(self) -> int:
|
def wind_bearing(self) -> int:
|
||||||
"""Return the wind bearing."""
|
"""Return the wind bearing."""
|
||||||
return cast(int, self.coordinator.data["Wind"]["Direction"]["Degrees"])
|
return cast(int, self.coordinator.data["Wind"][ATTR_DIRECTION]["Degrees"])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def native_visibility(self) -> float:
|
def native_visibility(self) -> float:
|
||||||
"""Return the visibility."""
|
"""Return the visibility."""
|
||||||
return cast(float, self.coordinator.data["Visibility"][API_METRIC]["Value"])
|
return cast(float, self.coordinator.data["Visibility"][API_METRIC][ATTR_VALUE])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def forecast(self) -> list[Forecast] | None:
|
def forecast(self) -> list[Forecast] | None:
|
||||||
@@ -117,14 +156,23 @@ class AccuWeatherEntity(
|
|||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
ATTR_FORECAST_TIME: utc_from_timestamp(item["EpochDate"]).isoformat(),
|
ATTR_FORECAST_TIME: utc_from_timestamp(item["EpochDate"]).isoformat(),
|
||||||
ATTR_FORECAST_NATIVE_TEMP: item["TemperatureMax"]["Value"],
|
ATTR_FORECAST_CLOUD_COVERAGE: item["CloudCoverDay"],
|
||||||
ATTR_FORECAST_NATIVE_TEMP_LOW: item["TemperatureMin"]["Value"],
|
ATTR_FORECAST_NATIVE_TEMP: item["TemperatureMax"][ATTR_VALUE],
|
||||||
ATTR_FORECAST_NATIVE_PRECIPITATION: item["TotalLiquidDay"]["Value"],
|
ATTR_FORECAST_NATIVE_TEMP_LOW: item["TemperatureMin"][ATTR_VALUE],
|
||||||
|
ATTR_FORECAST_NATIVE_APPARENT_TEMP: item["RealFeelTemperatureMax"][
|
||||||
|
ATTR_VALUE
|
||||||
|
],
|
||||||
|
ATTR_FORECAST_NATIVE_PRECIPITATION: item["TotalLiquidDay"][ATTR_VALUE],
|
||||||
ATTR_FORECAST_PRECIPITATION_PROBABILITY: item[
|
ATTR_FORECAST_PRECIPITATION_PROBABILITY: item[
|
||||||
"PrecipitationProbabilityDay"
|
"PrecipitationProbabilityDay"
|
||||||
],
|
],
|
||||||
ATTR_FORECAST_NATIVE_WIND_SPEED: item["WindDay"]["Speed"]["Value"],
|
ATTR_FORECAST_NATIVE_WIND_SPEED: item["WindDay"][ATTR_SPEED][
|
||||||
ATTR_FORECAST_WIND_BEARING: item["WindDay"]["Direction"]["Degrees"],
|
ATTR_VALUE
|
||||||
|
],
|
||||||
|
ATTR_FORECAST_NATIVE_WIND_GUST_SPEED: item["WindGustDay"][ATTR_SPEED][
|
||||||
|
ATTR_VALUE
|
||||||
|
],
|
||||||
|
ATTR_FORECAST_WIND_BEARING: item["WindDay"][ATTR_DIRECTION]["Degrees"],
|
||||||
ATTR_FORECAST_CONDITION: [
|
ATTR_FORECAST_CONDITION: [
|
||||||
k for k, v in CONDITION_CLASSES.items() if item["IconDay"] in v
|
k for k, v in CONDITION_CLASSES.items() if item["IconDay"] in v
|
||||||
][0],
|
][0],
|
||||||
|
@@ -14,6 +14,7 @@ class AcmedaBase(entity.Entity):
|
|||||||
"""Base representation of an Acmeda roller."""
|
"""Base representation of an Acmeda roller."""
|
||||||
|
|
||||||
_attr_should_poll = False
|
_attr_should_poll = False
|
||||||
|
_attr_has_entity_name = True
|
||||||
|
|
||||||
def __init__(self, roller: aiopulse.Roller) -> None:
|
def __init__(self, roller: aiopulse.Roller) -> None:
|
||||||
"""Initialize the roller."""
|
"""Initialize the roller."""
|
||||||
@@ -72,11 +73,6 @@ class AcmedaBase(entity.Entity):
|
|||||||
"""Return the ID of this roller."""
|
"""Return the ID of this roller."""
|
||||||
return self.roller.id
|
return self.roller.id
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self) -> str | None:
|
|
||||||
"""Return the name of roller."""
|
|
||||||
return self.roller.name
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_info(self) -> entity.DeviceInfo:
|
def device_info(self) -> entity.DeviceInfo:
|
||||||
"""Return the device info."""
|
"""Return the device info."""
|
||||||
|
@@ -45,7 +45,9 @@ async def async_setup_entry(
|
|||||||
|
|
||||||
|
|
||||||
class AcmedaCover(AcmedaBase, CoverEntity):
|
class AcmedaCover(AcmedaBase, CoverEntity):
|
||||||
"""Representation of a Acmeda cover device."""
|
"""Representation of an Acmeda cover device."""
|
||||||
|
|
||||||
|
_attr_name = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_cover_position(self) -> int | None:
|
def current_cover_position(self) -> int | None:
|
||||||
|
@@ -40,16 +40,11 @@ async def async_setup_entry(
|
|||||||
|
|
||||||
|
|
||||||
class AcmedaBattery(AcmedaBase, SensorEntity):
|
class AcmedaBattery(AcmedaBase, SensorEntity):
|
||||||
"""Representation of a Acmeda cover device."""
|
"""Representation of an Acmeda cover sensor."""
|
||||||
|
|
||||||
_attr_device_class = SensorDeviceClass.BATTERY
|
_attr_device_class = SensorDeviceClass.BATTERY
|
||||||
_attr_native_unit_of_measurement = PERCENTAGE
|
_attr_native_unit_of_measurement = PERCENTAGE
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self) -> str:
|
|
||||||
"""Return the name of roller."""
|
|
||||||
return f"{super().name} Battery"
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def native_value(self) -> float | int | None:
|
def native_value(self) -> float | int | None:
|
||||||
"""Return the state of the device."""
|
"""Return the state of the device."""
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
"""Support for Adax wifi-enabled home heaters."""
|
"""Support for Adax wifi-enabled home heaters."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any
|
from typing import Any, cast
|
||||||
|
|
||||||
from adax import Adax
|
from adax import Adax
|
||||||
from adax_local import Adax as AdaxLocal
|
from adax_local import Adax as AdaxLocal
|
||||||
@@ -79,7 +79,10 @@ class AdaxDevice(ClimateEntity):
|
|||||||
self._attr_unique_id = f"{heater_data['homeId']}_{heater_data['id']}"
|
self._attr_unique_id = f"{heater_data['homeId']}_{heater_data['id']}"
|
||||||
self._attr_device_info = DeviceInfo(
|
self._attr_device_info = DeviceInfo(
|
||||||
identifiers={(DOMAIN, heater_data["id"])},
|
identifiers={(DOMAIN, heater_data["id"])},
|
||||||
name=self.name,
|
# Instead of setting the device name to the entity name, adax
|
||||||
|
# should be updated to set has_entity_name = True, and set the entity
|
||||||
|
# name to None
|
||||||
|
name=cast(str | None, self.name),
|
||||||
manufacturer="Adax",
|
manufacturer="Adax",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@@ -39,56 +39,56 @@ class AdGuardHomeEntityDescription(
|
|||||||
SENSORS: tuple[AdGuardHomeEntityDescription, ...] = (
|
SENSORS: tuple[AdGuardHomeEntityDescription, ...] = (
|
||||||
AdGuardHomeEntityDescription(
|
AdGuardHomeEntityDescription(
|
||||||
key="dns_queries",
|
key="dns_queries",
|
||||||
name="DNS queries",
|
translation_key="dns_queries",
|
||||||
icon="mdi:magnify",
|
icon="mdi:magnify",
|
||||||
native_unit_of_measurement="queries",
|
native_unit_of_measurement="queries",
|
||||||
value_fn=lambda adguard: adguard.stats.dns_queries(),
|
value_fn=lambda adguard: adguard.stats.dns_queries(),
|
||||||
),
|
),
|
||||||
AdGuardHomeEntityDescription(
|
AdGuardHomeEntityDescription(
|
||||||
key="blocked_filtering",
|
key="blocked_filtering",
|
||||||
name="DNS queries blocked",
|
translation_key="dns_queries_blocked",
|
||||||
icon="mdi:magnify-close",
|
icon="mdi:magnify-close",
|
||||||
native_unit_of_measurement="queries",
|
native_unit_of_measurement="queries",
|
||||||
value_fn=lambda adguard: adguard.stats.blocked_filtering(),
|
value_fn=lambda adguard: adguard.stats.blocked_filtering(),
|
||||||
),
|
),
|
||||||
AdGuardHomeEntityDescription(
|
AdGuardHomeEntityDescription(
|
||||||
key="blocked_percentage",
|
key="blocked_percentage",
|
||||||
name="DNS queries blocked ratio",
|
translation_key="dns_queries_blocked_ratio",
|
||||||
icon="mdi:magnify-close",
|
icon="mdi:magnify-close",
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
value_fn=lambda adguard: adguard.stats.blocked_percentage(),
|
value_fn=lambda adguard: adguard.stats.blocked_percentage(),
|
||||||
),
|
),
|
||||||
AdGuardHomeEntityDescription(
|
AdGuardHomeEntityDescription(
|
||||||
key="blocked_parental",
|
key="blocked_parental",
|
||||||
name="Parental control blocked",
|
translation_key="parental_control_blocked",
|
||||||
icon="mdi:human-male-girl",
|
icon="mdi:human-male-girl",
|
||||||
native_unit_of_measurement="requests",
|
native_unit_of_measurement="requests",
|
||||||
value_fn=lambda adguard: adguard.stats.replaced_parental(),
|
value_fn=lambda adguard: adguard.stats.replaced_parental(),
|
||||||
),
|
),
|
||||||
AdGuardHomeEntityDescription(
|
AdGuardHomeEntityDescription(
|
||||||
key="blocked_safebrowsing",
|
key="blocked_safebrowsing",
|
||||||
name="Safe browsing blocked",
|
translation_key="safe_browsing_blocked",
|
||||||
icon="mdi:shield-half-full",
|
icon="mdi:shield-half-full",
|
||||||
native_unit_of_measurement="requests",
|
native_unit_of_measurement="requests",
|
||||||
value_fn=lambda adguard: adguard.stats.replaced_safebrowsing(),
|
value_fn=lambda adguard: adguard.stats.replaced_safebrowsing(),
|
||||||
),
|
),
|
||||||
AdGuardHomeEntityDescription(
|
AdGuardHomeEntityDescription(
|
||||||
key="enforced_safesearch",
|
key="enforced_safesearch",
|
||||||
name="Safe searches enforced",
|
translation_key="safe_searches_enforced",
|
||||||
icon="mdi:shield-search",
|
icon="mdi:shield-search",
|
||||||
native_unit_of_measurement="requests",
|
native_unit_of_measurement="requests",
|
||||||
value_fn=lambda adguard: adguard.stats.replaced_safesearch(),
|
value_fn=lambda adguard: adguard.stats.replaced_safesearch(),
|
||||||
),
|
),
|
||||||
AdGuardHomeEntityDescription(
|
AdGuardHomeEntityDescription(
|
||||||
key="average_speed",
|
key="average_speed",
|
||||||
name="Average processing speed",
|
translation_key="average_processing_speed",
|
||||||
icon="mdi:speedometer",
|
icon="mdi:speedometer",
|
||||||
native_unit_of_measurement=UnitOfTime.MILLISECONDS,
|
native_unit_of_measurement=UnitOfTime.MILLISECONDS,
|
||||||
value_fn=lambda adguard: adguard.stats.avg_processing_time(),
|
value_fn=lambda adguard: adguard.stats.avg_processing_time(),
|
||||||
),
|
),
|
||||||
AdGuardHomeEntityDescription(
|
AdGuardHomeEntityDescription(
|
||||||
key="rules_count",
|
key="rules_count",
|
||||||
name="Rules count",
|
translation_key="rules_count",
|
||||||
icon="mdi:counter",
|
icon="mdi:counter",
|
||||||
native_unit_of_measurement="rules",
|
native_unit_of_measurement="rules",
|
||||||
value_fn=lambda adguard: adguard.filtering.rules_count(allowlist=False),
|
value_fn=lambda adguard: adguard.filtering.rules_count(allowlist=False),
|
||||||
|
@@ -24,5 +24,53 @@
|
|||||||
"existing_instance_updated": "Updated existing configuration.",
|
"existing_instance_updated": "Updated existing configuration.",
|
||||||
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]"
|
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"entity": {
|
||||||
|
"sensor": {
|
||||||
|
"dns_queries": {
|
||||||
|
"name": "DNS queries"
|
||||||
|
},
|
||||||
|
"dns_queries_blocked": {
|
||||||
|
"name": "DNS queries blocked"
|
||||||
|
},
|
||||||
|
"dns_queries_blocked_ratio": {
|
||||||
|
"name": "DNS queries blocked ratio"
|
||||||
|
},
|
||||||
|
"parental_control_blocked": {
|
||||||
|
"name": "Parental control blocked"
|
||||||
|
},
|
||||||
|
"safe_browsing_blocked": {
|
||||||
|
"name": "Safe browsing blocked"
|
||||||
|
},
|
||||||
|
"safe_searches_enforced": {
|
||||||
|
"name": "Safe searches enforced"
|
||||||
|
},
|
||||||
|
"average_processing_speed": {
|
||||||
|
"name": "Average processing speed"
|
||||||
|
},
|
||||||
|
"rules_count": {
|
||||||
|
"name": "Rules count"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"switch": {
|
||||||
|
"protection": {
|
||||||
|
"name": "Protection"
|
||||||
|
},
|
||||||
|
"parental": {
|
||||||
|
"name": "Parental control"
|
||||||
|
},
|
||||||
|
"safe_search": {
|
||||||
|
"name": "Safe search"
|
||||||
|
},
|
||||||
|
"safe_browsing": {
|
||||||
|
"name": "Safe browsing"
|
||||||
|
},
|
||||||
|
"filtering": {
|
||||||
|
"name": "Filtering"
|
||||||
|
},
|
||||||
|
"query_log": {
|
||||||
|
"name": "Query log"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -40,7 +40,7 @@ class AdGuardHomeSwitchEntityDescription(
|
|||||||
SWITCHES: tuple[AdGuardHomeSwitchEntityDescription, ...] = (
|
SWITCHES: tuple[AdGuardHomeSwitchEntityDescription, ...] = (
|
||||||
AdGuardHomeSwitchEntityDescription(
|
AdGuardHomeSwitchEntityDescription(
|
||||||
key="protection",
|
key="protection",
|
||||||
name="Protection",
|
translation_key="protection",
|
||||||
icon="mdi:shield-check",
|
icon="mdi:shield-check",
|
||||||
is_on_fn=lambda adguard: adguard.protection_enabled,
|
is_on_fn=lambda adguard: adguard.protection_enabled,
|
||||||
turn_on_fn=lambda adguard: adguard.enable_protection,
|
turn_on_fn=lambda adguard: adguard.enable_protection,
|
||||||
@@ -48,7 +48,7 @@ SWITCHES: tuple[AdGuardHomeSwitchEntityDescription, ...] = (
|
|||||||
),
|
),
|
||||||
AdGuardHomeSwitchEntityDescription(
|
AdGuardHomeSwitchEntityDescription(
|
||||||
key="parental",
|
key="parental",
|
||||||
name="Parental control",
|
translation_key="parental",
|
||||||
icon="mdi:shield-check",
|
icon="mdi:shield-check",
|
||||||
is_on_fn=lambda adguard: adguard.parental.enabled,
|
is_on_fn=lambda adguard: adguard.parental.enabled,
|
||||||
turn_on_fn=lambda adguard: adguard.parental.enable,
|
turn_on_fn=lambda adguard: adguard.parental.enable,
|
||||||
@@ -56,7 +56,7 @@ SWITCHES: tuple[AdGuardHomeSwitchEntityDescription, ...] = (
|
|||||||
),
|
),
|
||||||
AdGuardHomeSwitchEntityDescription(
|
AdGuardHomeSwitchEntityDescription(
|
||||||
key="safesearch",
|
key="safesearch",
|
||||||
name="Safe search",
|
translation_key="safe_search",
|
||||||
icon="mdi:shield-check",
|
icon="mdi:shield-check",
|
||||||
is_on_fn=lambda adguard: adguard.safesearch.enabled,
|
is_on_fn=lambda adguard: adguard.safesearch.enabled,
|
||||||
turn_on_fn=lambda adguard: adguard.safesearch.enable,
|
turn_on_fn=lambda adguard: adguard.safesearch.enable,
|
||||||
@@ -64,7 +64,7 @@ SWITCHES: tuple[AdGuardHomeSwitchEntityDescription, ...] = (
|
|||||||
),
|
),
|
||||||
AdGuardHomeSwitchEntityDescription(
|
AdGuardHomeSwitchEntityDescription(
|
||||||
key="safebrowsing",
|
key="safebrowsing",
|
||||||
name="Safe browsing",
|
translation_key="safe_browsing",
|
||||||
icon="mdi:shield-check",
|
icon="mdi:shield-check",
|
||||||
is_on_fn=lambda adguard: adguard.safebrowsing.enabled,
|
is_on_fn=lambda adguard: adguard.safebrowsing.enabled,
|
||||||
turn_on_fn=lambda adguard: adguard.safebrowsing.enable,
|
turn_on_fn=lambda adguard: adguard.safebrowsing.enable,
|
||||||
@@ -72,7 +72,7 @@ SWITCHES: tuple[AdGuardHomeSwitchEntityDescription, ...] = (
|
|||||||
),
|
),
|
||||||
AdGuardHomeSwitchEntityDescription(
|
AdGuardHomeSwitchEntityDescription(
|
||||||
key="filtering",
|
key="filtering",
|
||||||
name="Filtering",
|
translation_key="filtering",
|
||||||
icon="mdi:shield-check",
|
icon="mdi:shield-check",
|
||||||
is_on_fn=lambda adguard: adguard.filtering.enabled,
|
is_on_fn=lambda adguard: adguard.filtering.enabled,
|
||||||
turn_on_fn=lambda adguard: adguard.filtering.enable,
|
turn_on_fn=lambda adguard: adguard.filtering.enable,
|
||||||
@@ -80,7 +80,7 @@ SWITCHES: tuple[AdGuardHomeSwitchEntityDescription, ...] = (
|
|||||||
),
|
),
|
||||||
AdGuardHomeSwitchEntityDescription(
|
AdGuardHomeSwitchEntityDescription(
|
||||||
key="querylog",
|
key="querylog",
|
||||||
name="Query log",
|
translation_key="query_log",
|
||||||
icon="mdi:shield-check",
|
icon="mdi:shield-check",
|
||||||
is_on_fn=lambda adguard: adguard.querylog.enabled,
|
is_on_fn=lambda adguard: adguard.querylog.enabled,
|
||||||
turn_on_fn=lambda adguard: adguard.querylog.enable,
|
turn_on_fn=lambda adguard: adguard.querylog.enable,
|
||||||
|
@@ -10,6 +10,7 @@ import homeassistant.helpers.config_validation as cv
|
|||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, StateType
|
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, StateType
|
||||||
|
|
||||||
|
from .. import ads
|
||||||
from . import (
|
from . import (
|
||||||
ADS_TYPEMAP,
|
ADS_TYPEMAP,
|
||||||
CONF_ADS_FACTOR,
|
CONF_ADS_FACTOR,
|
||||||
@@ -18,7 +19,6 @@ from . import (
|
|||||||
STATE_KEY_STATE,
|
STATE_KEY_STATE,
|
||||||
AdsEntity,
|
AdsEntity,
|
||||||
)
|
)
|
||||||
from .. import ads
|
|
||||||
|
|
||||||
DEFAULT_NAME = "ADS sensor"
|
DEFAULT_NAME = "ADS sensor"
|
||||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||||
|
@@ -90,6 +90,7 @@ class AdvantageAirAC(AdvantageAirAcEntity, ClimateEntity):
|
|||||||
_attr_target_temperature_step = PRECISION_WHOLE
|
_attr_target_temperature_step = PRECISION_WHOLE
|
||||||
_attr_max_temp = 32
|
_attr_max_temp = 32
|
||||||
_attr_min_temp = 16
|
_attr_min_temp = 16
|
||||||
|
_attr_name = None
|
||||||
|
|
||||||
_attr_hvac_modes = [
|
_attr_hvac_modes = [
|
||||||
HVACMode.OFF,
|
HVACMode.OFF,
|
||||||
|
@@ -9,17 +9,30 @@ from homeassistant.core import HomeAssistant
|
|||||||
|
|
||||||
from .const import DOMAIN as ADVANTAGE_AIR_DOMAIN
|
from .const import DOMAIN as ADVANTAGE_AIR_DOMAIN
|
||||||
|
|
||||||
TO_REDACT = ["dealerPhoneNumber", "latitude", "logoPIN", "longitude", "postCode"]
|
TO_REDACT = [
|
||||||
|
"dealerPhoneNumber",
|
||||||
|
"latitude",
|
||||||
|
"logoPIN",
|
||||||
|
"longitude",
|
||||||
|
"postCode",
|
||||||
|
"rid",
|
||||||
|
"deviceNames",
|
||||||
|
"deviceIds",
|
||||||
|
"deviceIdsV2",
|
||||||
|
"backupId",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
async def async_get_config_entry_diagnostics(
|
async def async_get_config_entry_diagnostics(
|
||||||
hass: HomeAssistant, config_entry: ConfigEntry
|
hass: HomeAssistant, config_entry: ConfigEntry
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""Return diagnostics for a config entry."""
|
"""Return diagnostics for a config entry."""
|
||||||
data = hass.data[ADVANTAGE_AIR_DOMAIN][config_entry.entry_id]["coordinator"].data
|
data = hass.data[ADVANTAGE_AIR_DOMAIN][config_entry.entry_id].coordinator.data
|
||||||
|
|
||||||
# Return only the relevant children
|
# Return only the relevant children
|
||||||
return {
|
return {
|
||||||
"aircons": data["aircons"],
|
"aircons": data.get("aircons"),
|
||||||
|
"myLights": data.get("myLights"),
|
||||||
|
"myThings": data.get("myThings"),
|
||||||
"system": async_redact_data(data["system"], TO_REDACT),
|
"system": async_redact_data(data["system"], TO_REDACT),
|
||||||
}
|
}
|
||||||
|
@@ -84,6 +84,8 @@ class AdvantageAirZoneEntity(AdvantageAirAcEntity):
|
|||||||
class AdvantageAirThingEntity(AdvantageAirEntity):
|
class AdvantageAirThingEntity(AdvantageAirEntity):
|
||||||
"""Parent class for Advantage Air Things Entities."""
|
"""Parent class for Advantage Air Things Entities."""
|
||||||
|
|
||||||
|
_attr_name = None
|
||||||
|
|
||||||
def __init__(self, instance: AdvantageAirData, thing: dict[str, Any]) -> None:
|
def __init__(self, instance: AdvantageAirData, thing: dict[str, Any]) -> None:
|
||||||
"""Initialize common aspects of an Advantage Air Things entity."""
|
"""Initialize common aspects of an Advantage Air Things entity."""
|
||||||
super().__init__(instance)
|
super().__init__(instance)
|
||||||
|
@@ -41,6 +41,7 @@ class AdvantageAirLight(AdvantageAirEntity, LightEntity):
|
|||||||
"""Representation of Advantage Air Light."""
|
"""Representation of Advantage Air Light."""
|
||||||
|
|
||||||
_attr_supported_color_modes = {ColorMode.ONOFF}
|
_attr_supported_color_modes = {ColorMode.ONOFF}
|
||||||
|
_attr_name = None
|
||||||
|
|
||||||
def __init__(self, instance: AdvantageAirData, light: dict[str, Any]) -> None:
|
def __init__(self, instance: AdvantageAirData, light: dict[str, Any]) -> None:
|
||||||
"""Initialize an Advantage Air Light."""
|
"""Initialize an Advantage Air Light."""
|
||||||
|
@@ -7,5 +7,5 @@
|
|||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["advantage_air"],
|
"loggers": ["advantage_air"],
|
||||||
"quality_scale": "platinum",
|
"quality_scale": "platinum",
|
||||||
"requirements": ["advantage_air==0.4.4"]
|
"requirements": ["advantage-air==0.4.4"]
|
||||||
}
|
}
|
||||||
|
@@ -80,7 +80,6 @@ SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = (
|
|||||||
AirlySensorEntityDescription(
|
AirlySensorEntityDescription(
|
||||||
key=ATTR_API_PM1,
|
key=ATTR_API_PM1,
|
||||||
device_class=SensorDeviceClass.PM1,
|
device_class=SensorDeviceClass.PM1,
|
||||||
translation_key="pm1",
|
|
||||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
suggested_display_precision=0,
|
suggested_display_precision=0,
|
||||||
@@ -88,7 +87,6 @@ SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = (
|
|||||||
AirlySensorEntityDescription(
|
AirlySensorEntityDescription(
|
||||||
key=ATTR_API_PM25,
|
key=ATTR_API_PM25,
|
||||||
device_class=SensorDeviceClass.PM25,
|
device_class=SensorDeviceClass.PM25,
|
||||||
translation_key="pm25",
|
|
||||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
suggested_display_precision=0,
|
suggested_display_precision=0,
|
||||||
@@ -100,7 +98,6 @@ SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = (
|
|||||||
AirlySensorEntityDescription(
|
AirlySensorEntityDescription(
|
||||||
key=ATTR_API_PM10,
|
key=ATTR_API_PM10,
|
||||||
device_class=SensorDeviceClass.PM10,
|
device_class=SensorDeviceClass.PM10,
|
||||||
translation_key="pm10",
|
|
||||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
suggested_display_precision=0,
|
suggested_display_precision=0,
|
||||||
@@ -112,7 +109,6 @@ SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = (
|
|||||||
AirlySensorEntityDescription(
|
AirlySensorEntityDescription(
|
||||||
key=ATTR_API_HUMIDITY,
|
key=ATTR_API_HUMIDITY,
|
||||||
device_class=SensorDeviceClass.HUMIDITY,
|
device_class=SensorDeviceClass.HUMIDITY,
|
||||||
translation_key="humidity",
|
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
suggested_display_precision=1,
|
suggested_display_precision=1,
|
||||||
@@ -120,7 +116,6 @@ SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = (
|
|||||||
AirlySensorEntityDescription(
|
AirlySensorEntityDescription(
|
||||||
key=ATTR_API_PRESSURE,
|
key=ATTR_API_PRESSURE,
|
||||||
device_class=SensorDeviceClass.PRESSURE,
|
device_class=SensorDeviceClass.PRESSURE,
|
||||||
translation_key="pressure",
|
|
||||||
native_unit_of_measurement=UnitOfPressure.HPA,
|
native_unit_of_measurement=UnitOfPressure.HPA,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
suggested_display_precision=0,
|
suggested_display_precision=0,
|
||||||
@@ -128,7 +123,6 @@ SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = (
|
|||||||
AirlySensorEntityDescription(
|
AirlySensorEntityDescription(
|
||||||
key=ATTR_API_TEMPERATURE,
|
key=ATTR_API_TEMPERATURE,
|
||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
translation_key="temperature",
|
|
||||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
suggested_display_precision=1,
|
suggested_display_precision=1,
|
||||||
@@ -147,7 +141,6 @@ SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = (
|
|||||||
AirlySensorEntityDescription(
|
AirlySensorEntityDescription(
|
||||||
key=ATTR_API_NO2,
|
key=ATTR_API_NO2,
|
||||||
device_class=SensorDeviceClass.NITROGEN_DIOXIDE,
|
device_class=SensorDeviceClass.NITROGEN_DIOXIDE,
|
||||||
translation_key="no2",
|
|
||||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
suggested_display_precision=0,
|
suggested_display_precision=0,
|
||||||
@@ -159,7 +152,6 @@ SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = (
|
|||||||
AirlySensorEntityDescription(
|
AirlySensorEntityDescription(
|
||||||
key=ATTR_API_SO2,
|
key=ATTR_API_SO2,
|
||||||
device_class=SensorDeviceClass.SULPHUR_DIOXIDE,
|
device_class=SensorDeviceClass.SULPHUR_DIOXIDE,
|
||||||
translation_key="so2",
|
|
||||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
suggested_display_precision=0,
|
suggested_display_precision=0,
|
||||||
@@ -171,7 +163,6 @@ SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = (
|
|||||||
AirlySensorEntityDescription(
|
AirlySensorEntityDescription(
|
||||||
key=ATTR_API_O3,
|
key=ATTR_API_O3,
|
||||||
device_class=SensorDeviceClass.OZONE,
|
device_class=SensorDeviceClass.OZONE,
|
||||||
translation_key="o3",
|
|
||||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
suggested_display_precision=0,
|
suggested_display_precision=0,
|
||||||
|
@@ -32,35 +32,8 @@
|
|||||||
"caqi": {
|
"caqi": {
|
||||||
"name": "Common air quality index"
|
"name": "Common air quality index"
|
||||||
},
|
},
|
||||||
"pm1": {
|
|
||||||
"name": "[%key:component::sensor::entity_component::pm1::name%]"
|
|
||||||
},
|
|
||||||
"pm25": {
|
|
||||||
"name": "[%key:component::sensor::entity_component::pm25::name%]"
|
|
||||||
},
|
|
||||||
"pm10": {
|
|
||||||
"name": "[%key:component::sensor::entity_component::pm10::name%]"
|
|
||||||
},
|
|
||||||
"humidity": {
|
|
||||||
"name": "[%key:component::sensor::entity_component::humidity::name%]"
|
|
||||||
},
|
|
||||||
"pressure": {
|
|
||||||
"name": "[%key:component::sensor::entity_component::pressure::name%]"
|
|
||||||
},
|
|
||||||
"temperature": {
|
|
||||||
"name": "[%key:component::sensor::entity_component::temperature::name%]"
|
|
||||||
},
|
|
||||||
"co": {
|
"co": {
|
||||||
"name": "[%key:component::sensor::entity_component::carbon_monoxide::name%]"
|
"name": "[%key:component::sensor::entity_component::carbon_monoxide::name%]"
|
||||||
},
|
|
||||||
"no2": {
|
|
||||||
"name": "[%key:component::sensor::entity_component::nitrogen_dioxide::name%]"
|
|
||||||
},
|
|
||||||
"so2": {
|
|
||||||
"name": "[%key:component::sensor::entity_component::sulphur_dioxide::name%]"
|
|
||||||
},
|
|
||||||
"o3": {
|
|
||||||
"name": "[%key:component::sensor::entity_component::ozone::name%]"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -17,5 +17,3 @@ ATTR_API_STATION_LATITUDE = "Latitude"
|
|||||||
ATTR_API_STATION_LONGITUDE = "Longitude"
|
ATTR_API_STATION_LONGITUDE = "Longitude"
|
||||||
DEFAULT_NAME = "AirNow"
|
DEFAULT_NAME = "AirNow"
|
||||||
DOMAIN = "airnow"
|
DOMAIN = "airnow"
|
||||||
SENSOR_AQI_ATTR_DESCR = "description"
|
|
||||||
SENSOR_AQI_ATTR_LEVEL = "level"
|
|
||||||
|
@@ -1,7 +1,12 @@
|
|||||||
"""Support for the AirNow sensor service."""
|
"""Support for the AirNow sensor service."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Callable
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from homeassistant.components.sensor import (
|
from homeassistant.components.sensor import (
|
||||||
|
SensorDeviceClass,
|
||||||
SensorEntity,
|
SensorEntity,
|
||||||
SensorEntityDescription,
|
SensorEntityDescription,
|
||||||
SensorStateClass,
|
SensorStateClass,
|
||||||
@@ -12,7 +17,10 @@ from homeassistant.const import (
|
|||||||
CONCENTRATION_PARTS_PER_MILLION,
|
CONCENTRATION_PARTS_PER_MILLION,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.device_registry import DeviceEntryType
|
||||||
|
from homeassistant.helpers.entity import DeviceInfo
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
from homeassistant.helpers.typing import StateType
|
||||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
|
|
||||||
from . import AirNowDataUpdateCoordinator
|
from . import AirNowDataUpdateCoordinator
|
||||||
@@ -22,36 +30,60 @@ from .const import (
|
|||||||
ATTR_API_AQI_LEVEL,
|
ATTR_API_AQI_LEVEL,
|
||||||
ATTR_API_O3,
|
ATTR_API_O3,
|
||||||
ATTR_API_PM25,
|
ATTR_API_PM25,
|
||||||
|
DEFAULT_NAME,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
SENSOR_AQI_ATTR_DESCR,
|
|
||||||
SENSOR_AQI_ATTR_LEVEL,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
ATTRIBUTION = "Data provided by AirNow"
|
ATTRIBUTION = "Data provided by AirNow"
|
||||||
|
|
||||||
PARALLEL_UPDATES = 1
|
PARALLEL_UPDATES = 1
|
||||||
|
|
||||||
SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
|
ATTR_DESCR = "description"
|
||||||
SensorEntityDescription(
|
ATTR_LEVEL = "level"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class AirNowEntityDescriptionMixin:
|
||||||
|
"""Mixin for required keys."""
|
||||||
|
|
||||||
|
value_fn: Callable[[Any], StateType]
|
||||||
|
extra_state_attributes_fn: Callable[[Any], dict[str, str]] | None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class AirNowEntityDescription(SensorEntityDescription, AirNowEntityDescriptionMixin):
|
||||||
|
"""Describes Airnow sensor entity."""
|
||||||
|
|
||||||
|
|
||||||
|
SENSOR_TYPES: tuple[AirNowEntityDescription, ...] = (
|
||||||
|
AirNowEntityDescription(
|
||||||
key=ATTR_API_AQI,
|
key=ATTR_API_AQI,
|
||||||
icon="mdi:blur",
|
icon="mdi:blur",
|
||||||
name=ATTR_API_AQI,
|
|
||||||
native_unit_of_measurement="aqi",
|
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
device_class=SensorDeviceClass.AQI,
|
||||||
|
value_fn=lambda data: data.get(ATTR_API_AQI),
|
||||||
|
extra_state_attributes_fn=lambda data: {
|
||||||
|
ATTR_DESCR: data[ATTR_API_AQI_DESCRIPTION],
|
||||||
|
ATTR_LEVEL: data[ATTR_API_AQI_LEVEL],
|
||||||
|
},
|
||||||
),
|
),
|
||||||
SensorEntityDescription(
|
AirNowEntityDescription(
|
||||||
key=ATTR_API_PM25,
|
key=ATTR_API_PM25,
|
||||||
icon="mdi:blur",
|
icon="mdi:blur",
|
||||||
name=ATTR_API_PM25,
|
|
||||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
device_class=SensorDeviceClass.PM25,
|
||||||
|
value_fn=lambda data: data.get(ATTR_API_PM25),
|
||||||
|
extra_state_attributes_fn=None,
|
||||||
),
|
),
|
||||||
SensorEntityDescription(
|
AirNowEntityDescription(
|
||||||
key=ATTR_API_O3,
|
key=ATTR_API_O3,
|
||||||
|
translation_key="o3",
|
||||||
icon="mdi:blur",
|
icon="mdi:blur",
|
||||||
name=ATTR_API_O3,
|
|
||||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
value_fn=lambda data: data.get(ATTR_API_O3),
|
||||||
|
extra_state_attributes_fn=None,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -73,38 +105,38 @@ class AirNowSensor(CoordinatorEntity[AirNowDataUpdateCoordinator], SensorEntity)
|
|||||||
"""Define an AirNow sensor."""
|
"""Define an AirNow sensor."""
|
||||||
|
|
||||||
_attr_attribution = ATTRIBUTION
|
_attr_attribution = ATTRIBUTION
|
||||||
|
_attr_has_entity_name = True
|
||||||
|
|
||||||
|
entity_description: AirNowEntityDescription
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
coordinator: AirNowDataUpdateCoordinator,
|
coordinator: AirNowDataUpdateCoordinator,
|
||||||
description: SensorEntityDescription,
|
description: AirNowEntityDescription,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize."""
|
"""Initialize."""
|
||||||
super().__init__(coordinator)
|
super().__init__(coordinator)
|
||||||
self.entity_description = description
|
self.entity_description = description
|
||||||
self._state = None
|
|
||||||
self._attrs: dict[str, str] = {}
|
|
||||||
self._attr_name = f"AirNow {description.name}"
|
|
||||||
self._attr_unique_id = (
|
self._attr_unique_id = (
|
||||||
f"{coordinator.latitude}-{coordinator.longitude}-{description.key.lower()}"
|
f"{coordinator.latitude}-{coordinator.longitude}-{description.key.lower()}"
|
||||||
)
|
)
|
||||||
|
self._attr_device_info = DeviceInfo(
|
||||||
|
entry_type=DeviceEntryType.SERVICE,
|
||||||
|
identifiers={(DOMAIN, self._attr_unique_id)},
|
||||||
|
manufacturer=DEFAULT_NAME,
|
||||||
|
name=DEFAULT_NAME,
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def native_value(self):
|
def native_value(self) -> StateType:
|
||||||
"""Return the state."""
|
"""Return the state."""
|
||||||
self._state = self.coordinator.data.get(self.entity_description.key)
|
return self.entity_description.value_fn(self.coordinator.data)
|
||||||
|
|
||||||
return self._state
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def extra_state_attributes(self):
|
def extra_state_attributes(self) -> dict[str, str] | None:
|
||||||
"""Return the state attributes."""
|
"""Return the state attributes."""
|
||||||
if self.entity_description.key == ATTR_API_AQI:
|
if self.entity_description.extra_state_attributes_fn:
|
||||||
self._attrs[SENSOR_AQI_ATTR_DESCR] = self.coordinator.data[
|
return self.entity_description.extra_state_attributes_fn(
|
||||||
ATTR_API_AQI_DESCRIPTION
|
self.coordinator.data
|
||||||
]
|
)
|
||||||
self._attrs[SENSOR_AQI_ATTR_LEVEL] = self.coordinator.data[
|
return None
|
||||||
ATTR_API_AQI_LEVEL
|
|
||||||
]
|
|
||||||
|
|
||||||
return self._attrs
|
|
||||||
|
@@ -20,5 +20,12 @@
|
|||||||
"abort": {
|
"abort": {
|
||||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"entity": {
|
||||||
|
"sensor": {
|
||||||
|
"o3": {
|
||||||
|
"name": "[%key:component::sensor::entity_component::ozone::name%]"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -53,63 +53,62 @@ class AirQEntityDescription(SensorEntityDescription, AirQEntityDescriptionMixin)
|
|||||||
SENSOR_TYPES: list[AirQEntityDescription] = [
|
SENSOR_TYPES: list[AirQEntityDescription] = [
|
||||||
AirQEntityDescription(
|
AirQEntityDescription(
|
||||||
key="c2h4o",
|
key="c2h4o",
|
||||||
name="Acetaldehyde",
|
translation_key="acetaldehyde",
|
||||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
value=lambda data: data.get("c2h4o"),
|
value=lambda data: data.get("c2h4o"),
|
||||||
),
|
),
|
||||||
AirQEntityDescription(
|
AirQEntityDescription(
|
||||||
key="nh3_MR100",
|
key="nh3_MR100",
|
||||||
name="Ammonia",
|
translation_key="ammonia",
|
||||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
value=lambda data: data.get("nh3_MR100"),
|
value=lambda data: data.get("nh3_MR100"),
|
||||||
),
|
),
|
||||||
AirQEntityDescription(
|
AirQEntityDescription(
|
||||||
key="ash3",
|
key="ash3",
|
||||||
name="Arsine",
|
translation_key="arsine",
|
||||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
value=lambda data: data.get("ash3"),
|
value=lambda data: data.get("ash3"),
|
||||||
),
|
),
|
||||||
AirQEntityDescription(
|
AirQEntityDescription(
|
||||||
key="br2",
|
key="br2",
|
||||||
name="Bromine",
|
translation_key="bromine",
|
||||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
value=lambda data: data.get("br2"),
|
value=lambda data: data.get("br2"),
|
||||||
),
|
),
|
||||||
AirQEntityDescription(
|
AirQEntityDescription(
|
||||||
key="ch4s",
|
key="ch4s",
|
||||||
name="CH4S",
|
translation_key="methanethiol",
|
||||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
value=lambda data: data.get("ch4s"),
|
value=lambda data: data.get("ch4s"),
|
||||||
),
|
),
|
||||||
AirQEntityDescription(
|
AirQEntityDescription(
|
||||||
key="cl2_M20",
|
key="cl2_M20",
|
||||||
name="Chlorine",
|
translation_key="chlorine",
|
||||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
value=lambda data: data.get("cl2_M20"),
|
value=lambda data: data.get("cl2_M20"),
|
||||||
),
|
),
|
||||||
AirQEntityDescription(
|
AirQEntityDescription(
|
||||||
key="clo2",
|
key="clo2",
|
||||||
name="ClO2",
|
translation_key="chlorine_dioxide",
|
||||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
value=lambda data: data.get("clo2"),
|
value=lambda data: data.get("clo2"),
|
||||||
),
|
),
|
||||||
AirQEntityDescription(
|
AirQEntityDescription(
|
||||||
key="co",
|
key="co",
|
||||||
name="CO",
|
translation_key="carbon_monoxide",
|
||||||
native_unit_of_measurement=CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
value=lambda data: data.get("co"),
|
value=lambda data: data.get("co"),
|
||||||
),
|
),
|
||||||
AirQEntityDescription(
|
AirQEntityDescription(
|
||||||
key="co2",
|
key="co2",
|
||||||
name="CO2",
|
|
||||||
device_class=SensorDeviceClass.CO2,
|
device_class=SensorDeviceClass.CO2,
|
||||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
@@ -117,14 +116,14 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
|
|||||||
),
|
),
|
||||||
AirQEntityDescription(
|
AirQEntityDescription(
|
||||||
key="cs2",
|
key="cs2",
|
||||||
name="CS2",
|
translation_key="carbon_disulfide",
|
||||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
value=lambda data: data.get("cs2"),
|
value=lambda data: data.get("cs2"),
|
||||||
),
|
),
|
||||||
AirQEntityDescription(
|
AirQEntityDescription(
|
||||||
key="dewpt",
|
key="dewpt",
|
||||||
name="Dew point",
|
translation_key="dew_point",
|
||||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
value=lambda data: data.get("dewpt"),
|
value=lambda data: data.get("dewpt"),
|
||||||
@@ -132,63 +131,63 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
|
|||||||
),
|
),
|
||||||
AirQEntityDescription(
|
AirQEntityDescription(
|
||||||
key="ethanol",
|
key="ethanol",
|
||||||
name="Ethanol",
|
translation_key="ethanol",
|
||||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
value=lambda data: data.get("ethanol"),
|
value=lambda data: data.get("ethanol"),
|
||||||
),
|
),
|
||||||
AirQEntityDescription(
|
AirQEntityDescription(
|
||||||
key="c2h4",
|
key="c2h4",
|
||||||
name="Ethylene",
|
translation_key="ethylene",
|
||||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
value=lambda data: data.get("c2h4"),
|
value=lambda data: data.get("c2h4"),
|
||||||
),
|
),
|
||||||
AirQEntityDescription(
|
AirQEntityDescription(
|
||||||
key="ch2o_M10",
|
key="ch2o_M10",
|
||||||
name="Formaldehyde",
|
translation_key="formaldehyde",
|
||||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
value=lambda data: data.get("ch2o_M10"),
|
value=lambda data: data.get("ch2o_M10"),
|
||||||
),
|
),
|
||||||
AirQEntityDescription(
|
AirQEntityDescription(
|
||||||
key="f2",
|
key="f2",
|
||||||
name="Fluorine",
|
translation_key="fluorine",
|
||||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
value=lambda data: data.get("f2"),
|
value=lambda data: data.get("f2"),
|
||||||
),
|
),
|
||||||
AirQEntityDescription(
|
AirQEntityDescription(
|
||||||
key="h2s",
|
key="h2s",
|
||||||
name="H2S",
|
translation_key="hydrogen_sulfide",
|
||||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
value=lambda data: data.get("h2s"),
|
value=lambda data: data.get("h2s"),
|
||||||
),
|
),
|
||||||
AirQEntityDescription(
|
AirQEntityDescription(
|
||||||
key="hcl",
|
key="hcl",
|
||||||
name="HCl",
|
translation_key="hydrochloric_acid",
|
||||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
value=lambda data: data.get("hcl"),
|
value=lambda data: data.get("hcl"),
|
||||||
),
|
),
|
||||||
AirQEntityDescription(
|
AirQEntityDescription(
|
||||||
key="hcn",
|
key="hcn",
|
||||||
name="HCN",
|
translation_key="hydrogen_cyanide",
|
||||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
value=lambda data: data.get("hcn"),
|
value=lambda data: data.get("hcn"),
|
||||||
),
|
),
|
||||||
AirQEntityDescription(
|
AirQEntityDescription(
|
||||||
key="hf",
|
key="hf",
|
||||||
name="HF",
|
translation_key="hydrogen_fluoride",
|
||||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
value=lambda data: data.get("hf"),
|
value=lambda data: data.get("hf"),
|
||||||
),
|
),
|
||||||
AirQEntityDescription(
|
AirQEntityDescription(
|
||||||
key="health",
|
key="health",
|
||||||
name="Health Index",
|
translation_key="health_index",
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
icon="mdi:heart-pulse",
|
icon="mdi:heart-pulse",
|
||||||
@@ -196,7 +195,6 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
|
|||||||
),
|
),
|
||||||
AirQEntityDescription(
|
AirQEntityDescription(
|
||||||
key="humidity",
|
key="humidity",
|
||||||
name="Humidity",
|
|
||||||
device_class=SensorDeviceClass.HUMIDITY,
|
device_class=SensorDeviceClass.HUMIDITY,
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
@@ -204,7 +202,7 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
|
|||||||
),
|
),
|
||||||
AirQEntityDescription(
|
AirQEntityDescription(
|
||||||
key="humidity_abs",
|
key="humidity_abs",
|
||||||
name="Absolute humidity",
|
translation_key="absolute_humidity",
|
||||||
native_unit_of_measurement=CONCENTRATION_GRAMS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_GRAMS_PER_CUBIC_METER,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
value=lambda data: data.get("humidity_abs"),
|
value=lambda data: data.get("humidity_abs"),
|
||||||
@@ -212,28 +210,27 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
|
|||||||
),
|
),
|
||||||
AirQEntityDescription(
|
AirQEntityDescription(
|
||||||
key="h2_M1000",
|
key="h2_M1000",
|
||||||
name="Hydrogen",
|
translation_key="hydrogen",
|
||||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
value=lambda data: data.get("h2_M1000"),
|
value=lambda data: data.get("h2_M1000"),
|
||||||
),
|
),
|
||||||
AirQEntityDescription(
|
AirQEntityDescription(
|
||||||
key="h2o2",
|
key="h2o2",
|
||||||
name="Hydrogen peroxide",
|
translation_key="hydrogen_peroxide",
|
||||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
value=lambda data: data.get("h2o2"),
|
value=lambda data: data.get("h2o2"),
|
||||||
),
|
),
|
||||||
AirQEntityDescription(
|
AirQEntityDescription(
|
||||||
key="ch4_MIPEX",
|
key="ch4_MIPEX",
|
||||||
name="Methane",
|
translation_key="methane",
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
value=lambda data: data.get("ch4_MIPEX"),
|
value=lambda data: data.get("ch4_MIPEX"),
|
||||||
),
|
),
|
||||||
AirQEntityDescription(
|
AirQEntityDescription(
|
||||||
key="n2o",
|
key="n2o",
|
||||||
name="N2O",
|
|
||||||
device_class=SensorDeviceClass.NITROUS_OXIDE,
|
device_class=SensorDeviceClass.NITROUS_OXIDE,
|
||||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
@@ -241,7 +238,6 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
|
|||||||
),
|
),
|
||||||
AirQEntityDescription(
|
AirQEntityDescription(
|
||||||
key="no_M250",
|
key="no_M250",
|
||||||
name="NO",
|
|
||||||
device_class=SensorDeviceClass.NITROGEN_MONOXIDE,
|
device_class=SensorDeviceClass.NITROGEN_MONOXIDE,
|
||||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
@@ -249,7 +245,6 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
|
|||||||
),
|
),
|
||||||
AirQEntityDescription(
|
AirQEntityDescription(
|
||||||
key="no2",
|
key="no2",
|
||||||
name="NO2",
|
|
||||||
device_class=SensorDeviceClass.NITROGEN_DIOXIDE,
|
device_class=SensorDeviceClass.NITROGEN_DIOXIDE,
|
||||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
@@ -257,14 +252,14 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
|
|||||||
),
|
),
|
||||||
AirQEntityDescription(
|
AirQEntityDescription(
|
||||||
key="acid_M100",
|
key="acid_M100",
|
||||||
name="Organic acid",
|
translation_key="organic_acid",
|
||||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
|
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
value=lambda data: data.get("acid_M100"),
|
value=lambda data: data.get("acid_M100"),
|
||||||
),
|
),
|
||||||
AirQEntityDescription(
|
AirQEntityDescription(
|
||||||
key="oxygen",
|
key="oxygen",
|
||||||
name="Oxygen",
|
translation_key="oxygen",
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
value=lambda data: data.get("oxygen"),
|
value=lambda data: data.get("oxygen"),
|
||||||
@@ -272,7 +267,6 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
|
|||||||
),
|
),
|
||||||
AirQEntityDescription(
|
AirQEntityDescription(
|
||||||
key="o3",
|
key="o3",
|
||||||
name="Ozone",
|
|
||||||
device_class=SensorDeviceClass.OZONE,
|
device_class=SensorDeviceClass.OZONE,
|
||||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
@@ -280,7 +274,7 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
|
|||||||
),
|
),
|
||||||
AirQEntityDescription(
|
AirQEntityDescription(
|
||||||
key="performance",
|
key="performance",
|
||||||
name="Performance Index",
|
translation_key="performance_index",
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
icon="mdi:head-check",
|
icon="mdi:head-check",
|
||||||
@@ -288,14 +282,13 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
|
|||||||
),
|
),
|
||||||
AirQEntityDescription(
|
AirQEntityDescription(
|
||||||
key="ph3",
|
key="ph3",
|
||||||
name="PH3",
|
translation_key="hydrogen_phosphide",
|
||||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
value=lambda data: data.get("ph3"),
|
value=lambda data: data.get("ph3"),
|
||||||
),
|
),
|
||||||
AirQEntityDescription(
|
AirQEntityDescription(
|
||||||
key="pm1",
|
key="pm1",
|
||||||
name="PM1",
|
|
||||||
device_class=SensorDeviceClass.PM1,
|
device_class=SensorDeviceClass.PM1,
|
||||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
@@ -304,7 +297,6 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
|
|||||||
),
|
),
|
||||||
AirQEntityDescription(
|
AirQEntityDescription(
|
||||||
key="pm2_5",
|
key="pm2_5",
|
||||||
name="PM2.5",
|
|
||||||
device_class=SensorDeviceClass.PM25,
|
device_class=SensorDeviceClass.PM25,
|
||||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
@@ -313,7 +305,6 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
|
|||||||
),
|
),
|
||||||
AirQEntityDescription(
|
AirQEntityDescription(
|
||||||
key="pm10",
|
key="pm10",
|
||||||
name="PM10",
|
|
||||||
device_class=SensorDeviceClass.PM10,
|
device_class=SensorDeviceClass.PM10,
|
||||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
@@ -322,7 +313,6 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
|
|||||||
),
|
),
|
||||||
AirQEntityDescription(
|
AirQEntityDescription(
|
||||||
key="pressure",
|
key="pressure",
|
||||||
name="Pressure",
|
|
||||||
device_class=SensorDeviceClass.PRESSURE,
|
device_class=SensorDeviceClass.PRESSURE,
|
||||||
native_unit_of_measurement=UnitOfPressure.HPA,
|
native_unit_of_measurement=UnitOfPressure.HPA,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
@@ -330,7 +320,7 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
|
|||||||
),
|
),
|
||||||
AirQEntityDescription(
|
AirQEntityDescription(
|
||||||
key="pressure_rel",
|
key="pressure_rel",
|
||||||
name="Relative pressure",
|
translation_key="relative_pressure",
|
||||||
native_unit_of_measurement=UnitOfPressure.HPA,
|
native_unit_of_measurement=UnitOfPressure.HPA,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
value=lambda data: data.get("pressure_rel"),
|
value=lambda data: data.get("pressure_rel"),
|
||||||
@@ -338,28 +328,27 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
|
|||||||
),
|
),
|
||||||
AirQEntityDescription(
|
AirQEntityDescription(
|
||||||
key="c3h8_MIPEX",
|
key="c3h8_MIPEX",
|
||||||
name="Propane",
|
translation_key="propane",
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
value=lambda data: data.get("c3h8_MIPEX"),
|
value=lambda data: data.get("c3h8_MIPEX"),
|
||||||
),
|
),
|
||||||
AirQEntityDescription(
|
AirQEntityDescription(
|
||||||
key="refigerant",
|
key="refigerant",
|
||||||
name="Refrigerant",
|
translation_key="refigerant",
|
||||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
value=lambda data: data.get("refigerant"),
|
value=lambda data: data.get("refigerant"),
|
||||||
),
|
),
|
||||||
AirQEntityDescription(
|
AirQEntityDescription(
|
||||||
key="sih4",
|
key="sih4",
|
||||||
name="SiH4",
|
translation_key="silicon_hydride",
|
||||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
value=lambda data: data.get("sih4"),
|
value=lambda data: data.get("sih4"),
|
||||||
),
|
),
|
||||||
AirQEntityDescription(
|
AirQEntityDescription(
|
||||||
key="so2",
|
key="so2",
|
||||||
name="SO2",
|
|
||||||
device_class=SensorDeviceClass.SULPHUR_DIOXIDE,
|
device_class=SensorDeviceClass.SULPHUR_DIOXIDE,
|
||||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
@@ -367,7 +356,7 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
|
|||||||
),
|
),
|
||||||
AirQEntityDescription(
|
AirQEntityDescription(
|
||||||
key="sound",
|
key="sound",
|
||||||
name="Noise",
|
translation_key="noise",
|
||||||
native_unit_of_measurement=UnitOfSoundPressure.WEIGHTED_DECIBEL_A,
|
native_unit_of_measurement=UnitOfSoundPressure.WEIGHTED_DECIBEL_A,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
value=lambda data: data.get("sound"),
|
value=lambda data: data.get("sound"),
|
||||||
@@ -375,7 +364,7 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
|
|||||||
),
|
),
|
||||||
AirQEntityDescription(
|
AirQEntityDescription(
|
||||||
key="sound_max",
|
key="sound_max",
|
||||||
name="Noise (Maximum)",
|
translation_key="maximum_noise",
|
||||||
native_unit_of_measurement=UnitOfSoundPressure.WEIGHTED_DECIBEL_A,
|
native_unit_of_measurement=UnitOfSoundPressure.WEIGHTED_DECIBEL_A,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
value=lambda data: data.get("sound_max"),
|
value=lambda data: data.get("sound_max"),
|
||||||
@@ -383,7 +372,7 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
|
|||||||
),
|
),
|
||||||
AirQEntityDescription(
|
AirQEntityDescription(
|
||||||
key="radon",
|
key="radon",
|
||||||
name="Radon",
|
translation_key="radon",
|
||||||
native_unit_of_measurement=ACTIVITY_BECQUEREL_PER_CUBIC_METER,
|
native_unit_of_measurement=ACTIVITY_BECQUEREL_PER_CUBIC_METER,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
value=lambda data: data.get("radon"),
|
value=lambda data: data.get("radon"),
|
||||||
@@ -391,7 +380,6 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
|
|||||||
),
|
),
|
||||||
AirQEntityDescription(
|
AirQEntityDescription(
|
||||||
key="temperature",
|
key="temperature",
|
||||||
name="Temperature",
|
|
||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
@@ -399,21 +387,22 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
|
|||||||
),
|
),
|
||||||
AirQEntityDescription(
|
AirQEntityDescription(
|
||||||
key="tvoc",
|
key="tvoc",
|
||||||
name="VOC",
|
device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS_PARTS,
|
||||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
|
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
value=lambda data: data.get("tvoc"),
|
value=lambda data: data.get("tvoc"),
|
||||||
),
|
),
|
||||||
AirQEntityDescription(
|
AirQEntityDescription(
|
||||||
key="tvoc_ionsc",
|
key="tvoc_ionsc",
|
||||||
name="VOC (Industrial)",
|
translation_key="industrial_volatile_organic_compounds",
|
||||||
|
device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS_PARTS,
|
||||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
|
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
value=lambda data: data.get("tvoc_ionsc"),
|
value=lambda data: data.get("tvoc_ionsc"),
|
||||||
),
|
),
|
||||||
AirQEntityDescription(
|
AirQEntityDescription(
|
||||||
key="virus",
|
key="virus",
|
||||||
name="Virus Index",
|
translation_key="virus_index",
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
icon="mdi:virus-off",
|
icon="mdi:virus-off",
|
||||||
|
@@ -18,5 +18,117 @@
|
|||||||
"abort": {
|
"abort": {
|
||||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"entity": {
|
||||||
|
"sensor": {
|
||||||
|
"acetaldehyde": {
|
||||||
|
"name": "Acetaldehyde"
|
||||||
|
},
|
||||||
|
"ammonia": {
|
||||||
|
"name": "Ammonia"
|
||||||
|
},
|
||||||
|
"arsine": {
|
||||||
|
"name": "Arsine"
|
||||||
|
},
|
||||||
|
"bromine": {
|
||||||
|
"name": "Bromine"
|
||||||
|
},
|
||||||
|
"methanethiol": {
|
||||||
|
"name": "Methanethiol"
|
||||||
|
},
|
||||||
|
"chlorine": {
|
||||||
|
"name": "Chlorine"
|
||||||
|
},
|
||||||
|
"chlorine_dioxide": {
|
||||||
|
"name": "Chlorine dioxide"
|
||||||
|
},
|
||||||
|
"carbon_disulfide": {
|
||||||
|
"name": "Carbon disulfide"
|
||||||
|
},
|
||||||
|
"carbon_monoxide": {
|
||||||
|
"name": "[%key:component::sensor::entity_component::carbon_monoxide::name%]"
|
||||||
|
},
|
||||||
|
"dew_point": {
|
||||||
|
"name": "Dew point"
|
||||||
|
},
|
||||||
|
"ethanol": {
|
||||||
|
"name": "Ethanol"
|
||||||
|
},
|
||||||
|
"ethylene": {
|
||||||
|
"name": "Ethylene"
|
||||||
|
},
|
||||||
|
"formaldehyde": {
|
||||||
|
"name": "Formaldehyde"
|
||||||
|
},
|
||||||
|
"fluorine": {
|
||||||
|
"name": "Fluorine"
|
||||||
|
},
|
||||||
|
"hydrogen_sulfide": {
|
||||||
|
"name": "Hydrogen sulfide"
|
||||||
|
},
|
||||||
|
"hydrochloric_acid": {
|
||||||
|
"name": "Hydrochloric acid"
|
||||||
|
},
|
||||||
|
"hydrogen_cyanide": {
|
||||||
|
"name": "Hydrogen cyanide"
|
||||||
|
},
|
||||||
|
"hydrogen_fluoride": {
|
||||||
|
"name": "Hydrogen fluoride"
|
||||||
|
},
|
||||||
|
"health_index": {
|
||||||
|
"name": "Health Index"
|
||||||
|
},
|
||||||
|
"absolute_humidity": {
|
||||||
|
"name": "Absolute humidity"
|
||||||
|
},
|
||||||
|
"hydrogen": {
|
||||||
|
"name": "Hydrogen"
|
||||||
|
},
|
||||||
|
"hydrogen_peroxide": {
|
||||||
|
"name": "Hydrogen peroxide"
|
||||||
|
},
|
||||||
|
"methane": {
|
||||||
|
"name": "Methane"
|
||||||
|
},
|
||||||
|
"organic_acid": {
|
||||||
|
"name": "Organic acid"
|
||||||
|
},
|
||||||
|
"oxygen": {
|
||||||
|
"name": "Oxygen"
|
||||||
|
},
|
||||||
|
"performance_index": {
|
||||||
|
"name": "Performance Index"
|
||||||
|
},
|
||||||
|
"hydrogen_phosphide": {
|
||||||
|
"name": "Hydrogen Phosphide"
|
||||||
|
},
|
||||||
|
"relative_pressure": {
|
||||||
|
"name": "Relative pressure"
|
||||||
|
},
|
||||||
|
"propane": {
|
||||||
|
"name": "Propane"
|
||||||
|
},
|
||||||
|
"refigerant": {
|
||||||
|
"name": "Refrigerant"
|
||||||
|
},
|
||||||
|
"silicon_hydride": {
|
||||||
|
"name": "Silicon Hydride"
|
||||||
|
},
|
||||||
|
"noise": {
|
||||||
|
"name": "Noise"
|
||||||
|
},
|
||||||
|
"maximum_noise": {
|
||||||
|
"name": "Noise (Maximum)"
|
||||||
|
},
|
||||||
|
"radon": {
|
||||||
|
"name": "Radon"
|
||||||
|
},
|
||||||
|
"industrial_volatile_organic_compounds": {
|
||||||
|
"name": "VOCs (Industrial)"
|
||||||
|
},
|
||||||
|
"virus_index": {
|
||||||
|
"name": "Virus Index"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -6,5 +6,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/airthings",
|
"documentation": "https://www.home-assistant.io/integrations/airthings",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["airthings"],
|
"loggers": ["airthings"],
|
||||||
"requirements": ["airthings_cloud==0.1.0"]
|
"requirements": ["airthings-cloud==0.1.0"]
|
||||||
}
|
}
|
||||||
|
@@ -35,62 +35,56 @@ SENSORS: dict[str, SensorEntityDescription] = {
|
|||||||
"radonShortTermAvg": SensorEntityDescription(
|
"radonShortTermAvg": SensorEntityDescription(
|
||||||
key="radonShortTermAvg",
|
key="radonShortTermAvg",
|
||||||
native_unit_of_measurement="Bq/m³",
|
native_unit_of_measurement="Bq/m³",
|
||||||
name="Radon",
|
translation_key="radon",
|
||||||
),
|
),
|
||||||
"temp": SensorEntityDescription(
|
"temp": SensorEntityDescription(
|
||||||
key="temp",
|
key="temp",
|
||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
name="Temperature",
|
|
||||||
),
|
),
|
||||||
"humidity": SensorEntityDescription(
|
"humidity": SensorEntityDescription(
|
||||||
key="humidity",
|
key="humidity",
|
||||||
device_class=SensorDeviceClass.HUMIDITY,
|
device_class=SensorDeviceClass.HUMIDITY,
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
name="Humidity",
|
|
||||||
),
|
),
|
||||||
"pressure": SensorEntityDescription(
|
"pressure": SensorEntityDescription(
|
||||||
key="pressure",
|
key="pressure",
|
||||||
device_class=SensorDeviceClass.PRESSURE,
|
device_class=SensorDeviceClass.PRESSURE,
|
||||||
native_unit_of_measurement=UnitOfPressure.MBAR,
|
native_unit_of_measurement=UnitOfPressure.MBAR,
|
||||||
name="Pressure",
|
|
||||||
),
|
),
|
||||||
"battery": SensorEntityDescription(
|
"battery": SensorEntityDescription(
|
||||||
key="battery",
|
key="battery",
|
||||||
device_class=SensorDeviceClass.BATTERY,
|
device_class=SensorDeviceClass.BATTERY,
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
name="Battery",
|
|
||||||
),
|
),
|
||||||
"co2": SensorEntityDescription(
|
"co2": SensorEntityDescription(
|
||||||
key="co2",
|
key="co2",
|
||||||
device_class=SensorDeviceClass.CO2,
|
device_class=SensorDeviceClass.CO2,
|
||||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||||
name="CO2",
|
|
||||||
),
|
),
|
||||||
"voc": SensorEntityDescription(
|
"voc": SensorEntityDescription(
|
||||||
key="voc",
|
key="voc",
|
||||||
|
device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS_PARTS,
|
||||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
|
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
|
||||||
name="VOC",
|
|
||||||
),
|
),
|
||||||
"light": SensorEntityDescription(
|
"light": SensorEntityDescription(
|
||||||
key="light",
|
key="light",
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
name="Light",
|
translation_key="light",
|
||||||
),
|
),
|
||||||
"virusRisk": SensorEntityDescription(
|
"virusRisk": SensorEntityDescription(
|
||||||
key="virusRisk",
|
key="virusRisk",
|
||||||
name="Virus Risk",
|
translation_key="virus_risk",
|
||||||
),
|
),
|
||||||
"mold": SensorEntityDescription(
|
"mold": SensorEntityDescription(
|
||||||
key="mold",
|
key="mold",
|
||||||
name="Mold",
|
translation_key="mold",
|
||||||
),
|
),
|
||||||
"rssi": SensorEntityDescription(
|
"rssi": SensorEntityDescription(
|
||||||
key="rssi",
|
key="rssi",
|
||||||
native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS,
|
native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS,
|
||||||
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
|
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
|
||||||
name="RSSI",
|
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
),
|
),
|
||||||
@@ -98,13 +92,11 @@ SENSORS: dict[str, SensorEntityDescription] = {
|
|||||||
key="pm1",
|
key="pm1",
|
||||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
device_class=SensorDeviceClass.PM1,
|
device_class=SensorDeviceClass.PM1,
|
||||||
name="PM1",
|
|
||||||
),
|
),
|
||||||
"pm25": SensorEntityDescription(
|
"pm25": SensorEntityDescription(
|
||||||
key="pm25",
|
key="pm25",
|
||||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
device_class=SensorDeviceClass.PM25,
|
device_class=SensorDeviceClass.PM25,
|
||||||
name="PM25",
|
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,6 +126,7 @@ class AirthingsHeaterEnergySensor(CoordinatorEntity, SensorEntity):
|
|||||||
"""Representation of a Airthings Sensor device."""
|
"""Representation of a Airthings Sensor device."""
|
||||||
|
|
||||||
_attr_state_class = SensorStateClass.MEASUREMENT
|
_attr_state_class = SensorStateClass.MEASUREMENT
|
||||||
|
_attr_has_entity_name = True
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@@ -146,7 +139,6 @@ class AirthingsHeaterEnergySensor(CoordinatorEntity, SensorEntity):
|
|||||||
|
|
||||||
self.entity_description = entity_description
|
self.entity_description = entity_description
|
||||||
|
|
||||||
self._attr_name = f"{airthings_device.name} {entity_description.name}"
|
|
||||||
self._attr_unique_id = f"{airthings_device.device_id}_{entity_description.key}"
|
self._attr_unique_id = f"{airthings_device.device_id}_{entity_description.key}"
|
||||||
self._id = airthings_device.device_id
|
self._id = airthings_device.device_id
|
||||||
self._attr_device_info = DeviceInfo(
|
self._attr_device_info = DeviceInfo(
|
||||||
|
@@ -17,5 +17,21 @@
|
|||||||
"abort": {
|
"abort": {
|
||||||
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]"
|
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"entity": {
|
||||||
|
"sensor": {
|
||||||
|
"radon": {
|
||||||
|
"name": "Radon"
|
||||||
|
},
|
||||||
|
"light": {
|
||||||
|
"name": "Light"
|
||||||
|
},
|
||||||
|
"virus_risk": {
|
||||||
|
"name": "Virus Risk"
|
||||||
|
},
|
||||||
|
"mold": {
|
||||||
|
"name": "Mold"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -39,26 +39,26 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
SENSORS_MAPPING_TEMPLATE: dict[str, SensorEntityDescription] = {
|
SENSORS_MAPPING_TEMPLATE: dict[str, SensorEntityDescription] = {
|
||||||
"radon_1day_avg": SensorEntityDescription(
|
"radon_1day_avg": SensorEntityDescription(
|
||||||
key="radon_1day_avg",
|
key="radon_1day_avg",
|
||||||
|
translation_key="radon_1day_avg",
|
||||||
native_unit_of_measurement=VOLUME_BECQUEREL,
|
native_unit_of_measurement=VOLUME_BECQUEREL,
|
||||||
name="Radon 1-day average",
|
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
icon="mdi:radioactive",
|
icon="mdi:radioactive",
|
||||||
),
|
),
|
||||||
"radon_longterm_avg": SensorEntityDescription(
|
"radon_longterm_avg": SensorEntityDescription(
|
||||||
key="radon_longterm_avg",
|
key="radon_longterm_avg",
|
||||||
|
translation_key="radon_longterm_avg",
|
||||||
native_unit_of_measurement=VOLUME_BECQUEREL,
|
native_unit_of_measurement=VOLUME_BECQUEREL,
|
||||||
name="Radon longterm average",
|
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
icon="mdi:radioactive",
|
icon="mdi:radioactive",
|
||||||
),
|
),
|
||||||
"radon_1day_level": SensorEntityDescription(
|
"radon_1day_level": SensorEntityDescription(
|
||||||
key="radon_1day_level",
|
key="radon_1day_level",
|
||||||
name="Radon 1-day level",
|
translation_key="radon_1day_level",
|
||||||
icon="mdi:radioactive",
|
icon="mdi:radioactive",
|
||||||
),
|
),
|
||||||
"radon_longterm_level": SensorEntityDescription(
|
"radon_longterm_level": SensorEntityDescription(
|
||||||
key="radon_longterm_level",
|
key="radon_longterm_level",
|
||||||
name="Radon longterm level",
|
translation_key="radon_longterm_level",
|
||||||
icon="mdi:radioactive",
|
icon="mdi:radioactive",
|
||||||
),
|
),
|
||||||
"temperature": SensorEntityDescription(
|
"temperature": SensorEntityDescription(
|
||||||
@@ -66,21 +66,18 @@ SENSORS_MAPPING_TEMPLATE: dict[str, SensorEntityDescription] = {
|
|||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
name="Temperature",
|
|
||||||
),
|
),
|
||||||
"humidity": SensorEntityDescription(
|
"humidity": SensorEntityDescription(
|
||||||
key="humidity",
|
key="humidity",
|
||||||
device_class=SensorDeviceClass.HUMIDITY,
|
device_class=SensorDeviceClass.HUMIDITY,
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
name="Humidity",
|
|
||||||
),
|
),
|
||||||
"pressure": SensorEntityDescription(
|
"pressure": SensorEntityDescription(
|
||||||
key="pressure",
|
key="pressure",
|
||||||
device_class=SensorDeviceClass.PRESSURE,
|
device_class=SensorDeviceClass.PRESSURE,
|
||||||
native_unit_of_measurement=UnitOfPressure.MBAR,
|
native_unit_of_measurement=UnitOfPressure.MBAR,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
name="Pressure",
|
|
||||||
),
|
),
|
||||||
"battery": SensorEntityDescription(
|
"battery": SensorEntityDescription(
|
||||||
key="battery",
|
key="battery",
|
||||||
@@ -88,20 +85,18 @@ SENSORS_MAPPING_TEMPLATE: dict[str, SensorEntityDescription] = {
|
|||||||
native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
name="Battery",
|
|
||||||
),
|
),
|
||||||
"co2": SensorEntityDescription(
|
"co2": SensorEntityDescription(
|
||||||
key="co2",
|
key="co2",
|
||||||
device_class=SensorDeviceClass.CO2,
|
device_class=SensorDeviceClass.CO2,
|
||||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
name="co2",
|
|
||||||
),
|
),
|
||||||
"voc": SensorEntityDescription(
|
"voc": SensorEntityDescription(
|
||||||
key="voc",
|
key="voc",
|
||||||
|
device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS_PARTS,
|
||||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
|
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
name="VOC",
|
|
||||||
icon="mdi:cloud",
|
icon="mdi:cloud",
|
||||||
),
|
),
|
||||||
"illuminance": SensorEntityDescription(
|
"illuminance": SensorEntityDescription(
|
||||||
@@ -109,7 +104,6 @@ SENSORS_MAPPING_TEMPLATE: dict[str, SensorEntityDescription] = {
|
|||||||
device_class=SensorDeviceClass.ILLUMINANCE,
|
device_class=SensorDeviceClass.ILLUMINANCE,
|
||||||
native_unit_of_measurement=LIGHT_LUX,
|
native_unit_of_measurement=LIGHT_LUX,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
name="Illuminance",
|
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -19,5 +19,21 @@
|
|||||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"entity": {
|
||||||
|
"sensor": {
|
||||||
|
"radon_1day_avg": {
|
||||||
|
"name": "Radon 1-day average"
|
||||||
|
},
|
||||||
|
"radon_longterm_avg": {
|
||||||
|
"name": "Radon longterm average"
|
||||||
|
},
|
||||||
|
"radon_1day_level": {
|
||||||
|
"name": "Radon 1-day level"
|
||||||
|
},
|
||||||
|
"radon_longterm_level": {
|
||||||
|
"name": "Radon longterm level"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -17,7 +17,7 @@
|
|||||||
"api_key": "[%key:common::config_flow::data::api_key%]",
|
"api_key": "[%key:common::config_flow::data::api_key%]",
|
||||||
"city": "City",
|
"city": "City",
|
||||||
"country": "Country",
|
"country": "Country",
|
||||||
"state": "state"
|
"state": "State"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"reauth_confirm": {
|
"reauth_confirm": {
|
||||||
|
@@ -193,6 +193,8 @@ class AirzoneClimate(AirzoneZoneEntity, ClimateEntity):
|
|||||||
|
|
||||||
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
|
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
|
||||||
"""Set hvac mode."""
|
"""Set hvac mode."""
|
||||||
|
slave_raise = False
|
||||||
|
|
||||||
params = {}
|
params = {}
|
||||||
if hvac_mode == HVACMode.OFF:
|
if hvac_mode == HVACMode.OFF:
|
||||||
params[API_ON] = 0
|
params[API_ON] = 0
|
||||||
@@ -202,12 +204,13 @@ class AirzoneClimate(AirzoneZoneEntity, ClimateEntity):
|
|||||||
if self.get_airzone_value(AZD_MASTER):
|
if self.get_airzone_value(AZD_MASTER):
|
||||||
params[API_MODE] = mode
|
params[API_MODE] = mode
|
||||||
else:
|
else:
|
||||||
raise HomeAssistantError(
|
slave_raise = True
|
||||||
f"Mode can't be changed on slave zone {self.name}"
|
|
||||||
)
|
|
||||||
params[API_ON] = 1
|
params[API_ON] = 1
|
||||||
await self._async_update_hvac_params(params)
|
await self._async_update_hvac_params(params)
|
||||||
|
|
||||||
|
if slave_raise:
|
||||||
|
raise HomeAssistantError(f"Mode can't be changed on slave zone {self.name}")
|
||||||
|
|
||||||
async def async_set_temperature(self, **kwargs: Any) -> None:
|
async def async_set_temperature(self, **kwargs: Any) -> None:
|
||||||
"""Set new target temperature."""
|
"""Set new target temperature."""
|
||||||
params = {}
|
params = {}
|
||||||
|
@@ -12,7 +12,10 @@ from homeassistant.helpers import aiohttp_client
|
|||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
from .coordinator import AirzoneUpdateCoordinator
|
from .coordinator import AirzoneUpdateCoordinator
|
||||||
|
|
||||||
PLATFORMS: list[Platform] = [Platform.SENSOR]
|
PLATFORMS: list[Platform] = [
|
||||||
|
Platform.BINARY_SENSOR,
|
||||||
|
Platform.SENSOR,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
|
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,
|
AZD_ZONES,
|
||||||
)
|
)
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
|
||||||
from homeassistant.helpers import device_registry as dr
|
from homeassistant.helpers import device_registry as dr
|
||||||
from homeassistant.helpers.entity import DeviceInfo
|
from homeassistant.helpers.entity import DeviceInfo
|
||||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
@@ -43,7 +42,6 @@ class AirzoneAidooEntity(AirzoneEntity):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
coordinator: AirzoneUpdateCoordinator,
|
coordinator: AirzoneUpdateCoordinator,
|
||||||
entry: ConfigEntry,
|
|
||||||
aidoo_id: str,
|
aidoo_id: str,
|
||||||
aidoo_data: dict[str, Any],
|
aidoo_data: dict[str, Any],
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -73,7 +71,6 @@ class AirzoneWebServerEntity(AirzoneEntity):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
coordinator: AirzoneUpdateCoordinator,
|
coordinator: AirzoneUpdateCoordinator,
|
||||||
entry: ConfigEntry,
|
|
||||||
ws_id: str,
|
ws_id: str,
|
||||||
ws_data: dict[str, Any],
|
ws_data: dict[str, Any],
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -86,7 +83,7 @@ class AirzoneWebServerEntity(AirzoneEntity):
|
|||||||
connections={(dr.CONNECTION_NETWORK_MAC, ws_id)},
|
connections={(dr.CONNECTION_NETWORK_MAC, ws_id)},
|
||||||
identifiers={(DOMAIN, ws_id)},
|
identifiers={(DOMAIN, ws_id)},
|
||||||
manufacturer=MANUFACTURER,
|
manufacturer=MANUFACTURER,
|
||||||
name=f"WebServer {ws_id}",
|
name=ws_data[AZD_NAME],
|
||||||
sw_version=ws_data[AZD_FIRMWARE],
|
sw_version=ws_data[AZD_FIRMWARE],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -104,7 +101,6 @@ class AirzoneZoneEntity(AirzoneEntity):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
coordinator: AirzoneUpdateCoordinator,
|
coordinator: AirzoneUpdateCoordinator,
|
||||||
entry: ConfigEntry,
|
|
||||||
zone_id: str,
|
zone_id: str,
|
||||||
zone_data: dict[str, Any],
|
zone_data: dict[str, Any],
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@@ -6,5 +6,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/airzone_cloud",
|
"documentation": "https://www.home-assistant.io/integrations/airzone_cloud",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["aioairzone_cloud"],
|
"loggers": ["aioairzone_cloud"],
|
||||||
"requirements": ["aioairzone-cloud==0.1.8"]
|
"requirements": ["aioairzone-cloud==0.2.0"]
|
||||||
}
|
}
|
||||||
|
@@ -6,7 +6,6 @@ from typing import Any, Final
|
|||||||
from aioairzone_cloud.const import (
|
from aioairzone_cloud.const import (
|
||||||
AZD_AIDOOS,
|
AZD_AIDOOS,
|
||||||
AZD_HUMIDITY,
|
AZD_HUMIDITY,
|
||||||
AZD_NAME,
|
|
||||||
AZD_TEMP,
|
AZD_TEMP,
|
||||||
AZD_WEBSERVERS,
|
AZD_WEBSERVERS,
|
||||||
AZD_WIFI_RSSI,
|
AZD_WIFI_RSSI,
|
||||||
@@ -42,7 +41,6 @@ AIDOO_SENSOR_TYPES: Final[tuple[SensorEntityDescription, ...]] = (
|
|||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
key=AZD_TEMP,
|
key=AZD_TEMP,
|
||||||
name="Temperature",
|
|
||||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
@@ -53,9 +51,7 @@ WEBSERVER_SENSOR_TYPES: Final[tuple[SensorEntityDescription, ...]] = (
|
|||||||
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
|
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
has_entity_name=True,
|
|
||||||
key=AZD_WIFI_RSSI,
|
key=AZD_WIFI_RSSI,
|
||||||
name="RSSI",
|
|
||||||
native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
|
native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
@@ -65,14 +61,12 @@ ZONE_SENSOR_TYPES: Final[tuple[SensorEntityDescription, ...]] = (
|
|||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
key=AZD_TEMP,
|
key=AZD_TEMP,
|
||||||
name="Temperature",
|
|
||||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
device_class=SensorDeviceClass.HUMIDITY,
|
device_class=SensorDeviceClass.HUMIDITY,
|
||||||
key=AZD_HUMIDITY,
|
key=AZD_HUMIDITY,
|
||||||
name="Humidity",
|
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
@@ -95,7 +89,6 @@ async def async_setup_entry(
|
|||||||
AirzoneAidooSensor(
|
AirzoneAidooSensor(
|
||||||
coordinator,
|
coordinator,
|
||||||
description,
|
description,
|
||||||
entry,
|
|
||||||
aidoo_id,
|
aidoo_id,
|
||||||
aidoo_data,
|
aidoo_data,
|
||||||
)
|
)
|
||||||
@@ -109,7 +102,6 @@ async def async_setup_entry(
|
|||||||
AirzoneWebServerSensor(
|
AirzoneWebServerSensor(
|
||||||
coordinator,
|
coordinator,
|
||||||
description,
|
description,
|
||||||
entry,
|
|
||||||
ws_id,
|
ws_id,
|
||||||
ws_data,
|
ws_data,
|
||||||
)
|
)
|
||||||
@@ -123,7 +115,6 @@ async def async_setup_entry(
|
|||||||
AirzoneZoneSensor(
|
AirzoneZoneSensor(
|
||||||
coordinator,
|
coordinator,
|
||||||
description,
|
description,
|
||||||
entry,
|
|
||||||
zone_id,
|
zone_id,
|
||||||
zone_data,
|
zone_data,
|
||||||
)
|
)
|
||||||
@@ -154,14 +145,13 @@ class AirzoneAidooSensor(AirzoneAidooEntity, AirzoneSensor):
|
|||||||
self,
|
self,
|
||||||
coordinator: AirzoneUpdateCoordinator,
|
coordinator: AirzoneUpdateCoordinator,
|
||||||
description: SensorEntityDescription,
|
description: SensorEntityDescription,
|
||||||
entry: ConfigEntry,
|
|
||||||
aidoo_id: str,
|
aidoo_id: str,
|
||||||
aidoo_data: dict[str, Any],
|
aidoo_data: dict[str, Any],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize."""
|
"""Initialize."""
|
||||||
super().__init__(coordinator, entry, aidoo_id, aidoo_data)
|
super().__init__(coordinator, aidoo_id, aidoo_data)
|
||||||
|
|
||||||
self._attr_name = f"{aidoo_data[AZD_NAME]} {description.name}"
|
self._attr_has_entity_name = True
|
||||||
self._attr_unique_id = f"{aidoo_id}_{description.key}"
|
self._attr_unique_id = f"{aidoo_id}_{description.key}"
|
||||||
self.entity_description = description
|
self.entity_description = description
|
||||||
|
|
||||||
@@ -175,13 +165,13 @@ class AirzoneWebServerSensor(AirzoneWebServerEntity, AirzoneSensor):
|
|||||||
self,
|
self,
|
||||||
coordinator: AirzoneUpdateCoordinator,
|
coordinator: AirzoneUpdateCoordinator,
|
||||||
description: SensorEntityDescription,
|
description: SensorEntityDescription,
|
||||||
entry: ConfigEntry,
|
|
||||||
ws_id: str,
|
ws_id: str,
|
||||||
ws_data: dict[str, Any],
|
ws_data: dict[str, Any],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize."""
|
"""Initialize."""
|
||||||
super().__init__(coordinator, entry, ws_id, ws_data)
|
super().__init__(coordinator, ws_id, ws_data)
|
||||||
|
|
||||||
|
self._attr_has_entity_name = True
|
||||||
self._attr_unique_id = f"{ws_id}_{description.key}"
|
self._attr_unique_id = f"{ws_id}_{description.key}"
|
||||||
self.entity_description = description
|
self.entity_description = description
|
||||||
|
|
||||||
@@ -195,14 +185,13 @@ class AirzoneZoneSensor(AirzoneZoneEntity, AirzoneSensor):
|
|||||||
self,
|
self,
|
||||||
coordinator: AirzoneUpdateCoordinator,
|
coordinator: AirzoneUpdateCoordinator,
|
||||||
description: SensorEntityDescription,
|
description: SensorEntityDescription,
|
||||||
entry: ConfigEntry,
|
|
||||||
zone_id: str,
|
zone_id: str,
|
||||||
zone_data: dict[str, Any],
|
zone_data: dict[str, Any],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize."""
|
"""Initialize."""
|
||||||
super().__init__(coordinator, entry, zone_id, zone_data)
|
super().__init__(coordinator, zone_id, zone_data)
|
||||||
|
|
||||||
self._attr_name = f"{zone_data[AZD_NAME]} {description.name}"
|
self._attr_has_entity_name = True
|
||||||
self._attr_unique_id = f"{zone_id}_{description.key}"
|
self._attr_unique_id = f"{zone_id}_{description.key}"
|
||||||
self.entity_description = description
|
self.entity_description = description
|
||||||
|
|
||||||
|
@@ -40,26 +40,24 @@ class AladdinDevice(CoverEntity):
|
|||||||
|
|
||||||
_attr_device_class = CoverDeviceClass.GARAGE
|
_attr_device_class = CoverDeviceClass.GARAGE
|
||||||
_attr_supported_features = SUPPORTED_FEATURES
|
_attr_supported_features = SUPPORTED_FEATURES
|
||||||
|
_attr_has_entity_name = True
|
||||||
|
_attr_name = None
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, acc: AladdinConnectClient, device: DoorDevice, entry: ConfigEntry
|
self, acc: AladdinConnectClient, device: DoorDevice, entry: ConfigEntry
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the Aladdin Connect cover."""
|
"""Initialize the Aladdin Connect cover."""
|
||||||
self._acc = acc
|
self._acc = acc
|
||||||
self._entry_id = entry.entry_id
|
|
||||||
self._device_id = device["device_id"]
|
self._device_id = device["device_id"]
|
||||||
self._number = device["door_number"]
|
self._number = device["door_number"]
|
||||||
self._name = device["name"]
|
|
||||||
self._serial = device["serial"]
|
self._serial = device["serial"]
|
||||||
self._model = device["model"]
|
|
||||||
|
|
||||||
self._attr_device_info = DeviceInfo(
|
self._attr_device_info = DeviceInfo(
|
||||||
identifiers={(DOMAIN, f"{self._device_id}-{self._number}")},
|
identifiers={(DOMAIN, f"{self._device_id}-{self._number}")},
|
||||||
name=self._name,
|
name=device["name"],
|
||||||
manufacturer="Overhead Door",
|
manufacturer="Overhead Door",
|
||||||
model=self._model,
|
model=device["model"],
|
||||||
)
|
)
|
||||||
self._attr_has_entity_name = True
|
|
||||||
self._attr_unique_id = f"{self._device_id}-{self._number}"
|
self._attr_unique_id = f"{self._device_id}-{self._number}"
|
||||||
|
|
||||||
async def async_added_to_hass(self) -> None:
|
async def async_added_to_hass(self) -> None:
|
||||||
|
@@ -40,7 +40,6 @@ class AccSensorEntityDescription(
|
|||||||
SENSORS: tuple[AccSensorEntityDescription, ...] = (
|
SENSORS: tuple[AccSensorEntityDescription, ...] = (
|
||||||
AccSensorEntityDescription(
|
AccSensorEntityDescription(
|
||||||
key="battery_level",
|
key="battery_level",
|
||||||
name="Battery level",
|
|
||||||
device_class=SensorDeviceClass.BATTERY,
|
device_class=SensorDeviceClass.BATTERY,
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
@@ -49,7 +48,7 @@ SENSORS: tuple[AccSensorEntityDescription, ...] = (
|
|||||||
),
|
),
|
||||||
AccSensorEntityDescription(
|
AccSensorEntityDescription(
|
||||||
key="rssi",
|
key="rssi",
|
||||||
name="Wi-Fi RSSI",
|
translation_key="wifi_strength",
|
||||||
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
|
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS,
|
native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS,
|
||||||
@@ -58,7 +57,7 @@ SENSORS: tuple[AccSensorEntityDescription, ...] = (
|
|||||||
),
|
),
|
||||||
AccSensorEntityDescription(
|
AccSensorEntityDescription(
|
||||||
key="ble_strength",
|
key="ble_strength",
|
||||||
name="BLE Strength",
|
translation_key="ble_strength",
|
||||||
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
|
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS,
|
native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS,
|
||||||
@@ -89,8 +88,8 @@ async def async_setup_entry(
|
|||||||
class AladdinConnectSensor(SensorEntity):
|
class AladdinConnectSensor(SensorEntity):
|
||||||
"""A sensor implementation for Aladdin Connect devices."""
|
"""A sensor implementation for Aladdin Connect devices."""
|
||||||
|
|
||||||
_device: AladdinConnectSensor
|
|
||||||
entity_description: AccSensorEntityDescription
|
entity_description: AccSensorEntityDescription
|
||||||
|
_attr_has_entity_name = True
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@@ -101,24 +100,20 @@ class AladdinConnectSensor(SensorEntity):
|
|||||||
"""Initialize a sensor for an Aladdin Connect device."""
|
"""Initialize a sensor for an Aladdin Connect device."""
|
||||||
self._device_id = device["device_id"]
|
self._device_id = device["device_id"]
|
||||||
self._number = device["door_number"]
|
self._number = device["door_number"]
|
||||||
self._name = device["name"]
|
|
||||||
self._model = device["model"]
|
|
||||||
self._acc = acc
|
self._acc = acc
|
||||||
self.entity_description = description
|
self.entity_description = description
|
||||||
self._attr_unique_id = f"{self._device_id}-{self._number}-{description.key}"
|
self._attr_unique_id = f"{self._device_id}-{self._number}-{description.key}"
|
||||||
self._attr_has_entity_name = True
|
self._attr_device_info = DeviceInfo(
|
||||||
if self._model == "01" and description.key in ("battery_level", "ble_strength"):
|
|
||||||
self._attr_entity_registry_enabled_default = True
|
|
||||||
|
|
||||||
@property
|
|
||||||
def device_info(self) -> DeviceInfo | None:
|
|
||||||
"""Device information for Aladdin Connect sensors."""
|
|
||||||
return DeviceInfo(
|
|
||||||
identifiers={(DOMAIN, f"{self._device_id}-{self._number}")},
|
identifiers={(DOMAIN, f"{self._device_id}-{self._number}")},
|
||||||
name=self._name,
|
name=device["name"],
|
||||||
manufacturer="Overhead Door",
|
manufacturer="Overhead Door",
|
||||||
model=self._model,
|
model=device["model"],
|
||||||
)
|
)
|
||||||
|
if device["model"] == "01" and description.key in (
|
||||||
|
"battery_level",
|
||||||
|
"ble_strength",
|
||||||
|
):
|
||||||
|
self._attr_entity_registry_enabled_default = True
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def native_value(self) -> float | None:
|
def native_value(self) -> float | None:
|
||||||
|
@@ -25,5 +25,15 @@
|
|||||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
|
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"entity": {
|
||||||
|
"sensor": {
|
||||||
|
"wifi_strength": {
|
||||||
|
"name": "Wi-Fi RSSI"
|
||||||
|
},
|
||||||
|
"ble_strength": {
|
||||||
|
"name": "BLE Strength"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -5,6 +5,7 @@ from typing import Final
|
|||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.components.device_automation import async_validate_entity_schema
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_CODE,
|
ATTR_CODE,
|
||||||
ATTR_ENTITY_ID,
|
ATTR_ENTITY_ID,
|
||||||
@@ -44,15 +45,22 @@ ACTION_TYPES: Final[set[str]] = {
|
|||||||
"trigger",
|
"trigger",
|
||||||
}
|
}
|
||||||
|
|
||||||
ACTION_SCHEMA: Final = cv.DEVICE_ACTION_BASE_SCHEMA.extend(
|
_ACTION_SCHEMA: Final = cv.DEVICE_ACTION_BASE_SCHEMA.extend(
|
||||||
{
|
{
|
||||||
vol.Required(CONF_TYPE): vol.In(ACTION_TYPES),
|
vol.Required(CONF_TYPE): vol.In(ACTION_TYPES),
|
||||||
vol.Required(CONF_ENTITY_ID): cv.entity_domain(DOMAIN),
|
vol.Required(CONF_ENTITY_ID): cv.entity_id_or_uuid,
|
||||||
vol.Optional(CONF_CODE): cv.string,
|
vol.Optional(CONF_CODE): cv.string,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_validate_action_config(
|
||||||
|
hass: HomeAssistant, config: ConfigType
|
||||||
|
) -> ConfigType:
|
||||||
|
"""Validate config."""
|
||||||
|
return async_validate_entity_schema(hass, config, _ACTION_SCHEMA)
|
||||||
|
|
||||||
|
|
||||||
async def async_get_actions(
|
async def async_get_actions(
|
||||||
hass: HomeAssistant, device_id: str
|
hass: HomeAssistant, device_id: str
|
||||||
) -> list[dict[str, str]]:
|
) -> list[dict[str, str]]:
|
||||||
@@ -70,7 +78,7 @@ async def async_get_actions(
|
|||||||
base_action: dict = {
|
base_action: dict = {
|
||||||
CONF_DEVICE_ID: device_id,
|
CONF_DEVICE_ID: device_id,
|
||||||
CONF_DOMAIN: DOMAIN,
|
CONF_DOMAIN: DOMAIN,
|
||||||
CONF_ENTITY_ID: entry.entity_id,
|
CONF_ENTITY_ID: entry.id,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Add actions for each entity that belongs to this integration
|
# Add actions for each entity that belongs to this integration
|
||||||
@@ -124,7 +132,9 @@ async def async_get_action_capabilities(
|
|||||||
"""List action capabilities."""
|
"""List action capabilities."""
|
||||||
# We need to refer to the state directly because ATTR_CODE_ARM_REQUIRED is not a
|
# We need to refer to the state directly because ATTR_CODE_ARM_REQUIRED is not a
|
||||||
# capability attribute
|
# capability attribute
|
||||||
state = hass.states.get(config[CONF_ENTITY_ID])
|
registry = er.async_get(hass)
|
||||||
|
entity_id = er.async_resolve_entity_id(registry, config[CONF_ENTITY_ID])
|
||||||
|
state = hass.states.get(entity_id) if entity_id else None
|
||||||
code_required = state.attributes.get(ATTR_CODE_ARM_REQUIRED) if state else False
|
code_required = state.attributes.get(ATTR_CODE_ARM_REQUIRED) if state else False
|
||||||
|
|
||||||
if config[CONF_TYPE] == "trigger" or (
|
if config[CONF_TYPE] == "trigger" or (
|
||||||
|
@@ -58,7 +58,7 @@ CONDITION_TYPES: Final[set[str]] = {
|
|||||||
|
|
||||||
CONDITION_SCHEMA: Final = DEVICE_CONDITION_BASE_SCHEMA.extend(
|
CONDITION_SCHEMA: Final = DEVICE_CONDITION_BASE_SCHEMA.extend(
|
||||||
{
|
{
|
||||||
vol.Required(CONF_ENTITY_ID): cv.entity_id,
|
vol.Required(CONF_ENTITY_ID): cv.entity_id_or_uuid,
|
||||||
vol.Required(CONF_TYPE): vol.In(CONDITION_TYPES),
|
vol.Required(CONF_TYPE): vol.In(CONDITION_TYPES),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -83,7 +83,7 @@ async def async_get_conditions(
|
|||||||
CONF_CONDITION: "device",
|
CONF_CONDITION: "device",
|
||||||
CONF_DEVICE_ID: device_id,
|
CONF_DEVICE_ID: device_id,
|
||||||
CONF_DOMAIN: DOMAIN,
|
CONF_DOMAIN: DOMAIN,
|
||||||
CONF_ENTITY_ID: entry.entity_id,
|
CONF_ENTITY_ID: entry.id,
|
||||||
}
|
}
|
||||||
|
|
||||||
conditions += [
|
conditions += [
|
||||||
@@ -126,8 +126,11 @@ def async_condition_from_config(
|
|||||||
elif config[CONF_TYPE] == CONDITION_ARMED_CUSTOM_BYPASS:
|
elif config[CONF_TYPE] == CONDITION_ARMED_CUSTOM_BYPASS:
|
||||||
state = STATE_ALARM_ARMED_CUSTOM_BYPASS
|
state = STATE_ALARM_ARMED_CUSTOM_BYPASS
|
||||||
|
|
||||||
|
registry = er.async_get(hass)
|
||||||
|
entity_id = er.async_resolve_entity_id(registry, config[ATTR_ENTITY_ID])
|
||||||
|
|
||||||
def test_is_state(hass: HomeAssistant, variables: TemplateVarsType) -> bool:
|
def test_is_state(hass: HomeAssistant, variables: TemplateVarsType) -> bool:
|
||||||
"""Test if an entity is a certain state."""
|
"""Test if an entity is a certain state."""
|
||||||
return condition.state(hass, config[ATTR_ENTITY_ID], state)
|
return condition.state(hass, entity_id, state)
|
||||||
|
|
||||||
return test_is_state
|
return test_is_state
|
||||||
|
@@ -46,7 +46,7 @@ TRIGGER_TYPES: Final[set[str]] = BASIC_TRIGGER_TYPES | {
|
|||||||
|
|
||||||
TRIGGER_SCHEMA: Final = DEVICE_TRIGGER_BASE_SCHEMA.extend(
|
TRIGGER_SCHEMA: Final = DEVICE_TRIGGER_BASE_SCHEMA.extend(
|
||||||
{
|
{
|
||||||
vol.Required(CONF_ENTITY_ID): cv.entity_id,
|
vol.Required(CONF_ENTITY_ID): cv.entity_id_or_uuid,
|
||||||
vol.Required(CONF_TYPE): vol.In(TRIGGER_TYPES),
|
vol.Required(CONF_TYPE): vol.In(TRIGGER_TYPES),
|
||||||
vol.Optional(CONF_FOR): cv.positive_time_period_dict,
|
vol.Optional(CONF_FOR): cv.positive_time_period_dict,
|
||||||
}
|
}
|
||||||
@@ -72,7 +72,7 @@ async def async_get_triggers(
|
|||||||
CONF_PLATFORM: "device",
|
CONF_PLATFORM: "device",
|
||||||
CONF_DEVICE_ID: device_id,
|
CONF_DEVICE_ID: device_id,
|
||||||
CONF_DOMAIN: DOMAIN,
|
CONF_DOMAIN: DOMAIN,
|
||||||
CONF_ENTITY_ID: entry.entity_id,
|
CONF_ENTITY_ID: entry.id,
|
||||||
}
|
}
|
||||||
|
|
||||||
triggers += [
|
triggers += [
|
||||||
|
@@ -5,5 +5,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/alpha_vantage",
|
"documentation": "https://www.home-assistant.io/integrations/alpha_vantage",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["alpha_vantage"],
|
"loggers": ["alpha_vantage"],
|
||||||
"requirements": ["alpha_vantage==2.3.1"]
|
"requirements": ["alpha-vantage==2.3.1"]
|
||||||
}
|
}
|
||||||
|
@@ -7,5 +7,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/ambiclimate",
|
"documentation": "https://www.home-assistant.io/integrations/ambiclimate",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["ambiclimate"],
|
"loggers": ["ambiclimate"],
|
||||||
"requirements": ["ambiclimate==0.2.1"]
|
"requirements": ["Ambiclimate==0.2.1"]
|
||||||
}
|
}
|
||||||
|
@@ -3,6 +3,8 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from homeassistant.helpers.typing import UndefinedType
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
|
|
||||||
|
|
||||||
@@ -14,7 +16,7 @@ def service_signal(service: str, *args: str) -> str:
|
|||||||
def log_update_error(
|
def log_update_error(
|
||||||
logger: logging.Logger,
|
logger: logging.Logger,
|
||||||
action: str,
|
action: str,
|
||||||
name: str | None,
|
name: str | UndefinedType | None,
|
||||||
entity_type: str,
|
entity_type: str,
|
||||||
error: Exception,
|
error: Exception,
|
||||||
level: int = logging.ERROR,
|
level: int = logging.ERROR,
|
||||||
|
@@ -21,12 +21,22 @@ from homeassistant.components.recorder import (
|
|||||||
DOMAIN as RECORDER_DOMAIN,
|
DOMAIN as RECORDER_DOMAIN,
|
||||||
get_instance as get_recorder_instance,
|
get_instance as get_recorder_instance,
|
||||||
)
|
)
|
||||||
|
import homeassistant.config as conf_util
|
||||||
|
from homeassistant.config_entries import (
|
||||||
|
SOURCE_IGNORE,
|
||||||
|
)
|
||||||
from homeassistant.const import ATTR_DOMAIN, __version__ as HA_VERSION
|
from homeassistant.const import ATTR_DOMAIN, __version__ as HA_VERSION
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant, callback
|
||||||
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
import homeassistant.helpers.entity_registry as er
|
||||||
from homeassistant.helpers.storage import Store
|
from homeassistant.helpers.storage import Store
|
||||||
from homeassistant.helpers.system_info import async_get_system_info
|
from homeassistant.helpers.system_info import async_get_system_info
|
||||||
from homeassistant.loader import IntegrationNotFound, async_get_integrations
|
from homeassistant.loader import (
|
||||||
|
Integration,
|
||||||
|
IntegrationNotFound,
|
||||||
|
async_get_integrations,
|
||||||
|
)
|
||||||
from homeassistant.setup import async_get_loaded_integrations
|
from homeassistant.setup import async_get_loaded_integrations
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
@@ -206,8 +216,25 @@ class Analytics:
|
|||||||
if self.preferences.get(ATTR_USAGE, False) or self.preferences.get(
|
if self.preferences.get(ATTR_USAGE, False) or self.preferences.get(
|
||||||
ATTR_STATISTICS, False
|
ATTR_STATISTICS, False
|
||||||
):
|
):
|
||||||
|
ent_reg = er.async_get(self.hass)
|
||||||
|
|
||||||
|
try:
|
||||||
|
yaml_configuration = await conf_util.async_hass_config_yaml(self.hass)
|
||||||
|
except HomeAssistantError as err:
|
||||||
|
LOGGER.error(err)
|
||||||
|
return
|
||||||
|
|
||||||
|
configuration_set = set(yaml_configuration)
|
||||||
|
er_platforms = {
|
||||||
|
entity.platform
|
||||||
|
for entity in ent_reg.entities.values()
|
||||||
|
if not entity.disabled
|
||||||
|
}
|
||||||
|
|
||||||
domains = async_get_loaded_integrations(self.hass)
|
domains = async_get_loaded_integrations(self.hass)
|
||||||
configured_integrations = await async_get_integrations(self.hass, domains)
|
configured_integrations = await async_get_integrations(self.hass, domains)
|
||||||
|
enabled_domains = set(configured_integrations)
|
||||||
|
|
||||||
for integration in configured_integrations.values():
|
for integration in configured_integrations.values():
|
||||||
if isinstance(integration, IntegrationNotFound):
|
if isinstance(integration, IntegrationNotFound):
|
||||||
continue
|
continue
|
||||||
@@ -215,7 +242,11 @@ class Analytics:
|
|||||||
if isinstance(integration, BaseException):
|
if isinstance(integration, BaseException):
|
||||||
raise integration
|
raise integration
|
||||||
|
|
||||||
if integration.disabled:
|
if not self._async_should_report_integration(
|
||||||
|
integration=integration,
|
||||||
|
yaml_domains=configuration_set,
|
||||||
|
entity_registry_platforms=er_platforms,
|
||||||
|
):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if not integration.is_built_in:
|
if not integration.is_built_in:
|
||||||
@@ -253,12 +284,12 @@ class Analytics:
|
|||||||
if supervisor_info is not None:
|
if supervisor_info is not None:
|
||||||
payload[ATTR_ADDONS] = addons
|
payload[ATTR_ADDONS] = addons
|
||||||
|
|
||||||
if ENERGY_DOMAIN in integrations:
|
if ENERGY_DOMAIN in enabled_domains:
|
||||||
payload[ATTR_ENERGY] = {
|
payload[ATTR_ENERGY] = {
|
||||||
ATTR_CONFIGURED: await energy_is_configured(self.hass)
|
ATTR_CONFIGURED: await energy_is_configured(self.hass)
|
||||||
}
|
}
|
||||||
|
|
||||||
if RECORDER_DOMAIN in integrations:
|
if RECORDER_DOMAIN in enabled_domains:
|
||||||
instance = get_recorder_instance(self.hass)
|
instance = get_recorder_instance(self.hass)
|
||||||
engine = instance.database_engine
|
engine = instance.database_engine
|
||||||
if engine and engine.version is not None:
|
if engine and engine.version is not None:
|
||||||
@@ -306,3 +337,34 @@ class Analytics:
|
|||||||
LOGGER.error(
|
LOGGER.error(
|
||||||
"Error sending analytics to %s: %r", ANALYTICS_ENDPOINT_URL, err
|
"Error sending analytics to %s: %r", ANALYTICS_ENDPOINT_URL, err
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _async_should_report_integration(
|
||||||
|
self,
|
||||||
|
integration: Integration,
|
||||||
|
yaml_domains: set[str],
|
||||||
|
entity_registry_platforms: set[str],
|
||||||
|
) -> bool:
|
||||||
|
"""Return a bool to indicate if this integration should be reported."""
|
||||||
|
if integration.disabled:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Check if the integration is defined in YAML or in the entity registry
|
||||||
|
if (
|
||||||
|
integration.domain in yaml_domains
|
||||||
|
or integration.domain in entity_registry_platforms
|
||||||
|
):
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Check if the integration provide a config flow
|
||||||
|
if not integration.config_flow:
|
||||||
|
return False
|
||||||
|
|
||||||
|
entries = self.hass.config_entries.async_entries(integration.domain)
|
||||||
|
|
||||||
|
# Filter out ignored and disabled entries
|
||||||
|
return any(
|
||||||
|
entry
|
||||||
|
for entry in entries
|
||||||
|
if entry.source != SOURCE_IGNORE and entry.disabled_by is None
|
||||||
|
)
|
||||||
|
@@ -296,7 +296,6 @@ class ADBDevice(MediaPlayerEntity):
|
|||||||
self._process_config,
|
self._process_config,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def media_image_hash(self) -> str | None:
|
def media_image_hash(self) -> str | None:
|
||||||
|
@@ -16,6 +16,7 @@ from .const import DOMAIN
|
|||||||
class AndroidTVRemoteBaseEntity(Entity):
|
class AndroidTVRemoteBaseEntity(Entity):
|
||||||
"""Android TV Remote Base Entity."""
|
"""Android TV Remote Base Entity."""
|
||||||
|
|
||||||
|
_attr_name = None
|
||||||
_attr_has_entity_name = True
|
_attr_has_entity_name = True
|
||||||
_attr_should_poll = False
|
_attr_should_poll = False
|
||||||
|
|
||||||
|
@@ -5,5 +5,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/anel_pwrctrl",
|
"documentation": "https://www.home-assistant.io/integrations/anel_pwrctrl",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["anel_pwrctrl"],
|
"loggers": ["anel_pwrctrl"],
|
||||||
"requirements": ["anel_pwrctrl-homeassistant==0.0.1.dev2"]
|
"requirements": ["anel-pwrctrl-homeassistant==0.0.1.dev2"]
|
||||||
}
|
}
|
||||||
|
@@ -80,6 +80,7 @@ class AnthemAVR(MediaPlayerEntity):
|
|||||||
self._attr_name = f"zone {zone_number}"
|
self._attr_name = f"zone {zone_number}"
|
||||||
self._attr_unique_id = f"{mac_address}_{zone_number}"
|
self._attr_unique_id = f"{mac_address}_{zone_number}"
|
||||||
else:
|
else:
|
||||||
|
self._attr_name = None
|
||||||
self._attr_unique_id = mac_address
|
self._attr_unique_id = mac_address
|
||||||
|
|
||||||
self._attr_device_info = DeviceInfo(
|
self._attr_device_info = DeviceInfo(
|
||||||
|
@@ -7,7 +7,7 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/apple_tv",
|
"documentation": "https://www.home-assistant.io/integrations/apple_tv",
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["pyatv", "srptools"],
|
"loggers": ["pyatv", "srptools"],
|
||||||
"requirements": ["pyatv==0.12.0"],
|
"requirements": ["pyatv==0.13.2"],
|
||||||
"zeroconf": [
|
"zeroconf": [
|
||||||
"_mediaremotetv._tcp.local.",
|
"_mediaremotetv._tcp.local.",
|
||||||
"_companion-link._tcp.local.",
|
"_companion-link._tcp.local.",
|
||||||
|
@@ -282,7 +282,7 @@ class AppleTvMediaPlayer(AppleTVEntity, MediaPlayerEntity):
|
|||||||
"""Send the play_media command to the media player."""
|
"""Send the play_media command to the media player."""
|
||||||
# If input (file) has a file format supported by pyatv, then stream it with
|
# If input (file) has a file format supported by pyatv, then stream it with
|
||||||
# RAOP. Otherwise try to play it with regular AirPlay.
|
# RAOP. Otherwise try to play it with regular AirPlay.
|
||||||
if media_type == MediaType.APP:
|
if media_type in {MediaType.APP, MediaType.URL}:
|
||||||
await self.atv.apps.launch_app(media_id)
|
await self.atv.apps.launch_app(media_id)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@@ -49,6 +49,7 @@
|
|||||||
},
|
},
|
||||||
"abort": {
|
"abort": {
|
||||||
"ipv6_not_supported": "IPv6 is not supported.",
|
"ipv6_not_supported": "IPv6 is not supported.",
|
||||||
|
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
||||||
"no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]",
|
"no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]",
|
||||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||||
"device_did_not_pair": "No attempt to finish pairing process was made from the device.",
|
"device_did_not_pair": "No attempt to finish pairing process was made from the device.",
|
||||||
|
@@ -5,5 +5,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/aquostv",
|
"documentation": "https://www.home-assistant.io/integrations/aquostv",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["sharp_aquos_rc"],
|
"loggers": ["sharp_aquos_rc"],
|
||||||
"requirements": ["sharp_aquos_rc==0.3.2"]
|
"requirements": ["sharp-aquos-rc==0.3.2"]
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
"""Support for Aranet sensors."""
|
"""Support for Aranet sensors."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
from aranet4.client import Aranet4Advertisement
|
from aranet4.client import Aranet4Advertisement
|
||||||
from bleak.backends.device import BLEDevice
|
from bleak.backends.device import BLEDevice
|
||||||
|
|
||||||
@@ -23,6 +25,7 @@ from homeassistant.const import (
|
|||||||
ATTR_SW_VERSION,
|
ATTR_SW_VERSION,
|
||||||
CONCENTRATION_PARTS_PER_MILLION,
|
CONCENTRATION_PARTS_PER_MILLION,
|
||||||
PERCENTAGE,
|
PERCENTAGE,
|
||||||
|
EntityCategory,
|
||||||
UnitOfPressure,
|
UnitOfPressure,
|
||||||
UnitOfTemperature,
|
UnitOfTemperature,
|
||||||
UnitOfTime,
|
UnitOfTime,
|
||||||
@@ -33,43 +36,55 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|||||||
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class AranetSensorEntityDescription(SensorEntityDescription):
|
||||||
|
"""Class to describe an Aranet sensor entity."""
|
||||||
|
|
||||||
|
# PassiveBluetoothDataUpdate does not support UNDEFINED
|
||||||
|
# Restrict the type to satisfy the type checker and catch attempts
|
||||||
|
# to use UNDEFINED in the entity descriptions.
|
||||||
|
name: str | None = None
|
||||||
|
|
||||||
|
|
||||||
SENSOR_DESCRIPTIONS = {
|
SENSOR_DESCRIPTIONS = {
|
||||||
"temperature": SensorEntityDescription(
|
"temperature": AranetSensorEntityDescription(
|
||||||
key="temperature",
|
key="temperature",
|
||||||
name="Temperature",
|
name="Temperature",
|
||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
"humidity": SensorEntityDescription(
|
"humidity": AranetSensorEntityDescription(
|
||||||
key="humidity",
|
key="humidity",
|
||||||
name="Humidity",
|
name="Humidity",
|
||||||
device_class=SensorDeviceClass.HUMIDITY,
|
device_class=SensorDeviceClass.HUMIDITY,
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
"pressure": SensorEntityDescription(
|
"pressure": AranetSensorEntityDescription(
|
||||||
key="pressure",
|
key="pressure",
|
||||||
name="Pressure",
|
name="Pressure",
|
||||||
device_class=SensorDeviceClass.PRESSURE,
|
device_class=SensorDeviceClass.PRESSURE,
|
||||||
native_unit_of_measurement=UnitOfPressure.HPA,
|
native_unit_of_measurement=UnitOfPressure.HPA,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
"co2": SensorEntityDescription(
|
"co2": AranetSensorEntityDescription(
|
||||||
key="co2",
|
key="co2",
|
||||||
name="Carbon Dioxide",
|
name="Carbon Dioxide",
|
||||||
device_class=SensorDeviceClass.CO2,
|
device_class=SensorDeviceClass.CO2,
|
||||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
"battery": SensorEntityDescription(
|
"battery": AranetSensorEntityDescription(
|
||||||
key="battery",
|
key="battery",
|
||||||
name="Battery",
|
name="Battery",
|
||||||
device_class=SensorDeviceClass.BATTERY,
|
device_class=SensorDeviceClass.BATTERY,
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
),
|
),
|
||||||
"interval": SensorEntityDescription(
|
"interval": AranetSensorEntityDescription(
|
||||||
key="update_interval",
|
key="update_interval",
|
||||||
name="Update Interval",
|
name="Update Interval",
|
||||||
device_class=SensorDeviceClass.DURATION,
|
device_class=SensorDeviceClass.DURATION,
|
||||||
@@ -77,6 +92,7 @@ SENSOR_DESCRIPTIONS = {
|
|||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
# The interval setting is not a generally useful entity for most users.
|
# The interval setting is not a generally useful entity for most users.
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -3,7 +3,9 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.device_automation import DEVICE_TRIGGER_BASE_SCHEMA
|
from homeassistant.components.device_automation import (
|
||||||
|
DEVICE_TRIGGER_BASE_SCHEMA,
|
||||||
|
)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ENTITY_ID,
|
ATTR_ENTITY_ID,
|
||||||
CONF_DEVICE_ID,
|
CONF_DEVICE_ID,
|
||||||
@@ -22,7 +24,7 @@ from .const import DOMAIN, EVENT_TURN_ON
|
|||||||
TRIGGER_TYPES = {"turn_on"}
|
TRIGGER_TYPES = {"turn_on"}
|
||||||
TRIGGER_SCHEMA = DEVICE_TRIGGER_BASE_SCHEMA.extend(
|
TRIGGER_SCHEMA = DEVICE_TRIGGER_BASE_SCHEMA.extend(
|
||||||
{
|
{
|
||||||
vol.Required(CONF_ENTITY_ID): cv.entity_id,
|
vol.Required(CONF_ENTITY_ID): cv.entity_id_or_uuid,
|
||||||
vol.Required(CONF_TYPE): vol.In(TRIGGER_TYPES),
|
vol.Required(CONF_TYPE): vol.In(TRIGGER_TYPES),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -43,7 +45,7 @@ async def async_get_triggers(
|
|||||||
CONF_PLATFORM: "device",
|
CONF_PLATFORM: "device",
|
||||||
CONF_DEVICE_ID: device_id,
|
CONF_DEVICE_ID: device_id,
|
||||||
CONF_DOMAIN: DOMAIN,
|
CONF_DOMAIN: DOMAIN,
|
||||||
CONF_ENTITY_ID: entry.entity_id,
|
CONF_ENTITY_ID: entry.id,
|
||||||
CONF_TYPE: "turn_on",
|
CONF_TYPE: "turn_on",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -62,7 +64,8 @@ async def async_attach_trigger(
|
|||||||
job = HassJob(action)
|
job = HassJob(action)
|
||||||
|
|
||||||
if config[CONF_TYPE] == "turn_on":
|
if config[CONF_TYPE] == "turn_on":
|
||||||
entity_id = config[CONF_ENTITY_ID]
|
registry = er.async_get(hass)
|
||||||
|
entity_id = er.async_resolve_entity_id(registry, config[ATTR_ENTITY_ID])
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _handle_event(event: Event) -> None:
|
def _handle_event(event: Event) -> None:
|
||||||
@@ -71,9 +74,10 @@ async def async_attach_trigger(
|
|||||||
job,
|
job,
|
||||||
{
|
{
|
||||||
"trigger": {
|
"trigger": {
|
||||||
**trigger_data, # type: ignore[arg-type] # https://github.com/python/mypy/issues/9117
|
**trigger_data,
|
||||||
**config,
|
**config,
|
||||||
"description": f"{DOMAIN} - {entity_id}",
|
"description": f"{DOMAIN} - {entity_id}",
|
||||||
|
"entity_id": entity_id,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
event.context,
|
event.context,
|
||||||
|
@@ -6,7 +6,7 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/arcam_fmj",
|
"documentation": "https://www.home-assistant.io/integrations/arcam_fmj",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["arcam"],
|
"loggers": ["arcam"],
|
||||||
"requirements": ["arcam-fmj==1.3.0"],
|
"requirements": ["arcam-fmj==1.4.0"],
|
||||||
"ssdp": [
|
"ssdp": [
|
||||||
{
|
{
|
||||||
"deviceType": "urn:schemas-upnp-org:device:MediaRenderer:1",
|
"deviceType": "urn:schemas-upnp-org:device:MediaRenderer:1",
|
||||||
|
@@ -57,6 +57,7 @@ async def async_pipeline_from_audio_stream(
|
|||||||
pipeline_id: str | None = None,
|
pipeline_id: str | None = None,
|
||||||
conversation_id: str | None = None,
|
conversation_id: str | None = None,
|
||||||
tts_audio_output: str | None = None,
|
tts_audio_output: str | None = None,
|
||||||
|
device_id: str | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Create an audio pipeline from an audio stream.
|
"""Create an audio pipeline from an audio stream.
|
||||||
|
|
||||||
@@ -64,6 +65,7 @@ async def async_pipeline_from_audio_stream(
|
|||||||
"""
|
"""
|
||||||
pipeline_input = PipelineInput(
|
pipeline_input = PipelineInput(
|
||||||
conversation_id=conversation_id,
|
conversation_id=conversation_id,
|
||||||
|
device_id=device_id,
|
||||||
stt_metadata=stt_metadata,
|
stt_metadata=stt_metadata,
|
||||||
stt_stream=stt_stream,
|
stt_stream=stt_stream,
|
||||||
run=PipelineRun(
|
run=PipelineRun(
|
||||||
|
@@ -499,7 +499,7 @@ class PipelineRun:
|
|||||||
self.intent_agent = agent_info.id
|
self.intent_agent = agent_info.id
|
||||||
|
|
||||||
async def recognize_intent(
|
async def recognize_intent(
|
||||||
self, intent_input: str, conversation_id: str | None
|
self, intent_input: str, conversation_id: str | None, device_id: str | None
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Run intent recognition portion of pipeline. Returns text to speak."""
|
"""Run intent recognition portion of pipeline. Returns text to speak."""
|
||||||
if self.intent_agent is None:
|
if self.intent_agent is None:
|
||||||
@@ -512,6 +512,8 @@ class PipelineRun:
|
|||||||
"engine": self.intent_agent,
|
"engine": self.intent_agent,
|
||||||
"language": self.pipeline.conversation_language,
|
"language": self.pipeline.conversation_language,
|
||||||
"intent_input": intent_input,
|
"intent_input": intent_input,
|
||||||
|
"conversation_id": conversation_id,
|
||||||
|
"device_id": device_id,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -521,6 +523,7 @@ class PipelineRun:
|
|||||||
hass=self.hass,
|
hass=self.hass,
|
||||||
text=intent_input,
|
text=intent_input,
|
||||||
conversation_id=conversation_id,
|
conversation_id=conversation_id,
|
||||||
|
device_id=device_id,
|
||||||
context=self.context,
|
context=self.context,
|
||||||
language=self.pipeline.conversation_language,
|
language=self.pipeline.conversation_language,
|
||||||
agent_id=self.intent_agent,
|
agent_id=self.intent_agent,
|
||||||
@@ -655,6 +658,8 @@ class PipelineInput:
|
|||||||
|
|
||||||
conversation_id: str | None = None
|
conversation_id: str | None = None
|
||||||
|
|
||||||
|
device_id: str | None = None
|
||||||
|
|
||||||
async def execute(self) -> None:
|
async def execute(self) -> None:
|
||||||
"""Run pipeline."""
|
"""Run pipeline."""
|
||||||
self.run.start()
|
self.run.start()
|
||||||
@@ -678,7 +683,9 @@ class PipelineInput:
|
|||||||
if current_stage == PipelineStage.INTENT:
|
if current_stage == PipelineStage.INTENT:
|
||||||
assert intent_input is not None
|
assert intent_input is not None
|
||||||
tts_input = await self.run.recognize_intent(
|
tts_input = await self.run.recognize_intent(
|
||||||
intent_input, self.conversation_id
|
intent_input,
|
||||||
|
self.conversation_id,
|
||||||
|
self.device_id,
|
||||||
)
|
)
|
||||||
current_stage = PipelineStage.TTS
|
current_stage = PipelineStage.TTS
|
||||||
|
|
||||||
@@ -730,17 +737,30 @@ class PipelineInput:
|
|||||||
)
|
)
|
||||||
|
|
||||||
start_stage_index = PIPELINE_STAGE_ORDER.index(self.run.start_stage)
|
start_stage_index = PIPELINE_STAGE_ORDER.index(self.run.start_stage)
|
||||||
|
end_stage_index = PIPELINE_STAGE_ORDER.index(self.run.end_stage)
|
||||||
|
|
||||||
prepare_tasks = []
|
prepare_tasks = []
|
||||||
|
|
||||||
if start_stage_index <= PIPELINE_STAGE_ORDER.index(PipelineStage.STT):
|
if (
|
||||||
|
start_stage_index
|
||||||
|
<= PIPELINE_STAGE_ORDER.index(PipelineStage.STT)
|
||||||
|
<= end_stage_index
|
||||||
|
):
|
||||||
# self.stt_metadata can't be None or we'd raise above
|
# self.stt_metadata can't be None or we'd raise above
|
||||||
prepare_tasks.append(self.run.prepare_speech_to_text(self.stt_metadata)) # type: ignore[arg-type]
|
prepare_tasks.append(self.run.prepare_speech_to_text(self.stt_metadata)) # type: ignore[arg-type]
|
||||||
|
|
||||||
if start_stage_index <= PIPELINE_STAGE_ORDER.index(PipelineStage.INTENT):
|
if (
|
||||||
|
start_stage_index
|
||||||
|
<= PIPELINE_STAGE_ORDER.index(PipelineStage.INTENT)
|
||||||
|
<= end_stage_index
|
||||||
|
):
|
||||||
prepare_tasks.append(self.run.prepare_recognize_intent())
|
prepare_tasks.append(self.run.prepare_recognize_intent())
|
||||||
|
|
||||||
if start_stage_index <= PIPELINE_STAGE_ORDER.index(PipelineStage.TTS):
|
if (
|
||||||
|
start_stage_index
|
||||||
|
<= PIPELINE_STAGE_ORDER.index(PipelineStage.TTS)
|
||||||
|
<= end_stage_index
|
||||||
|
):
|
||||||
prepare_tasks.append(self.run.prepare_text_to_speech())
|
prepare_tasks.append(self.run.prepare_text_to_speech())
|
||||||
|
|
||||||
if prepare_tasks:
|
if prepare_tasks:
|
||||||
@@ -944,6 +964,7 @@ class PipelineData:
|
|||||||
|
|
||||||
pipeline_runs: dict[str, LimitedSizeDict[str, PipelineRunDebug]]
|
pipeline_runs: dict[str, LimitedSizeDict[str, PipelineRunDebug]]
|
||||||
pipeline_store: PipelineStorageCollection
|
pipeline_store: PipelineStorageCollection
|
||||||
|
pipeline_devices: set[str] = field(default_factory=set, init=False)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
@@ -10,7 +10,8 @@ from homeassistant.core import HomeAssistant, callback
|
|||||||
from homeassistant.helpers import collection, entity_registry as er, restore_state
|
from homeassistant.helpers import collection, entity_registry as er, restore_state
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
from .pipeline import PipelineStorageCollection
|
from .pipeline import PipelineData, PipelineStorageCollection
|
||||||
|
from .vad import VadSensitivity
|
||||||
|
|
||||||
OPTION_PREFERRED = "preferred"
|
OPTION_PREFERRED = "preferred"
|
||||||
|
|
||||||
@@ -38,6 +39,25 @@ def get_chosen_pipeline(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def get_vad_sensitivity(
|
||||||
|
hass: HomeAssistant, domain: str, unique_id_prefix: str
|
||||||
|
) -> VadSensitivity:
|
||||||
|
"""Get the chosen vad sensitivity for a domain."""
|
||||||
|
ent_reg = er.async_get(hass)
|
||||||
|
sensitivity_entity_id = ent_reg.async_get_entity_id(
|
||||||
|
Platform.SELECT, domain, f"{unique_id_prefix}-vad_sensitivity"
|
||||||
|
)
|
||||||
|
if sensitivity_entity_id is None:
|
||||||
|
return VadSensitivity.DEFAULT
|
||||||
|
|
||||||
|
state = hass.states.get(sensitivity_entity_id)
|
||||||
|
if state is None:
|
||||||
|
return VadSensitivity.DEFAULT
|
||||||
|
|
||||||
|
return VadSensitivity(state.state)
|
||||||
|
|
||||||
|
|
||||||
class AssistPipelineSelect(SelectEntity, restore_state.RestoreEntity):
|
class AssistPipelineSelect(SelectEntity, restore_state.RestoreEntity):
|
||||||
"""Entity to represent a pipeline selector."""
|
"""Entity to represent a pipeline selector."""
|
||||||
|
|
||||||
@@ -60,15 +80,24 @@ class AssistPipelineSelect(SelectEntity, restore_state.RestoreEntity):
|
|||||||
"""When entity is added to Home Assistant."""
|
"""When entity is added to Home Assistant."""
|
||||||
await super().async_added_to_hass()
|
await super().async_added_to_hass()
|
||||||
|
|
||||||
pipeline_store: PipelineStorageCollection = self.hass.data[
|
pipeline_data: PipelineData = self.hass.data[DOMAIN]
|
||||||
DOMAIN
|
pipeline_store = pipeline_data.pipeline_store
|
||||||
].pipeline_store
|
self.async_on_remove(
|
||||||
pipeline_store.async_add_change_set_listener(self._pipelines_updated)
|
pipeline_store.async_add_change_set_listener(self._pipelines_updated)
|
||||||
|
)
|
||||||
|
|
||||||
state = await self.async_get_last_state()
|
state = await self.async_get_last_state()
|
||||||
if state is not None and state.state in self.options:
|
if state is not None and state.state in self.options:
|
||||||
self._attr_current_option = state.state
|
self._attr_current_option = state.state
|
||||||
|
|
||||||
|
if self.registry_entry and (device_id := self.registry_entry.device_id):
|
||||||
|
pipeline_data.pipeline_devices.add(device_id)
|
||||||
|
self.async_on_remove(
|
||||||
|
lambda: pipeline_data.pipeline_devices.discard(
|
||||||
|
device_id # type: ignore[arg-type]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
async def async_select_option(self, option: str) -> None:
|
async def async_select_option(self, option: str) -> None:
|
||||||
"""Select an option."""
|
"""Select an option."""
|
||||||
self._attr_current_option = option
|
self._attr_current_option = option
|
||||||
@@ -93,3 +122,34 @@ class AssistPipelineSelect(SelectEntity, restore_state.RestoreEntity):
|
|||||||
|
|
||||||
if self._attr_current_option not in options:
|
if self._attr_current_option not in options:
|
||||||
self._attr_current_option = OPTION_PREFERRED
|
self._attr_current_option = OPTION_PREFERRED
|
||||||
|
|
||||||
|
|
||||||
|
class VadSensitivitySelect(SelectEntity, restore_state.RestoreEntity):
|
||||||
|
"""Entity to represent VAD sensitivity."""
|
||||||
|
|
||||||
|
entity_description = SelectEntityDescription(
|
||||||
|
key="vad_sensitivity",
|
||||||
|
translation_key="vad_sensitivity",
|
||||||
|
entity_category=EntityCategory.CONFIG,
|
||||||
|
)
|
||||||
|
_attr_should_poll = False
|
||||||
|
_attr_current_option = VadSensitivity.DEFAULT.value
|
||||||
|
_attr_options = [vs.value for vs in VadSensitivity]
|
||||||
|
|
||||||
|
def __init__(self, hass: HomeAssistant, unique_id_prefix: str) -> None:
|
||||||
|
"""Initialize a pipeline selector."""
|
||||||
|
self._attr_unique_id = f"{unique_id_prefix}-vad_sensitivity"
|
||||||
|
self.hass = hass
|
||||||
|
|
||||||
|
async def async_added_to_hass(self) -> None:
|
||||||
|
"""When entity is added to Home Assistant."""
|
||||||
|
await super().async_added_to_hass()
|
||||||
|
|
||||||
|
state = await self.async_get_last_state()
|
||||||
|
if state is not None and state.state in self.options:
|
||||||
|
self._attr_current_option = state.state
|
||||||
|
|
||||||
|
async def async_select_option(self, option: str) -> None:
|
||||||
|
"""Select an option."""
|
||||||
|
self._attr_current_option = option
|
||||||
|
self.async_write_ha_state()
|
||||||
|
@@ -11,6 +11,14 @@
|
|||||||
"state": {
|
"state": {
|
||||||
"preferred": "Preferred"
|
"preferred": "Preferred"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"vad_sensitivity": {
|
||||||
|
"name": "Finished speaking detection",
|
||||||
|
"state": {
|
||||||
|
"default": "Default",
|
||||||
|
"aggressive": "Aggressive",
|
||||||
|
"relaxed": "Relaxed"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,11 +1,35 @@
|
|||||||
"""Voice activity detection."""
|
"""Voice activity detection."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
|
|
||||||
import webrtcvad
|
import webrtcvad
|
||||||
|
|
||||||
|
from homeassistant.backports.enum import StrEnum
|
||||||
|
|
||||||
_SAMPLE_RATE = 16000
|
_SAMPLE_RATE = 16000
|
||||||
|
|
||||||
|
|
||||||
|
class VadSensitivity(StrEnum):
|
||||||
|
"""How quickly the end of a voice command is detected."""
|
||||||
|
|
||||||
|
DEFAULT = "default"
|
||||||
|
RELAXED = "relaxed"
|
||||||
|
AGGRESSIVE = "aggressive"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def to_seconds(sensitivity: VadSensitivity | str) -> float:
|
||||||
|
"""Return seconds of silence for sensitivity level."""
|
||||||
|
sensitivity = VadSensitivity(sensitivity)
|
||||||
|
if sensitivity == VadSensitivity.RELAXED:
|
||||||
|
return 2.0
|
||||||
|
|
||||||
|
if sensitivity == VadSensitivity.AGGRESSIVE:
|
||||||
|
return 0.5
|
||||||
|
|
||||||
|
return 1.0
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class VoiceCommandSegmenter:
|
class VoiceCommandSegmenter:
|
||||||
"""Segments an audio stream into voice commands using webrtcvad."""
|
"""Segments an audio stream into voice commands using webrtcvad."""
|
||||||
@@ -113,16 +137,15 @@ class VoiceCommandSegmenter:
|
|||||||
self._reset_seconds_left -= self._seconds_per_chunk
|
self._reset_seconds_left -= self._seconds_per_chunk
|
||||||
if self._reset_seconds_left <= 0:
|
if self._reset_seconds_left <= 0:
|
||||||
self._speech_seconds_left = self.speech_seconds
|
self._speech_seconds_left = self.speech_seconds
|
||||||
|
elif not is_speech:
|
||||||
|
self._reset_seconds_left = self.reset_seconds
|
||||||
|
self._silence_seconds_left -= self._seconds_per_chunk
|
||||||
|
if self._silence_seconds_left <= 0:
|
||||||
|
return False
|
||||||
else:
|
else:
|
||||||
if not is_speech:
|
# Reset if enough speech
|
||||||
self._reset_seconds_left = self.reset_seconds
|
self._reset_seconds_left -= self._seconds_per_chunk
|
||||||
self._silence_seconds_left -= self._seconds_per_chunk
|
if self._reset_seconds_left <= 0:
|
||||||
if self._silence_seconds_left <= 0:
|
self._silence_seconds_left = self.silence_seconds
|
||||||
return False
|
|
||||||
else:
|
|
||||||
# Reset if enough speech
|
|
||||||
self._reset_seconds_left -= self._seconds_per_chunk
|
|
||||||
if self._reset_seconds_left <= 0:
|
|
||||||
self._silence_seconds_left = self.silence_seconds
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
@@ -56,6 +56,7 @@ def async_register_websocket_api(hass: HomeAssistant) -> None:
|
|||||||
vol.Optional("input"): dict,
|
vol.Optional("input"): dict,
|
||||||
vol.Optional("pipeline"): str,
|
vol.Optional("pipeline"): str,
|
||||||
vol.Optional("conversation_id"): vol.Any(str, None),
|
vol.Optional("conversation_id"): vol.Any(str, None),
|
||||||
|
vol.Optional("device_id"): vol.Any(str, None),
|
||||||
vol.Optional("timeout"): vol.Any(float, int),
|
vol.Optional("timeout"): vol.Any(float, int),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -105,6 +106,7 @@ async def websocket_run(
|
|||||||
# Arguments to PipelineInput
|
# Arguments to PipelineInput
|
||||||
input_args: dict[str, Any] = {
|
input_args: dict[str, Any] = {
|
||||||
"conversation_id": msg.get("conversation_id"),
|
"conversation_id": msg.get("conversation_id"),
|
||||||
|
"device_id": msg.get("device_id"),
|
||||||
}
|
}
|
||||||
|
|
||||||
if start_stage == PipelineStage.STT:
|
if start_stage == PipelineStage.STT:
|
||||||
@@ -280,7 +282,6 @@ def websocket_get_run(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@callback
|
|
||||||
@websocket_api.websocket_command(
|
@websocket_api.websocket_command(
|
||||||
{
|
{
|
||||||
vol.Required("type"): "assist_pipeline/language/list",
|
vol.Required("type"): "assist_pipeline/language/list",
|
||||||
|
@@ -5,5 +5,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/asterisk_mbox",
|
"documentation": "https://www.home-assistant.io/integrations/asterisk_mbox",
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["asterisk_mbox"],
|
"loggers": ["asterisk_mbox"],
|
||||||
"requirements": ["asterisk_mbox==0.5.0"]
|
"requirements": ["asterisk-mbox==0.5.0"]
|
||||||
}
|
}
|
||||||
|
@@ -15,6 +15,7 @@ from homeassistant.config_entries import ConfigEntry
|
|||||||
from homeassistant.const import ATTR_TEMPERATURE, Platform
|
from homeassistant.const import ATTR_TEMPERATURE, Platform
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
from homeassistant.util.enum import try_parse_enum
|
||||||
|
|
||||||
from . import DOMAIN, AtagEntity
|
from . import DOMAIN, AtagEntity
|
||||||
|
|
||||||
@@ -52,14 +53,12 @@ class AtagThermostat(AtagEntity, ClimateEntity):
|
|||||||
self._attr_temperature_unit = coordinator.data.climate.temp_unit
|
self._attr_temperature_unit = coordinator.data.climate.temp_unit
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hvac_mode(self) -> str | None:
|
def hvac_mode(self) -> HVACMode | None:
|
||||||
"""Return hvac operation ie. heat, cool mode."""
|
"""Return hvac operation ie. heat, cool mode."""
|
||||||
if self.coordinator.data.climate.hvac_mode in HVAC_MODES:
|
return try_parse_enum(HVACMode, self.coordinator.data.climate.hvac_mode)
|
||||||
return self.coordinator.data.climate.hvac_mode
|
|
||||||
return None
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hvac_action(self) -> str | None:
|
def hvac_action(self) -> HVACAction | None:
|
||||||
"""Return the current running hvac operation."""
|
"""Return the current running hvac operation."""
|
||||||
is_active = self.coordinator.data.climate.status
|
is_active = self.coordinator.data.climate.status
|
||||||
return HVACAction.HEATING if is_active else HVACAction.IDLE
|
return HVACAction.HEATING if is_active else HVACAction.IDLE
|
||||||
|
@@ -5,5 +5,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/atome",
|
"documentation": "https://www.home-assistant.io/integrations/atome",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["pyatome"],
|
"loggers": ["pyatome"],
|
||||||
"requirements": ["pyatome==0.1.1"]
|
"requirements": ["pyAtome==0.1.1"]
|
||||||
}
|
}
|
||||||
|
@@ -5,7 +5,6 @@ from collections.abc import Callable
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
import logging
|
import logging
|
||||||
from typing import cast
|
|
||||||
|
|
||||||
from yalexs.activity import (
|
from yalexs.activity import (
|
||||||
ACTION_DOORBELL_CALL_MISSED,
|
ACTION_DOORBELL_CALL_MISSED,
|
||||||
@@ -104,7 +103,16 @@ def _native_datetime() -> datetime:
|
|||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class AugustRequiredKeysMixin:
|
class AugustBinarySensorEntityDescription(BinarySensorEntityDescription):
|
||||||
|
"""Describes August binary_sensor entity."""
|
||||||
|
|
||||||
|
# AugustBinarySensor does not support UNDEFINED or None,
|
||||||
|
# restrict the type to str.
|
||||||
|
name: str = ""
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class AugustDoorbellRequiredKeysMixin:
|
||||||
"""Mixin for required keys."""
|
"""Mixin for required keys."""
|
||||||
|
|
||||||
value_fn: Callable[[AugustData, DoorbellDetail], bool]
|
value_fn: Callable[[AugustData, DoorbellDetail], bool]
|
||||||
@@ -112,41 +120,45 @@ class AugustRequiredKeysMixin:
|
|||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class AugustBinarySensorEntityDescription(
|
class AugustDoorbellBinarySensorEntityDescription(
|
||||||
BinarySensorEntityDescription, AugustRequiredKeysMixin
|
BinarySensorEntityDescription, AugustDoorbellRequiredKeysMixin
|
||||||
):
|
):
|
||||||
"""Describes August binary_sensor entity."""
|
"""Describes August binary_sensor entity."""
|
||||||
|
|
||||||
|
# AugustDoorbellBinarySensor does not support UNDEFINED or None,
|
||||||
|
# restrict the type to str.
|
||||||
|
name: str = ""
|
||||||
|
|
||||||
SENSOR_TYPE_DOOR = BinarySensorEntityDescription(
|
|
||||||
|
SENSOR_TYPE_DOOR = AugustBinarySensorEntityDescription(
|
||||||
key="door_open",
|
key="door_open",
|
||||||
name="Open",
|
name="Open",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
SENSOR_TYPES_DOORBELL: tuple[AugustBinarySensorEntityDescription, ...] = (
|
SENSOR_TYPES_DOORBELL: tuple[AugustDoorbellBinarySensorEntityDescription, ...] = (
|
||||||
AugustBinarySensorEntityDescription(
|
AugustDoorbellBinarySensorEntityDescription(
|
||||||
key="doorbell_ding",
|
key="doorbell_ding",
|
||||||
name="Ding",
|
name="Ding",
|
||||||
device_class=BinarySensorDeviceClass.OCCUPANCY,
|
device_class=BinarySensorDeviceClass.OCCUPANCY,
|
||||||
value_fn=_retrieve_ding_state,
|
value_fn=_retrieve_ding_state,
|
||||||
is_time_based=True,
|
is_time_based=True,
|
||||||
),
|
),
|
||||||
AugustBinarySensorEntityDescription(
|
AugustDoorbellBinarySensorEntityDescription(
|
||||||
key="doorbell_motion",
|
key="doorbell_motion",
|
||||||
name="Motion",
|
name="Motion",
|
||||||
device_class=BinarySensorDeviceClass.MOTION,
|
device_class=BinarySensorDeviceClass.MOTION,
|
||||||
value_fn=_retrieve_motion_state,
|
value_fn=_retrieve_motion_state,
|
||||||
is_time_based=True,
|
is_time_based=True,
|
||||||
),
|
),
|
||||||
AugustBinarySensorEntityDescription(
|
AugustDoorbellBinarySensorEntityDescription(
|
||||||
key="doorbell_image_capture",
|
key="doorbell_image_capture",
|
||||||
name="Image Capture",
|
name="Image Capture",
|
||||||
icon="mdi:file-image",
|
icon="mdi:file-image",
|
||||||
value_fn=_retrieve_image_capture_state,
|
value_fn=_retrieve_image_capture_state,
|
||||||
is_time_based=True,
|
is_time_based=True,
|
||||||
),
|
),
|
||||||
AugustBinarySensorEntityDescription(
|
AugustDoorbellBinarySensorEntityDescription(
|
||||||
key="doorbell_online",
|
key="doorbell_online",
|
||||||
name="Online",
|
name="Online",
|
||||||
device_class=BinarySensorDeviceClass.CONNECTIVITY,
|
device_class=BinarySensorDeviceClass.CONNECTIVITY,
|
||||||
@@ -199,7 +211,10 @@ class AugustDoorBinarySensor(AugustEntityMixin, BinarySensorEntity):
|
|||||||
_attr_device_class = BinarySensorDeviceClass.DOOR
|
_attr_device_class = BinarySensorDeviceClass.DOOR
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, data: AugustData, device: Lock, description: BinarySensorEntityDescription
|
self,
|
||||||
|
data: AugustData,
|
||||||
|
device: Lock,
|
||||||
|
description: AugustBinarySensorEntityDescription,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the sensor."""
|
"""Initialize the sensor."""
|
||||||
super().__init__(data, device)
|
super().__init__(data, device)
|
||||||
@@ -207,9 +222,7 @@ class AugustDoorBinarySensor(AugustEntityMixin, BinarySensorEntity):
|
|||||||
self._data = data
|
self._data = data
|
||||||
self._device = device
|
self._device = device
|
||||||
self._attr_name = f"{device.device_name} {description.name}"
|
self._attr_name = f"{device.device_name} {description.name}"
|
||||||
self._attr_unique_id = (
|
self._attr_unique_id = f"{self._device_id}_{description.name.lower()}"
|
||||||
f"{self._device_id}_{cast(str, description.name).lower()}"
|
|
||||||
)
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _update_from_data(self):
|
def _update_from_data(self):
|
||||||
@@ -243,13 +256,13 @@ class AugustDoorBinarySensor(AugustEntityMixin, BinarySensorEntity):
|
|||||||
class AugustDoorbellBinarySensor(AugustEntityMixin, BinarySensorEntity):
|
class AugustDoorbellBinarySensor(AugustEntityMixin, BinarySensorEntity):
|
||||||
"""Representation of an August binary sensor."""
|
"""Representation of an August binary sensor."""
|
||||||
|
|
||||||
entity_description: AugustBinarySensorEntityDescription
|
entity_description: AugustDoorbellBinarySensorEntityDescription
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
data: AugustData,
|
data: AugustData,
|
||||||
device: Doorbell,
|
device: Doorbell,
|
||||||
description: AugustBinarySensorEntityDescription,
|
description: AugustDoorbellBinarySensorEntityDescription,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the sensor."""
|
"""Initialize the sensor."""
|
||||||
super().__init__(data, device)
|
super().__init__(data, device)
|
||||||
@@ -257,9 +270,7 @@ class AugustDoorbellBinarySensor(AugustEntityMixin, BinarySensorEntity):
|
|||||||
self._check_for_off_update_listener = None
|
self._check_for_off_update_listener = None
|
||||||
self._data = data
|
self._data = data
|
||||||
self._attr_name = f"{device.device_name} {description.name}"
|
self._attr_name = f"{device.device_name} {description.name}"
|
||||||
self._attr_unique_id = (
|
self._attr_unique_id = f"{self._device_id}_{description.name.lower()}"
|
||||||
f"{self._device_id}_{cast(str, description.name).lower()}"
|
|
||||||
)
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _update_from_data(self):
|
def _update_from_data(self):
|
||||||
|
@@ -1,25 +1,15 @@
|
|||||||
"""The aurora component."""
|
"""The aurora component."""
|
||||||
|
|
||||||
from datetime import timedelta
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from aiohttp import ClientError
|
|
||||||
from auroranoaa import AuroraForecast
|
from auroranoaa import AuroraForecast
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, Platform
|
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, Platform
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import aiohttp_client
|
from homeassistant.helpers import aiohttp_client
|
||||||
from homeassistant.helpers.device_registry import DeviceEntryType
|
|
||||||
from homeassistant.helpers.entity import DeviceInfo
|
|
||||||
from homeassistant.helpers.update_coordinator import (
|
|
||||||
CoordinatorEntity,
|
|
||||||
DataUpdateCoordinator,
|
|
||||||
UpdateFailed,
|
|
||||||
)
|
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
ATTRIBUTION,
|
|
||||||
AURORA_API,
|
AURORA_API,
|
||||||
CONF_THRESHOLD,
|
CONF_THRESHOLD,
|
||||||
COORDINATOR,
|
COORDINATOR,
|
||||||
@@ -27,6 +17,7 @@ from .const import (
|
|||||||
DEFAULT_THRESHOLD,
|
DEFAULT_THRESHOLD,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
)
|
)
|
||||||
|
from .coordinator import AuroraDataUpdateCoordinator
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -79,71 +70,3 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
hass.data[DOMAIN].pop(entry.entry_id)
|
hass.data[DOMAIN].pop(entry.entry_id)
|
||||||
|
|
||||||
return unload_ok
|
return unload_ok
|
||||||
|
|
||||||
|
|
||||||
class AuroraDataUpdateCoordinator(DataUpdateCoordinator):
|
|
||||||
"""Class to manage fetching data from the NOAA Aurora API."""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
hass: HomeAssistant,
|
|
||||||
name: str,
|
|
||||||
polling_interval: int,
|
|
||||||
api: str,
|
|
||||||
latitude: float,
|
|
||||||
longitude: float,
|
|
||||||
threshold: float,
|
|
||||||
) -> None:
|
|
||||||
"""Initialize the data updater."""
|
|
||||||
|
|
||||||
super().__init__(
|
|
||||||
hass=hass,
|
|
||||||
logger=_LOGGER,
|
|
||||||
name=name,
|
|
||||||
update_interval=timedelta(minutes=polling_interval),
|
|
||||||
)
|
|
||||||
|
|
||||||
self.api = api
|
|
||||||
self.name = name
|
|
||||||
self.latitude = int(latitude)
|
|
||||||
self.longitude = int(longitude)
|
|
||||||
self.threshold = int(threshold)
|
|
||||||
|
|
||||||
async def _async_update_data(self):
|
|
||||||
"""Fetch the data from the NOAA Aurora Forecast."""
|
|
||||||
|
|
||||||
try:
|
|
||||||
return await self.api.get_forecast_data(self.longitude, self.latitude)
|
|
||||||
except ClientError as error:
|
|
||||||
raise UpdateFailed(f"Error updating from NOAA: {error}") from error
|
|
||||||
|
|
||||||
|
|
||||||
class AuroraEntity(CoordinatorEntity[AuroraDataUpdateCoordinator]):
|
|
||||||
"""Implementation of the base Aurora Entity."""
|
|
||||||
|
|
||||||
_attr_attribution = ATTRIBUTION
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
coordinator: AuroraDataUpdateCoordinator,
|
|
||||||
name: str,
|
|
||||||
icon: str,
|
|
||||||
) -> None:
|
|
||||||
"""Initialize the Aurora Entity."""
|
|
||||||
|
|
||||||
super().__init__(coordinator=coordinator)
|
|
||||||
|
|
||||||
self._attr_name = name
|
|
||||||
self._attr_unique_id = f"{coordinator.latitude}_{coordinator.longitude}"
|
|
||||||
self._attr_icon = icon
|
|
||||||
|
|
||||||
@property
|
|
||||||
def device_info(self) -> DeviceInfo:
|
|
||||||
"""Define the device based on name."""
|
|
||||||
return DeviceInfo(
|
|
||||||
entry_type=DeviceEntryType.SERVICE,
|
|
||||||
identifiers={(DOMAIN, str(self.unique_id))},
|
|
||||||
manufacturer="NOAA",
|
|
||||||
model="Aurora Visibility Sensor",
|
|
||||||
name=self.coordinator.name,
|
|
||||||
)
|
|
||||||
|
@@ -4,8 +4,8 @@ from homeassistant.config_entries import ConfigEntry
|
|||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
from . import AuroraEntity
|
|
||||||
from .const import COORDINATOR, DOMAIN
|
from .const import COORDINATOR, DOMAIN
|
||||||
|
from .entity import AuroraEntity
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
|
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.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
from . import AuroraEntity
|
|
||||||
from .const import COORDINATOR, DOMAIN
|
from .const import COORDINATOR, DOMAIN
|
||||||
|
from .entity import AuroraEntity
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
|
@@ -47,10 +47,10 @@ class AuroraEntity(Entity):
|
|||||||
@property
|
@property
|
||||||
def device_info(self) -> DeviceInfo:
|
def device_info(self) -> DeviceInfo:
|
||||||
"""Return device specific attributes."""
|
"""Return device specific attributes."""
|
||||||
return {
|
return DeviceInfo(
|
||||||
"identifiers": {(DOMAIN, self._data[ATTR_SERIAL_NUMBER])},
|
identifiers={(DOMAIN, self._data[ATTR_SERIAL_NUMBER])},
|
||||||
"manufacturer": MANUFACTURER,
|
manufacturer=MANUFACTURER,
|
||||||
"model": self._data[ATTR_MODEL],
|
model=self._data[ATTR_MODEL],
|
||||||
"name": self._data.get(ATTR_DEVICE_NAME, DEFAULT_DEVICE_NAME),
|
name=self._data.get(ATTR_DEVICE_NAME, DEFAULT_DEVICE_NAME),
|
||||||
"sw_version": self._data[ATTR_FIRMWARE],
|
sw_version=self._data[ATTR_FIRMWARE],
|
||||||
}
|
)
|
||||||
|
@@ -34,7 +34,7 @@ SENSOR_TYPES = [
|
|||||||
device_class=SensorDeviceClass.POWER,
|
device_class=SensorDeviceClass.POWER,
|
||||||
native_unit_of_measurement=UnitOfPower.WATT,
|
native_unit_of_measurement=UnitOfPower.WATT,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
name="Power Output",
|
translation_key="power_output",
|
||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key="temp",
|
key="temp",
|
||||||
@@ -42,14 +42,13 @@ SENSOR_TYPES = [
|
|||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
name="Temperature",
|
|
||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key="totalenergy",
|
key="totalenergy",
|
||||||
device_class=SensorDeviceClass.ENERGY,
|
device_class=SensorDeviceClass.ENERGY,
|
||||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||||
name="Total Energy",
|
translation_key="total_energy",
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -75,6 +74,8 @@ async def async_setup_entry(
|
|||||||
class AuroraSensor(AuroraEntity, SensorEntity):
|
class AuroraSensor(AuroraEntity, SensorEntity):
|
||||||
"""Representation of a Sensor on a Aurora ABB PowerOne Solar inverter."""
|
"""Representation of a Sensor on a Aurora ABB PowerOne Solar inverter."""
|
||||||
|
|
||||||
|
_attr_has_entity_name = True
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
client: AuroraSerialClient,
|
client: AuroraSerialClient,
|
||||||
|
@@ -18,5 +18,15 @@
|
|||||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||||
"no_serial_ports": "No com ports found. Need a valid RS485 device to communicate."
|
"no_serial_ports": "No com ports found. Need a valid RS485 device to communicate."
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"entity": {
|
||||||
|
"sensor": {
|
||||||
|
"power_output": {
|
||||||
|
"name": "Power Output"
|
||||||
|
},
|
||||||
|
"total_energy": {
|
||||||
|
"name": "Total Energy"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -35,7 +35,7 @@ SENSOR_DESCRIPTIONS: tuple[SensorValueEntityDescription, ...] = (
|
|||||||
# Internet Services sensors
|
# Internet Services sensors
|
||||||
SensorValueEntityDescription(
|
SensorValueEntityDescription(
|
||||||
key="usedMb",
|
key="usedMb",
|
||||||
name="Data used",
|
translation_key="data_used",
|
||||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||||
native_unit_of_measurement=UnitOfInformation.MEGABYTES,
|
native_unit_of_measurement=UnitOfInformation.MEGABYTES,
|
||||||
device_class=SensorDeviceClass.DATA_SIZE,
|
device_class=SensorDeviceClass.DATA_SIZE,
|
||||||
@@ -43,7 +43,7 @@ SENSOR_DESCRIPTIONS: tuple[SensorValueEntityDescription, ...] = (
|
|||||||
),
|
),
|
||||||
SensorValueEntityDescription(
|
SensorValueEntityDescription(
|
||||||
key="downloadedMb",
|
key="downloadedMb",
|
||||||
name="Downloaded",
|
translation_key="downloaded",
|
||||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||||
native_unit_of_measurement=UnitOfInformation.MEGABYTES,
|
native_unit_of_measurement=UnitOfInformation.MEGABYTES,
|
||||||
device_class=SensorDeviceClass.DATA_SIZE,
|
device_class=SensorDeviceClass.DATA_SIZE,
|
||||||
@@ -51,7 +51,7 @@ SENSOR_DESCRIPTIONS: tuple[SensorValueEntityDescription, ...] = (
|
|||||||
),
|
),
|
||||||
SensorValueEntityDescription(
|
SensorValueEntityDescription(
|
||||||
key="uploadedMb",
|
key="uploadedMb",
|
||||||
name="Uploaded",
|
translation_key="uploaded",
|
||||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||||
native_unit_of_measurement=UnitOfInformation.MEGABYTES,
|
native_unit_of_measurement=UnitOfInformation.MEGABYTES,
|
||||||
device_class=SensorDeviceClass.DATA_SIZE,
|
device_class=SensorDeviceClass.DATA_SIZE,
|
||||||
@@ -60,21 +60,21 @@ SENSOR_DESCRIPTIONS: tuple[SensorValueEntityDescription, ...] = (
|
|||||||
# Mobile Phone Services sensors
|
# Mobile Phone Services sensors
|
||||||
SensorValueEntityDescription(
|
SensorValueEntityDescription(
|
||||||
key="national",
|
key="national",
|
||||||
name="National calls",
|
translation_key="national_calls",
|
||||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||||
icon="mdi:phone",
|
icon="mdi:phone",
|
||||||
value=lambda x: x.get("calls"),
|
value=lambda x: x.get("calls"),
|
||||||
),
|
),
|
||||||
SensorValueEntityDescription(
|
SensorValueEntityDescription(
|
||||||
key="mobile",
|
key="mobile",
|
||||||
name="Mobile calls",
|
translation_key="mobile_calls",
|
||||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||||
icon="mdi:phone",
|
icon="mdi:phone",
|
||||||
value=lambda x: x.get("calls"),
|
value=lambda x: x.get("calls"),
|
||||||
),
|
),
|
||||||
SensorValueEntityDescription(
|
SensorValueEntityDescription(
|
||||||
key="international",
|
key="international",
|
||||||
name="International calls",
|
translation_key="international_calls",
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||||
icon="mdi:phone-plus",
|
icon="mdi:phone-plus",
|
||||||
@@ -82,14 +82,14 @@ SENSOR_DESCRIPTIONS: tuple[SensorValueEntityDescription, ...] = (
|
|||||||
),
|
),
|
||||||
SensorValueEntityDescription(
|
SensorValueEntityDescription(
|
||||||
key="sms",
|
key="sms",
|
||||||
name="SMS sent",
|
translation_key="sms_sent",
|
||||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||||
icon="mdi:message-processing",
|
icon="mdi:message-processing",
|
||||||
value=lambda x: x.get("calls"),
|
value=lambda x: x.get("calls"),
|
||||||
),
|
),
|
||||||
SensorValueEntityDescription(
|
SensorValueEntityDescription(
|
||||||
key="internet",
|
key="internet",
|
||||||
name="Data used",
|
translation_key="data_used",
|
||||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||||
native_unit_of_measurement=UnitOfInformation.KILOBYTES,
|
native_unit_of_measurement=UnitOfInformation.KILOBYTES,
|
||||||
device_class=SensorDeviceClass.DATA_SIZE,
|
device_class=SensorDeviceClass.DATA_SIZE,
|
||||||
@@ -98,7 +98,7 @@ SENSOR_DESCRIPTIONS: tuple[SensorValueEntityDescription, ...] = (
|
|||||||
),
|
),
|
||||||
SensorValueEntityDescription(
|
SensorValueEntityDescription(
|
||||||
key="voicemail",
|
key="voicemail",
|
||||||
name="Voicemail calls",
|
translation_key="voicemail_calls",
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||||
icon="mdi:phone",
|
icon="mdi:phone",
|
||||||
@@ -106,7 +106,7 @@ SENSOR_DESCRIPTIONS: tuple[SensorValueEntityDescription, ...] = (
|
|||||||
),
|
),
|
||||||
SensorValueEntityDescription(
|
SensorValueEntityDescription(
|
||||||
key="other",
|
key="other",
|
||||||
name="Other calls",
|
translation_key="other_calls",
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||||
icon="mdi:phone",
|
icon="mdi:phone",
|
||||||
@@ -115,13 +115,13 @@ SENSOR_DESCRIPTIONS: tuple[SensorValueEntityDescription, ...] = (
|
|||||||
# Generic sensors
|
# Generic sensors
|
||||||
SensorValueEntityDescription(
|
SensorValueEntityDescription(
|
||||||
key="daysTotal",
|
key="daysTotal",
|
||||||
name="Billing cycle length",
|
translation_key="billing_cycle_length",
|
||||||
native_unit_of_measurement=UnitOfTime.DAYS,
|
native_unit_of_measurement=UnitOfTime.DAYS,
|
||||||
icon="mdi:calendar-range",
|
icon="mdi:calendar-range",
|
||||||
),
|
),
|
||||||
SensorValueEntityDescription(
|
SensorValueEntityDescription(
|
||||||
key="daysRemaining",
|
key="daysRemaining",
|
||||||
name="Billing cycle remaining",
|
translation_key="billing_cycle_remaining",
|
||||||
native_unit_of_measurement=UnitOfTime.DAYS,
|
native_unit_of_measurement=UnitOfTime.DAYS,
|
||||||
icon="mdi:calendar-clock",
|
icon="mdi:calendar-clock",
|
||||||
),
|
),
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user