Compare commits

...

16 Commits

Author SHA1 Message Date
Robert Resch
4a68a1a78c Merge remote-tracking branch 'origin/dev' into edenhaus/docker-syntax 2026-04-16 12:44:11 +00:00
Robert Resch
1f6e078d1d Extract dynamically package version at build time in hassfest image (#168347) 2026-04-16 14:40:13 +02:00
Marc Mueller
71d857b5e1 Update pydantic to 2.13.1 (#168311) 2026-04-16 14:34:30 +02:00
Barry vd. Heuvel
0de75a013b Add weheat standby electricity usage (#168363) 2026-04-16 14:33:36 +02:00
Robert Resch
f87ec0a7b8 Just copy explicit files in the Dockerfile (#168197) 2026-04-16 14:30:54 +02:00
Ariel Ebersberger
6d1bd15256 Fix synology_dsm test for Python 3.14.3 (#168359) 2026-04-16 13:23:09 +02:00
Jürgen
9fe9064884 Fix sonos availability (#161024)
Co-authored-by: Pete Sage <76050312+PeteRager@users.noreply.github.com>
Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>
2026-04-16 12:14:19 +01:00
Jamin
f9f57b00bb Fix VOIP blocking call in event loop (#168331) 2026-04-16 12:14:58 +02:00
johanzander
2b65b06003 Fix unit of measurement for SPH power sensors in growatt_server (#168251)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 12:14:13 +02:00
Leo Periou
206c498027 Bump pyaxencoapi to 1.0.7 (#168286) 2026-04-16 12:10:24 +02:00
renovate[bot]
0ac62b241e Update home-assistant-bluetooth to 2.0.0 (#168353)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-16 12:06:34 +02:00
renovate[bot]
4ba123a1a8 Update PyTurboJPEG to 2.2.0 (#168354)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-16 12:02:56 +02:00
Robert Resch
8818d56ac5 Use sha pinning 2026-04-16 09:35:50 +00:00
Maciej Bieniek
8b8b39c1b7 Bump imgw-pib to 2.1.0 (#168319) 2026-04-16 11:27:44 +02:00
renovate[bot]
5b70e5f829 Update lru-dict to 1.4.1 (#168336)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-16 11:25:00 +02:00
Robert Resch
20517a6a80 Add docker syntax to all Docker files 2026-04-16 09:20:46 +00:00
42 changed files with 222 additions and 123 deletions

6
Dockerfile generated
View File

@@ -1,3 +1,4 @@
# syntax=docker/dockerfile@sha256:2780b5c3bab67f1f76c781860de469442999ed1a0d7992a5efdf2cffc0e3d769
# Automatically generated by hassfest.
#
# To update, run python3 -m script.hassfest -p docker
@@ -28,8 +29,7 @@ COPY rootfs /
COPY --from=ghcr.io/alexxit/go2rtc@sha256:675c318b23c06fd862a61d262240c9a63436b4050d177ffc68a32710d9e05bae /usr/local/bin/go2rtc /bin/go2rtc
## Setup Home Assistant Core dependencies
COPY requirements.txt homeassistant/
COPY homeassistant/package_constraints.txt homeassistant/homeassistant/
COPY --parents requirements.txt homeassistant/package_constraints.txt homeassistant/
RUN \
# Verify go2rtc can be executed
go2rtc --version \
@@ -49,7 +49,7 @@ RUN \
-r homeassistant/requirements_all.txt
## Setup Home Assistant Core
COPY . homeassistant/
COPY --parents LICENSE* README* homeassistant/ pyproject.toml homeassistant/
RUN \
uv pip install \
-e ./homeassistant \

View File

@@ -1,3 +1,4 @@
# syntax=docker/dockerfile@sha256:2780b5c3bab67f1f76c781860de469442999ed1a0d7992a5efdf2cffc0e3d769
FROM mcr.microsoft.com/vscode/devcontainers/base:debian
SHELL ["/bin/bash", "-o", "pipefail", "-c"]

View File

@@ -7,5 +7,5 @@
"documentation": "https://www.home-assistant.io/integrations/camera",
"integration_type": "entity",
"quality_scale": "internal",
"requirements": ["PyTurboJPEG==1.8.3"]
"requirements": ["PyTurboJPEG==2.2.0"]
}

View File

@@ -72,7 +72,7 @@ SPH_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
key="mix_export_to_grid",
translation_key="mix_export_to_grid",
api_key="pacToGridTotal",
native_unit_of_measurement=UnitOfPower.KILO_WATT,
native_unit_of_measurement=UnitOfPower.WATT,
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
),
@@ -80,7 +80,7 @@ SPH_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
key="mix_import_from_grid",
translation_key="mix_import_from_grid",
api_key="pacToUserR",
native_unit_of_measurement=UnitOfPower.KILO_WATT,
native_unit_of_measurement=UnitOfPower.WATT,
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
),

View File

@@ -7,5 +7,5 @@
"integration_type": "service",
"iot_class": "cloud_polling",
"quality_scale": "platinum",
"requirements": ["imgw_pib==2.0.2"]
"requirements": ["imgw_pib==2.1.0"]
}

View File

@@ -7,5 +7,5 @@
"integration_type": "hub",
"iot_class": "cloud_push",
"quality_scale": "bronze",
"requirements": ["pyaxencoapi==1.0.6"]
"requirements": ["pyaxencoapi==1.0.7"]
}

View File

@@ -6,7 +6,7 @@ from collections.abc import Iterator
import logging
from typing import TYPE_CHECKING, Any
from soco import SoCo
from soco import SoCo, SoCoException
from soco.alarms import Alarm, Alarms
from soco.events_base import Event as SonosEvent
@@ -30,6 +30,7 @@ class SonosAlarms(SonosHouseholdCoordinator):
super().__init__(*args)
self.alarms: Alarms = Alarms()
self.created_alarm_ids: set[str] = set()
self._household_mismatch_logged = False
def __iter__(self) -> Iterator:
"""Return an iterator for the known alarms."""
@@ -76,21 +77,40 @@ class SonosAlarms(SonosHouseholdCoordinator):
await self.async_update_entities(speaker.soco, event_id)
@soco_error()
def update_cache(self, soco: SoCo, update_id: int | None = None) -> bool:
"""Update cache of known alarms and return if cache has changed."""
self.alarms.update(soco)
def update_cache(
self,
soco: SoCo,
update_id: int | None = None,
) -> bool:
"""Update cache of known alarms and return whether any were seen."""
try:
self.alarms.update(soco)
except SoCoException as err:
err_msg = str(err)
# Only catch the specific household mismatch error
if "Alarm list UID" in err_msg and "does not match" in err_msg:
if not self._household_mismatch_logged:
_LOGGER.warning(
"Sonos alarms for %s cannot be updated due to a household mismatch. "
"This is a known limitation in setups with multiple households. "
"You can safely ignore this warning, or to silence it, remove the "
"affected household from your Sonos system. Error: %s",
soco.player_name,
err_msg,
)
self._household_mismatch_logged = True
return False
# Let all other exceptions bubble up to be handled by @soco_error()
raise
if update_id and self.alarms.last_id < update_id:
# Skip updates if latest query result is outdated or lagging
return False
if (
self.last_processed_event_id
and self.alarms.last_id <= self.last_processed_event_id
):
# Skip updates already processed
return False
_LOGGER.debug(
"Updating processed event %s from %s (was %s)",
self.alarms.last_id,

View File

@@ -7,5 +7,5 @@
"integration_type": "system",
"iot_class": "local_push",
"quality_scale": "internal",
"requirements": ["PyTurboJPEG==1.8.3", "av==16.0.1", "numpy==2.3.2"]
"requirements": ["PyTurboJPEG==2.2.0", "av==16.0.1", "numpy==2.3.2"]
}

View File

@@ -150,11 +150,6 @@ class PreRecordMessageProtocol(RtpDatagramProtocol):
if self.transport is None:
return
if self._audio_bytes is None:
# 16Khz, 16-bit mono audio message
file_path = Path(__file__).parent / self.file_name
self._audio_bytes = file_path.read_bytes()
if self._audio_task is None:
self._audio_task = self.hass.async_create_background_task(
self._play_message(),
@@ -162,6 +157,11 @@ class PreRecordMessageProtocol(RtpDatagramProtocol):
)
async def _play_message(self) -> None:
if self._audio_bytes is None:
_LOGGER.debug("Loading audio from file %s", self.file_name)
self._audio_bytes = await self._load_audio()
_LOGGER.debug("Read %s bytes", len(self._audio_bytes))
await self.hass.async_add_executor_job(
partial(
self.send_audio,
@@ -175,3 +175,8 @@ class PreRecordMessageProtocol(RtpDatagramProtocol):
# Allow message to play again
self._audio_task = None
async def _load_audio(self) -> bytes:
# 16Khz, 16-bit mono audio message
file_path = Path(__file__).parent / self.file_name
return await self.hass.async_add_executor_job(file_path.read_bytes)

View File

@@ -245,6 +245,14 @@ ENERGY_SENSORS = [
state_class=SensorStateClass.TOTAL_INCREASING,
value_fn=lambda status: status.energy_in_defrost,
),
WeHeatSensorEntityDescription(
translation_key="electricity_used_standby",
key="electricity_used_standby",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
value_fn=lambda status: status.energy_in_standby,
),
WeHeatSensorEntityDescription(
translation_key="energy_output_heating",
key="energy_output_heating",

View File

@@ -96,6 +96,9 @@
"electricity_used_heating": {
"name": "Electricity used heating"
},
"electricity_used_standby": {
"name": "Electricity used standby"
},
"energy_output": {
"name": "Total energy output"
},

View File

@@ -38,13 +38,13 @@ ha-ffmpeg==3.2.2
habluetooth==6.0.0
hass-nabucasa==2.2.0
hassil==3.5.0
home-assistant-bluetooth==1.13.1
home-assistant-bluetooth==2.0.0
home-assistant-frontend==20260325.7
home-assistant-intents==2026.3.24
httpx==0.28.1
ifaddr==0.2.0
Jinja2==3.1.6
lru-dict==1.3.0
lru-dict==1.4.1
mutagen==1.47.0
openai==2.21.0
orjson==3.11.8
@@ -59,7 +59,7 @@ PyNaCl==1.6.2
pyOpenSSL==26.0.0
pyspeex-noise==1.0.2
python-slugify==8.0.4
PyTurboJPEG==1.8.3
PyTurboJPEG==2.2.0
PyYAML==6.0.3
requests==2.33.1
securetar==2026.4.1
@@ -134,7 +134,7 @@ backoff>=2.0
Brotli>=1.2.0
# ensure pydantic version does not float since it might have breaking changes
pydantic==2.13.0
pydantic==2.13.1
# Required for Python 3.14.0 compatibility (#119223).
mashumaro>=3.17.0

View File

@@ -1,3 +1,4 @@
# syntax=docker/dockerfile@sha256:2780b5c3bab67f1f76c781860de469442999ed1a0d7992a5efdf2cffc0e3d769
# Automatically generated by hassfest.
#
# To update, run python3 -m script.hassfest -p docker

1
machine/green generated
View File

@@ -1,3 +1,4 @@
# syntax=docker/dockerfile@sha256:2780b5c3bab67f1f76c781860de469442999ed1a0d7992a5efdf2cffc0e3d769
# Automatically generated by hassfest.
#
# To update, run python3 -m script.hassfest -p docker

1
machine/khadas-vim3 generated
View File

@@ -1,3 +1,4 @@
# syntax=docker/dockerfile@sha256:2780b5c3bab67f1f76c781860de469442999ed1a0d7992a5efdf2cffc0e3d769
# Automatically generated by hassfest.
#
# To update, run python3 -m script.hassfest -p docker

1
machine/odroid-c2 generated
View File

@@ -1,3 +1,4 @@
# syntax=docker/dockerfile@sha256:2780b5c3bab67f1f76c781860de469442999ed1a0d7992a5efdf2cffc0e3d769
# Automatically generated by hassfest.
#
# To update, run python3 -m script.hassfest -p docker

1
machine/odroid-c4 generated
View File

@@ -1,3 +1,4 @@
# syntax=docker/dockerfile@sha256:2780b5c3bab67f1f76c781860de469442999ed1a0d7992a5efdf2cffc0e3d769
# Automatically generated by hassfest.
#
# To update, run python3 -m script.hassfest -p docker

1
machine/odroid-m1 generated
View File

@@ -1,3 +1,4 @@
# syntax=docker/dockerfile@sha256:2780b5c3bab67f1f76c781860de469442999ed1a0d7992a5efdf2cffc0e3d769
# Automatically generated by hassfest.
#
# To update, run python3 -m script.hassfest -p docker

1
machine/odroid-n2 generated
View File

@@ -1,3 +1,4 @@
# syntax=docker/dockerfile@sha256:2780b5c3bab67f1f76c781860de469442999ed1a0d7992a5efdf2cffc0e3d769
# Automatically generated by hassfest.
#
# To update, run python3 -m script.hassfest -p docker

1
machine/qemuarm-64 generated
View File

@@ -1,3 +1,4 @@
# syntax=docker/dockerfile@sha256:2780b5c3bab67f1f76c781860de469442999ed1a0d7992a5efdf2cffc0e3d769
# Automatically generated by hassfest.
#
# To update, run python3 -m script.hassfest -p docker

1
machine/qemux86-64 generated
View File

@@ -1,3 +1,4 @@
# syntax=docker/dockerfile@sha256:2780b5c3bab67f1f76c781860de469442999ed1a0d7992a5efdf2cffc0e3d769
# Automatically generated by hassfest.
#
# To update, run python3 -m script.hassfest -p docker

View File

@@ -1,3 +1,4 @@
# syntax=docker/dockerfile@sha256:2780b5c3bab67f1f76c781860de469442999ed1a0d7992a5efdf2cffc0e3d769
# Automatically generated by hassfest.
#
# To update, run python3 -m script.hassfest -p docker

View File

@@ -1,3 +1,4 @@
# syntax=docker/dockerfile@sha256:2780b5c3bab67f1f76c781860de469442999ed1a0d7992a5efdf2cffc0e3d769
# Automatically generated by hassfest.
#
# To update, run python3 -m script.hassfest -p docker

View File

@@ -1,3 +1,4 @@
# syntax=docker/dockerfile@sha256:2780b5c3bab67f1f76c781860de469442999ed1a0d7992a5efdf2cffc0e3d769
# Automatically generated by hassfest.
#
# To update, run python3 -m script.hassfest -p docker

1
machine/yellow generated
View File

@@ -1,3 +1,4 @@
# syntax=docker/dockerfile@sha256:2780b5c3bab67f1f76c781860de469442999ed1a0d7992a5efdf2cffc0e3d769
# Automatically generated by hassfest.
#
# To update, run python3 -m script.hassfest -p docker

View File

@@ -51,10 +51,10 @@ dependencies = [
# When bumping httpx, please check the version pins of
# httpcore, anyio, and h11 in gen_requirements_all
"httpx==0.28.1",
"home-assistant-bluetooth==1.13.1",
"home-assistant-bluetooth==2.0.0",
"ifaddr==0.2.0",
"Jinja2==3.1.6",
"lru-dict==1.3.0",
"lru-dict==1.4.1",
"PyJWT==2.12.1",
# PyJWT has loose dependency. We want the latest one.
"cryptography==46.0.7",

6
requirements.txt generated
View File

@@ -26,13 +26,13 @@ fnv-hash-fast==2.0.2
ha-ffmpeg==3.2.2
hass-nabucasa==2.2.0
hassil==3.5.0
home-assistant-bluetooth==1.13.1
home-assistant-bluetooth==2.0.0
home-assistant-intents==2026.3.24
httpx==0.28.1
ifaddr==0.2.0
infrared-protocols==1.2.0
Jinja2==3.1.6
lru-dict==1.3.0
lru-dict==1.4.1
mutagen==1.47.0
orjson==3.11.8
packaging>=23.1
@@ -44,7 +44,7 @@ pymicro-vad==1.0.1
pyOpenSSL==26.0.0
pyspeex-noise==1.0.2
python-slugify==8.0.4
PyTurboJPEG==1.8.3
PyTurboJPEG==2.2.0
PyYAML==6.0.3
requests==2.33.1
securetar==2026.4.1

6
requirements_all.txt generated
View File

@@ -96,7 +96,7 @@ PyTransportNSW==0.1.1
# homeassistant.components.camera
# homeassistant.components.stream
PyTurboJPEG==1.8.3
PyTurboJPEG==2.2.0
# homeassistant.components.vicare
PyViCare==2.59.0
@@ -1313,7 +1313,7 @@ ihcsdk==2.8.5
imeon_inverter_api==0.4.0
# homeassistant.components.imgw_pib
imgw_pib==2.0.2
imgw_pib==2.1.0
# homeassistant.components.incomfort
incomfort-client==0.7.0
@@ -1980,7 +1980,7 @@ pyatv==0.17.0
pyaussiebb==0.1.5
# homeassistant.components.myneomitis
pyaxencoapi==1.0.6
pyaxencoapi==1.0.7
# homeassistant.components.balboa
pybalboa==1.1.3

View File

@@ -16,7 +16,7 @@ license-expression==30.4.3
mock-open==1.4.0
mypy==1.20.1
prek==0.2.28
pydantic==2.13.0
pydantic==2.13.1
pylint==4.0.5
pylint-per-file-ignores==3.2.1
pipdeptree==2.26.1

View File

@@ -93,7 +93,7 @@ PyTransportNSW==0.1.1
# homeassistant.components.camera
# homeassistant.components.stream
PyTurboJPEG==1.8.3
PyTurboJPEG==2.2.0
# homeassistant.components.vicare
PyViCare==2.59.0
@@ -1165,7 +1165,7 @@ igloohome-api==0.1.1
imeon_inverter_api==0.4.0
# homeassistant.components.imgw_pib
imgw_pib==2.0.2
imgw_pib==2.1.0
# homeassistant.components.incomfort
incomfort-client==0.7.0
@@ -1717,7 +1717,7 @@ pyatv==0.17.0
pyaussiebb==0.1.5
# homeassistant.components.myneomitis
pyaxencoapi==1.0.6
pyaxencoapi==1.0.7
# homeassistant.components.balboa
pybalboa==1.1.3

View File

@@ -124,7 +124,7 @@ backoff>=2.0
Brotli>=1.2.0
# ensure pydantic version does not float since it might have breaking changes
pydantic==2.13.0
pydantic==2.13.1
# Required for Python 3.14.0 compatibility (#119223).
mashumaro>=3.17.0

View File

@@ -7,13 +7,18 @@ from homeassistant import core
from homeassistant.util import executor, thread
from .model import Config, Integration
from .requirements import PACKAGE_REGEX, PIP_VERSION_RANGE_SEPARATOR
# Don't forget to update also Dockerfile.dev when updating this.
_DOCKERFILE_SYNTAX_SHA = (
"2780b5c3bab67f1f76c781860de469442999ed1a0d7992a5efdf2cffc0e3d769" # 1.23.0
)
_GO2RTC_SHA = (
"675c318b23c06fd862a61d262240c9a63436b4050d177ffc68a32710d9e05bae" # 1.9.14
)
DOCKERFILE_TEMPLATE = r"""# Automatically generated by hassfest.
DOCKERFILE_TEMPLATE = r"""# syntax=docker/dockerfile@sha256:{dockerfile_syntax}
# Automatically generated by hassfest.
#
# To update, run python3 -m script.hassfest -p docker
ARG BUILD_FROM
@@ -43,8 +48,7 @@ COPY rootfs /
COPY --from=ghcr.io/alexxit/go2rtc@sha256:{go2rtc} /usr/local/bin/go2rtc /bin/go2rtc
## Setup Home Assistant Core dependencies
COPY requirements.txt homeassistant/
COPY homeassistant/package_constraints.txt homeassistant/homeassistant/
COPY --parents requirements.txt homeassistant/package_constraints.txt homeassistant/
RUN \
# Verify go2rtc can be executed
go2rtc --version \
@@ -64,7 +68,7 @@ RUN \
-r homeassistant/requirements_all.txt
## Setup Home Assistant Core
COPY . homeassistant/
COPY --parents LICENSE* README* homeassistant/ pyproject.toml homeassistant/
RUN \
uv pip install \
-e ./homeassistant \
@@ -99,7 +103,8 @@ _MACHINES = {
"yellow": _MachineConfig(arch="aarch64", packages=("raspberrypi-utils",)),
}
_MACHINE_DOCKERFILE_TEMPLATE = r"""# Automatically generated by hassfest.
_MACHINE_DOCKERFILE_TEMPLATE = r"""# syntax=docker/dockerfile@sha256:{dockerfile_syntax}
# Automatically generated by hassfest.
#
# To update, run python3 -m script.hassfest -p docker
ARG BUILD_FROM=ghcr.io/home-assistant/{arch}-homeassistant:latest
@@ -120,13 +125,15 @@ def _generate_machine_dockerfile(
extra_packages = ""
return _MACHINE_DOCKERFILE_TEMPLATE.format(
dockerfile_syntax=_DOCKERFILE_SYNTAX_SHA,
arch=machine_config.arch,
extra_packages=extra_packages,
machine=machine_name,
)
_HASSFEST_TEMPLATE = r"""# Automatically generated by hassfest.
_HASSFEST_TEMPLATE = r"""# syntax=docker/dockerfile@sha256:{dockerfile_syntax}
# Automatically generated by hassfest.
#
# To update, run python3 -m script.hassfest -p docker
FROM python:3.14-alpine
@@ -139,10 +146,12 @@ SHELL ["/bin/sh", "-o", "pipefail", "-c"]
ENTRYPOINT ["/usr/src/homeassistant/script/hassfest/docker/entrypoint.sh"]
WORKDIR "/github/workspace"
COPY . /usr/src/homeassistant
COPY --parents requirements.txt homeassistant/ script /usr/src/homeassistant/
# Uv creates a lock file in /tmp
RUN --mount=type=tmpfs,target=/tmp \
--mount=type=bind,source=requirements_test.txt,target=/tmp/requirements_test.txt,readonly \
--mount=type=bind,source=requirements_test_pre_commit.txt,target=/tmp/requirements_test_pre_commit.txt,readonly \
# Required for PyTurboJPEG
apk add --no-cache libturbojpeg \
# Install uv at the version pinned in the requirements file
@@ -152,9 +161,9 @@ RUN --mount=type=tmpfs,target=/tmp \
--no-cache \
-c /usr/src/homeassistant/homeassistant/package_constraints.txt \
-r /usr/src/homeassistant/requirements.txt \
pipdeptree=={pipdeptree} \
tqdm=={tqdm} \
ruff=={ruff}
"pipdeptree==$(awk -F'==' '/^pipdeptree==/{{print $2}}' /tmp/requirements_test.txt)" \
"tqdm==$(awk -F'==' '/^tqdm==/{{print $2}}' /tmp/requirements_test.txt)" \
"ruff==$(awk -F'==' '/^ruff==/{{print $2}}' /tmp/requirements_test_pre_commit.txt)"
LABEL "name"="hassfest"
LABEL "maintainer"="Home Assistant <hello@home-assistant.io>"
@@ -166,36 +175,6 @@ LABEL "com.github.actions.color"="gray-dark"
"""
def _get_package_versions(file: Path, packages: set[str]) -> dict[str, str]:
package_versions: dict[str, str] = {}
with file.open(encoding="UTF-8") as fp:
for _, line in enumerate(fp):
if package_versions.keys() == packages:
return package_versions
if match := PACKAGE_REGEX.match(line):
pkg, sep, version = match.groups()
if pkg not in packages:
continue
if sep != "==" or not version:
raise RuntimeError(
f'Requirement {pkg} need to be pinned "{pkg}==<version>".'
)
for part in version.split(";", 1)[0].split(","):
version_part = PIP_VERSION_RANGE_SEPARATOR.match(part)
if version_part:
package_versions[pkg] = version_part.group(2)
break
if package_versions.keys() == packages:
return package_versions
raise RuntimeError("At least one package was not found in the requirements file.")
@dataclass
class File:
"""File."""
@@ -215,26 +194,18 @@ def _generate_files(config: Config) -> list[File]:
+ 10
) * 1000
package_versions = _get_package_versions(
config.root / "requirements_test.txt", {"pipdeptree", "tqdm"}
)
package_versions |= _get_package_versions(
config.root / "requirements_test_pre_commit.txt", {"ruff"}
)
files = [
File(
DOCKERFILE_TEMPLATE.format(
dockerfile_syntax=_DOCKERFILE_SYNTAX_SHA,
timeout=timeout,
**package_versions,
go2rtc=_GO2RTC_SHA,
),
config.root / "Dockerfile",
),
File(
_HASSFEST_TEMPLATE.format(
timeout=timeout,
**package_versions,
dockerfile_syntax=_DOCKERFILE_SYNTAX_SHA,
),
config.root / "script/hassfest/docker/Dockerfile",
),

View File

@@ -1,3 +1,4 @@
# syntax=docker/dockerfile@sha256:2780b5c3bab67f1f76c781860de469442999ed1a0d7992a5efdf2cffc0e3d769
# Automatically generated by hassfest.
#
# To update, run python3 -m script.hassfest -p docker
@@ -11,10 +12,12 @@ SHELL ["/bin/sh", "-o", "pipefail", "-c"]
ENTRYPOINT ["/usr/src/homeassistant/script/hassfest/docker/entrypoint.sh"]
WORKDIR "/github/workspace"
COPY . /usr/src/homeassistant
COPY --parents requirements.txt homeassistant/ script /usr/src/homeassistant/
# Uv creates a lock file in /tmp
RUN --mount=type=tmpfs,target=/tmp \
--mount=type=bind,source=requirements_test.txt,target=/tmp/requirements_test.txt,readonly \
--mount=type=bind,source=requirements_test_pre_commit.txt,target=/tmp/requirements_test_pre_commit.txt,readonly \
# Required for PyTurboJPEG
apk add --no-cache libturbojpeg \
# Install uv at the version pinned in the requirements file
@@ -24,9 +27,9 @@ RUN --mount=type=tmpfs,target=/tmp \
--no-cache \
-c /usr/src/homeassistant/homeassistant/package_constraints.txt \
-r /usr/src/homeassistant/requirements.txt \
pipdeptree==2.26.1 \
tqdm==4.67.1 \
ruff==0.15.1
"pipdeptree==$(awk -F'==' '/^pipdeptree==/{print $2}' /tmp/requirements_test.txt)" \
"tqdm==$(awk -F'==' '/^tqdm==/{print $2}' /tmp/requirements_test.txt)" \
"ruff==$(awk -F'==' '/^ruff==/{print $2}' /tmp/requirements_test_pre_commit.txt)"
LABEL "name"="hassfest"
LABEL "maintainer"="Home Assistant <hello@home-assistant.io>"

View File

@@ -1,11 +1,5 @@
# Ignore everything except the specified files
*
!homeassistant/
!requirements.txt
!script/
script/hassfest/docker/
!script/hassfest/docker/entrypoint.sh
# No need to include the Dockerfile
script/hassfest/docker/Dockerfile*
# Temporary files
**/__pycache__

View File

@@ -14143,7 +14143,7 @@
'object_id_base': 'Export to grid',
'options': dict({
'sensor': dict({
'suggested_display_precision': 2,
'suggested_display_precision': 0,
}),
}),
'original_device_class': <SensorDeviceClass.POWER: 'power'>,
@@ -14155,7 +14155,7 @@
'supported_features': 0,
'translation_key': 'mix_export_to_grid',
'unique_id': 'SPH123456-mix_export_to_grid',
'unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
})
# ---
# name: test_sph_sensors_v1_api[sensor.sph123456_export_to_grid-state]
@@ -14164,7 +14164,7 @@
'device_class': 'power',
'friendly_name': 'SPH123456 Export to grid',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
}),
'context': <ANY>,
'entity_id': 'sensor.sph123456_export_to_grid',
@@ -14314,7 +14314,7 @@
'object_id_base': 'Import from grid',
'options': dict({
'sensor': dict({
'suggested_display_precision': 2,
'suggested_display_precision': 0,
}),
}),
'original_device_class': <SensorDeviceClass.POWER: 'power'>,
@@ -14326,7 +14326,7 @@
'supported_features': 0,
'translation_key': 'mix_import_from_grid',
'unique_id': 'SPH123456-mix_import_from_grid',
'unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
})
# ---
# name: test_sph_sensors_v1_api[sensor.sph123456_import_from_grid-state]
@@ -14335,7 +14335,7 @@
'device_class': 'power',
'friendly_name': 'SPH123456 Import from grid',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
}),
'context': <ANY>,
'entity_id': 'sensor.sph123456_import_from_grid',

View File

@@ -21,6 +21,8 @@ HYDROLOGICAL_DATA = HydrologicalData(
water_temperature=SensorData(name="Water Temperature", value=10.8),
flood_alarm=None,
flood_warning=None,
ice_phenomenon=SensorData(name="Ice Phenomenon", value=20),
ice_phenomenon_measurement_date=datetime(2024, 4, 27, 10, 0, tzinfo=UTC),
water_level_measurement_date=datetime(2024, 4, 27, 10, 0, tzinfo=UTC),
water_temperature_measurement_date=datetime(2024, 4, 27, 10, 10, tzinfo=UTC),
water_flow=SensorData(name="Water Flow", value=123.45),

View File

@@ -41,6 +41,12 @@
'valid_to': '2024-04-28T11:00:00+00:00',
'value': 'rapid_water_level_rise',
}),
'ice_phenomenon': dict({
'name': 'Ice Phenomenon',
'unit': None,
'value': 20,
}),
'ice_phenomenon_measurement_date': '2024-04-27T10:00:00+00:00',
'latitude': None,
'longitude': None,
'river': 'River Name',

View File

@@ -5,6 +5,7 @@ from datetime import timedelta
from unittest.mock import patch
import pytest
from soco.exceptions import SoCoException
from homeassistant.components.sonos import DOMAIN
from homeassistant.components.sonos.const import (
@@ -341,6 +342,28 @@ async def test_alarm_change_device(
assert device.name == soco_br.get_speaker_info()["zone_name"]
async def test_alarm_update_exception_logs_warning(
hass: HomeAssistant,
async_setup_sonos,
entity_registry: er.EntityRegistry,
soco: MockSoCo,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test household mismatch logs warning and alarm update/setup is skipped."""
with patch(
"homeassistant.components.sonos.alarms.Alarms.update",
side_effect=SoCoException(
"Alarm list UID RINCON_0001234567890:31 does not match RINCON_000E987654321:0"
),
):
await async_setup_sonos()
await hass.async_block_till_done()
# Alarm should not be set up due to household mismatch
assert "switch.sonos_alarm_14" not in entity_registry.entities
assert "cannot be updated due to a household mismatch" in caplog.text
async def test_alarm_setup_for_undiscovered_speaker(
hass: HomeAssistant,
async_setup_sonos,

View File

@@ -11,6 +11,7 @@ from homeassistant.components.synology_dsm.const import (
DOMAIN,
SERVICES,
)
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
from homeassistant.const import (
CONF_HOST,
CONF_MAC,
@@ -22,7 +23,6 @@ from homeassistant.const import (
CONF_VERIFY_SSL,
)
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from .consts import HOST, MACS, PASSWORD, PORT, USE_SSL, USERNAME
@@ -57,19 +57,9 @@ async def test_services_registered(hass: HomeAssistant, mock_dsm: MagicMock) ->
async def test_reauth_triggered(hass: HomeAssistant) -> None:
"""Test if reauthentication flow is triggered."""
with (
patch(
"homeassistant.components.synology_dsm.SynoApi.async_setup",
side_effect=SynologyDSMLoginInvalidException(USERNAME),
),
patch(
"homeassistant.components.synology_dsm.config_flow.SynologyDSMFlowHandler.async_step_reauth",
return_value={
"type": FlowResultType.FORM,
"flow_id": "mock_flow",
"step_id": "reauth_confirm",
},
) as mock_async_step_reauth,
with patch(
"homeassistant.components.synology_dsm.SynoApi.async_setup",
side_effect=SynologyDSMLoginInvalidException(USERNAME),
):
entry = MockConfigEntry(
domain=DOMAIN,
@@ -85,7 +75,8 @@ async def test_reauth_triggered(hass: HomeAssistant) -> None:
entry.add_to_hass(hass)
assert not await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
mock_async_step_reauth.assert_called_once()
assert entry.state is ConfigEntryState.SETUP_ERROR
assert any(entry.async_get_active_flows(hass, {SOURCE_REAUTH}))
async def test_config_entry_migrations(

View File

@@ -124,6 +124,7 @@ def mock_weheat_heat_pump_instance() -> MagicMock:
mock_heat_pump_instance.energy_in_dhw = 6789
mock_heat_pump_instance.energy_in_defrost = 555
mock_heat_pump_instance.energy_in_cooling = 9000
mock_heat_pump_instance.energy_in_standby = 684
mock_heat_pump_instance.energy_total = 28689
mock_heat_pump_instance.energy_out_heating = 10000
mock_heat_pump_instance.energy_out_dhw = 6677

View File

@@ -877,6 +877,64 @@
'state': '12345',
})
# ---
# name: test_all_entities[sensor.test_model_electricity_used_standby-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.test_model_electricity_used_standby',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Electricity used standby',
'options': dict({
'sensor': dict({
'suggested_display_precision': 2,
}),
}),
'original_device_class': <SensorDeviceClass.ENERGY: 'energy'>,
'original_icon': None,
'original_name': 'Electricity used standby',
'platform': 'weheat',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'electricity_used_standby',
'unique_id': '0000-1111-2222-3333_electricity_used_standby',
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
})
# ---
# name: test_all_entities[sensor.test_model_electricity_used_standby-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'energy',
'friendly_name': 'Test Model Electricity used standby',
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
}),
'context': <ANY>,
'entity_id': 'sensor.test_model_electricity_used_standby',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '684',
})
# ---
# name: test_all_entities[sensor.test_model_energy_output_cooling-entry]
EntityRegistryEntrySnapshot({
'aliases': list([

View File

@@ -33,7 +33,7 @@ async def test_all_entities(
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
@pytest.mark.parametrize(("has_dhw", "nr_of_entities"), [(False, 22), (True, 27)])
@pytest.mark.parametrize(("has_dhw", "nr_of_entities"), [(False, 23), (True, 28)])
async def test_create_entities(
hass: HomeAssistant,
mock_weheat_discover: AsyncMock,