Compare commits

..

2 Commits

Author SHA1 Message Date
jbouwh
5f3bcee97e Refactor url fetch code to use base platform 2023-06-27 08:42:38 +02:00
jbouwh
51edc007fe Add url support for mqtt image platform 2023-06-27 08:42:38 +02:00
716 changed files with 5840 additions and 15643 deletions

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -10,7 +10,7 @@ on:
- "**strings.json"
env:
DEFAULT_PYTHON: "3.11"
DEFAULT_PYTHON: "3.10"
jobs:
upload:

View File

@@ -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

View File

@@ -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.*

View File

@@ -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

View File

@@ -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

View File

@@ -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."""

View File

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

View File

@@ -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."""

View File

@@ -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."""

View File

@@ -53,6 +53,7 @@ class AbodeSensor(AbodeDevice, SensorEntity):
"""A sensor implementation for Abode devices."""
_device: AbodeSense
_attr_has_entity_name = True
def __init__(
self,

View File

@@ -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."""

View File

@@ -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,

View File

@@ -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),
}

View File

@@ -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)

View File

@@ -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."""

View File

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

View File

@@ -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 = {}

View File

@@ -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"]
}

View File

@@ -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
)

View File

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

View File

@@ -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

View File

@@ -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(

View File

@@ -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": {

View File

@@ -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,
),
}

View File

@@ -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,

View File

@@ -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

View File

@@ -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,

View File

@@ -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"
}
}
}
}

View File

@@ -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"])

View File

@@ -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:

View File

@@ -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},

View File

@@ -302,7 +302,7 @@
}
},
"device_class": {
"co": "carbon monoxide",
"co": "carbon_monoxide",
"cold": "cold",
"gas": "gas",
"heat": "heat",

View File

@@ -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")

View File

@@ -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"
]
}

View File

@@ -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)
]
)

View File

@@ -21,9 +21,3 @@ UNIT_MAP = {
"LITERS": UnitOfVolume.LITERS,
"GALLONS": UnitOfVolume.GALLONS,
}
SCAN_INTERVALS = {
"china": 300,
"north_america": 600,
"rest_of_world": 300,
}

View File

@@ -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:

View File

@@ -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"]
}

View File

@@ -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."""

View File

@@ -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."""

View File

@@ -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:

View File

@@ -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."""

View File

@@ -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": {

View File

@@ -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

View File

@@ -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

View File

@@ -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,
}

View File

@@ -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,
}

View File

@@ -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

View File

@@ -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:

View File

@@ -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 = []

View File

@@ -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 }}"

View File

@@ -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()

View File

@@ -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"]
}

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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."""

View File

@@ -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"]
}

View File

@@ -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:

View File

@@ -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)

View File

@@ -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,

View File

@@ -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):

View File

@@ -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"]
}

View File

@@ -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,

View File

@@ -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

View File

@@ -108,7 +108,6 @@ async def async_setup_entry(
):
device_info = DeviceInfo(
identifiers=device.identifiers,
connections=device.connections,
)
else:
device_info = None

View File

@@ -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,
}

View File

@@ -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

View File

@@ -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:

View File

@@ -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."""

View File

@@ -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:

View File

@@ -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

View File

@@ -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] = []

View File

@@ -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:

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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",

View File

@@ -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."
}
},

View File

@@ -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)

View File

@@ -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"]
}

View File

@@ -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,

View File

@@ -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"
}
}
}
}

View File

@@ -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 = []

View File

@@ -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(),
),

View File

@@ -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

View File

@@ -23,6 +23,11 @@
}
},
"entity": {
"button": {
"identify": {
"name": "Identify"
}
},
"sensor": {
"charge_power": {
"name": "Charging power"

View File

@@ -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"]
}

View File

@@ -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)

View File

@@ -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)
)

View File

@@ -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

View File

@@ -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:

View File

@@ -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."]

View File

@@ -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",

View File

@@ -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