mirror of
https://github.com/home-assistant/core.git
synced 2026-01-08 08:38:24 +01:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f3bcee97e | ||
|
|
51edc007fe |
13
.coveragerc
13
.coveragerc
@@ -124,6 +124,7 @@ omit =
|
||||
homeassistant/components/bluetooth_tracker/*
|
||||
homeassistant/components/bmw_connected_drive/__init__.py
|
||||
homeassistant/components/bmw_connected_drive/binary_sensor.py
|
||||
homeassistant/components/bmw_connected_drive/button.py
|
||||
homeassistant/components/bmw_connected_drive/coordinator.py
|
||||
homeassistant/components/bmw_connected_drive/lock.py
|
||||
homeassistant/components/bmw_connected_drive/notify.py
|
||||
@@ -310,6 +311,7 @@ omit =
|
||||
homeassistant/components/esphome/camera.py
|
||||
homeassistant/components/esphome/domain_data.py
|
||||
homeassistant/components/esphome/entry_data.py
|
||||
homeassistant/components/esphome/light.py
|
||||
homeassistant/components/etherscan/sensor.py
|
||||
homeassistant/components/eufy/*
|
||||
homeassistant/components/eufylife_ble/__init__.py
|
||||
@@ -355,13 +357,10 @@ omit =
|
||||
homeassistant/components/fitbit/*
|
||||
homeassistant/components/fivem/__init__.py
|
||||
homeassistant/components/fivem/binary_sensor.py
|
||||
homeassistant/components/fivem/coordinator.py
|
||||
homeassistant/components/fivem/entity.py
|
||||
homeassistant/components/fivem/sensor.py
|
||||
homeassistant/components/fixer/sensor.py
|
||||
homeassistant/components/fjaraskupan/__init__.py
|
||||
homeassistant/components/fjaraskupan/binary_sensor.py
|
||||
homeassistant/components/fjaraskupan/coordinator.py
|
||||
homeassistant/components/fjaraskupan/fan.py
|
||||
homeassistant/components/fjaraskupan/light.py
|
||||
homeassistant/components/fjaraskupan/number.py
|
||||
@@ -941,8 +940,6 @@ omit =
|
||||
homeassistant/components/pyload/sensor.py
|
||||
homeassistant/components/qbittorrent/__init__.py
|
||||
homeassistant/components/qbittorrent/sensor.py
|
||||
homeassistant/components/qnap/__init__.py
|
||||
homeassistant/components/qnap/coordinator.py
|
||||
homeassistant/components/qnap/sensor.py
|
||||
homeassistant/components/qrcode/image_processing.py
|
||||
homeassistant/components/quantum_gateway/device_tracker.py
|
||||
@@ -1047,6 +1044,12 @@ omit =
|
||||
homeassistant/components/sense/__init__.py
|
||||
homeassistant/components/sense/binary_sensor.py
|
||||
homeassistant/components/sense/sensor.py
|
||||
homeassistant/components/senseme/__init__.py
|
||||
homeassistant/components/senseme/discovery.py
|
||||
homeassistant/components/senseme/entity.py
|
||||
homeassistant/components/senseme/fan.py
|
||||
homeassistant/components/senseme/light.py
|
||||
homeassistant/components/senseme/switch.py
|
||||
homeassistant/components/senz/__init__.py
|
||||
homeassistant/components/senz/api.py
|
||||
homeassistant/components/senz/climate.py
|
||||
|
||||
12
.github/workflows/builder.yml
vendored
12
.github/workflows/builder.yml
vendored
@@ -10,7 +10,7 @@ on:
|
||||
|
||||
env:
|
||||
BUILD_TYPE: core
|
||||
DEFAULT_PYTHON: "3.11"
|
||||
DEFAULT_PYTHON: "3.10"
|
||||
|
||||
jobs:
|
||||
init:
|
||||
@@ -197,7 +197,7 @@ jobs:
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build base image
|
||||
uses: home-assistant/builder@2023.06.1
|
||||
uses: home-assistant/builder@2023.06.0
|
||||
with:
|
||||
args: |
|
||||
$BUILD_ARGS \
|
||||
@@ -274,7 +274,7 @@ jobs:
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build base image
|
||||
uses: home-assistant/builder@2023.06.1
|
||||
uses: home-assistant/builder@2023.06.0
|
||||
with:
|
||||
args: |
|
||||
$BUILD_ARGS \
|
||||
@@ -324,16 +324,12 @@ jobs:
|
||||
if: github.repository_owner == 'home-assistant'
|
||||
needs: ["init", "build_base"]
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v3.5.3
|
||||
|
||||
- name: Install Cosign
|
||||
uses: sigstore/cosign-installer@v3.1.1
|
||||
uses: sigstore/cosign-installer@v3.0.5
|
||||
with:
|
||||
cosign-release: "v2.0.2"
|
||||
|
||||
|
||||
2
.github/workflows/ci.yaml
vendored
2
.github/workflows/ci.yaml
vendored
@@ -495,7 +495,7 @@ jobs:
|
||||
pip install --cache-dir=$PIP_CACHE -U "pip>=21.3.1,<23.2" setuptools wheel
|
||||
pip install --cache-dir=$PIP_CACHE -r requirements_all.txt
|
||||
pip install --cache-dir=$PIP_CACHE -r requirements_test.txt
|
||||
pip install -e . --config-settings editable_mode=compat
|
||||
pip install .
|
||||
|
||||
hassfest:
|
||||
name: Check hassfest
|
||||
|
||||
2
.github/workflows/translations.yml
vendored
2
.github/workflows/translations.yml
vendored
@@ -10,7 +10,7 @@ on:
|
||||
- "**strings.json"
|
||||
|
||||
env:
|
||||
DEFAULT_PYTHON: "3.11"
|
||||
DEFAULT_PYTHON: "3.10"
|
||||
|
||||
jobs:
|
||||
upload:
|
||||
|
||||
190
.github/workflows/wheels.yml
vendored
190
.github/workflows/wheels.yml
vendored
@@ -47,7 +47,10 @@ jobs:
|
||||
echo "GRPC_PYTHON_BUILD_SYSTEM_OPENSSL=true"
|
||||
echo "GRPC_PYTHON_BUILD_WITH_CYTHON=true"
|
||||
echo "GRPC_PYTHON_DISABLE_LIBC_COMPATIBILITY=true"
|
||||
echo "GRPC_PYTHON_LDFLAGS=-lpthread -Wl,-wrap,memcpy -static-libgcc"
|
||||
# GRPC on armv7 needs -lexecinfo (issue #56669) since home assistant installs
|
||||
# execinfo-dev when building wheels. The setuptools build setup does not have an option for
|
||||
# adding a single LDFLAG so copy all relevant linux flags here (as of 1.43.0)
|
||||
echo "GRPC_PYTHON_LDFLAGS=-lpthread -Wl,-wrap,memcpy -static-libgcc -lexecinfo"
|
||||
|
||||
# Fix out of memory issues with rust
|
||||
echo "CARGO_NET_GIT_FETCH_WITH_CLI=true"
|
||||
@@ -80,7 +83,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
abi: ["cp311"]
|
||||
abi: ["cp310", "cp311"]
|
||||
arch: ${{ fromJson(needs.init.outputs.architectures) }}
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
@@ -110,7 +113,7 @@ jobs:
|
||||
requirements-diff: "requirements_diff.txt"
|
||||
requirements: "requirements.txt"
|
||||
|
||||
integrations_cp311:
|
||||
integrations_cp310:
|
||||
name: Build wheels ${{ matrix.abi }} for ${{ matrix.arch }}
|
||||
if: github.repository_owner == 'home-assistant'
|
||||
needs: init
|
||||
@@ -118,7 +121,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
abi: ["cp311"]
|
||||
abi: ["cp310"]
|
||||
arch: ${{ fromJson(needs.init.outputs.architectures) }}
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
@@ -134,30 +137,20 @@ jobs:
|
||||
with:
|
||||
name: requirements_diff
|
||||
|
||||
- name: (Un)comment packages
|
||||
- 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|# decora_wifi|decora_wifi|g" ${requirement_file}
|
||||
sed -i "s|# python-gammu|python-gammu|g" ${requirement_file}
|
||||
|
||||
# Some packages are not buildable on armhf anymore
|
||||
if [ "${{ matrix.arch }}" = "armhf" ]; then
|
||||
|
||||
# Pandas has issues building on armhf, it is expected they
|
||||
# will drop the platform in the near future (they consider it
|
||||
# "flimsy" on 386). The following packages depend on pandas,
|
||||
# so we comment them out.
|
||||
sed -i "s|env-canada|# env-canada|g" ${requirement_file}
|
||||
sed -i "s|noaa-coops|# noaa-coops|g" ${requirement_file}
|
||||
sed -i "s|pyezviz|# pyezviz|g" ${requirement_file}
|
||||
sed -i "s|pykrakenapi|# pykrakenapi|g" ${requirement_file}
|
||||
fi
|
||||
sed -i "s|# opencv-python-headless|opencv-python-headless|g" ${requirement_file}
|
||||
done
|
||||
|
||||
- name: Split requirements all
|
||||
@@ -174,6 +167,165 @@ jobs:
|
||||
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"
|
||||
|
||||
- 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"
|
||||
|
||||
# Wheels building for the cp311 ABI is currently split
|
||||
# This is mainly until we have figured out to get all wheels built.
|
||||
# Without harming our current workflow.
|
||||
integrations_cp311:
|
||||
name: Build wheels ${{ matrix.abi }} for ${{ matrix.arch }}
|
||||
if: github.repository_owner == 'home-assistant'
|
||||
needs: init
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
abi: ["cp311"]
|
||||
arch: ${{ fromJson(needs.init.outputs.architectures) }}
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v3.5.3
|
||||
|
||||
- name: Write alternative env-file for cp311
|
||||
run: |
|
||||
(
|
||||
echo "GRPC_BUILD_WITH_BORING_SSL_ASM=false"
|
||||
echo "GRPC_PYTHON_BUILD_SYSTEM_OPENSSL=true"
|
||||
echo "GRPC_PYTHON_BUILD_WITH_CYTHON=true"
|
||||
echo "GRPC_PYTHON_DISABLE_LIBC_COMPATIBILITY=true"
|
||||
|
||||
# GRPC on armv7 needed -lexecinfo (issue #56669) since home assistant installed
|
||||
# execinfo-dev when building wheels. However, this package is no longer available
|
||||
# Alpine 3.17, which we use for the cp311 ABI, so the flag should no longer be needed.
|
||||
echo "GRPC_PYTHON_LDFLAGS=-lpthread -Wl,-wrap,memcpy -static-libgcc" # -lexecinfo
|
||||
|
||||
# Fix out of memory issues with rust
|
||||
echo "CARGO_NET_GIT_FETCH_WITH_CLI=true"
|
||||
|
||||
# OpenCV headless installation
|
||||
echo "CI_BUILD=1"
|
||||
echo "ENABLE_HEADLESS=1"
|
||||
|
||||
# Use C-Extension for sqlalchemy
|
||||
echo "REQUIRE_SQLALCHEMY_CEXT=1"
|
||||
) > .env_file
|
||||
|
||||
- name: Download requirements_diff
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: requirements_diff
|
||||
|
||||
- name: (Un)comment packages
|
||||
run: |
|
||||
requirement_files="requirements_all.txt requirements_diff.txt"
|
||||
for requirement_file in ${requirement_files}; do
|
||||
|
||||
# PyBluez no longer compiles. Commented it out for now.
|
||||
# It need further cleanup down the line, as all machine images
|
||||
# try to install it.
|
||||
# sed -i "s|# pybluez|pybluez|g" ${requirement_file}
|
||||
|
||||
# beacontools requires PyBluez.
|
||||
# sed -i "s|# beacontools|beacontools|g" ${requirement_file}
|
||||
|
||||
# It doesn't build for some reason, so we skip it for now.
|
||||
# Bumping to the latest version (4.7.0.72) supporting Python 3.11
|
||||
# doesn't help. Reverted bump in #91871. There are 8 registered
|
||||
# instances using this integration according to analytics.
|
||||
# sed -i "s|# opencv-python-headless|opencv-python-headless|g" ${requirement_file}
|
||||
|
||||
sed -i "s|# fritzconnection|fritzconnection|g" ${requirement_file}
|
||||
sed -i "s|# pyuserinput|pyuserinput|g" ${requirement_file}
|
||||
sed -i "s|# evdev|evdev|g" ${requirement_file}
|
||||
sed -i "s|# pycups|pycups|g" ${requirement_file}
|
||||
sed -i "s|# homekit|homekit|g" ${requirement_file}
|
||||
sed -i "s|# decora-wifi|decora-wifi|g" ${requirement_file}
|
||||
sed -i "s|# python-gammu|python-gammu|g" ${requirement_file}
|
||||
|
||||
# Some packages are not buildable on armhf anymore
|
||||
if [ "${{ matrix.arch }}" = "armhf" ]; then
|
||||
|
||||
# Pandas has issues building on armhf, it is expected they
|
||||
# will drop the platform in the near future (they consider it
|
||||
# "flimsy" on 386). The following packages depend on pandas,
|
||||
# so we comment them out.
|
||||
sed -i "s|env-canada|# env-canada|g" ${requirement_file}
|
||||
sed -i "s|noaa-coops|# noaa-coops|g" ${requirement_file}
|
||||
sed -i "s|pyezviz|# pyezviz|g" ${requirement_file}
|
||||
sed -i "s|pykrakenapi|# pykrakenapi|g" ${requirement_file}
|
||||
fi
|
||||
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) / 3) requirements_all.txt requirements_all.txt
|
||||
|
||||
- name: Adjust build env
|
||||
run: |
|
||||
if [ "${{ matrix.arch }}" = "i386" ]; then
|
||||
echo "NPY_DISABLE_SVML=1" >> .env_file
|
||||
fi
|
||||
|
||||
# Probably not an issue anymore. Removing for now.
|
||||
# (
|
||||
# # cmake > 3.22.2 have issue on arm
|
||||
# # Tested until 3.22.5
|
||||
# echo "cmake==3.22.2"
|
||||
# ) >> homeassistant/package_constraints.txt
|
||||
|
||||
# Do not pin numpy in wheels building
|
||||
sed -i "/numpy/d" homeassistant/package_constraints.txt
|
||||
|
||||
|
||||
@@ -277,6 +277,7 @@ homeassistant.components.scene.*
|
||||
homeassistant.components.schedule.*
|
||||
homeassistant.components.scrape.*
|
||||
homeassistant.components.select.*
|
||||
homeassistant.components.senseme.*
|
||||
homeassistant.components.sensibo.*
|
||||
homeassistant.components.sensirion_ble.*
|
||||
homeassistant.components.sensor.*
|
||||
|
||||
@@ -703,8 +703,6 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/logi_circle/ @evanjd
|
||||
/homeassistant/components/lookin/ @ANMalko @bdraco
|
||||
/tests/components/lookin/ @ANMalko @bdraco
|
||||
/homeassistant/components/loqed/ @mikewoudenberg
|
||||
/tests/components/loqed/ @mikewoudenberg
|
||||
/homeassistant/components/lovelace/ @home-assistant/frontend
|
||||
/tests/components/lovelace/ @home-assistant/frontend
|
||||
/homeassistant/components/luci/ @mzdrale
|
||||
@@ -972,7 +970,6 @@ build.json @home-assistant/supervisor
|
||||
/homeassistant/components/qld_bushfire/ @exxamalte
|
||||
/tests/components/qld_bushfire/ @exxamalte
|
||||
/homeassistant/components/qnap/ @disforw
|
||||
/tests/components/qnap/ @disforw
|
||||
/homeassistant/components/qnap_qsw/ @Noltari
|
||||
/tests/components/qnap_qsw/ @Noltari
|
||||
/homeassistant/components/quantum_gateway/ @cisasteelersfan
|
||||
@@ -1082,6 +1079,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/select/ @home-assistant/core
|
||||
/homeassistant/components/sense/ @kbickar
|
||||
/tests/components/sense/ @kbickar
|
||||
/homeassistant/components/senseme/ @mikelawrence @bdraco
|
||||
/tests/components/senseme/ @mikelawrence @bdraco
|
||||
/homeassistant/components/sensibo/ @andrey-git @gjohansson-ST
|
||||
/tests/components/sensibo/ @andrey-git @gjohansson-ST
|
||||
/homeassistant/components/sensirion_ble/ @akx
|
||||
|
||||
@@ -34,7 +34,6 @@ class AbodeAlarm(AbodeDevice, alarm.AlarmControlPanelEntity):
|
||||
"""An alarm_control_panel implementation for Abode."""
|
||||
|
||||
_attr_icon = ICON
|
||||
_attr_name = None
|
||||
_attr_code_arm_required = False
|
||||
_attr_supported_features = (
|
||||
AlarmControlPanelEntityFeature.ARM_HOME
|
||||
|
||||
@@ -39,7 +39,6 @@ class AbodeCamera(AbodeDevice, Camera):
|
||||
"""Representation of an Abode camera."""
|
||||
|
||||
_device: AbodeCam
|
||||
_attr_name = None
|
||||
|
||||
def __init__(self, data: AbodeSystem, device: AbodeDev, event: Event) -> None:
|
||||
"""Initialize the Abode device."""
|
||||
|
||||
@@ -29,7 +29,6 @@ class AbodeCover(AbodeDevice, CoverEntity):
|
||||
"""Representation of an Abode cover."""
|
||||
|
||||
_device: AbodeCV
|
||||
_attr_name = None
|
||||
|
||||
@property
|
||||
def is_closed(self) -> bool:
|
||||
|
||||
@@ -42,7 +42,6 @@ class AbodeLight(AbodeDevice, LightEntity):
|
||||
"""Representation of an Abode light."""
|
||||
|
||||
_device: AbodeLT
|
||||
_attr_name = None
|
||||
|
||||
def turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn on the light."""
|
||||
|
||||
@@ -29,7 +29,6 @@ class AbodeLock(AbodeDevice, LockEntity):
|
||||
"""Representation of an Abode lock."""
|
||||
|
||||
_device: AbodeLK
|
||||
_attr_name = None
|
||||
|
||||
def lock(self, **kwargs: Any) -> None:
|
||||
"""Lock the device."""
|
||||
|
||||
@@ -53,6 +53,7 @@ class AbodeSensor(AbodeDevice, SensorEntity):
|
||||
"""A sensor implementation for Abode devices."""
|
||||
|
||||
_device: AbodeSense
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
||||
@@ -44,7 +44,6 @@ class AbodeSwitch(AbodeDevice, SwitchEntity):
|
||||
"""Representation of an Abode switch."""
|
||||
|
||||
_device: AbodeSW
|
||||
_attr_name = None
|
||||
|
||||
def turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn on the device."""
|
||||
|
||||
@@ -90,7 +90,6 @@ class AdvantageAirAC(AdvantageAirAcEntity, ClimateEntity):
|
||||
_attr_target_temperature_step = PRECISION_WHOLE
|
||||
_attr_max_temp = 32
|
||||
_attr_min_temp = 16
|
||||
_attr_name = None
|
||||
|
||||
_attr_hvac_modes = [
|
||||
HVACMode.OFF,
|
||||
|
||||
@@ -9,30 +9,17 @@ from homeassistant.core import HomeAssistant
|
||||
|
||||
from .const import DOMAIN as ADVANTAGE_AIR_DOMAIN
|
||||
|
||||
TO_REDACT = [
|
||||
"dealerPhoneNumber",
|
||||
"latitude",
|
||||
"logoPIN",
|
||||
"longitude",
|
||||
"postCode",
|
||||
"rid",
|
||||
"deviceNames",
|
||||
"deviceIds",
|
||||
"deviceIdsV2",
|
||||
"backupId",
|
||||
]
|
||||
TO_REDACT = ["dealerPhoneNumber", "latitude", "logoPIN", "longitude", "postCode"]
|
||||
|
||||
|
||||
async def async_get_config_entry_diagnostics(
|
||||
hass: HomeAssistant, config_entry: ConfigEntry
|
||||
) -> dict[str, Any]:
|
||||
"""Return diagnostics for a config entry."""
|
||||
data = hass.data[ADVANTAGE_AIR_DOMAIN][config_entry.entry_id].coordinator.data
|
||||
data = hass.data[ADVANTAGE_AIR_DOMAIN][config_entry.entry_id]["coordinator"].data
|
||||
|
||||
# Return only the relevant children
|
||||
return {
|
||||
"aircons": data.get("aircons"),
|
||||
"myLights": data.get("myLights"),
|
||||
"myThings": data.get("myThings"),
|
||||
"aircons": data["aircons"],
|
||||
"system": async_redact_data(data["system"], TO_REDACT),
|
||||
}
|
||||
|
||||
@@ -84,8 +84,6 @@ class AdvantageAirZoneEntity(AdvantageAirAcEntity):
|
||||
class AdvantageAirThingEntity(AdvantageAirEntity):
|
||||
"""Parent class for Advantage Air Things Entities."""
|
||||
|
||||
_attr_name = None
|
||||
|
||||
def __init__(self, instance: AdvantageAirData, thing: dict[str, Any]) -> None:
|
||||
"""Initialize common aspects of an Advantage Air Things entity."""
|
||||
super().__init__(instance)
|
||||
|
||||
@@ -41,7 +41,6 @@ class AdvantageAirLight(AdvantageAirEntity, LightEntity):
|
||||
"""Representation of Advantage Air Light."""
|
||||
|
||||
_attr_supported_color_modes = {ColorMode.ONOFF}
|
||||
_attr_name = None
|
||||
|
||||
def __init__(self, instance: AdvantageAirData, light: dict[str, Any]) -> None:
|
||||
"""Initialize an Advantage Air Light."""
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
"api_key": "[%key:common::config_flow::data::api_key%]",
|
||||
"city": "City",
|
||||
"country": "Country",
|
||||
"state": "State"
|
||||
"state": "state"
|
||||
}
|
||||
},
|
||||
"reauth_confirm": {
|
||||
|
||||
@@ -193,8 +193,6 @@ class AirzoneClimate(AirzoneZoneEntity, ClimateEntity):
|
||||
|
||||
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
|
||||
"""Set hvac mode."""
|
||||
slave_raise = False
|
||||
|
||||
params = {}
|
||||
if hvac_mode == HVACMode.OFF:
|
||||
params[API_ON] = 0
|
||||
@@ -204,13 +202,12 @@ class AirzoneClimate(AirzoneZoneEntity, ClimateEntity):
|
||||
if self.get_airzone_value(AZD_MASTER):
|
||||
params[API_MODE] = mode
|
||||
else:
|
||||
slave_raise = True
|
||||
raise HomeAssistantError(
|
||||
f"Mode can't be changed on slave zone {self.name}"
|
||||
)
|
||||
params[API_ON] = 1
|
||||
await self._async_update_hvac_params(params)
|
||||
|
||||
if slave_raise:
|
||||
raise HomeAssistantError(f"Mode can't be changed on slave zone {self.name}")
|
||||
|
||||
async def async_set_temperature(self, **kwargs: Any) -> None:
|
||||
"""Set new target temperature."""
|
||||
params = {}
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/airzone_cloud",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["aioairzone_cloud"],
|
||||
"requirements": ["aioairzone-cloud==0.2.0"]
|
||||
"requirements": ["aioairzone-cloud==0.1.9"]
|
||||
}
|
||||
|
||||
@@ -21,22 +21,12 @@ from homeassistant.components.recorder import (
|
||||
DOMAIN as RECORDER_DOMAIN,
|
||||
get_instance as get_recorder_instance,
|
||||
)
|
||||
import homeassistant.config as conf_util
|
||||
from homeassistant.config_entries import (
|
||||
SOURCE_IGNORE,
|
||||
)
|
||||
from homeassistant.const import ATTR_DOMAIN, __version__ as HA_VERSION
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
import homeassistant.helpers.entity_registry as er
|
||||
from homeassistant.helpers.storage import Store
|
||||
from homeassistant.helpers.system_info import async_get_system_info
|
||||
from homeassistant.loader import (
|
||||
Integration,
|
||||
IntegrationNotFound,
|
||||
async_get_integrations,
|
||||
)
|
||||
from homeassistant.loader import IntegrationNotFound, async_get_integrations
|
||||
from homeassistant.setup import async_get_loaded_integrations
|
||||
|
||||
from .const import (
|
||||
@@ -216,25 +206,8 @@ class Analytics:
|
||||
if self.preferences.get(ATTR_USAGE, False) or self.preferences.get(
|
||||
ATTR_STATISTICS, False
|
||||
):
|
||||
ent_reg = er.async_get(self.hass)
|
||||
|
||||
try:
|
||||
yaml_configuration = await conf_util.async_hass_config_yaml(self.hass)
|
||||
except HomeAssistantError as err:
|
||||
LOGGER.error(err)
|
||||
return
|
||||
|
||||
configuration_set = set(yaml_configuration)
|
||||
er_platforms = {
|
||||
entity.platform
|
||||
for entity in ent_reg.entities.values()
|
||||
if not entity.disabled
|
||||
}
|
||||
|
||||
domains = async_get_loaded_integrations(self.hass)
|
||||
configured_integrations = await async_get_integrations(self.hass, domains)
|
||||
enabled_domains = set(configured_integrations)
|
||||
|
||||
for integration in configured_integrations.values():
|
||||
if isinstance(integration, IntegrationNotFound):
|
||||
continue
|
||||
@@ -242,11 +215,7 @@ class Analytics:
|
||||
if isinstance(integration, BaseException):
|
||||
raise integration
|
||||
|
||||
if not self._async_should_report_integration(
|
||||
integration=integration,
|
||||
yaml_domains=configuration_set,
|
||||
entity_registry_platforms=er_platforms,
|
||||
):
|
||||
if integration.disabled:
|
||||
continue
|
||||
|
||||
if not integration.is_built_in:
|
||||
@@ -284,12 +253,12 @@ class Analytics:
|
||||
if supervisor_info is not None:
|
||||
payload[ATTR_ADDONS] = addons
|
||||
|
||||
if ENERGY_DOMAIN in enabled_domains:
|
||||
if ENERGY_DOMAIN in integrations:
|
||||
payload[ATTR_ENERGY] = {
|
||||
ATTR_CONFIGURED: await energy_is_configured(self.hass)
|
||||
}
|
||||
|
||||
if RECORDER_DOMAIN in enabled_domains:
|
||||
if RECORDER_DOMAIN in integrations:
|
||||
instance = get_recorder_instance(self.hass)
|
||||
engine = instance.database_engine
|
||||
if engine and engine.version is not None:
|
||||
@@ -337,34 +306,3 @@ class Analytics:
|
||||
LOGGER.error(
|
||||
"Error sending analytics to %s: %r", ANALYTICS_ENDPOINT_URL, err
|
||||
)
|
||||
|
||||
@callback
|
||||
def _async_should_report_integration(
|
||||
self,
|
||||
integration: Integration,
|
||||
yaml_domains: set[str],
|
||||
entity_registry_platforms: set[str],
|
||||
) -> bool:
|
||||
"""Return a bool to indicate if this integration should be reported."""
|
||||
if integration.disabled:
|
||||
return False
|
||||
|
||||
# Check if the integration is defined in YAML or in the entity registry
|
||||
if (
|
||||
integration.domain in yaml_domains
|
||||
or integration.domain in entity_registry_platforms
|
||||
):
|
||||
return True
|
||||
|
||||
# Check if the integration provide a config flow
|
||||
if not integration.config_flow:
|
||||
return False
|
||||
|
||||
entries = self.hass.config_entries.async_entries(integration.domain)
|
||||
|
||||
# Filter out ignored and disabled entries
|
||||
return any(
|
||||
entry
|
||||
for entry in entries
|
||||
if entry.source != SOURCE_IGNORE and entry.disabled_by is None
|
||||
)
|
||||
|
||||
@@ -296,6 +296,7 @@ class ADBDevice(MediaPlayerEntity):
|
||||
self._process_config,
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
@property
|
||||
def media_image_hash(self) -> str | None:
|
||||
|
||||
@@ -16,7 +16,6 @@ from .const import DOMAIN
|
||||
class AndroidTVRemoteBaseEntity(Entity):
|
||||
"""Android TV Remote Base Entity."""
|
||||
|
||||
_attr_name = None
|
||||
_attr_has_entity_name = True
|
||||
_attr_should_poll = False
|
||||
|
||||
|
||||
@@ -80,7 +80,6 @@ class AnthemAVR(MediaPlayerEntity):
|
||||
self._attr_name = f"zone {zone_number}"
|
||||
self._attr_unique_id = f"{mac_address}_{zone_number}"
|
||||
else:
|
||||
self._attr_name = None
|
||||
self._attr_unique_id = mac_address
|
||||
|
||||
self._attr_device_info = DeviceInfo(
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/apple_tv",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["pyatv", "srptools"],
|
||||
"requirements": ["pyatv==0.13.2"],
|
||||
"requirements": ["pyatv==0.13.0"],
|
||||
"zeroconf": [
|
||||
"_mediaremotetv._tcp.local.",
|
||||
"_companion-link._tcp.local.",
|
||||
@@ -16,24 +16,7 @@
|
||||
"_touch-able._tcp.local.",
|
||||
"_appletv-v2._tcp.local.",
|
||||
"_hscp._tcp.local.",
|
||||
{
|
||||
"type": "_airplay._tcp.local.",
|
||||
"properties": {
|
||||
"model": "appletv*"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "_airplay._tcp.local.",
|
||||
"properties": {
|
||||
"model": "audioaccessory*"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "_airplay._tcp.local.",
|
||||
"properties": {
|
||||
"am": "airport*"
|
||||
}
|
||||
},
|
||||
"_airplay._tcp.local.",
|
||||
{
|
||||
"type": "_raop._tcp.local.",
|
||||
"properties": {
|
||||
|
||||
@@ -25,7 +25,6 @@ from homeassistant.const import (
|
||||
ATTR_SW_VERSION,
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
PERCENTAGE,
|
||||
EntityCategory,
|
||||
UnitOfPressure,
|
||||
UnitOfTemperature,
|
||||
UnitOfTime,
|
||||
@@ -82,7 +81,6 @@ SENSOR_DESCRIPTIONS = {
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
"interval": AranetSensorEntityDescription(
|
||||
key="update_interval",
|
||||
@@ -92,7 +90,6 @@ SENSOR_DESCRIPTIONS = {
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
# The interval setting is not a generally useful entity for most users.
|
||||
entity_registry_enabled_default=False,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@@ -3,9 +3,7 @@ from __future__ import annotations
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.device_automation import (
|
||||
DEVICE_TRIGGER_BASE_SCHEMA,
|
||||
)
|
||||
from homeassistant.components.device_automation import DEVICE_TRIGGER_BASE_SCHEMA
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
CONF_DEVICE_ID,
|
||||
@@ -24,7 +22,7 @@ from .const import DOMAIN, EVENT_TURN_ON
|
||||
TRIGGER_TYPES = {"turn_on"}
|
||||
TRIGGER_SCHEMA = DEVICE_TRIGGER_BASE_SCHEMA.extend(
|
||||
{
|
||||
vol.Required(CONF_ENTITY_ID): cv.entity_id_or_uuid,
|
||||
vol.Required(CONF_ENTITY_ID): cv.entity_id,
|
||||
vol.Required(CONF_TYPE): vol.In(TRIGGER_TYPES),
|
||||
}
|
||||
)
|
||||
@@ -45,7 +43,7 @@ async def async_get_triggers(
|
||||
CONF_PLATFORM: "device",
|
||||
CONF_DEVICE_ID: device_id,
|
||||
CONF_DOMAIN: DOMAIN,
|
||||
CONF_ENTITY_ID: entry.id,
|
||||
CONF_ENTITY_ID: entry.entity_id,
|
||||
CONF_TYPE: "turn_on",
|
||||
}
|
||||
)
|
||||
@@ -64,8 +62,7 @@ async def async_attach_trigger(
|
||||
job = HassJob(action)
|
||||
|
||||
if config[CONF_TYPE] == "turn_on":
|
||||
registry = er.async_get(hass)
|
||||
entity_id = er.async_resolve_entity_id(registry, config[ATTR_ENTITY_ID])
|
||||
entity_id = config[CONF_ENTITY_ID]
|
||||
|
||||
@callback
|
||||
def _handle_event(event: Event) -> None:
|
||||
@@ -77,7 +74,6 @@ async def async_attach_trigger(
|
||||
**trigger_data,
|
||||
**config,
|
||||
"description": f"{DOMAIN} - {entity_id}",
|
||||
"entity_id": entity_id,
|
||||
}
|
||||
},
|
||||
event.context,
|
||||
|
||||
@@ -137,15 +137,16 @@ class VoiceCommandSegmenter:
|
||||
self._reset_seconds_left -= self._seconds_per_chunk
|
||||
if self._reset_seconds_left <= 0:
|
||||
self._speech_seconds_left = self.speech_seconds
|
||||
elif not is_speech:
|
||||
self._reset_seconds_left = self.reset_seconds
|
||||
self._silence_seconds_left -= self._seconds_per_chunk
|
||||
if self._silence_seconds_left <= 0:
|
||||
return False
|
||||
else:
|
||||
# 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
|
||||
if not is_speech:
|
||||
self._reset_seconds_left = self.reset_seconds
|
||||
self._silence_seconds_left -= self._seconds_per_chunk
|
||||
if self._silence_seconds_left <= 0:
|
||||
return False
|
||||
else:
|
||||
# Reset if enough speech
|
||||
self._reset_seconds_left -= self._seconds_per_chunk
|
||||
if self._reset_seconds_left <= 0:
|
||||
self._silence_seconds_left = self.silence_seconds
|
||||
|
||||
return True
|
||||
|
||||
@@ -34,7 +34,7 @@ SENSOR_TYPES = [
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
translation_key="power_output",
|
||||
name="Power Output",
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="temp",
|
||||
@@ -42,13 +42,14 @@ SENSOR_TYPES = [
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
name="Temperature",
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="totalenergy",
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
translation_key="total_energy",
|
||||
name="Total Energy",
|
||||
),
|
||||
]
|
||||
|
||||
@@ -74,8 +75,6 @@ async def async_setup_entry(
|
||||
class AuroraSensor(AuroraEntity, SensorEntity):
|
||||
"""Representation of a Sensor on a Aurora ABB PowerOne Solar inverter."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
client: AuroraSerialClient,
|
||||
|
||||
@@ -18,15 +18,5 @@
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||
"no_serial_ports": "No com ports found. Need a valid RS485 device to communicate."
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"sensor": {
|
||||
"power_output": {
|
||||
"name": "Power Output"
|
||||
},
|
||||
"total_energy": {
|
||||
"name": "Total Energy"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"""Allow to set up simple automation rules via the config file."""
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
import asyncio
|
||||
from collections.abc import Callable, Mapping
|
||||
from dataclasses import dataclass
|
||||
@@ -154,7 +153,7 @@ def _automations_with_x(
|
||||
if DOMAIN not in hass.data:
|
||||
return []
|
||||
|
||||
component: EntityComponent[BaseAutomationEntity] = hass.data[DOMAIN]
|
||||
component: EntityComponent[AutomationEntity] = hass.data[DOMAIN]
|
||||
|
||||
return [
|
||||
automation_entity.entity_id
|
||||
@@ -170,7 +169,7 @@ def _x_in_automation(
|
||||
if DOMAIN not in hass.data:
|
||||
return []
|
||||
|
||||
component: EntityComponent[BaseAutomationEntity] = hass.data[DOMAIN]
|
||||
component: EntityComponent[AutomationEntity] = hass.data[DOMAIN]
|
||||
|
||||
if (automation_entity := component.get_entity(entity_id)) is None:
|
||||
return []
|
||||
@@ -220,7 +219,7 @@ def automations_with_blueprint(hass: HomeAssistant, blueprint_path: str) -> list
|
||||
if DOMAIN not in hass.data:
|
||||
return []
|
||||
|
||||
component: EntityComponent[BaseAutomationEntity] = hass.data[DOMAIN]
|
||||
component: EntityComponent[AutomationEntity] = hass.data[DOMAIN]
|
||||
|
||||
return [
|
||||
automation_entity.entity_id
|
||||
@@ -235,7 +234,7 @@ def blueprint_in_automation(hass: HomeAssistant, entity_id: str) -> str | None:
|
||||
if DOMAIN not in hass.data:
|
||||
return None
|
||||
|
||||
component: EntityComponent[BaseAutomationEntity] = hass.data[DOMAIN]
|
||||
component: EntityComponent[AutomationEntity] = hass.data[DOMAIN]
|
||||
|
||||
if (automation_entity := component.get_entity(entity_id)) is None:
|
||||
return None
|
||||
@@ -245,7 +244,7 @@ def blueprint_in_automation(hass: HomeAssistant, entity_id: str) -> str | None:
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Set up all automations."""
|
||||
hass.data[DOMAIN] = component = EntityComponent[BaseAutomationEntity](
|
||||
hass.data[DOMAIN] = component = EntityComponent[AutomationEntity](
|
||||
LOGGER, DOMAIN, hass
|
||||
)
|
||||
|
||||
@@ -263,7 +262,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
await async_get_blueprints(hass).async_populate()
|
||||
|
||||
async def trigger_service_handler(
|
||||
entity: BaseAutomationEntity, service_call: ServiceCall
|
||||
entity: AutomationEntity, service_call: ServiceCall
|
||||
) -> None:
|
||||
"""Handle forced automation trigger, e.g. from frontend."""
|
||||
await entity.async_trigger(
|
||||
@@ -311,103 +310,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
class BaseAutomationEntity(ToggleEntity, ABC):
|
||||
"""Base class for automation entities."""
|
||||
|
||||
raw_config: ConfigType | None
|
||||
|
||||
@property
|
||||
def capability_attributes(self) -> dict[str, Any] | None:
|
||||
"""Return capability attributes."""
|
||||
if self.unique_id is not None:
|
||||
return {CONF_ID: self.unique_id}
|
||||
return None
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def referenced_areas(self) -> set[str]:
|
||||
"""Return a set of referenced areas."""
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def referenced_blueprint(self) -> str | None:
|
||||
"""Return referenced blueprint or None."""
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def referenced_devices(self) -> set[str]:
|
||||
"""Return a set of referenced devices."""
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def referenced_entities(self) -> set[str]:
|
||||
"""Return a set of referenced entities."""
|
||||
|
||||
@abstractmethod
|
||||
async def async_trigger(
|
||||
self,
|
||||
run_variables: dict[str, Any],
|
||||
context: Context | None = None,
|
||||
skip_condition: bool = False,
|
||||
) -> None:
|
||||
"""Trigger automation."""
|
||||
|
||||
|
||||
class UnavailableAutomationEntity(BaseAutomationEntity):
|
||||
"""A non-functional automation entity with its state set to unavailable.
|
||||
|
||||
This class is instatiated when an automation fails to validate.
|
||||
"""
|
||||
|
||||
_attr_should_poll = False
|
||||
_attr_available = False
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
automation_id: str | None,
|
||||
name: str,
|
||||
raw_config: ConfigType | None,
|
||||
) -> None:
|
||||
"""Initialize an automation entity."""
|
||||
self._name = name
|
||||
self._attr_unique_id = automation_id
|
||||
self.raw_config = raw_config
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""Return the name of the entity."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def referenced_areas(self) -> set[str]:
|
||||
"""Return a set of referenced areas."""
|
||||
return set()
|
||||
|
||||
@property
|
||||
def referenced_blueprint(self) -> str | None:
|
||||
"""Return referenced blueprint or None."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def referenced_devices(self) -> set[str]:
|
||||
"""Return a set of referenced devices."""
|
||||
return set()
|
||||
|
||||
@property
|
||||
def referenced_entities(self) -> set[str]:
|
||||
"""Return a set of referenced entities."""
|
||||
return set()
|
||||
|
||||
async def async_trigger(
|
||||
self,
|
||||
run_variables: dict[str, Any],
|
||||
context: Context | None = None,
|
||||
skip_condition: bool = False,
|
||||
) -> None:
|
||||
"""Trigger automation."""
|
||||
|
||||
|
||||
class AutomationEntity(BaseAutomationEntity, RestoreEntity):
|
||||
class AutomationEntity(ToggleEntity, RestoreEntity):
|
||||
"""Entity to show status of entity."""
|
||||
|
||||
_attr_should_poll = False
|
||||
@@ -460,6 +363,8 @@ class AutomationEntity(BaseAutomationEntity, RestoreEntity):
|
||||
}
|
||||
if self.action_script.supports_max:
|
||||
attrs[ATTR_MAX] = self.action_script.max_runs
|
||||
if self.unique_id is not None:
|
||||
attrs[CONF_ID] = self.unique_id
|
||||
return attrs
|
||||
|
||||
@property
|
||||
@@ -781,7 +686,6 @@ class AutomationEntityConfig:
|
||||
list_no: int
|
||||
raw_blueprint_inputs: ConfigType | None
|
||||
raw_config: ConfigType | None
|
||||
validation_failed: bool
|
||||
|
||||
|
||||
async def _prepare_automation_config(
|
||||
@@ -796,14 +700,9 @@ async def _prepare_automation_config(
|
||||
for list_no, config_block in enumerate(conf):
|
||||
raw_config = cast(AutomationConfig, config_block).raw_config
|
||||
raw_blueprint_inputs = cast(AutomationConfig, config_block).raw_blueprint_inputs
|
||||
validation_failed = cast(AutomationConfig, config_block).validation_failed
|
||||
automation_configs.append(
|
||||
AutomationEntityConfig(
|
||||
config_block,
|
||||
list_no,
|
||||
raw_blueprint_inputs,
|
||||
raw_config,
|
||||
validation_failed,
|
||||
config_block, list_no, raw_blueprint_inputs, raw_config
|
||||
)
|
||||
)
|
||||
|
||||
@@ -819,9 +718,9 @@ def _automation_name(automation_config: AutomationEntityConfig) -> str:
|
||||
|
||||
async def _create_automation_entities(
|
||||
hass: HomeAssistant, automation_configs: list[AutomationEntityConfig]
|
||||
) -> list[BaseAutomationEntity]:
|
||||
) -> list[AutomationEntity]:
|
||||
"""Create automation entities from prepared configuration."""
|
||||
entities: list[BaseAutomationEntity] = []
|
||||
entities: list[AutomationEntity] = []
|
||||
|
||||
for automation_config in automation_configs:
|
||||
config_block = automation_config.config_block
|
||||
@@ -829,16 +728,6 @@ async def _create_automation_entities(
|
||||
automation_id: str | None = config_block.get(CONF_ID)
|
||||
name = _automation_name(automation_config)
|
||||
|
||||
if automation_config.validation_failed:
|
||||
entities.append(
|
||||
UnavailableAutomationEntity(
|
||||
automation_id,
|
||||
name,
|
||||
automation_config.raw_config,
|
||||
)
|
||||
)
|
||||
continue
|
||||
|
||||
initial_state: bool | None = config_block.get(CONF_INITIAL_STATE)
|
||||
|
||||
action_script = Script(
|
||||
@@ -897,18 +786,18 @@ async def _create_automation_entities(
|
||||
async def _async_process_config(
|
||||
hass: HomeAssistant,
|
||||
config: dict[str, Any],
|
||||
component: EntityComponent[BaseAutomationEntity],
|
||||
component: EntityComponent[AutomationEntity],
|
||||
) -> None:
|
||||
"""Process config and add automations."""
|
||||
|
||||
def automation_matches_config(
|
||||
automation: BaseAutomationEntity, config: AutomationEntityConfig
|
||||
automation: AutomationEntity, config: AutomationEntityConfig
|
||||
) -> bool:
|
||||
name = _automation_name(config)
|
||||
return automation.name == name and automation.raw_config == config.raw_config
|
||||
|
||||
def find_matches(
|
||||
automations: list[BaseAutomationEntity],
|
||||
automations: list[AutomationEntity],
|
||||
automation_configs: list[AutomationEntityConfig],
|
||||
) -> tuple[set[int], set[int]]:
|
||||
"""Find matches between a list of automation entities and a list of configurations.
|
||||
@@ -954,7 +843,7 @@ async def _async_process_config(
|
||||
return automation_matches, config_matches
|
||||
|
||||
automation_configs = await _prepare_automation_config(hass, config)
|
||||
automations: list[BaseAutomationEntity] = list(component.entities)
|
||||
automations: list[AutomationEntity] = list(component.entities)
|
||||
|
||||
# Find automations and configurations which have matches
|
||||
automation_matches, config_matches = find_matches(automations, automation_configs)
|
||||
@@ -976,6 +865,8 @@ async def _async_process_config(
|
||||
entities = await _create_automation_entities(hass, updated_automation_configs)
|
||||
await component.async_add_entities(entities)
|
||||
|
||||
return
|
||||
|
||||
|
||||
async def _async_process_if(
|
||||
hass: HomeAssistant, name: str, config: dict[str, Any]
|
||||
@@ -1079,7 +970,7 @@ def websocket_config(
|
||||
msg: dict[str, Any],
|
||||
) -> None:
|
||||
"""Get automation config."""
|
||||
component: EntityComponent[BaseAutomationEntity] = hass.data[DOMAIN]
|
||||
component: EntityComponent[AutomationEntity] = hass.data[DOMAIN]
|
||||
|
||||
automation = component.get_entity(msg["entity_id"])
|
||||
|
||||
|
||||
@@ -41,15 +41,7 @@ from .helpers import async_get_blueprints
|
||||
|
||||
PACKAGE_MERGE_HINT = "list"
|
||||
|
||||
_MINIMAL_PLATFORM_SCHEMA = vol.Schema(
|
||||
{
|
||||
CONF_ID: str,
|
||||
CONF_ALIAS: cv.string,
|
||||
vol.Optional(CONF_DESCRIPTION): cv.string,
|
||||
},
|
||||
extra=vol.ALLOW_EXTRA,
|
||||
)
|
||||
|
||||
_CONDITION_SCHEMA = vol.All(cv.ensure_list, [cv.CONDITION_SCHEMA])
|
||||
|
||||
PLATFORM_SCHEMA = vol.All(
|
||||
cv.deprecated(CONF_HIDE_ENTITY),
|
||||
@@ -63,7 +55,7 @@ PLATFORM_SCHEMA = vol.All(
|
||||
vol.Optional(CONF_INITIAL_STATE): cv.boolean,
|
||||
vol.Optional(CONF_HIDE_ENTITY): cv.boolean,
|
||||
vol.Required(CONF_TRIGGER): cv.TRIGGER_SCHEMA,
|
||||
vol.Optional(CONF_CONDITION): cv.CONDITIONS_SCHEMA,
|
||||
vol.Optional(CONF_CONDITION): _CONDITION_SCHEMA,
|
||||
vol.Optional(CONF_VARIABLES): cv.SCRIPT_VARIABLES_SCHEMA,
|
||||
vol.Optional(CONF_TRIGGER_VARIABLES): cv.SCRIPT_VARIABLES_SCHEMA,
|
||||
vol.Required(CONF_ACTION): cv.SCRIPT_SCHEMA,
|
||||
@@ -76,7 +68,6 @@ PLATFORM_SCHEMA = vol.All(
|
||||
async def _async_validate_config_item(
|
||||
hass: HomeAssistant,
|
||||
config: ConfigType,
|
||||
raise_on_errors: bool,
|
||||
warn_on_errors: bool,
|
||||
) -> AutomationConfig:
|
||||
"""Validate config item."""
|
||||
@@ -113,15 +104,6 @@ async def _async_validate_config_item(
|
||||
)
|
||||
return
|
||||
|
||||
def _minimal_config() -> AutomationConfig:
|
||||
"""Try validating id, alias and description."""
|
||||
minimal_config = _MINIMAL_PLATFORM_SCHEMA(config)
|
||||
automation_config = AutomationConfig(minimal_config)
|
||||
automation_config.raw_blueprint_inputs = raw_blueprint_inputs
|
||||
automation_config.raw_config = raw_config
|
||||
automation_config.validation_failed = True
|
||||
return automation_config
|
||||
|
||||
if blueprint.is_blueprint_instance_config(config):
|
||||
uses_blueprint = True
|
||||
blueprints = async_get_blueprints(hass)
|
||||
@@ -133,9 +115,7 @@ async def _async_validate_config_item(
|
||||
"Failed to generate automation from blueprint: %s",
|
||||
err,
|
||||
)
|
||||
if raise_on_errors:
|
||||
raise
|
||||
return _minimal_config()
|
||||
raise
|
||||
|
||||
raw_blueprint_inputs = blueprint_inputs.config_with_inputs
|
||||
|
||||
@@ -150,9 +130,7 @@ async def _async_validate_config_item(
|
||||
blueprint_inputs.inputs,
|
||||
err,
|
||||
)
|
||||
if raise_on_errors:
|
||||
raise HomeAssistantError(err) from err
|
||||
return _minimal_config()
|
||||
raise HomeAssistantError from err
|
||||
|
||||
automation_name = "Unnamed automation"
|
||||
if isinstance(config, Mapping):
|
||||
@@ -165,16 +143,10 @@ async def _async_validate_config_item(
|
||||
validated_config = PLATFORM_SCHEMA(config)
|
||||
except vol.Invalid as err:
|
||||
_log_invalid_automation(err, automation_name, "could not be validated", config)
|
||||
if raise_on_errors:
|
||||
raise
|
||||
return _minimal_config()
|
||||
|
||||
automation_config = AutomationConfig(validated_config)
|
||||
automation_config.raw_blueprint_inputs = raw_blueprint_inputs
|
||||
automation_config.raw_config = raw_config
|
||||
raise
|
||||
|
||||
try:
|
||||
automation_config[CONF_TRIGGER] = await async_validate_trigger_config(
|
||||
validated_config[CONF_TRIGGER] = await async_validate_trigger_config(
|
||||
hass, validated_config[CONF_TRIGGER]
|
||||
)
|
||||
except (
|
||||
@@ -184,14 +156,11 @@ async def _async_validate_config_item(
|
||||
_log_invalid_automation(
|
||||
err, automation_name, "failed to setup triggers", validated_config
|
||||
)
|
||||
if raise_on_errors:
|
||||
raise
|
||||
automation_config.validation_failed = True
|
||||
return automation_config
|
||||
raise
|
||||
|
||||
if CONF_CONDITION in validated_config:
|
||||
try:
|
||||
automation_config[CONF_CONDITION] = await async_validate_conditions_config(
|
||||
validated_config[CONF_CONDITION] = await async_validate_conditions_config(
|
||||
hass, validated_config[CONF_CONDITION]
|
||||
)
|
||||
except (
|
||||
@@ -201,13 +170,10 @@ async def _async_validate_config_item(
|
||||
_log_invalid_automation(
|
||||
err, automation_name, "failed to setup conditions", validated_config
|
||||
)
|
||||
if raise_on_errors:
|
||||
raise
|
||||
automation_config.validation_failed = True
|
||||
return automation_config
|
||||
raise
|
||||
|
||||
try:
|
||||
automation_config[CONF_ACTION] = await script.async_validate_actions_config(
|
||||
validated_config[CONF_ACTION] = await script.async_validate_actions_config(
|
||||
hass, validated_config[CONF_ACTION]
|
||||
)
|
||||
except (
|
||||
@@ -217,11 +183,11 @@ async def _async_validate_config_item(
|
||||
_log_invalid_automation(
|
||||
err, automation_name, "failed to setup actions", validated_config
|
||||
)
|
||||
if raise_on_errors:
|
||||
raise
|
||||
automation_config.validation_failed = True
|
||||
return automation_config
|
||||
raise
|
||||
|
||||
automation_config = AutomationConfig(validated_config)
|
||||
automation_config.raw_blueprint_inputs = raw_blueprint_inputs
|
||||
automation_config.raw_config = raw_config
|
||||
return automation_config
|
||||
|
||||
|
||||
@@ -230,7 +196,6 @@ class AutomationConfig(dict):
|
||||
|
||||
raw_config: dict[str, Any] | None = None
|
||||
raw_blueprint_inputs: dict[str, Any] | None = None
|
||||
validation_failed: bool = False
|
||||
|
||||
|
||||
async def _try_async_validate_config_item(
|
||||
@@ -239,7 +204,7 @@ async def _try_async_validate_config_item(
|
||||
) -> AutomationConfig | None:
|
||||
"""Validate config item."""
|
||||
try:
|
||||
return await _async_validate_config_item(hass, config, False, True)
|
||||
return await _async_validate_config_item(hass, config, True)
|
||||
except (vol.Invalid, HomeAssistantError):
|
||||
return None
|
||||
|
||||
@@ -250,7 +215,7 @@ async def async_validate_config_item(
|
||||
config: dict[str, Any],
|
||||
) -> AutomationConfig | None:
|
||||
"""Validate config item, called by EditAutomationConfigView."""
|
||||
return await _async_validate_config_item(hass, config, True, False)
|
||||
return await _async_validate_config_item(hass, config, False)
|
||||
|
||||
|
||||
async def async_validate_config(hass: HomeAssistant, config: ConfigType) -> ConfigType:
|
||||
|
||||
@@ -112,8 +112,8 @@ ENTITY_TRIGGERS = {
|
||||
{CONF_TYPE: CONF_NO_LIGHT},
|
||||
],
|
||||
BinarySensorDeviceClass.LOCK: [
|
||||
{CONF_TYPE: CONF_NOT_LOCKED},
|
||||
{CONF_TYPE: CONF_LOCKED},
|
||||
{CONF_TYPE: CONF_NOT_LOCKED},
|
||||
],
|
||||
BinarySensorDeviceClass.MOISTURE: [
|
||||
{CONF_TYPE: CONF_MOIST},
|
||||
|
||||
@@ -302,7 +302,7 @@
|
||||
}
|
||||
},
|
||||
"device_class": {
|
||||
"co": "carbon monoxide",
|
||||
"co": "carbon_monoxide",
|
||||
"cold": "cold",
|
||||
"gas": "gas",
|
||||
"heat": "heat",
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
"""Import logic for blueprint."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from contextlib import suppress
|
||||
from dataclasses import dataclass
|
||||
import html
|
||||
import re
|
||||
@@ -30,10 +28,6 @@ GITHUB_FILE_PATTERN = re.compile(
|
||||
r"^https://github.com/(?P<repository>.+)/blob/(?P<path>.+)$"
|
||||
)
|
||||
|
||||
WEBSITE_PATTERN = re.compile(
|
||||
r"^https://(?P<subdomain>[a-z0-9-]+)\.home-assistant\.io/(?P<path>.+).yaml$"
|
||||
)
|
||||
|
||||
COMMUNITY_TOPIC_SCHEMA = vol.Schema(
|
||||
{
|
||||
"slug": str,
|
||||
@@ -225,37 +219,18 @@ async def fetch_blueprint_from_github_gist_url(
|
||||
)
|
||||
|
||||
|
||||
async def fetch_blueprint_from_website_url(
|
||||
hass: HomeAssistant, url: str
|
||||
) -> ImportedBlueprint:
|
||||
"""Get a blueprint from our website."""
|
||||
if (WEBSITE_PATTERN.match(url)) is None:
|
||||
raise UnsupportedUrl("Not a Home Assistant website URL")
|
||||
|
||||
session = aiohttp_client.async_get_clientsession(hass)
|
||||
|
||||
resp = await session.get(url, raise_for_status=True)
|
||||
raw_yaml = await resp.text()
|
||||
data = yaml.parse_yaml(raw_yaml)
|
||||
assert isinstance(data, dict)
|
||||
blueprint = Blueprint(data)
|
||||
|
||||
parsed_import_url = yarl.URL(url)
|
||||
suggested_filename = f"homeassistant/{parsed_import_url.parts[-1][:-5]}"
|
||||
return ImportedBlueprint(suggested_filename, raw_yaml, blueprint)
|
||||
|
||||
|
||||
async def fetch_blueprint_from_url(hass: HomeAssistant, url: str) -> ImportedBlueprint:
|
||||
"""Get a blueprint from a url."""
|
||||
for func in (
|
||||
fetch_blueprint_from_community_post,
|
||||
fetch_blueprint_from_github_url,
|
||||
fetch_blueprint_from_github_gist_url,
|
||||
fetch_blueprint_from_website_url,
|
||||
):
|
||||
with suppress(UnsupportedUrl):
|
||||
try:
|
||||
imported_bp = await func(hass, url)
|
||||
imported_bp.blueprint.update_metadata(source_url=url)
|
||||
return imported_bp
|
||||
except UnsupportedUrl:
|
||||
pass
|
||||
|
||||
raise HomeAssistantError("Unsupported URL")
|
||||
raise HomeAssistantError("Unsupported url")
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"bleak-retry-connector==3.0.2",
|
||||
"bluetooth-adapters==0.15.3",
|
||||
"bluetooth-auto-recovery==1.2.0",
|
||||
"bluetooth-data-tools==1.3.0",
|
||||
"bluetooth-data-tools==1.2.0",
|
||||
"dbus-fast==1.86.0"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -34,7 +34,6 @@ class BMWButtonEntityDescription(ButtonEntityDescription):
|
||||
[MyBMWVehicle], Coroutine[Any, Any, RemoteServiceStatus]
|
||||
] | None = None
|
||||
account_function: Callable[[BMWDataUpdateCoordinator], Coroutine] | None = None
|
||||
is_available: Callable[[MyBMWVehicle], bool] = lambda _: True
|
||||
|
||||
|
||||
BUTTON_TYPES: tuple[BMWButtonEntityDescription, ...] = (
|
||||
@@ -56,13 +55,6 @@ BUTTON_TYPES: tuple[BMWButtonEntityDescription, ...] = (
|
||||
icon="mdi:hvac",
|
||||
remote_function=lambda vehicle: vehicle.remote_services.trigger_remote_air_conditioning(),
|
||||
),
|
||||
BMWButtonEntityDescription(
|
||||
key="deactivate_air_conditioning",
|
||||
icon="mdi:hvac-off",
|
||||
name="Deactivate air conditioning",
|
||||
remote_function=lambda vehicle: vehicle.remote_services.trigger_remote_air_conditioning_stop(),
|
||||
is_available=lambda vehicle: vehicle.is_remote_climate_stop_enabled,
|
||||
),
|
||||
BMWButtonEntityDescription(
|
||||
key="find_vehicle",
|
||||
translation_key="find_vehicle",
|
||||
@@ -94,7 +86,7 @@ async def async_setup_entry(
|
||||
[
|
||||
BMWButton(coordinator, vehicle, description)
|
||||
for description in BUTTON_TYPES
|
||||
if (not coordinator.read_only and description.is_available(vehicle))
|
||||
if not coordinator.read_only
|
||||
or (coordinator.read_only and description.enabled_when_read_only)
|
||||
]
|
||||
)
|
||||
|
||||
@@ -21,9 +21,3 @@ UNIT_MAP = {
|
||||
"LITERS": UnitOfVolume.LITERS,
|
||||
"GALLONS": UnitOfVolume.GALLONS,
|
||||
}
|
||||
|
||||
SCAN_INTERVALS = {
|
||||
"china": 300,
|
||||
"north_america": 600,
|
||||
"rest_of_world": 300,
|
||||
}
|
||||
|
||||
@@ -15,8 +15,10 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import CONF_GCID, CONF_READ_ONLY, CONF_REFRESH_TOKEN, DOMAIN, SCAN_INTERVALS
|
||||
from .const import CONF_GCID, CONF_READ_ONLY, CONF_REFRESH_TOKEN, DOMAIN
|
||||
|
||||
DEFAULT_SCAN_INTERVAL_SECONDS = 300
|
||||
SCAN_INTERVAL = timedelta(seconds=DEFAULT_SCAN_INTERVAL_SECONDS)
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -48,7 +50,7 @@ class BMWDataUpdateCoordinator(DataUpdateCoordinator[None]):
|
||||
hass,
|
||||
_LOGGER,
|
||||
name=f"{DOMAIN}-{entry.data['username']}",
|
||||
update_interval=timedelta(seconds=SCAN_INTERVALS[entry.data[CONF_REGION]]),
|
||||
update_interval=SCAN_INTERVAL,
|
||||
)
|
||||
|
||||
async def _async_update_data(self) -> None:
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/bmw_connected_drive",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["bimmer_connected"],
|
||||
"requirements": ["bimmer-connected==0.13.8"]
|
||||
"requirements": ["bimmer-connected==0.13.7"]
|
||||
}
|
||||
|
||||
@@ -107,7 +107,6 @@ class BroadlinkRemote(BroadlinkEntity, RemoteEntity, RestoreEntity):
|
||||
"""Representation of a Broadlink remote."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
_attr_name = None
|
||||
|
||||
def __init__(self, device, codes, flags):
|
||||
"""Initialize the remote."""
|
||||
|
||||
@@ -221,7 +221,6 @@ class BroadlinkSP2Switch(BroadlinkSP1Switch):
|
||||
|
||||
_attr_assumed_state = False
|
||||
_attr_has_entity_name = True
|
||||
_attr_name = None
|
||||
|
||||
def __init__(self, device, *args, **kwargs):
|
||||
"""Initialize the switch."""
|
||||
|
||||
@@ -34,21 +34,6 @@ class BPKConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
|
||||
VERSION = 1
|
||||
|
||||
async def async_step_import(self, config: dict[str, Any]) -> FlowResult:
|
||||
"""Import a configuration from config.yaml."""
|
||||
|
||||
if config.get(CONF_LATITUDE):
|
||||
config[CONF_LOCATION] = {
|
||||
CONF_LATITUDE: config[CONF_LATITUDE],
|
||||
CONF_LONGITUDE: config[CONF_LONGITUDE],
|
||||
}
|
||||
if not config.get(CONF_AREA):
|
||||
config[CONF_AREA] = "none"
|
||||
else:
|
||||
config[CONF_AREA] = config[CONF_AREA][0]
|
||||
|
||||
return await self.async_step_user(user_input=config)
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
|
||||
@@ -5,62 +5,19 @@ from collections import defaultdict
|
||||
from datetime import timedelta
|
||||
|
||||
from brottsplatskartan import ATTRIBUTION, BrottsplatsKartan
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
PLATFORM_SCHEMA as PARENT_PLATFORM_SCHEMA,
|
||||
SensorEntity,
|
||||
)
|
||||
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
|
||||
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME
|
||||
from homeassistant.components.sensor import SensorEntity
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE
|
||||
from homeassistant.core import HomeAssistant
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
||||
from .const import AREAS, CONF_APP_ID, CONF_AREA, DEFAULT_NAME, DOMAIN, LOGGER
|
||||
from .const import CONF_APP_ID, CONF_AREA, DOMAIN, LOGGER
|
||||
|
||||
SCAN_INTERVAL = timedelta(minutes=30)
|
||||
|
||||
PLATFORM_SCHEMA = PARENT_PLATFORM_SCHEMA.extend(
|
||||
{
|
||||
vol.Inclusive(CONF_LATITUDE, "coordinates"): cv.latitude,
|
||||
vol.Inclusive(CONF_LONGITUDE, "coordinates"): cv.longitude,
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_AREA, default=[]): vol.All(cv.ensure_list, [vol.In(AREAS)]),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_platform(
|
||||
hass: HomeAssistant,
|
||||
config: ConfigType,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
discovery_info: DiscoveryInfoType | None = None,
|
||||
) -> None:
|
||||
"""Set up the Brottsplatskartan platform."""
|
||||
|
||||
async_create_issue(
|
||||
hass,
|
||||
DOMAIN,
|
||||
"deprecated_yaml",
|
||||
breaks_in_ha_version="2023.11.0",
|
||||
is_fixable=False,
|
||||
severity=IssueSeverity.WARNING,
|
||||
translation_key="deprecated_yaml",
|
||||
)
|
||||
|
||||
hass.async_create_task(
|
||||
hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_IMPORT},
|
||||
data=config,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
@@ -83,7 +40,6 @@ class BrottsplatskartanSensor(SensorEntity):
|
||||
|
||||
_attr_attribution = ATTRIBUTION
|
||||
_attr_has_entity_name = True
|
||||
_attr_name = None
|
||||
|
||||
def __init__(self, bpk: BrottsplatsKartan, name: str, entry_id: str) -> None:
|
||||
"""Initialize the Brottsplatskartan sensor."""
|
||||
|
||||
@@ -16,12 +16,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"issues": {
|
||||
"deprecated_yaml": {
|
||||
"title": "The Brottsplatskartan YAML configuration is being removed",
|
||||
"description": "Configuring Brottsplatskartan using YAML is being removed.\n\nYour existing YAML configuration has been imported into the UI automatically.\n\nRemove the Brottsplatskartan YAML configuration from your configuration.yaml file and restart Home Assistant to fix this issue."
|
||||
}
|
||||
},
|
||||
"selector": {
|
||||
"areas": {
|
||||
"options": {
|
||||
|
||||
@@ -71,7 +71,6 @@ class BSBLANClimate(
|
||||
"""Defines a BSBLAN climate device."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
_attr_name = None
|
||||
# Determine preset modes
|
||||
_attr_supported_features = (
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE
|
||||
|
||||
@@ -92,7 +92,7 @@ class ButtonEntity(RestoreEntity):
|
||||
def _default_to_device_class_name(self) -> bool:
|
||||
"""Return True if an unnamed entity should be named by its device class.
|
||||
|
||||
For buttons this is True if the entity has a device class.
|
||||
For sensors this is True if the entity has a device class.
|
||||
"""
|
||||
return self.device_class is not None
|
||||
|
||||
|
||||
@@ -60,7 +60,6 @@ from .const import (
|
||||
EVENT_TIME_FIELDS,
|
||||
EVENT_TYPES,
|
||||
EVENT_UID,
|
||||
LIST_EVENT_FIELDS,
|
||||
CalendarEntityFeature,
|
||||
)
|
||||
|
||||
@@ -264,8 +263,8 @@ SERVICE_LIST_EVENTS_SCHEMA: Final = vol.All(
|
||||
cv.has_at_most_one_key(EVENT_END_DATETIME, EVENT_DURATION),
|
||||
cv.make_entity_service_schema(
|
||||
{
|
||||
vol.Optional(EVENT_START_DATETIME): cv.datetime,
|
||||
vol.Optional(EVENT_END_DATETIME): cv.datetime,
|
||||
vol.Optional(EVENT_START_DATETIME): datetime.datetime,
|
||||
vol.Optional(EVENT_END_DATETIME): datetime.datetime,
|
||||
vol.Optional(EVENT_DURATION): vol.All(
|
||||
cv.time_period, cv.positive_timedelta
|
||||
),
|
||||
@@ -416,17 +415,6 @@ def _api_event_dict_factory(obj: Iterable[tuple[str, Any]]) -> dict[str, Any]:
|
||||
return result
|
||||
|
||||
|
||||
def _list_events_dict_factory(
|
||||
obj: Iterable[tuple[str, Any]]
|
||||
) -> dict[str, JsonValueType]:
|
||||
"""Convert CalendarEvent dataclass items to dictionary of attributes."""
|
||||
return {
|
||||
name: value
|
||||
for name, value in _event_dict_factory(obj).items()
|
||||
if name in LIST_EVENT_FIELDS and value is not None
|
||||
}
|
||||
|
||||
|
||||
def _get_datetime_local(
|
||||
dt_or_d: datetime.datetime | datetime.date,
|
||||
) -> datetime.datetime:
|
||||
@@ -793,12 +781,10 @@ async def async_list_events_service(
|
||||
end = start + service_call.data[EVENT_DURATION]
|
||||
else:
|
||||
end = service_call.data[EVENT_END_DATETIME]
|
||||
calendar_event_list = await calendar.async_get_events(
|
||||
calendar.hass, dt_util.as_local(start), dt_util.as_local(end)
|
||||
)
|
||||
calendar_event_list = await calendar.async_get_events(calendar.hass, start, end)
|
||||
events: list[JsonValueType] = [
|
||||
dataclasses.asdict(event) for event in calendar_event_list
|
||||
]
|
||||
return {
|
||||
"events": [
|
||||
dataclasses.asdict(event, dict_factory=_list_events_dict_factory)
|
||||
for event in calendar_event_list
|
||||
]
|
||||
"events": events,
|
||||
}
|
||||
|
||||
@@ -41,12 +41,3 @@ EVENT_TIME_FIELDS = {
|
||||
}
|
||||
EVENT_TYPES = "event_types"
|
||||
EVENT_DURATION = "duration"
|
||||
|
||||
# Fields for the list events service
|
||||
LIST_EVENT_FIELDS = {
|
||||
"start",
|
||||
"end",
|
||||
EVENT_SUMMARY,
|
||||
EVENT_DESCRIPTION,
|
||||
EVENT_LOCATION,
|
||||
}
|
||||
|
||||
@@ -8,10 +8,10 @@ from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
CONF_HOST,
|
||||
CONF_PORT,
|
||||
EVENT_HOMEASSISTANT_STARTED,
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.start import async_at_started
|
||||
from homeassistant.core import CoreState, HomeAssistant
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import DEFAULT_PORT, DOMAIN
|
||||
@@ -38,11 +38,19 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
if entry.unique_id is None:
|
||||
hass.config_entries.async_update_entry(entry, unique_id=f"{host}:{port}")
|
||||
|
||||
async def _async_finish_startup(_):
|
||||
async def async_finish_startup(_):
|
||||
await coordinator.async_refresh()
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
async_at_started(hass, _async_finish_startup)
|
||||
if hass.state == CoreState.running:
|
||||
await async_finish_startup(None)
|
||||
else:
|
||||
entry.async_on_unload(
|
||||
hass.bus.async_listen_once(
|
||||
EVENT_HOMEASSISTANT_STARTED, async_finish_startup
|
||||
)
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
|
||||
@@ -534,14 +534,6 @@ class ClimateEntity(Entity):
|
||||
await self.hass.async_add_executor_job(self.turn_on)
|
||||
return
|
||||
|
||||
# If there are only two HVAC modes, and one of those modes is OFF,
|
||||
# then we can just turn on the other mode.
|
||||
if len(self.hvac_modes) == 2 and HVACMode.OFF in self.hvac_modes:
|
||||
for mode in self.hvac_modes:
|
||||
if mode != HVACMode.OFF:
|
||||
await self.async_set_hvac_mode(mode)
|
||||
return
|
||||
|
||||
# Fake turn on
|
||||
for mode in (HVACMode.HEAT_COOL, HVACMode.HEAT, HVACMode.COOL):
|
||||
if mode not in self.hvac_modes:
|
||||
|
||||
@@ -3,10 +3,6 @@ from __future__ import annotations
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.device_automation import (
|
||||
async_get_entity_registry_entry_or_raise,
|
||||
async_validate_entity_schema,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
CONF_DEVICE_ID,
|
||||
@@ -28,7 +24,7 @@ ACTION_TYPES = {"set_hvac_mode", "set_preset_mode"}
|
||||
SET_HVAC_MODE_SCHEMA = cv.DEVICE_ACTION_BASE_SCHEMA.extend(
|
||||
{
|
||||
vol.Required(CONF_TYPE): "set_hvac_mode",
|
||||
vol.Required(CONF_ENTITY_ID): cv.entity_id_or_uuid,
|
||||
vol.Required(CONF_ENTITY_ID): cv.entity_domain(DOMAIN),
|
||||
vol.Required(const.ATTR_HVAC_MODE): vol.In(const.HVAC_MODES),
|
||||
}
|
||||
)
|
||||
@@ -36,19 +32,12 @@ SET_HVAC_MODE_SCHEMA = cv.DEVICE_ACTION_BASE_SCHEMA.extend(
|
||||
SET_PRESET_MODE_SCHEMA = cv.DEVICE_ACTION_BASE_SCHEMA.extend(
|
||||
{
|
||||
vol.Required(CONF_TYPE): "set_preset_mode",
|
||||
vol.Required(CONF_ENTITY_ID): cv.entity_id_or_uuid,
|
||||
vol.Required(CONF_ENTITY_ID): cv.entity_domain(DOMAIN),
|
||||
vol.Required(const.ATTR_PRESET_MODE): str,
|
||||
}
|
||||
)
|
||||
|
||||
_ACTION_SCHEMA = vol.Any(SET_HVAC_MODE_SCHEMA, SET_PRESET_MODE_SCHEMA)
|
||||
|
||||
|
||||
async def async_validate_action_config(
|
||||
hass: HomeAssistant, config: ConfigType
|
||||
) -> ConfigType:
|
||||
"""Validate config."""
|
||||
return async_validate_entity_schema(hass, config, _ACTION_SCHEMA)
|
||||
ACTION_SCHEMA = vol.Any(SET_HVAC_MODE_SCHEMA, SET_PRESET_MODE_SCHEMA)
|
||||
|
||||
|
||||
async def async_get_actions(
|
||||
@@ -68,7 +57,7 @@ async def async_get_actions(
|
||||
base_action = {
|
||||
CONF_DEVICE_ID: device_id,
|
||||
CONF_DOMAIN: DOMAIN,
|
||||
CONF_ENTITY_ID: entry.id,
|
||||
CONF_ENTITY_ID: entry.entity_id,
|
||||
}
|
||||
|
||||
actions.append({**base_action, CONF_TYPE: "set_hvac_mode"})
|
||||
@@ -104,24 +93,23 @@ async def async_get_action_capabilities(
|
||||
) -> dict[str, vol.Schema]:
|
||||
"""List action capabilities."""
|
||||
action_type = config[CONF_TYPE]
|
||||
entity_id_or_uuid = config[CONF_ENTITY_ID]
|
||||
|
||||
fields = {}
|
||||
|
||||
if action_type == "set_hvac_mode":
|
||||
try:
|
||||
entry = async_get_entity_registry_entry_or_raise(hass, entity_id_or_uuid)
|
||||
hvac_modes = (
|
||||
get_capability(hass, entry.entity_id, const.ATTR_HVAC_MODES) or []
|
||||
get_capability(hass, config[ATTR_ENTITY_ID], const.ATTR_HVAC_MODES)
|
||||
or []
|
||||
)
|
||||
except HomeAssistantError:
|
||||
hvac_modes = []
|
||||
fields[vol.Required(const.ATTR_HVAC_MODE)] = vol.In(hvac_modes)
|
||||
elif action_type == "set_preset_mode":
|
||||
try:
|
||||
entry = async_get_entity_registry_entry_or_raise(hass, entity_id_or_uuid)
|
||||
preset_modes = (
|
||||
get_capability(hass, entry.entity_id, const.ATTR_PRESET_MODES) or []
|
||||
get_capability(hass, config[ATTR_ENTITY_ID], const.ATTR_PRESET_MODES)
|
||||
or []
|
||||
)
|
||||
except HomeAssistantError:
|
||||
preset_modes = []
|
||||
|
||||
@@ -142,7 +142,7 @@ async def async_attach_trigger(
|
||||
numeric_state_config[
|
||||
numeric_state_trigger.CONF_VALUE_TEMPLATE
|
||||
] = "{{ state.attributes.current_temperature }}"
|
||||
else: # trigger_type == "current_humidity_changed"
|
||||
else:
|
||||
numeric_state_config[
|
||||
numeric_state_trigger.CONF_VALUE_TEMPLATE
|
||||
] = "{{ state.attributes.current_humidity }}"
|
||||
|
||||
@@ -17,7 +17,6 @@ from homeassistant.components.alexa import (
|
||||
smart_home as alexa_smart_home,
|
||||
)
|
||||
from homeassistant.components.google_assistant import smart_home as ga
|
||||
from homeassistant.const import __version__ as HA_VERSION
|
||||
from homeassistant.core import Context, HassJob, HomeAssistant, callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
from homeassistant.helpers.event import async_call_later
|
||||
@@ -213,19 +212,6 @@ class CloudClient(Interface):
|
||||
"""Process cloud remote message to client."""
|
||||
await self._prefs.async_update(remote_enabled=connect)
|
||||
|
||||
async def async_cloud_connection_info(
|
||||
self, payload: dict[str, Any]
|
||||
) -> dict[str, Any]:
|
||||
"""Process cloud connection info message to client."""
|
||||
return {
|
||||
"remote": {
|
||||
"connected": self.cloud.remote.is_connected,
|
||||
"enabled": self._prefs.remote_enabled,
|
||||
"instance_domain": self.cloud.remote.instance_domain,
|
||||
},
|
||||
"version": HA_VERSION,
|
||||
}
|
||||
|
||||
async def async_alexa_message(self, payload: dict[Any, Any]) -> dict[Any, Any]:
|
||||
"""Process cloud alexa message to client."""
|
||||
cloud_user = await self._prefs.get_cloud_user()
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"integration_type": "system",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["hass_nabucasa"],
|
||||
"requirements": ["hass-nabucasa==0.69.0"]
|
||||
"requirements": ["hass-nabucasa==0.68.0"]
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ async def async_setup_platform(
|
||||
hass,
|
||||
DOMAIN,
|
||||
"deprecated_yaml_binary_sensor",
|
||||
breaks_in_ha_version="2023.12.0",
|
||||
breaks_in_ha_version="2023.8.0",
|
||||
is_fixable=False,
|
||||
severity=IssueSeverity.WARNING,
|
||||
translation_key="deprecated_platform_yaml",
|
||||
|
||||
@@ -73,7 +73,7 @@ async def async_setup_platform(
|
||||
hass,
|
||||
DOMAIN,
|
||||
"deprecated_yaml_cover",
|
||||
breaks_in_ha_version="2023.12.0",
|
||||
breaks_in_ha_version="2023.8.0",
|
||||
is_fixable=False,
|
||||
severity=IssueSeverity.WARNING,
|
||||
translation_key="deprecated_platform_yaml",
|
||||
|
||||
@@ -43,7 +43,7 @@ def get_service(
|
||||
hass,
|
||||
DOMAIN,
|
||||
"deprecated_yaml_notify",
|
||||
breaks_in_ha_version="2023.12.0",
|
||||
breaks_in_ha_version="2023.8.0",
|
||||
is_fixable=False,
|
||||
severity=IssueSeverity.WARNING,
|
||||
translation_key="deprecated_platform_yaml",
|
||||
|
||||
@@ -74,7 +74,7 @@ async def async_setup_platform(
|
||||
hass,
|
||||
DOMAIN,
|
||||
"deprecated_yaml_sensor",
|
||||
breaks_in_ha_version="2023.12.0",
|
||||
breaks_in_ha_version="2023.8.0",
|
||||
is_fixable=False,
|
||||
severity=IssueSeverity.WARNING,
|
||||
translation_key="deprecated_platform_yaml",
|
||||
|
||||
@@ -74,7 +74,7 @@ async def async_setup_platform(
|
||||
hass,
|
||||
DOMAIN,
|
||||
"deprecated_yaml_switch",
|
||||
breaks_in_ha_version="2023.12.0",
|
||||
breaks_in_ha_version="2023.8.0",
|
||||
is_fixable=False,
|
||||
severity=IssueSeverity.WARNING,
|
||||
translation_key="deprecated_platform_yaml",
|
||||
|
||||
@@ -8,7 +8,6 @@ import logging
|
||||
import re
|
||||
from typing import Any, Literal
|
||||
|
||||
from hassil.recognize import RecognizeResult
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import core
|
||||
@@ -354,10 +353,6 @@ async def websocket_hass_agent_debug(
|
||||
}
|
||||
for entity_key, entity in result.entities.items()
|
||||
},
|
||||
"targets": {
|
||||
state.entity_id: {"matched": is_matched}
|
||||
for state, is_matched in _get_debug_targets(hass, result)
|
||||
},
|
||||
}
|
||||
if result is not None
|
||||
else None
|
||||
@@ -367,49 +362,6 @@ async def websocket_hass_agent_debug(
|
||||
)
|
||||
|
||||
|
||||
def _get_debug_targets(
|
||||
hass: HomeAssistant,
|
||||
result: RecognizeResult,
|
||||
) -> Iterable[tuple[core.State, bool]]:
|
||||
"""Yield state/is_matched pairs for a hassil recognition."""
|
||||
entities = result.entities
|
||||
|
||||
name: str | None = None
|
||||
area_name: str | None = None
|
||||
domains: set[str] | None = None
|
||||
device_classes: set[str] | None = None
|
||||
state_names: set[str] | None = None
|
||||
|
||||
if "name" in entities:
|
||||
name = str(entities["name"].value)
|
||||
|
||||
if "area" in entities:
|
||||
area_name = str(entities["area"].value)
|
||||
|
||||
if "domain" in entities:
|
||||
domains = set(cv.ensure_list(entities["domain"].value))
|
||||
|
||||
if "device_class" in entities:
|
||||
device_classes = set(cv.ensure_list(entities["device_class"].value))
|
||||
|
||||
if "state" in entities:
|
||||
# HassGetState only
|
||||
state_names = set(cv.ensure_list(entities["state"].value))
|
||||
|
||||
states = intent.async_match_states(
|
||||
hass,
|
||||
name=name,
|
||||
area_name=area_name,
|
||||
domains=domains,
|
||||
device_classes=device_classes,
|
||||
)
|
||||
|
||||
for state in states:
|
||||
# For queries, a target is "matched" based on its state
|
||||
is_matched = (state_names is None) or (state.state in state_names)
|
||||
yield state, is_matched
|
||||
|
||||
|
||||
class ConversationProcessView(http.HomeAssistantView):
|
||||
"""View to process text."""
|
||||
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"integration_type": "system",
|
||||
"iot_class": "local_push",
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["hassil==1.0.6", "home-assistant-intents==2023.6.28"]
|
||||
"requirements": ["hassil==1.0.6", "home-assistant-intents==2023.6.5"]
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ process:
|
||||
name: Text
|
||||
description: Transcribed text
|
||||
example: Turn all lights on
|
||||
required: true
|
||||
selector:
|
||||
text:
|
||||
language:
|
||||
@@ -21,4 +20,4 @@ process:
|
||||
description: Assist engine to process your request
|
||||
example: homeassistant
|
||||
selector:
|
||||
conversation_agent:
|
||||
text:
|
||||
|
||||
@@ -3,7 +3,6 @@ from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from hassil.recognize import PUNCTUATION
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import CONF_COMMAND, CONF_PLATFORM
|
||||
@@ -16,22 +15,10 @@ from . import HOME_ASSISTANT_AGENT, _get_agent_manager
|
||||
from .const import DOMAIN
|
||||
from .default_agent import DefaultAgent
|
||||
|
||||
|
||||
def has_no_punctuation(value: list[str]) -> list[str]:
|
||||
"""Validate result does not contain punctuation."""
|
||||
for sentence in value:
|
||||
if PUNCTUATION.search(sentence):
|
||||
raise vol.Invalid("sentence should not contain punctuation")
|
||||
|
||||
return value
|
||||
|
||||
|
||||
TRIGGER_SCHEMA = cv.TRIGGER_BASE_SCHEMA.extend(
|
||||
{
|
||||
vol.Required(CONF_PLATFORM): DOMAIN,
|
||||
vol.Required(CONF_COMMAND): vol.All(
|
||||
cv.ensure_list, [cv.string], has_no_punctuation
|
||||
),
|
||||
vol.Required(CONF_COMMAND): vol.All(cv.ensure_list, [cv.string]),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -64,7 +51,7 @@ async def async_attach_trigger(
|
||||
):
|
||||
await future
|
||||
|
||||
return "Done"
|
||||
return None
|
||||
|
||||
default_agent = await _get_agent_manager(hass).async_get_agent(HOME_ASSISTANT_AGENT)
|
||||
assert isinstance(default_agent, DefaultAgent)
|
||||
|
||||
@@ -292,7 +292,7 @@ class Counter(collection.CollectionEntity, RestoreEntity):
|
||||
self.hass,
|
||||
DOMAIN,
|
||||
"deprecated_configure_service",
|
||||
breaks_in_ha_version="2023.12.0",
|
||||
breaks_in_ha_version="2023.8.0",
|
||||
is_fixable=True,
|
||||
is_persistent=True,
|
||||
severity=IssueSeverity.WARNING,
|
||||
|
||||
@@ -276,16 +276,17 @@ class DaikinClimate(ClimateEntity):
|
||||
await self._api.device.set_advanced_mode(
|
||||
HA_PRESET_TO_DAIKIN[PRESET_ECO], ATTR_STATE_ON
|
||||
)
|
||||
elif self.preset_mode == PRESET_AWAY:
|
||||
await self._api.device.set_holiday(ATTR_STATE_OFF)
|
||||
elif self.preset_mode == PRESET_BOOST:
|
||||
await self._api.device.set_advanced_mode(
|
||||
HA_PRESET_TO_DAIKIN[PRESET_BOOST], ATTR_STATE_OFF
|
||||
)
|
||||
elif self.preset_mode == PRESET_ECO:
|
||||
await self._api.device.set_advanced_mode(
|
||||
HA_PRESET_TO_DAIKIN[PRESET_ECO], ATTR_STATE_OFF
|
||||
)
|
||||
else:
|
||||
if self.preset_mode == PRESET_AWAY:
|
||||
await self._api.device.set_holiday(ATTR_STATE_OFF)
|
||||
elif self.preset_mode == PRESET_BOOST:
|
||||
await self._api.device.set_advanced_mode(
|
||||
HA_PRESET_TO_DAIKIN[PRESET_BOOST], ATTR_STATE_OFF
|
||||
)
|
||||
elif self.preset_mode == PRESET_ECO:
|
||||
await self._api.device.set_advanced_mode(
|
||||
HA_PRESET_TO_DAIKIN[PRESET_ECO], ATTR_STATE_OFF
|
||||
)
|
||||
|
||||
@property
|
||||
def preset_modes(self):
|
||||
|
||||
@@ -5,5 +5,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/delijn",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["pydelijn"],
|
||||
"requirements": ["pydelijn==1.1.0"]
|
||||
"requirements": ["pydelijn==1.0.0"]
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ COMPONENTS_WITH_CONFIG_ENTRY_DEMO_PLATFORM = [
|
||||
|
||||
COMPONENTS_WITH_DEMO_PLATFORM = [
|
||||
Platform.TTS,
|
||||
Platform.STT,
|
||||
Platform.MAILBOX,
|
||||
Platform.NOTIFY,
|
||||
Platform.IMAGE_PROCESSING,
|
||||
|
||||
@@ -4,7 +4,6 @@ from __future__ import annotations
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components.humidifier import (
|
||||
HumidifierAction,
|
||||
HumidifierDeviceClass,
|
||||
HumidifierEntity,
|
||||
HumidifierEntityFeature,
|
||||
@@ -31,7 +30,6 @@ async def async_setup_platform(
|
||||
mode=None,
|
||||
target_humidity=68,
|
||||
current_humidity=45,
|
||||
action=HumidifierAction.HUMIDIFYING,
|
||||
device_class=HumidifierDeviceClass.HUMIDIFIER,
|
||||
),
|
||||
DemoHumidifier(
|
||||
@@ -39,7 +37,6 @@ async def async_setup_platform(
|
||||
mode=None,
|
||||
target_humidity=54,
|
||||
current_humidity=59,
|
||||
action=HumidifierAction.DRYING,
|
||||
device_class=HumidifierDeviceClass.DEHUMIDIFIER,
|
||||
),
|
||||
DemoHumidifier(
|
||||
@@ -74,13 +71,11 @@ class DemoHumidifier(HumidifierEntity):
|
||||
current_humidity: int | None = None,
|
||||
available_modes: list[str] | None = None,
|
||||
is_on: bool = True,
|
||||
action: HumidifierAction | None = None,
|
||||
device_class: HumidifierDeviceClass | None = None,
|
||||
) -> None:
|
||||
"""Initialize the humidifier device."""
|
||||
self._attr_name = name
|
||||
self._attr_is_on = is_on
|
||||
self._attr_action = action
|
||||
self._attr_supported_features = SUPPORT_FLAGS
|
||||
if mode is not None:
|
||||
self._attr_supported_features |= HumidifierEntityFeature.MODES
|
||||
|
||||
@@ -108,7 +108,6 @@ async def async_setup_entry(
|
||||
):
|
||||
device_info = DeviceInfo(
|
||||
identifiers=device.identifiers,
|
||||
connections=device.connections,
|
||||
)
|
||||
else:
|
||||
device_info = None
|
||||
|
||||
@@ -28,7 +28,6 @@ STATIC_VALIDATOR = {
|
||||
ENTITY_PLATFORMS = {
|
||||
Platform.ALARM_CONTROL_PANEL.value,
|
||||
Platform.BUTTON.value,
|
||||
Platform.CLIMATE.value,
|
||||
Platform.COVER.value,
|
||||
Platform.FAN.value,
|
||||
Platform.HUMIDIFIER.value,
|
||||
@@ -38,7 +37,6 @@ ENTITY_PLATFORMS = {
|
||||
Platform.REMOTE.value,
|
||||
Platform.SELECT.value,
|
||||
Platform.SWITCH.value,
|
||||
Platform.TEXT.value,
|
||||
Platform.VACUUM.value,
|
||||
Platform.WATER_HEATER.value,
|
||||
}
|
||||
|
||||
@@ -726,10 +726,6 @@ class DeviceTracker:
|
||||
class Device(RestoreEntity):
|
||||
"""Base class for a tracked device."""
|
||||
|
||||
# This entity is legacy and does not have a platform.
|
||||
# We can't fix this easily without breaking changes.
|
||||
_no_platform_reported = True
|
||||
|
||||
host_name: str | None = None
|
||||
location_name: str | None = None
|
||||
gps: GPSType | None = None
|
||||
|
||||
@@ -8,8 +8,6 @@ from .devolo_device import DevoloDeviceEntity
|
||||
class DevoloMultiLevelSwitchDeviceEntity(DevoloDeviceEntity):
|
||||
"""Representation of a multi level switch device within devolo Home Control. Something like a dimmer or a thermostat."""
|
||||
|
||||
_attr_name = None
|
||||
|
||||
def __init__(
|
||||
self, homecontrol: HomeControl, device_instance: Zwave, element_uid: str
|
||||
) -> None:
|
||||
|
||||
@@ -71,12 +71,13 @@ class DevoloLightDeviceEntity(DevoloMultiLevelSwitchDeviceEntity, LightEntity):
|
||||
self._multi_level_switch_property.set(
|
||||
round(kwargs[ATTR_BRIGHTNESS] / 255 * 100)
|
||||
)
|
||||
elif self._binary_switch_property is not None:
|
||||
# Turn on the light device to the latest known value. The value is known by the device itself.
|
||||
self._binary_switch_property.set(True)
|
||||
else:
|
||||
# If there is no binary switch attached to the device, turn it on to 100 %.
|
||||
self._multi_level_switch_property.set(100)
|
||||
if self._binary_switch_property is not None:
|
||||
# Turn on the light device to the latest known value. The value is known by the device itself.
|
||||
self._binary_switch_property.set(True)
|
||||
else:
|
||||
# If there is no binary switch attached to the device, turn it on to 100 %.
|
||||
self._multi_level_switch_property.set(100)
|
||||
|
||||
def turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn device off."""
|
||||
|
||||
@@ -41,8 +41,6 @@ async def async_setup_entry(
|
||||
class DevoloSwitch(DevoloDeviceEntity, SwitchEntity):
|
||||
"""Representation of a switch."""
|
||||
|
||||
_attr_name = None
|
||||
|
||||
def __init__(
|
||||
self, homecontrol: HomeControl, device_instance: Zwave, element_uid: str
|
||||
) -> None:
|
||||
|
||||
@@ -32,23 +32,44 @@ async def async_setup_entry(
|
||||
class DexcomGlucoseValueSensor(CoordinatorEntity, SensorEntity):
|
||||
"""Representation of a Dexcom glucose value sensor."""
|
||||
|
||||
_attr_icon = GLUCOSE_VALUE_ICON
|
||||
|
||||
def __init__(self, coordinator, username, unit_of_measurement):
|
||||
"""Initialize the sensor."""
|
||||
super().__init__(coordinator)
|
||||
self._attr_native_unit_of_measurement = unit_of_measurement
|
||||
self._key = "mg_dl" if unit_of_measurement == MG_DL else "mmol_l"
|
||||
self._attr_name = f"{DOMAIN}_{username}_glucose_value"
|
||||
self._attr_unique_id = f"{username}-value"
|
||||
self._state = None
|
||||
self._unit_of_measurement = unit_of_measurement
|
||||
self._attribute_unit_of_measurement = (
|
||||
"mg_dl" if unit_of_measurement == MG_DL else "mmol_l"
|
||||
)
|
||||
self._name = f"{DOMAIN}_{username}_glucose_value"
|
||||
self._unique_id = f"{username}-value"
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the sensor."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Return the icon for the frontend."""
|
||||
return GLUCOSE_VALUE_ICON
|
||||
|
||||
@property
|
||||
def native_unit_of_measurement(self):
|
||||
"""Return the unit of measurement of the device."""
|
||||
return self._unit_of_measurement
|
||||
|
||||
@property
|
||||
def native_value(self):
|
||||
"""Return the state of the sensor."""
|
||||
if self.coordinator.data:
|
||||
return getattr(self.coordinator.data, self._key)
|
||||
return getattr(self.coordinator.data, self._attribute_unit_of_measurement)
|
||||
return None
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Device unique id."""
|
||||
return self._unique_id
|
||||
|
||||
|
||||
class DexcomGlucoseTrendSensor(CoordinatorEntity, SensorEntity):
|
||||
"""Representation of a Dexcom glucose trend sensor."""
|
||||
@@ -56,8 +77,14 @@ class DexcomGlucoseTrendSensor(CoordinatorEntity, SensorEntity):
|
||||
def __init__(self, coordinator, username):
|
||||
"""Initialize the sensor."""
|
||||
super().__init__(coordinator)
|
||||
self._attr_name = f"{DOMAIN}_{username}_glucose_trend"
|
||||
self._attr_unique_id = f"{username}-trend"
|
||||
self._state = None
|
||||
self._name = f"{DOMAIN}_{username}_glucose_trend"
|
||||
self._unique_id = f"{username}-trend"
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the sensor."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
@@ -72,3 +99,8 @@ class DexcomGlucoseTrendSensor(CoordinatorEntity, SensorEntity):
|
||||
if self.coordinator.data:
|
||||
return self.coordinator.data.trend_description
|
||||
return None
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Device unique id."""
|
||||
return self._unique_id
|
||||
|
||||
@@ -350,13 +350,14 @@ class Doods(ImageProcessingEntity):
|
||||
or boxes[3] > self._area[3]
|
||||
):
|
||||
continue
|
||||
elif (
|
||||
boxes[0] > self._area[2]
|
||||
or boxes[1] > self._area[3]
|
||||
or boxes[2] < self._area[0]
|
||||
or boxes[3] < self._area[1]
|
||||
):
|
||||
continue
|
||||
else:
|
||||
if (
|
||||
boxes[0] > self._area[2]
|
||||
or boxes[1] > self._area[3]
|
||||
or boxes[2] < self._area[0]
|
||||
or boxes[3] < self._area[1]
|
||||
):
|
||||
continue
|
||||
|
||||
# Exclude matches outside label specific area definition
|
||||
if self._label_areas.get(label):
|
||||
@@ -368,13 +369,14 @@ class Doods(ImageProcessingEntity):
|
||||
or boxes[3] > self._label_areas[label][3]
|
||||
):
|
||||
continue
|
||||
elif (
|
||||
boxes[0] > self._label_areas[label][2]
|
||||
or boxes[1] > self._label_areas[label][3]
|
||||
or boxes[2] < self._label_areas[label][0]
|
||||
or boxes[3] < self._label_areas[label][1]
|
||||
):
|
||||
continue
|
||||
else:
|
||||
if (
|
||||
boxes[0] > self._label_areas[label][2]
|
||||
or boxes[1] > self._label_areas[label][3]
|
||||
or boxes[2] < self._label_areas[label][0]
|
||||
or boxes[3] < self._label_areas[label][1]
|
||||
):
|
||||
continue
|
||||
|
||||
if label not in matches:
|
||||
matches[label] = []
|
||||
|
||||
@@ -12,7 +12,7 @@ from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from .const import CAMERA_MODEL, DOMAIN
|
||||
from .coordinator import Dremel3DPrinterDataUpdateCoordinator
|
||||
|
||||
PLATFORMS = [Platform.BINARY_SENSOR, Platform.BUTTON, Platform.CAMERA, Platform.SENSOR]
|
||||
PLATFORMS = [Platform.BINARY_SENSOR, Platform.CAMERA, Platform.SENSOR]
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
"""Support for Dremel 3D Printer buttons."""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
|
||||
from dremel3dpy import Dremel3DPrinter
|
||||
|
||||
from homeassistant.components.button import ButtonEntity, ButtonEntityDescription
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
from .entity import Dremel3DPrinterEntity
|
||||
|
||||
|
||||
@dataclass
|
||||
class Dremel3DPrinterButtonEntityMixin:
|
||||
"""Mixin for required keys."""
|
||||
|
||||
press_fn: Callable[[Dremel3DPrinter], None]
|
||||
|
||||
|
||||
@dataclass
|
||||
class Dremel3DPrinterButtonEntityDescription(
|
||||
ButtonEntityDescription, Dremel3DPrinterButtonEntityMixin
|
||||
):
|
||||
"""Describes a Dremel 3D Printer button entity."""
|
||||
|
||||
|
||||
BUTTON_TYPES: tuple[Dremel3DPrinterButtonEntityDescription, ...] = (
|
||||
Dremel3DPrinterButtonEntityDescription(
|
||||
key="cancel_job",
|
||||
translation_key="cancel_job",
|
||||
press_fn=lambda api: api.stop_print(),
|
||||
),
|
||||
Dremel3DPrinterButtonEntityDescription(
|
||||
key="pause_job",
|
||||
translation_key="pause_job",
|
||||
press_fn=lambda api: api.pause_print(),
|
||||
),
|
||||
Dremel3DPrinterButtonEntityDescription(
|
||||
key="resume_job",
|
||||
translation_key="resume_job",
|
||||
press_fn=lambda api: api.resume_print(),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up Dremel 3D Printer control buttons."""
|
||||
coordinator = hass.data[DOMAIN][config_entry.entry_id]
|
||||
async_add_entities(
|
||||
Dremel3DPrinterButtonEntity(coordinator, description)
|
||||
for description in BUTTON_TYPES
|
||||
)
|
||||
|
||||
|
||||
class Dremel3DPrinterButtonEntity(Dremel3DPrinterEntity, ButtonEntity):
|
||||
"""Represent a Dremel 3D Printer button."""
|
||||
|
||||
entity_description: Dremel3DPrinterButtonEntityDescription
|
||||
|
||||
def press(self) -> None:
|
||||
"""Handle the button press."""
|
||||
# api does not care about the current state
|
||||
try:
|
||||
self.entity_description.press_fn(self._api)
|
||||
except RuntimeError as ex:
|
||||
raise HomeAssistantError(
|
||||
"An error occurred while submitting command"
|
||||
) from ex
|
||||
@@ -16,17 +16,6 @@
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"button": {
|
||||
"cancel_job": {
|
||||
"name": "Cancel job"
|
||||
},
|
||||
"pause_job": {
|
||||
"name": "Pause job"
|
||||
},
|
||||
"resume_job": {
|
||||
"name": "Resume job"
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
"job_phase": {
|
||||
"name": "Job phase"
|
||||
|
||||
@@ -459,9 +459,9 @@ async def async_setup_entry(
|
||||
@callback
|
||||
def close_transport(_event: EventType) -> None:
|
||||
"""Close the transport on HA shutdown."""
|
||||
if not transport: # noqa: B023
|
||||
if not transport:
|
||||
return
|
||||
transport.close() # noqa: B023
|
||||
transport.close()
|
||||
|
||||
stop_listener = hass.bus.async_listen_once(
|
||||
EVENT_HOMEASSISTANT_STOP, close_transport
|
||||
|
||||
@@ -91,7 +91,7 @@ async def async_setup_platform(
|
||||
hass,
|
||||
DOMAIN,
|
||||
"deprecated_yaml",
|
||||
breaks_in_ha_version="2023.12.0",
|
||||
breaks_in_ha_version="2023.8.0",
|
||||
is_fixable=False,
|
||||
severity=IssueSeverity.WARNING,
|
||||
translation_key="deprecated_yaml",
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"title": "ecobee API key",
|
||||
"description": "Please enter the API key obtained from ecobee.com.",
|
||||
"data": {
|
||||
"api_key": "[%key:common::config_flow::data::api_key%]"
|
||||
}
|
||||
},
|
||||
"authorize": {
|
||||
"title": "Authorize app on ecobee.com",
|
||||
"description": "Please authorize this app at https://www.ecobee.com/consumerportal/index.html with PIN code:\n\n{pin}\n\nThen, press Submit."
|
||||
}
|
||||
},
|
||||
|
||||
@@ -125,7 +125,7 @@ ECOWITT_SENSORS_MAPPING: Final = {
|
||||
EcoWittSensorTypes.LIGHTNING_COUNT: SensorEntityDescription(
|
||||
key="LIGHTNING_COUNT",
|
||||
native_unit_of_measurement="strikes",
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
state_class=SensorStateClass.TOTAL,
|
||||
),
|
||||
EcoWittSensorTypes.TEMPERATURE_C: SensorEntityDescription(
|
||||
key="TEMPERATURE_C",
|
||||
@@ -143,13 +143,13 @@ ECOWITT_SENSORS_MAPPING: Final = {
|
||||
key="RAIN_COUNT_MM",
|
||||
native_unit_of_measurement=UnitOfPrecipitationDepth.MILLIMETERS,
|
||||
device_class=SensorDeviceClass.PRECIPITATION,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
state_class=SensorStateClass.TOTAL,
|
||||
),
|
||||
EcoWittSensorTypes.RAIN_COUNT_INCHES: SensorEntityDescription(
|
||||
key="RAIN_COUNT_INCHES",
|
||||
native_unit_of_measurement=UnitOfPrecipitationDepth.INCHES,
|
||||
device_class=SensorDeviceClass.PRECIPITATION,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
state_class=SensorStateClass.TOTAL,
|
||||
),
|
||||
EcoWittSensorTypes.RAIN_RATE_MM: SensorEntityDescription(
|
||||
key="RAIN_RATE_MM",
|
||||
@@ -230,13 +230,6 @@ async def async_setup_entry(
|
||||
name=sensor.name,
|
||||
)
|
||||
|
||||
# Hourly rain doesn't reset to fixed hours, it must be measurement state classes
|
||||
if sensor.key in ("hrain_piezomm", "hrain_piezo"):
|
||||
description = dataclasses.replace(
|
||||
description,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
)
|
||||
|
||||
async_add_entities([EcowittSensorEntity(sensor, description)])
|
||||
|
||||
ecowitt.new_sensor_cb.append(_new_sensor)
|
||||
|
||||
@@ -5,5 +5,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/eddystone_temperature",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["beacontools"],
|
||||
"requirements": ["beacontools[scan]==2.1.0"]
|
||||
"requirements": ["beacontools[scan]==2.1.0", "construct==2.10.56"]
|
||||
}
|
||||
|
||||
@@ -25,14 +25,14 @@ from .const import CONF_CURRENT_VALUES, DOMAIN, LOGGER
|
||||
SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
|
||||
SensorEntityDescription(
|
||||
key="instant_readings",
|
||||
translation_key="instant_readings",
|
||||
name="Power Usage",
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="energy_day",
|
||||
translation_key="energy_day",
|
||||
name="Daily Consumption",
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
@@ -40,7 +40,7 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="energy_week",
|
||||
translation_key="energy_week",
|
||||
name="Weekly Consumption",
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
@@ -48,14 +48,14 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="energy_month",
|
||||
translation_key="energy_month",
|
||||
name="Monthly Consumption",
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="energy_year",
|
||||
translation_key="energy_year",
|
||||
name="Yearly Consumption",
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
@@ -63,32 +63,32 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="budget",
|
||||
translation_key="budget",
|
||||
name="Energy Budget",
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="cost_day",
|
||||
translation_key="cost_day",
|
||||
name="Daily Energy Cost",
|
||||
device_class=SensorDeviceClass.MONETARY,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="cost_week",
|
||||
translation_key="cost_week",
|
||||
name="Weekly Energy Cost",
|
||||
device_class=SensorDeviceClass.MONETARY,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="cost_month",
|
||||
translation_key="cost_month",
|
||||
name="Monthly Energy Cost",
|
||||
device_class=SensorDeviceClass.MONETARY,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="cost_year",
|
||||
translation_key="cost_year",
|
||||
name="Yearly Energy Cost",
|
||||
device_class=SensorDeviceClass.MONETARY,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
entity_registry_enabled_default=False,
|
||||
@@ -137,8 +137,6 @@ async def async_setup_entry(
|
||||
class EfergySensor(EfergyEntity, SensorEntity):
|
||||
"""Implementation of an Efergy sensor."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
api: Efergy,
|
||||
|
||||
@@ -16,39 +16,5 @@
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"sensor": {
|
||||
"instant_readings": {
|
||||
"name": "Power usage"
|
||||
},
|
||||
"energy_day": {
|
||||
"name": "Daily consumption"
|
||||
},
|
||||
"energy_week": {
|
||||
"name": "Weekly consumption"
|
||||
},
|
||||
"energy_month": {
|
||||
"name": "Monthly consumption"
|
||||
},
|
||||
"energy_year": {
|
||||
"name": "Yearly consumption"
|
||||
},
|
||||
"budget": {
|
||||
"name": "Energy budget"
|
||||
},
|
||||
"cost_day": {
|
||||
"name": "Daily energy cost"
|
||||
},
|
||||
"cost_week": {
|
||||
"name": "Weekly energy cost"
|
||||
},
|
||||
"cost_month": {
|
||||
"name": "Monthly energy cost"
|
||||
},
|
||||
"cost_year": {
|
||||
"name": "Yearly energy cost"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,9 +118,7 @@ class ElectraClimateEntity(ClimateEntity):
|
||||
self._electra_ac_device = device
|
||||
self._attr_unique_id = device.mac
|
||||
self._attr_supported_features = (
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
| ClimateEntityFeature.FAN_MODE
|
||||
| ClimateEntityFeature.PRESET_MODE
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.FAN_MODE
|
||||
)
|
||||
|
||||
swing_modes: list = []
|
||||
|
||||
@@ -40,7 +40,8 @@ class ElgatoButtonEntityDescription(
|
||||
BUTTONS = [
|
||||
ElgatoButtonEntityDescription(
|
||||
key="identify",
|
||||
device_class=ButtonDeviceClass.IDENTIFY,
|
||||
translation_key="identify",
|
||||
icon="mdi:help",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
press_fn=lambda client: client.identify(),
|
||||
),
|
||||
|
||||
@@ -47,7 +47,6 @@ async def async_setup_entry(
|
||||
class ElgatoLight(ElgatoEntity, LightEntity):
|
||||
"""Defines an Elgato Light."""
|
||||
|
||||
_attr_name = None
|
||||
_attr_min_mireds = 143
|
||||
_attr_max_mireds = 344
|
||||
|
||||
|
||||
@@ -23,6 +23,11 @@
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"button": {
|
||||
"identify": {
|
||||
"name": "Identify"
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
"charge_power": {
|
||||
"name": "Charging power"
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/environment_canada",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["env_canada"],
|
||||
"requirements": ["env-canada==0.5.35"]
|
||||
"requirements": ["env-canada==0.5.34"]
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .entity import (
|
||||
EsphomeEntity,
|
||||
esphome_state_property,
|
||||
platform_async_setup_entry,
|
||||
)
|
||||
from .enum_mapper import EsphomeEnumMapper
|
||||
@@ -112,7 +111,6 @@ class EsphomeAlarmControlPanel(
|
||||
self._attr_code_arm_required = bool(static_info.requires_code_to_arm)
|
||||
|
||||
@property
|
||||
@esphome_state_property
|
||||
def state(self) -> str | None:
|
||||
"""Return the state of the device."""
|
||||
return _ESPHOME_ACP_STATE_TO_HASS_STATE.from_esphome(self._state.state)
|
||||
|
||||
@@ -11,7 +11,6 @@ from aioesphomeapi import (
|
||||
ClimatePreset,
|
||||
ClimateState,
|
||||
ClimateSwingMode,
|
||||
EntityInfo,
|
||||
)
|
||||
|
||||
from homeassistant.components.climate import (
|
||||
@@ -52,7 +51,7 @@ from homeassistant.const import (
|
||||
PRECISION_WHOLE,
|
||||
UnitOfTemperature,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .entity import (
|
||||
@@ -141,32 +140,71 @@ class EsphomeClimateEntity(EsphomeEntity[ClimateInfo, ClimateState], ClimateEnti
|
||||
|
||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
|
||||
@callback
|
||||
def _on_static_info_update(self, static_info: EntityInfo) -> None:
|
||||
"""Set attrs from static info."""
|
||||
super()._on_static_info_update(static_info)
|
||||
static_info = self._static_info
|
||||
self._attr_precision = self._get_precision()
|
||||
self._attr_hvac_modes = [
|
||||
_CLIMATE_MODES.from_esphome(mode) for mode in static_info.supported_modes
|
||||
@property
|
||||
def precision(self) -> float:
|
||||
"""Return the precision of the climate device."""
|
||||
precicions = [PRECISION_WHOLE, PRECISION_HALVES, PRECISION_TENTHS]
|
||||
if self._static_info.visual_current_temperature_step != 0:
|
||||
step = self._static_info.visual_current_temperature_step
|
||||
else:
|
||||
step = self._static_info.visual_target_temperature_step
|
||||
for prec in precicions:
|
||||
if step >= prec:
|
||||
return prec
|
||||
# Fall back to highest precision, tenths
|
||||
return PRECISION_TENTHS
|
||||
|
||||
@property
|
||||
def hvac_modes(self) -> list[HVACMode]:
|
||||
"""Return the list of available operation modes."""
|
||||
return [
|
||||
_CLIMATE_MODES.from_esphome(mode)
|
||||
for mode in self._static_info.supported_modes
|
||||
]
|
||||
self._attr_fan_modes = [
|
||||
_FAN_MODES.from_esphome(mode) for mode in static_info.supported_fan_modes
|
||||
] + static_info.supported_custom_fan_modes
|
||||
self._attr_preset_modes = [
|
||||
|
||||
@property
|
||||
def fan_modes(self) -> list[str]:
|
||||
"""Return the list of available fan modes."""
|
||||
return [
|
||||
_FAN_MODES.from_esphome(mode)
|
||||
for mode in self._static_info.supported_fan_modes
|
||||
] + self._static_info.supported_custom_fan_modes
|
||||
|
||||
@property
|
||||
def preset_modes(self) -> list[str]:
|
||||
"""Return preset modes."""
|
||||
return [
|
||||
_PRESETS.from_esphome(preset)
|
||||
for preset in static_info.supported_presets_compat(self._api_version)
|
||||
] + static_info.supported_custom_presets
|
||||
self._attr_swing_modes = [
|
||||
for preset in self._static_info.supported_presets_compat(self._api_version)
|
||||
] + self._static_info.supported_custom_presets
|
||||
|
||||
@property
|
||||
def swing_modes(self) -> list[str]:
|
||||
"""Return the list of available swing modes."""
|
||||
return [
|
||||
_SWING_MODES.from_esphome(mode)
|
||||
for mode in static_info.supported_swing_modes
|
||||
for mode in self._static_info.supported_swing_modes
|
||||
]
|
||||
|
||||
@property
|
||||
def target_temperature_step(self) -> float:
|
||||
"""Return the supported step of target temperature."""
|
||||
# Round to one digit because of floating point math
|
||||
self._attr_target_temperature_step = round(
|
||||
static_info.visual_target_temperature_step, 1
|
||||
)
|
||||
self._attr_min_temp = static_info.visual_min_temperature
|
||||
self._attr_max_temp = static_info.visual_max_temperature
|
||||
return round(self._static_info.visual_target_temperature_step, 1)
|
||||
|
||||
@property
|
||||
def min_temp(self) -> float:
|
||||
"""Return the minimum temperature."""
|
||||
return self._static_info.visual_min_temperature
|
||||
|
||||
@property
|
||||
def max_temp(self) -> float:
|
||||
"""Return the maximum temperature."""
|
||||
return self._static_info.visual_max_temperature
|
||||
|
||||
@property
|
||||
def supported_features(self) -> ClimateEntityFeature:
|
||||
"""Return the list of supported features."""
|
||||
features = ClimateEntityFeature(0)
|
||||
if self._static_info.supports_two_point_target_temperature:
|
||||
features |= ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
|
||||
@@ -178,21 +216,7 @@ class EsphomeClimateEntity(EsphomeEntity[ClimateInfo, ClimateState], ClimateEnti
|
||||
features |= ClimateEntityFeature.FAN_MODE
|
||||
if self.swing_modes:
|
||||
features |= ClimateEntityFeature.SWING_MODE
|
||||
self._attr_supported_features = features
|
||||
|
||||
def _get_precision(self) -> float:
|
||||
"""Return the precision of the climate device."""
|
||||
precicions = [PRECISION_WHOLE, PRECISION_HALVES, PRECISION_TENTHS]
|
||||
static_info = self._static_info
|
||||
if static_info.visual_current_temperature_step != 0:
|
||||
step = static_info.visual_current_temperature_step
|
||||
else:
|
||||
step = static_info.visual_target_temperature_step
|
||||
for prec in precicions:
|
||||
if step >= prec:
|
||||
return prec
|
||||
# Fall back to highest precision, tenths
|
||||
return PRECISION_TENTHS
|
||||
return features
|
||||
|
||||
@property
|
||||
@esphome_state_property
|
||||
@@ -213,16 +237,16 @@ class EsphomeClimateEntity(EsphomeEntity[ClimateInfo, ClimateState], ClimateEnti
|
||||
@esphome_state_property
|
||||
def fan_mode(self) -> str | None:
|
||||
"""Return current fan setting."""
|
||||
state = self._state
|
||||
return state.custom_fan_mode or _FAN_MODES.from_esphome(state.fan_mode)
|
||||
return self._state.custom_fan_mode or _FAN_MODES.from_esphome(
|
||||
self._state.fan_mode
|
||||
)
|
||||
|
||||
@property
|
||||
@esphome_state_property
|
||||
def preset_mode(self) -> str | None:
|
||||
"""Return current preset mode."""
|
||||
state = self._state
|
||||
return state.custom_preset or _PRESETS.from_esphome(
|
||||
state.preset_compat(self._api_version)
|
||||
return self._state.custom_preset or _PRESETS.from_esphome(
|
||||
self._state.preset_compat(self._api_version)
|
||||
)
|
||||
|
||||
@property
|
||||
@@ -257,7 +281,7 @@ class EsphomeClimateEntity(EsphomeEntity[ClimateInfo, ClimateState], ClimateEnti
|
||||
|
||||
async def async_set_temperature(self, **kwargs: Any) -> None:
|
||||
"""Set new target temperature (and operation mode if set)."""
|
||||
data: dict[str, Any] = {"key": self._key}
|
||||
data: dict[str, Any] = {"key": self._static_info.key}
|
||||
if ATTR_HVAC_MODE in kwargs:
|
||||
data["mode"] = _CLIMATE_MODES.from_hass(
|
||||
cast(HVACMode, kwargs[ATTR_HVAC_MODE])
|
||||
@@ -273,12 +297,12 @@ class EsphomeClimateEntity(EsphomeEntity[ClimateInfo, ClimateState], ClimateEnti
|
||||
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
|
||||
"""Set new target operation mode."""
|
||||
await self._client.climate_command(
|
||||
key=self._key, mode=_CLIMATE_MODES.from_hass(hvac_mode)
|
||||
key=self._static_info.key, mode=_CLIMATE_MODES.from_hass(hvac_mode)
|
||||
)
|
||||
|
||||
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
||||
"""Set preset mode."""
|
||||
kwargs: dict[str, Any] = {"key": self._key}
|
||||
kwargs: dict[str, Any] = {"key": self._static_info.key}
|
||||
if preset_mode in self._static_info.supported_custom_presets:
|
||||
kwargs["custom_preset"] = preset_mode
|
||||
else:
|
||||
@@ -287,7 +311,7 @@ class EsphomeClimateEntity(EsphomeEntity[ClimateInfo, ClimateState], ClimateEnti
|
||||
|
||||
async def async_set_fan_mode(self, fan_mode: str) -> None:
|
||||
"""Set new fan mode."""
|
||||
kwargs: dict[str, Any] = {"key": self._key}
|
||||
kwargs: dict[str, Any] = {"key": self._static_info.key}
|
||||
if fan_mode in self._static_info.supported_custom_fan_modes:
|
||||
kwargs["custom_fan_mode"] = fan_mode
|
||||
else:
|
||||
@@ -297,5 +321,5 @@ class EsphomeClimateEntity(EsphomeEntity[ClimateInfo, ClimateState], ClimateEnti
|
||||
async def async_set_swing_mode(self, swing_mode: str) -> None:
|
||||
"""Set new swing mode."""
|
||||
await self._client.climate_command(
|
||||
key=self._key, swing_mode=_SWING_MODES.from_hass(swing_mode)
|
||||
key=self._static_info.key, swing_mode=_SWING_MODES.from_hass(swing_mode)
|
||||
)
|
||||
|
||||
@@ -3,7 +3,6 @@ from __future__ import annotations
|
||||
|
||||
from collections import OrderedDict
|
||||
from collections.abc import Mapping
|
||||
import json
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
@@ -41,8 +40,6 @@ ERROR_INVALID_ENCRYPTION_KEY = "invalid_psk"
|
||||
ESPHOME_URL = "https://esphome.io/"
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
ZERO_NOISE_PSK = "MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA="
|
||||
|
||||
|
||||
class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a esphome config flow."""
|
||||
@@ -152,22 +149,11 @@ class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
async def _async_try_fetch_device_info(self) -> FlowResult:
|
||||
error = await self.fetch_device_info()
|
||||
|
||||
if error == ERROR_REQUIRES_ENCRYPTION_KEY:
|
||||
if not self._device_name and not self._noise_psk:
|
||||
# If device name is not set we can send a zero noise psk
|
||||
# to get the device name which will allow us to populate
|
||||
# the device name and hopefully get the encryption key
|
||||
# from the dashboard.
|
||||
self._noise_psk = ZERO_NOISE_PSK
|
||||
error = await self.fetch_device_info()
|
||||
self._noise_psk = None
|
||||
|
||||
if (
|
||||
self._device_name
|
||||
and await self._retrieve_encryption_key_from_dashboard()
|
||||
):
|
||||
error = await self.fetch_device_info()
|
||||
|
||||
if (
|
||||
error == ERROR_REQUIRES_ENCRYPTION_KEY
|
||||
and await self._retrieve_encryption_key_from_dashboard()
|
||||
):
|
||||
error = await self.fetch_device_info()
|
||||
# If the fetched key is invalid, unset it again.
|
||||
if error == ERROR_INVALID_ENCRYPTION_KEY:
|
||||
self._noise_psk = None
|
||||
@@ -337,10 +323,7 @@ class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
self._device_info = await cli.device_info()
|
||||
except RequiresEncryptionAPIError:
|
||||
return ERROR_REQUIRES_ENCRYPTION_KEY
|
||||
except InvalidEncryptionKeyAPIError as ex:
|
||||
if ex.received_name:
|
||||
self._device_name = ex.received_name
|
||||
self._name = ex.received_name
|
||||
except InvalidEncryptionKeyAPIError:
|
||||
return ERROR_INVALID_ENCRYPTION_KEY
|
||||
except ResolveAPIError:
|
||||
return "resolve_error"
|
||||
@@ -351,8 +334,9 @@ class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
|
||||
self._name = self._device_info.friendly_name or self._device_info.name
|
||||
self._device_name = self._device_info.name
|
||||
mac_address = format_mac(self._device_info.mac_address)
|
||||
await self.async_set_unique_id(mac_address, raise_on_progress=False)
|
||||
await self.async_set_unique_id(
|
||||
self._device_info.mac_address, raise_on_progress=False
|
||||
)
|
||||
if not self._reauth_entry:
|
||||
self._abort_if_unique_id_configured(
|
||||
updates={CONF_HOST: self._host, CONF_PORT: self._port}
|
||||
@@ -389,13 +373,14 @@ class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
|
||||
Return boolean if a key was retrieved.
|
||||
"""
|
||||
if (
|
||||
self._device_name is None
|
||||
or (dashboard := async_get_dashboard(self.hass)) is None
|
||||
):
|
||||
if self._device_name is None:
|
||||
return False
|
||||
|
||||
if (dashboard := async_get_dashboard(self.hass)) is None:
|
||||
return False
|
||||
|
||||
await dashboard.async_request_refresh()
|
||||
|
||||
if not dashboard.last_update_success:
|
||||
return False
|
||||
|
||||
@@ -409,11 +394,6 @@ class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
except aiohttp.ClientError as err:
|
||||
_LOGGER.error("Error talking to the dashboard: %s", err)
|
||||
return False
|
||||
except json.JSONDecodeError as err:
|
||||
_LOGGER.error(
|
||||
"Error parsing response from dashboard: %s", err, exc_info=True
|
||||
)
|
||||
return False
|
||||
|
||||
self._noise_psk = noise_psk
|
||||
return True
|
||||
|
||||
@@ -362,6 +362,12 @@ class EsphomeLight(EsphomeEntity[LightInfo, LightState], LightEntity):
|
||||
round(state.warm_white * 255),
|
||||
)
|
||||
|
||||
@property
|
||||
@esphome_state_property
|
||||
def color_temp(self) -> int:
|
||||
"""Return the CT color value in mireds."""
|
||||
return round(self._state.color_temperature)
|
||||
|
||||
@property
|
||||
@esphome_state_property
|
||||
def color_temp_kelvin(self) -> int:
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["aioesphomeapi", "noiseprotocol"],
|
||||
"requirements": [
|
||||
"aioesphomeapi==15.1.1",
|
||||
"bluetooth-data-tools==1.3.0",
|
||||
"aioesphomeapi==15.0.0",
|
||||
"bluetooth-data-tools==1.2.0",
|
||||
"esphome-dashboard-api==1.2.3"
|
||||
],
|
||||
"zeroconf": ["_esphomelib._tcp.local."]
|
||||
|
||||
@@ -312,7 +312,7 @@ class EzvizCamera(EzvizEntity, Camera):
|
||||
self.hass,
|
||||
DOMAIN,
|
||||
"service_depreciation_detection_sensibility",
|
||||
breaks_in_ha_version="2023.12.0",
|
||||
breaks_in_ha_version="2023.8.0",
|
||||
is_fixable=False,
|
||||
severity=ir.IssueSeverity.WARNING,
|
||||
translation_key="service_depreciation_detection_sensibility",
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
"issues": {
|
||||
"service_depreciation_detection_sensibility": {
|
||||
"title": "Ezviz Detection sensitivity service is being removed",
|
||||
"description": "Ezviz Detection sensitivity service is deprecated and will be removed in Home Assistant 2023.12; Please adjust the automation or script that uses the service and select submit below to mark this issue as resolved."
|
||||
"description": "Ezviz Detection sensitivity service is deprecated and will be removed in Home Assistant 2023.8; Please adjust the automation or script that uses the service and select submit below to mark this issue as resolved."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user